diff --git a/.gitattributes b/.gitattributes index 4dd2b78eab5..65d10f25cf3 100644 --- a/.gitattributes +++ b/.gitattributes @@ -43,6 +43,3 @@ *.ttf binary *.slf binary *.borders binary - -# Files for git lfs -FBAudienceNetwork filter=lfs diff=lfs merge=lfs -text diff --git a/iphone/Maps/3party/Bolts.framework/Bolts b/iphone/Maps/3party/Bolts.framework/Bolts index 05bbc229b9e..31808d9628c 100644 Binary files a/iphone/Maps/3party/Bolts.framework/Bolts and b/iphone/Maps/3party/Bolts.framework/Bolts differ diff --git a/iphone/Maps/3party/Bolts.framework/Info.plist b/iphone/Maps/3party/Bolts.framework/Info.plist index 774a36ef90c..b0efa1bf4c3 100644 Binary files a/iphone/Maps/3party/Bolts.framework/Info.plist and b/iphone/Maps/3party/Bolts.framework/Info.plist differ diff --git a/iphone/Maps/3party/FBAudienceNetwork.framework/FBAudienceNetwork b/iphone/Maps/3party/FBAudienceNetwork.framework/FBAudienceNetwork deleted file mode 100644 index 3c6d5979488..00000000000 --- a/iphone/Maps/3party/FBAudienceNetwork.framework/FBAudienceNetwork +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2c83a17cad6e8e7c89ac74e742f5916d474dbeef1da696f421148c79fd2a11d4 -size 104942232 diff --git a/iphone/Maps/3party/FBAudienceNetwork.framework/FBAudienceNetwork-DynamicFramework.xcconfig b/iphone/Maps/3party/FBAudienceNetwork.framework/FBAudienceNetwork-DynamicFramework.xcconfig deleted file mode 100644 index e585bc2e372..00000000000 --- a/iphone/Maps/3party/FBAudienceNetwork.framework/FBAudienceNetwork-DynamicFramework.xcconfig +++ /dev/null @@ -1,14 +0,0 @@ -#include "../../../Configurations/FacebookSDK-DynamicFramework.xcconfig" - -PRODUCT_NAME = FBAudienceNetworkDynamicFramework - -INFOPLIST_FILE = $(SRCROOT)/FBAudienceNetwork/Info.plist -MODULEMAP_FILE = $(SRCROOT)/FBAudienceNetwork/module.modulemap - -IPHONEOS_DEPLOYMENT_TARGET = 8.0 - -GCC_SYMBOLS_PRIVATE_EXTERN = YES - -//FRAMEWORK_SEARCH_PATHS = $(inherited) "$(SRCROOT)/../../vendor"/** -//LIBRARY_SEARCH_PATHS = $(inherited) "$(SRCROOT)/../../vendor"/** -HEADER_SEARCH_PATHS = $(inherited) "$(SDKROOT)/usr/include/libxml2" diff --git a/iphone/Maps/3party/FBAudienceNetwork.framework/Headers/FBInstreamAdView.h b/iphone/Maps/3party/FBAudienceNetwork.framework/Headers/FBInstreamAdView.h deleted file mode 100644 index 83c6a393bf7..00000000000 --- a/iphone/Maps/3party/FBAudienceNetwork.framework/Headers/FBInstreamAdView.h +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright 2004-present Facebook. All Rights Reserved. -// -// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, -// copy, modify, and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by Facebook. -// -// As with any software that integrates with the Facebook platform, your use of -// this software is subject to the Facebook Developer Principles and Policies -// [http://developers.facebook.com/policy/]. This copyright notice shall be -// included in all copies or substantial portions of the software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -#import - -@protocol FBInstreamAdViewDelegate; - -@interface FBInstreamAdView : UIView - -@property (nonatomic, getter=isAdValid, readonly) BOOL adValid; -@property (nonatomic, weak, nullable) id delegate; -@property (nonatomic, copy, readonly, nonnull) NSString *placementID; - -- (nullable instancetype)initWithPlacementID:(nonnull NSString *)placementID NS_DESIGNATED_INITIALIZER; - -- (void)loadAd; - -- (BOOL)showAdFromRootViewController:(nullable UIViewController *)rootViewController; - -@end - -@protocol FBInstreamAdViewDelegate - -- (void)adViewDidLoad:(nonnull FBInstreamAdView *)adView; - -- (void)adViewDidEnd:(nonnull FBInstreamAdView *)adView; - -- (void)adView:(nonnull FBInstreamAdView *)adView didFailWithError:(nonnull NSError *)error; - -@optional - -- (void)adViewDidClick:(nonnull FBInstreamAdView *)adView; - -- (void)adViewWillLogImpression:(nonnull FBInstreamAdView *)adView; - -@end diff --git a/iphone/Maps/3party/FBSDKCoreKit.framework/FBSDKCoreKit b/iphone/Maps/3party/FBSDKCoreKit.framework/FBSDKCoreKit index d465738227e..4386a2c6542 100644 Binary files a/iphone/Maps/3party/FBSDKCoreKit.framework/FBSDKCoreKit and b/iphone/Maps/3party/FBSDKCoreKit.framework/FBSDKCoreKit differ diff --git a/iphone/Maps/3party/FBSDKCoreKit.framework/Headers/FBSDKAppEvents.h b/iphone/Maps/3party/FBSDKCoreKit.framework/Headers/FBSDKAppEvents.h index 1608efafb1a..c35a52615be 100644 --- a/iphone/Maps/3party/FBSDKCoreKit.framework/Headers/FBSDKAppEvents.h +++ b/iphone/Maps/3party/FBSDKCoreKit.framework/Headers/FBSDKAppEvents.h @@ -405,11 +405,11 @@ FBSDK_EXTERN NSString *const FBSDKAppEventParameterValueNo; */ /** - Sets a device token to register the current application installation for push notifications. + Sets and sends device token to register the current application for push notifications. - Sets a device token from `NSData` representation that you get from `UIApplicationDelegate.-application:didRegisterForRemoteNotificationsWithDeviceToken:`. + Sets and sends a device token from `NSData` representation that you get from `UIApplicationDelegate.-application:didRegisterForRemoteNotificationsWithDeviceToken:`. - Parameter deviceToken: Device token data. */ diff --git a/iphone/Maps/3party/FBSDKCoreKit.framework/Headers/FBSDKCoreKit.h b/iphone/Maps/3party/FBSDKCoreKit.framework/Headers/FBSDKCoreKit.h index d006cf250be..9a034b6a4e1 100644 --- a/iphone/Maps/3party/FBSDKCoreKit.framework/Headers/FBSDKCoreKit.h +++ b/iphone/Maps/3party/FBSDKCoreKit.framework/Headers/FBSDKCoreKit.h @@ -44,5 +44,5 @@ #import #endif -#define FBSDK_VERSION_STRING @"4.19.0" +#define FBSDK_VERSION_STRING @"4.21.0" #define FBSDK_TARGET_PLATFORM_VERSION @"v2.8" diff --git a/iphone/Maps/3party/FBSDKCoreKit.framework/Info.plist b/iphone/Maps/3party/FBSDKCoreKit.framework/Info.plist index c66d2d94880..820dd540597 100644 Binary files a/iphone/Maps/3party/FBSDKCoreKit.framework/Info.plist and b/iphone/Maps/3party/FBSDKCoreKit.framework/Info.plist differ diff --git a/iphone/Maps/3party/FBSDKLoginKit.framework/FBSDKLoginKit b/iphone/Maps/3party/FBSDKLoginKit.framework/FBSDKLoginKit index eea8582b94a..240ce6a0902 100644 Binary files a/iphone/Maps/3party/FBSDKLoginKit.framework/FBSDKLoginKit and b/iphone/Maps/3party/FBSDKLoginKit.framework/FBSDKLoginKit differ diff --git a/iphone/Maps/3party/FBSDKLoginKit.framework/Info.plist b/iphone/Maps/3party/FBSDKLoginKit.framework/Info.plist index 97a538c950b..4f93202b675 100644 Binary files a/iphone/Maps/3party/FBSDKLoginKit.framework/Info.plist and b/iphone/Maps/3party/FBSDKLoginKit.framework/Info.plist differ diff --git a/iphone/Maps/3party/FBSDKShareKit.framework/FBSDKShareKit b/iphone/Maps/3party/FBSDKShareKit.framework/FBSDKShareKit index 6181c62e792..1147cc938cf 100644 Binary files a/iphone/Maps/3party/FBSDKShareKit.framework/FBSDKShareKit and b/iphone/Maps/3party/FBSDKShareKit.framework/FBSDKShareKit differ diff --git a/iphone/Maps/3party/FBSDKShareKit.framework/Info.plist b/iphone/Maps/3party/FBSDKShareKit.framework/Info.plist index 338243eb1fc..bd0a36088cd 100644 Binary files a/iphone/Maps/3party/FBSDKShareKit.framework/Info.plist and b/iphone/Maps/3party/FBSDKShareKit.framework/Info.plist differ diff --git a/iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/FacebookBannerCustomEvent.h b/iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/FacebookBannerCustomEvent.h new file mode 100755 index 00000000000..6955c1a6f1f --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/FacebookBannerCustomEvent.h @@ -0,0 +1,20 @@ +// +// FacebookBannerCustomEvent.h +// MoPub +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#if __has_include() + #import +#else + #import "MPBannerCustomEvent.h" +#endif + +/* + * Please reference the Supported Mediation Partner page at http://bit.ly/2mqsuFH for the + * latest version and ad format certifications. + */ +@interface FacebookBannerCustomEvent : MPBannerCustomEvent + +@end diff --git a/iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/FacebookBannerCustomEvent.m b/iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/FacebookBannerCustomEvent.m new file mode 100755 index 00000000000..06830d861dc --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/FacebookBannerCustomEvent.m @@ -0,0 +1,141 @@ +// +// FacebookBannerCustomEvent.m +// MoPub +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import +#import "FacebookBannerCustomEvent.h" + +#import "MPInstanceProvider.h" +#import "MPLogging.h" + +@interface MPInstanceProvider (FacebookBanners) + +- (FBAdView *)buildFBAdViewWithPlacementID:(NSString *)placementID + size:(FBAdSize)size + rootViewController:(UIViewController *)controller + delegate:(id)delegate; +@end + +@implementation MPInstanceProvider (FacebookBanners) + +- (FBAdView *)buildFBAdViewWithPlacementID:(NSString *)placementID + size:(FBAdSize)size + rootViewController:(UIViewController *)controller + delegate:(id)delegate +{ + FBAdView *adView = [[FBAdView alloc] initWithPlacementID:placementID + adSize:size + rootViewController:controller]; + adView.delegate = delegate; + [adView disableAutoRefresh]; + return adView; +} + +@end + +@interface FacebookBannerCustomEvent () + +@property (nonatomic, strong) FBAdView *fbAdView; + +@end + +@implementation FacebookBannerCustomEvent + +- (BOOL)enableAutomaticImpressionAndClickTracking +{ + return NO; +} + +- (void)requestAdWithSize:(CGSize)size customEventInfo:(NSDictionary *)info +{ + /** + * Facebook Banner ads can accept arbitrary widths for given heights of 50 and 90. We convert these sizes + * to Facebook's constants and set the fbAdView's size to the intended size ("size" passed to this method). + */ + FBAdSize fbAdSize; + if (CGSizeEqualToSize(size, kFBAdSize320x50.size)) { + fbAdSize = kFBAdSize320x50; + } else if (size.height == kFBAdSizeHeight250Rectangle.size.height) { + fbAdSize = kFBAdSizeHeight250Rectangle; + } else if (size.height == kFBAdSizeHeight90Banner.size.height) { + fbAdSize = kFBAdSizeHeight90Banner; + } else if (size.height == kFBAdSizeHeight50Banner.size.height) { + fbAdSize = kFBAdSizeHeight50Banner; + } else { + MPLogError(@"Invalid size for Facebook banner ad"); + [self.delegate bannerCustomEvent:self didFailToLoadAdWithError:nil]; + return; + } + + if (![info objectForKey:@"placement_id"]) { + MPLogError(@"Placement ID is required for Facebook banner ad"); + [self.delegate bannerCustomEvent:self didFailToLoadAdWithError:nil]; + return; + } + + MPLogInfo(@"Requesting Facebook banner ad"); + self.fbAdView = + [[MPInstanceProvider sharedProvider] buildFBAdViewWithPlacementID:[info objectForKey:@"placement_id"] + size:fbAdSize + rootViewController:[self.delegate viewControllerForPresentingModalView] + delegate:self]; + + if (!self.fbAdView) { + [self.delegate bannerCustomEvent:self didFailToLoadAdWithError:nil]; + return; + } + + /* + * Manually resize the frame of the FBAdView due to a bug in the Facebook SDK that sets the ad's width + * to the width of the device instead of the width of the container it's placed in. + * (Confirmed in email with a FB Solutions Engineer) + */ + CGRect fbAdFrame = self.fbAdView.frame; + fbAdFrame.size = size; + self.fbAdView.frame = fbAdFrame; + + [self.fbAdView loadAd]; +} + +- (void)dealloc +{ + _fbAdView.delegate = nil; +} + +#pragma mark FBAdViewDelegate methods + +- (void)adView:(FBAdView *)adView didFailWithError:(NSError *)error; +{ + MPLogInfo(@"Facebook banner failed to load with error: %@", error.description); + [self.delegate bannerCustomEvent:self didFailToLoadAdWithError:error]; +} + +- (void)adViewDidLoad:(FBAdView *)adView; +{ + MPLogInfo(@"Facebook banner ad did load"); + [self.delegate trackImpression]; + [self.delegate bannerCustomEvent:self didLoadAd:adView]; +} + +- (void)adViewDidClick:(FBAdView *)adView +{ + MPLogInfo(@"Facebook banner ad was clicked"); + [self.delegate trackClick]; + [self.delegate bannerCustomEventWillBeginAction:self]; +} + +- (void)adViewDidFinishHandlingClick:(FBAdView *)adView +{ + MPLogInfo(@"Facebook banner ad did finish handling click"); + [self.delegate bannerCustomEventDidFinishAction:self]; +} + +- (UIViewController *)viewControllerForPresentingModalView +{ + return [self.delegate viewControllerForPresentingModalView]; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/FacebookInterstitialCustomEvent.h b/iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/FacebookInterstitialCustomEvent.h new file mode 100755 index 00000000000..59c960c21eb --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/FacebookInterstitialCustomEvent.h @@ -0,0 +1,20 @@ +// +// FacebookInterstitialCustomEvent.h +// MoPub +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#if __has_include() + #import +#else + #import "MPInterstitialCustomEvent.h" +#endif + +/* + * Please reference the Supported Mediation Partner page at http://bit.ly/2mqsuFH for the + * latest version and ad format certifications. + */ +@interface FacebookInterstitialCustomEvent : MPInterstitialCustomEvent + +@end diff --git a/iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/FacebookInterstitialCustomEvent.m b/iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/FacebookInterstitialCustomEvent.m new file mode 100755 index 00000000000..2a624ff52a2 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/FacebookInterstitialCustomEvent.m @@ -0,0 +1,108 @@ +// +// FacebookInterstitialCustomEvent.m +// MoPub +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import +#import "FacebookInterstitialCustomEvent.h" + +#import "MPInstanceProvider.h" +#import "MPLogging.h" + +@interface MPInstanceProvider (FacebookInterstitials) + +- (FBInterstitialAd *)buildFBInterstitialAdWithPlacementID:(NSString *)placementID + delegate:(id)delegate; + +@end + +@implementation MPInstanceProvider (FacebookInterstitials) + +- (FBInterstitialAd *)buildFBInterstitialAdWithPlacementID:(NSString *)placementID + delegate:(id)delegate +{ + FBInterstitialAd *interstitialAd = [[FBInterstitialAd alloc] initWithPlacementID:placementID]; + interstitialAd.delegate = delegate; + return interstitialAd; +} + +@end + +@interface FacebookInterstitialCustomEvent () + +@property (nonatomic, strong) FBInterstitialAd *fbInterstitialAd; + +@end + +@implementation FacebookInterstitialCustomEvent + +- (void)requestInterstitialWithCustomEventInfo:(NSDictionary *)info +{ + if (![info objectForKey:@"placement_id"]) { + MPLogError(@"Placement ID is required for Facebook interstitial ad"); + [self.delegate interstitialCustomEvent:self didFailToLoadAdWithError:nil]; + return; + } + + MPLogInfo(@"Requesting Facebook interstitial ad"); + + self.fbInterstitialAd = + [[MPInstanceProvider sharedProvider] buildFBInterstitialAdWithPlacementID:[info objectForKey:@"placement_id"] + delegate:self]; + + [self.fbInterstitialAd loadAd]; +} + +- (void)showInterstitialFromRootViewController:(UIViewController *)controller { + if (!self.fbInterstitialAd || !self.fbInterstitialAd.isAdValid) { + MPLogError(@"Facebook interstitial ad was not loaded"); + [self.delegate interstitialCustomEventDidExpire:self]; + } else { + MPLogInfo(@"Facebook interstitial ad will be presented"); + [self.delegate interstitialCustomEventWillAppear:self]; + [self.fbInterstitialAd showAdFromRootViewController:controller]; + MPLogInfo(@"Facebook interstitial ad was presented"); + [self.delegate interstitialCustomEventDidAppear:self]; + } +} + +- (void)dealloc +{ + _fbInterstitialAd.delegate = nil; +} + +#pragma mark FBInterstitialAdDelegate methods + +- (void)interstitialAdDidLoad:(FBInterstitialAd *)interstitialAd +{ + MPLogInfo(@"Facebook intersitital ad was loaded. Can present now"); + [self.delegate interstitialCustomEvent:self didLoadAd:interstitialAd]; +} + +- (void)interstitialAd:(FBInterstitialAd *)interstitialAd didFailWithError:(NSError *)error +{ + MPLogInfo(@"Facebook intersitital ad failed to load with error: %@", error.description); + [self.delegate interstitialCustomEvent:self didFailToLoadAdWithError:nil]; +} + +- (void)interstitialAdDidClick:(FBInterstitialAd *)interstitialAd +{ + MPLogInfo(@"Facebook interstitial ad was clicked"); + [self.delegate interstitialCustomEventDidReceiveTapEvent:self]; +} + +- (void)interstitialAdDidClose:(FBInterstitialAd *)interstitialAd +{ + MPLogInfo(@"Facebook interstitial ad was closed"); + [self.delegate interstitialCustomEventDidDisappear:self]; +} + +- (void)interstitialAdWillClose:(FBInterstitialAd *)interstitialAd +{ + MPLogInfo(@"Facebook interstitial ad will close"); + [self.delegate interstitialCustomEventWillDisappear:self]; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/FacebookNativeAdAdapter.h b/iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/FacebookNativeAdAdapter.h new file mode 100755 index 00000000000..adf8cf0c8a6 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/FacebookNativeAdAdapter.h @@ -0,0 +1,24 @@ +// +// FacebookNativeAdAdapter.h +// MoPub +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#if __has_include() + #import +#else + #import "MPNativeAdAdapter.h" +#endif + +@class FBNativeAd; + +extern NSString *const kFBVideoAdsEnabledKey; + +@interface FacebookNativeAdAdapter : NSObject + +@property (nonatomic, weak) id delegate; + +- (instancetype)initWithFBNativeAd:(FBNativeAd *)fbNativeAd adProperties:(NSDictionary *)adProps; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/FacebookNativeAdAdapter.m b/iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/FacebookNativeAdAdapter.m new file mode 100755 index 00000000000..fa2dc3cafa9 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/FacebookNativeAdAdapter.m @@ -0,0 +1,152 @@ +// +// FacebookNativeAdAdapter.m +// MoPub +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import +#import "FacebookNativeAdAdapter.h" +#import "MPNativeAdConstants.h" +#import "MPNativeAdError.h" +#import "MPLogging.h" + +NSString *const kFBVideoAdsEnabledKey = @"video_enabled"; + +@interface FacebookNativeAdAdapter () + +@property (nonatomic, readonly) FBNativeAd *fbNativeAd; +@property (nonatomic, readonly) FBAdChoicesView *adChoicesView; +@property (nonatomic, readonly) FBMediaView *mediaView; + +@end + +@implementation FacebookNativeAdAdapter + +@synthesize properties = _properties; + +- (instancetype)initWithFBNativeAd:(FBNativeAd *)fbNativeAd adProperties:(NSDictionary *)adProps +{ + if (self = [super init]) { + _fbNativeAd = fbNativeAd; + _fbNativeAd.delegate = self; + + NSNumber *starRating = nil; + + // Normalize star rating to 5 stars. + if (fbNativeAd.starRating.scale != 0) { + CGFloat ratio = 0.0f; + ratio = kUniversalStarRatingScale/fbNativeAd.starRating.scale; + starRating = [NSNumber numberWithFloat:ratio*fbNativeAd.starRating.value]; + } + + NSMutableDictionary *properties; + if (adProps) { + properties = [NSMutableDictionary dictionaryWithDictionary:adProps]; + } else { + properties = [NSMutableDictionary dictionary]; + } + + + if (starRating) { + [properties setObject:starRating forKey:kAdStarRatingKey]; + } + + if (fbNativeAd.title) { + [properties setObject:fbNativeAd.title forKey:kAdTitleKey]; + } + + if (fbNativeAd.body) { + [properties setObject:fbNativeAd.body forKey:kAdTextKey]; + } + + if (fbNativeAd.callToAction) { + [properties setObject:fbNativeAd.callToAction forKey:kAdCTATextKey]; + } + + if (fbNativeAd.icon.url.absoluteString) { + [properties setObject:fbNativeAd.icon.url.absoluteString forKey:kAdIconImageKey]; + } + + if (fbNativeAd.placementID) { + [properties setObject:fbNativeAd.placementID forKey:@"placementID"]; + } + + if (fbNativeAd.socialContext) { + [properties setObject:fbNativeAd.socialContext forKey:@"socialContext"]; + } + + _properties = properties; + + _adChoicesView = [[FBAdChoicesView alloc] initWithNativeAd:fbNativeAd]; + _adChoicesView.backgroundShown = NO; + + // If video ad is enabled, use mediaView, otherwise use coverImage. + if ([[_properties objectForKey:kFBVideoAdsEnabledKey] boolValue]) { + _mediaView = [[FBMediaView alloc] initWithNativeAd:fbNativeAd]; + } else { + if (fbNativeAd.coverImage.url.absoluteString) { + [properties setObject:fbNativeAd.coverImage.url.absoluteString forKey:kAdMainImageKey]; + } + } + } + + return self; +} + + +#pragma mark - MPNativeAdAdapter + +- (NSURL *)defaultActionURL +{ + return nil; +} + +- (BOOL)enableThirdPartyClickTracking +{ + return YES; +} + +- (void)willAttachToView:(UIView *)view +{ + [self.fbNativeAd registerViewForInteraction:view withViewController:[self.delegate viewControllerForPresentingModalView]]; +} + +- (UIView *)privacyInformationIconView +{ + return self.adChoicesView; +} + +- (UIView *)mainMediaView +{ + return self.mediaView; +} + +#pragma mark - FBNativeAdDelegate + +- (void)nativeAdWillLogImpression:(FBNativeAd *)nativeAd +{ + if ([self.delegate respondsToSelector:@selector(nativeAdWillLogImpression:)]) { + [self.delegate nativeAdWillLogImpression:self]; + } else { + MPLogWarn(@"Delegate does not implement impression tracking callback. Impressions likely not being tracked."); + } +} + +- (void)nativeAdDidClick:(FBNativeAd *)nativeAd +{ + if ([self.delegate respondsToSelector:@selector(nativeAdDidClick:)]) { + [self.delegate nativeAdDidClick:self]; + } else { + MPLogWarn(@"Delegate does not implement click tracking callback. Clicks likely not being tracked."); + } + + [self.delegate nativeAdWillPresentModalForAdapter:self]; +} + +- (void)nativeAdDidFinishHandlingClick:(FBNativeAd *)nativeAd +{ + [self.delegate nativeAdDidDismissModalForAdapter:self]; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/FacebookNativeCustomEvent.h b/iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/FacebookNativeCustomEvent.h new file mode 100755 index 00000000000..de3ad52763a --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/FacebookNativeCustomEvent.h @@ -0,0 +1,31 @@ +// +// FacebookNativeCustomEvent.h +// MoPub +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#if __has_include() + #import +#else + #import "MPNativeCustomEvent.h" +#endif + +/* + * Please reference the Supported Mediation Partner page at http://bit.ly/2mqsuFH for the + * latest version and ad format certifications. + */ +@interface FacebookNativeCustomEvent : MPNativeCustomEvent + + +/** + * Toggle FB video ads on/off. If it is enabled, it means you are open yourself to video inventory. + * If it is not enabled, it is gauranteed you won't get video ads. + * + * IMPORTANT: If you choose to use this method, be sure to call it before making any ad requests, + * and avoid calling it more than once. + */ + ++ (void)setVideoEnabled:(BOOL)enabled; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/FacebookNativeCustomEvent.m b/iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/FacebookNativeCustomEvent.m new file mode 100755 index 00000000000..f21a8d80b2a --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/FacebookNativeCustomEvent.m @@ -0,0 +1,89 @@ +// +// FacebookNativeCustomEvent.m +// MoPub +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import +#import "FacebookNativeCustomEvent.h" +#import "FacebookNativeAdAdapter.h" +#import "MPNativeAd.h" +#import "MPNativeAdError.h" +#import "MPLogging.h" +#import "MPNativeAdConstants.h" + +static const NSInteger FacebookNoFillErrorCode = 1001; +static BOOL gVideoEnabled = NO; + +@interface FacebookNativeCustomEvent () + +@property (nonatomic, readwrite, strong) FBNativeAd *fbNativeAd; +@property (nonatomic) BOOL videoEnabled; + +@end + +@implementation FacebookNativeCustomEvent + ++ (void)setVideoEnabled:(BOOL)enabled +{ + gVideoEnabled = enabled; +} + +- (void)requestAdWithCustomEventInfo:(NSDictionary *)info +{ + NSString *placementID = [info objectForKey:@"placement_id"]; + + if ([info objectForKey:kFBVideoAdsEnabledKey] == nil) { + self.videoEnabled = gVideoEnabled; + } else { + self.videoEnabled = [[info objectForKey:kFBVideoAdsEnabledKey] boolValue]; + } + + if (placementID) { + _fbNativeAd = [[FBNativeAd alloc] initWithPlacementID:placementID]; + self.fbNativeAd.delegate = self; + [self.fbNativeAd loadAd]; + } else { + [self.delegate nativeCustomEvent:self didFailToLoadAdWithError:MPNativeAdNSErrorForInvalidAdServerResponse(@"Invalid Facebook placement ID")]; + } +} + +#pragma mark - FBNativeAdDelegate + +- (void)nativeAdDidLoad:(FBNativeAd *)nativeAd +{ + FacebookNativeAdAdapter *adAdapter = [[FacebookNativeAdAdapter alloc] initWithFBNativeAd:nativeAd adProperties:@{kFBVideoAdsEnabledKey:@(self.videoEnabled)}]; + MPNativeAd *interfaceAd = [[MPNativeAd alloc] initWithAdAdapter:adAdapter]; + + NSMutableArray *imageURLs = [NSMutableArray array]; + + if (nativeAd.icon.url) { + [imageURLs addObject:nativeAd.icon.url]; + } + + // If video is enabled, no need to load coverImage. + if (!self.videoEnabled && nativeAd.coverImage.url) { + [imageURLs addObject:nativeAd.coverImage.url]; + } + + [super precacheImagesWithURLs:imageURLs completionBlock:^(NSArray *errors) { + if (errors) { + MPLogDebug(@"%@", errors); + [self.delegate nativeCustomEvent:self didFailToLoadAdWithError:MPNativeAdNSErrorForImageDownloadFailure()]; + } else { + [self.delegate nativeCustomEvent:self didLoadAd:interfaceAd]; + } + }]; +} + +- (void)nativeAd:(FBNativeAd *)nativeAd didFailWithError:(NSError *)error +{ + if (error.code == FacebookNoFillErrorCode) { + [self.delegate nativeCustomEvent:self didFailToLoadAdWithError:MPNativeAdNSErrorForNoInventory()]; + } else { + [self.delegate nativeCustomEvent:self didFailToLoadAdWithError:MPNativeAdNSErrorForInvalidAdServerResponse(@"Facebook ad load error")]; + } +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK/FBAudienceNetwork.framework/FBAudienceNetwork b/iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK/FBAudienceNetwork.framework/FBAudienceNetwork new file mode 100644 index 00000000000..858db42d66f Binary files /dev/null and b/iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK/FBAudienceNetwork.framework/FBAudienceNetwork differ diff --git a/iphone/Maps/3party/FBAudienceNetwork.framework/Headers/FBAdChoicesView.h b/iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK/FBAudienceNetwork.framework/Headers/FBAdChoicesView.h similarity index 90% rename from iphone/Maps/3party/FBAudienceNetwork.framework/Headers/FBAdChoicesView.h rename to iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK/FBAudienceNetwork.framework/Headers/FBAdChoicesView.h index 8bcce7bde4a..d97bcc62314 100644 --- a/iphone/Maps/3party/FBAudienceNetwork.framework/Headers/FBAdChoicesView.h +++ b/iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK/FBAudienceNetwork.framework/Headers/FBAdChoicesView.h @@ -60,7 +60,7 @@ FB_CLASS_EXPORT FB_SUBCLASSING_RESTRICTED /* The view controller to present the ad choices info from. If nil, the top view controller is used. */ -@property (nonatomic, weak, readwrite, nullable) UIViewController *viewController; +@property (nonatomic, weak, readwrite, null_resettable) UIViewController *viewController; /** Initialize this view with a given native ad. Configuration is pulled from the native ad. @@ -87,8 +87,8 @@ FB_CLASS_EXPORT FB_SUBCLASSING_RESTRICTED - Parameter attributes: Attributes to configure look and feel. */ - (instancetype)initWithViewController:(nullable UIViewController *)viewController - adChoicesIcon:(FBAdImage *)adChoicesIcon - adChoicesLinkURL:(NSURL *)adChoicesLinkURL + adChoicesIcon:(nullable FBAdImage *)adChoicesIcon + adChoicesLinkURL:(nullable NSURL *)adChoicesLinkURL attributes:(nullable FBNativeAdViewAttributes *)attributes; /** @@ -101,8 +101,8 @@ FB_CLASS_EXPORT FB_SUBCLASSING_RESTRICTED - Parameter expandable: Controls whether view defaults to expanded or not, see property documentation */ - (instancetype)initWithViewController:(nullable UIViewController *)viewController - adChoicesIcon:(FBAdImage *)adChoicesIcon - adChoicesLinkURL:(NSURL *)adChoicesLinkURL + adChoicesIcon:(nullable FBAdImage *)adChoicesIcon + adChoicesLinkURL:(nullable NSURL *)adChoicesLinkURL attributes:(nullable FBNativeAdViewAttributes *)attributes expandable:(BOOL)expandable; @@ -117,8 +117,8 @@ FB_CLASS_EXPORT FB_SUBCLASSING_RESTRICTED - Parameter expandable: Controls whether view defaults to expanded or not, see property documentation */ - (instancetype)initWithViewController:(nullable UIViewController *)viewController - adChoicesIcon:(FBAdImage *)adChoicesIcon - adChoicesLinkURL:(NSURL *)adChoicesLinkURL + adChoicesIcon:(nullable FBAdImage *)adChoicesIcon + adChoicesLinkURL:(nullable NSURL *)adChoicesLinkURL adChoicesText:(nullable NSString*)adChoicesText attributes:(nullable FBNativeAdViewAttributes *)attributes expandable:(BOOL)expandable NS_DESIGNATED_INITIALIZER; diff --git a/iphone/Maps/3party/FBAudienceNetwork.framework/Headers/FBAdDefines.h b/iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK/FBAudienceNetwork.framework/Headers/FBAdDefines.h similarity index 100% rename from iphone/Maps/3party/FBAudienceNetwork.framework/Headers/FBAdDefines.h rename to iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK/FBAudienceNetwork.framework/Headers/FBAdDefines.h diff --git a/iphone/Maps/3party/FBAudienceNetwork.framework/Headers/FBAdSettings.h b/iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK/FBAudienceNetwork.framework/Headers/FBAdSettings.h similarity index 89% rename from iphone/Maps/3party/FBAudienceNetwork.framework/Headers/FBAdSettings.h rename to iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK/FBAudienceNetwork.framework/Headers/FBAdSettings.h index 3031c1f11b0..bb8f333121c 100644 --- a/iphone/Maps/3party/FBAudienceNetwork.framework/Headers/FBAdSettings.h +++ b/iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK/FBAudienceNetwork.framework/Headers/FBAdSettings.h @@ -27,20 +27,37 @@ NS_ASSUME_NONNULL_BEGIN */ FB_EXPORT NSString * const FBAudienceNetworkErrorDomain; +/** + Audience Network SDK logging levels + */ typedef NS_ENUM(NSInteger, FBAdLogLevel) { + /// No logging FBAdLogLevelNone, + /// Notifications FBAdLogLevelNotification, + /// Errors only FBAdLogLevelError, + /// Warnings only FBAdLogLevelWarning, + /// Standard log level FBAdLogLevelLog, + /// Debug logging FBAdLogLevelDebug, + /// Log everything (verbose) FBAdLogLevelVerbose }; +/** + Determines what method is used for rendering FBMediaView content + */ typedef NS_ENUM(NSInteger, FBMediaViewRenderingMethod) { + /// Automatic selection of rendering method FBMediaViewRenderingMethodDefault, + /// Force Metal rendering (only use for devices with support) FBMediaViewRenderingMethodMetal, + /// Force OpenGL rendering FBMediaViewRenderingMethodOpenGL, + /// Software fallback FBMediaViewRenderingMethodSoftware }; @@ -96,8 +113,6 @@ FB_CLASS_EXPORT FB_SUBCLASSING_RESTRICTED - Parameter isChildDirected: Indicates whether you would like your ad control to be treated as child-directed - - Note that you may have other legal obligations under the Children's Online Privacy Protection Act (COPPA). Please review the FTC's guidance and consult with your own legal counsel. */ @@ -113,11 +128,9 @@ FB_CLASS_EXPORT FB_SUBCLASSING_RESTRICTED /** Gets the url prefix to use when making ad requests. - - This method should never be used in production. */ -+ (NSString *)urlPrefix; ++ (nullable NSString *)urlPrefix; /** Sets the url prefix to use when making ad requests. @@ -126,7 +139,7 @@ FB_CLASS_EXPORT FB_SUBCLASSING_RESTRICTED This method should never be used in production. */ -+ (void)setUrlPrefix:(NSString *) urlPrefix; ++ (void)setUrlPrefix:(nullable NSString *) urlPrefix; /** Gets the current SDK logging level diff --git a/iphone/Maps/3party/FBAudienceNetwork.framework/Headers/FBAdSize.h b/iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK/FBAudienceNetwork.framework/Headers/FBAdSize.h similarity index 94% rename from iphone/Maps/3party/FBAudienceNetwork.framework/Headers/FBAdSize.h rename to iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK/FBAudienceNetwork.framework/Headers/FBAdSize.h index e0a5fdf8158..564872b16fd 100644 --- a/iphone/Maps/3party/FBAudienceNetwork.framework/Headers/FBAdSize.h +++ b/iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK/FBAudienceNetwork.framework/Headers/FBAdSize.h @@ -23,14 +23,13 @@ NS_ASSUME_NONNULL_BEGIN -/** - FBAdSize +/// Represents the ad size. +struct FBAdSize { + CGSize size; +}; - Represents the ad size. - */ -typedef struct FBAdSize { - CGSize size; -} FBAdSize; +/// Represents the ad size. +typedef struct FBAdSize FBAdSize; /** DEPRECATED - Represents the fixed banner ad size - 320pt by 50pt. diff --git a/iphone/Maps/3party/FBAudienceNetwork.framework/Headers/FBAdView.h b/iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK/FBAudienceNetwork.framework/Headers/FBAdView.h similarity index 99% rename from iphone/Maps/3party/FBAudienceNetwork.framework/Headers/FBAdView.h rename to iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK/FBAudienceNetwork.framework/Headers/FBAdView.h index 53477c15a2c..c2de367b531 100644 --- a/iphone/Maps/3party/FBAudienceNetwork.framework/Headers/FBAdView.h +++ b/iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK/FBAudienceNetwork.framework/Headers/FBAdView.h @@ -82,8 +82,6 @@ FB_CLASS_EXPORT @end /** - @protocol - The methods declared by the FBAdViewDelegate protocol allow the adopting delegate to respond to messages from the FBAdView class and thus respond to operations such as whether the ad has been loaded, the person has clicked the ad. diff --git a/iphone/Maps/3party/FBAudienceNetwork.framework/Headers/FBAudienceNetwork.h b/iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK/FBAudienceNetwork.framework/Headers/FBAudienceNetwork.h similarity index 98% rename from iphone/Maps/3party/FBAudienceNetwork.framework/Headers/FBAudienceNetwork.h rename to iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK/FBAudienceNetwork.framework/Headers/FBAudienceNetwork.h index 6d567ce3ab9..c294172ab31 100644 --- a/iphone/Maps/3party/FBAudienceNetwork.framework/Headers/FBAudienceNetwork.h +++ b/iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK/FBAudienceNetwork.framework/Headers/FBAudienceNetwork.h @@ -38,4 +38,4 @@ // NOTE: Any changes should also be made to the module.modulemap // to ensure comptability with Swift apps using Cocoapods -#define FB_AD_SDK_VERSION @"4.19.0" +#define FB_AD_SDK_VERSION @"4.21.0" diff --git a/iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK/FBAudienceNetwork.framework/Headers/FBInstreamAdView.h b/iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK/FBAudienceNetwork.framework/Headers/FBInstreamAdView.h new file mode 100644 index 00000000000..9a99650f288 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK/FBAudienceNetwork.framework/Headers/FBInstreamAdView.h @@ -0,0 +1,131 @@ +// Copyright 2004-present Facebook. All Rights Reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy, modify, and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by Facebook. +// +// As with any software that integrates with the Facebook platform, your use of +// this software is subject to the Facebook Developer Principles and Policies +// [http://developers.facebook.com/policy/]. This copyright notice shall be +// included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +@protocol FBInstreamAdViewDelegate; + +/** + A customized UIView to display an instream video ad by Facebook. + */ +FB_CLASS_EXPORT FB_SUBCLASSING_RESTRICTED +@interface FBInstreamAdView : UIView + +/** + Returns YES if the instream ad has been successfully loaded. + + Note that the `adView:didFailWithError:` delegate method will be also be called + instead of `adViewDidLoad:` if the ad fails to load for any reason. + */ +@property (nonatomic, getter=isAdValid, readonly) BOOL adValid; + +/** + This property must be set prior to calling `loadAd`, so that delegate method calls + are received and handled. + */ +@property (nonatomic, weak, nullable) id delegate; + +/** + Typed access to the id of the ad placement. + */ +@property (nonatomic, copy, readonly) NSString *placementID; + +/** + Initializes and returns a newly allocated FBInstreamAdView object with the + given placement id. + + - Parameter placementID: The id of the ad placement. You can create your placement id from Facebook developers page. + */ +- (nullable instancetype)initWithPlacementID:(NSString *)placementID NS_DESIGNATED_INITIALIZER; + +/** + Begins loading ad content. + + You should implement `adViewDidLoad:` and `adView:didFailWithError:` methods + of `FBInstreamAdViewDelegate` to be notified when loading succeeds or fails. + */ +- (void)loadAd; + +/** + Begins ad playback. This method should only be called after an `adViewDidLoad:` call + has been received. + + - Parameter rootViewController: The view controller that will be used to modally + present additional view controllers, to render the ad's landing page for example. + */ +- (BOOL)showAdFromRootViewController:(nullable UIViewController *)rootViewController; + +@end + +/** + The FBInstreamAdViewDelegate protocol defines methods that allow the owner of an + FBInstreamAdView to respond to various stages of ad operation. + */ +@protocol FBInstreamAdViewDelegate + +/** + Sent when an FBInstreamAdView instance successfully loads an ad. + + - Parameter adView: The FBInstreamAdView object sending the message. + */ +- (void)adViewDidLoad:(FBInstreamAdView *)adView; + +/** + Sent when ad playback has completed and the FBInstreamAdView is ready to be + deallocated. This method is mutually exclusive to `adView:didFailWithError:`, and + it is impossible for both methods to be received for a single ad session. + + - Parameter adView: The FBInstreamAdView object sending the message. + */ +- (void)adViewDidEnd:(FBInstreamAdView *)adView; + +/** + Sent when ad playback has failed to load or play an ad, and the FBInstreamAdView + is ready to be deallocated. It is possible for this method to be called after + `loadAd` (if they ad fails to load) or after `showAdFromRootViewController:` + (if the ad has a playback failure). + + - Parameter adView: The FBInstreamAdView object sending the message. + - Parameter error: An NSError object containing details of the error. + */ +- (void)adView:(FBInstreamAdView *)adView didFailWithError:(NSError *)error; + +@optional + +/** + Sent when the user has touched the click-through interface element. The ad's + landing page will be shown. + + - Parameter adView: The FBInstreamAdView object sending the message. + */ +- (void)adViewDidClick:(FBInstreamAdView *)adView; + +/** + Sent immediately before the impression of an FBInstreamAdView object will be logged. + + - Parameter adView: The FBInstreamAdView object sending the message. + */ +- (void)adViewWillLogImpression:(FBInstreamAdView *)adView; + +@end + +NS_ASSUME_NONNULL_END diff --git a/iphone/Maps/3party/FBAudienceNetwork.framework/Headers/FBInterstitialAd.h b/iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK/FBAudienceNetwork.framework/Headers/FBInterstitialAd.h similarity index 99% rename from iphone/Maps/3party/FBAudienceNetwork.framework/Headers/FBInterstitialAd.h rename to iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK/FBAudienceNetwork.framework/Headers/FBInterstitialAd.h index 1da6fbd1dcf..7b18ef3fc22 100644 --- a/iphone/Maps/3party/FBAudienceNetwork.framework/Headers/FBInterstitialAd.h +++ b/iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK/FBAudienceNetwork.framework/Headers/FBInterstitialAd.h @@ -80,8 +80,6 @@ FB_CLASS_EXPORT FB_SUBCLASSING_RESTRICTED @end /** - @protocol - The methods declared by the FBInterstitialAdDelegate protocol allow the adopting delegate to respond to messages from the FBInterstitialAd class and thus respond to operations such as whether the interstitial ad has been loaded, user has clicked or closed the interstitial. diff --git a/iphone/Maps/3party/FBAudienceNetwork.framework/Headers/FBMediaView.h b/iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK/FBAudienceNetwork.framework/Headers/FBMediaView.h similarity index 96% rename from iphone/Maps/3party/FBAudienceNetwork.framework/Headers/FBMediaView.h rename to iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK/FBAudienceNetwork.framework/Headers/FBMediaView.h index 7a595fed62c..467559be02a 100644 --- a/iphone/Maps/3party/FBAudienceNetwork.framework/Headers/FBMediaView.h +++ b/iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK/FBAudienceNetwork.framework/Headers/FBMediaView.h @@ -58,7 +58,7 @@ FB_CLASS_EXPORT @property (nonatomic, assign, getter=isAutoplayEnabled) BOOL autoplayEnabled; /** - The aspect ratio of the media view visual content. Returns a CGFloat ranging from 0.0 to 1.0. Returns 0.0 if no ad is currently loaded. + The aspect ratio of the media view visual content. Returns a positive CGFloat, or 0.0 if no ad is currently loaded. */ @property (nonatomic, assign, readonly) CGFloat aspectRatio; @@ -78,8 +78,6 @@ FB_CLASS_EXPORT @end /** - @protocol - The methods declared by the FBMediaViewDelegate protocol allow the adopting delegate to respond to messages from the FBMediaView class and thus respond to operations such as whether the media content has been loaded. */ @protocol FBMediaViewDelegate diff --git a/iphone/Maps/3party/FBAudienceNetwork.framework/Headers/FBNativeAd.h b/iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK/FBAudienceNetwork.framework/Headers/FBNativeAd.h similarity index 96% rename from iphone/Maps/3party/FBAudienceNetwork.framework/Headers/FBNativeAd.h rename to iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK/FBAudienceNetwork.framework/Headers/FBNativeAd.h index f814dec8883..8ed7464f401 100644 --- a/iphone/Maps/3party/FBAudienceNetwork.framework/Headers/FBNativeAd.h +++ b/iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK/FBAudienceNetwork.framework/Headers/FBNativeAd.h @@ -25,11 +25,19 @@ NS_ASSUME_NONNULL_BEGIN @protocol FBNativeAdDelegate; @class FBAdImage; +/** + Determines what parts of a native ad's content are cached through FBMediaView + */ typedef NS_OPTIONS(NSInteger, FBNativeAdsCachePolicy) { + /// No ad content is cached FBNativeAdsCachePolicyNone = 1 << 0, + /// Icon is cached FBNativeAdsCachePolicyIcon = 1 << 1, + /// Cover image is cached FBNativeAdsCachePolicyCoverImage = 1 << 2, + /// Video is cached FBNativeAdsCachePolicyVideo = 1 << 3, + /// All content is cached FBNativeAdsCachePolicyAll = FBNativeAdsCachePolicyCoverImage | FBNativeAdsCachePolicyIcon | FBNativeAdsCachePolicyVideo, }; @@ -143,8 +151,6 @@ FB_CLASS_EXPORT FB_SUBCLASSING_RESTRICTED @end /** - @protocol - The methods declared by the FBNativeAdDelegate protocol allow the adopting delegate to respond to messages from the FBNativeAd class and thus respond to operations such as whether the native ad has been loaded. */ @@ -196,7 +202,9 @@ FB_CLASS_EXPORT FB_SUBCLASSING_RESTRICTED Represents the Facebook ad star rating, which contains the rating value and rating scale. */ FB_EXPORT struct FBAdStarRating { + /// The value of the star rating, X in X/5 CGFloat value; + // The total possible star rating, Y in 4/Y NSInteger scale; } FBAdStarRating; diff --git a/iphone/Maps/3party/FBAudienceNetwork.framework/Headers/FBNativeAdCollectionViewAdProvider.h b/iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK/FBAudienceNetwork.framework/Headers/FBNativeAdCollectionViewAdProvider.h similarity index 99% rename from iphone/Maps/3party/FBAudienceNetwork.framework/Headers/FBNativeAdCollectionViewAdProvider.h rename to iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK/FBAudienceNetwork.framework/Headers/FBNativeAdCollectionViewAdProvider.h index 8ec901d5e2a..e18d37c803e 100644 --- a/iphone/Maps/3party/FBAudienceNetwork.framework/Headers/FBNativeAdCollectionViewAdProvider.h +++ b/iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK/FBAudienceNetwork.framework/Headers/FBNativeAdCollectionViewAdProvider.h @@ -28,7 +28,7 @@ NS_ASSUME_NONNULL_BEGIN /** Additional functionality on top of FBNativeAdsManager to assist in using native ads within a UICollectionView. This class contains a mechanism to map indexPaths to native ads in a stable manner as well as helpers which assist in doing the math to include ads at a regular interval within a collection view. */ -FB_CLASS_EXPORT FB_SUBCLASSING_RESTRICTED +FB_CLASS_EXPORT @interface FBNativeAdCollectionViewAdProvider : NSObject /** diff --git a/iphone/Maps/3party/FBAudienceNetwork.framework/Headers/FBNativeAdCollectionViewCellProvider.h b/iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK/FBAudienceNetwork.framework/Headers/FBNativeAdCollectionViewCellProvider.h similarity index 100% rename from iphone/Maps/3party/FBAudienceNetwork.framework/Headers/FBNativeAdCollectionViewCellProvider.h rename to iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK/FBAudienceNetwork.framework/Headers/FBNativeAdCollectionViewCellProvider.h diff --git a/iphone/Maps/3party/FBAudienceNetwork.framework/Headers/FBNativeAdScrollView.h b/iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK/FBAudienceNetwork.framework/Headers/FBNativeAdScrollView.h similarity index 100% rename from iphone/Maps/3party/FBAudienceNetwork.framework/Headers/FBNativeAdScrollView.h rename to iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK/FBAudienceNetwork.framework/Headers/FBNativeAdScrollView.h diff --git a/iphone/Maps/3party/FBAudienceNetwork.framework/Headers/FBNativeAdTableViewAdProvider.h b/iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK/FBAudienceNetwork.framework/Headers/FBNativeAdTableViewAdProvider.h similarity index 99% rename from iphone/Maps/3party/FBAudienceNetwork.framework/Headers/FBNativeAdTableViewAdProvider.h rename to iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK/FBAudienceNetwork.framework/Headers/FBNativeAdTableViewAdProvider.h index 0466df6787f..fbc17504e0a 100644 --- a/iphone/Maps/3party/FBAudienceNetwork.framework/Headers/FBNativeAdTableViewAdProvider.h +++ b/iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK/FBAudienceNetwork.framework/Headers/FBNativeAdTableViewAdProvider.h @@ -28,7 +28,7 @@ NS_ASSUME_NONNULL_BEGIN /** Additional functionality on top of FBNativeAdsManager to assist in using native ads within a UITableView. This class contains a mechanism to map indexPaths to native ads in a stable manner as well as helpers which assist in doing the math to include ads at a regular interval within a table view. */ -FB_CLASS_EXPORT FB_SUBCLASSING_RESTRICTED +FB_CLASS_EXPORT @interface FBNativeAdTableViewAdProvider : NSObject /** diff --git a/iphone/Maps/3party/FBAudienceNetwork.framework/Headers/FBNativeAdTableViewCellProvider.h b/iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK/FBAudienceNetwork.framework/Headers/FBNativeAdTableViewCellProvider.h similarity index 100% rename from iphone/Maps/3party/FBAudienceNetwork.framework/Headers/FBNativeAdTableViewCellProvider.h rename to iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK/FBAudienceNetwork.framework/Headers/FBNativeAdTableViewCellProvider.h diff --git a/iphone/Maps/3party/FBAudienceNetwork.framework/Headers/FBNativeAdView.h b/iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK/FBAudienceNetwork.framework/Headers/FBNativeAdView.h similarity index 94% rename from iphone/Maps/3party/FBAudienceNetwork.framework/Headers/FBNativeAdView.h rename to iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK/FBAudienceNetwork.framework/Headers/FBNativeAdView.h index 976fa19389b..323e3b8e968 100644 --- a/iphone/Maps/3party/FBAudienceNetwork.framework/Headers/FBNativeAdView.h +++ b/iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK/FBAudienceNetwork.framework/Headers/FBNativeAdView.h @@ -26,14 +26,17 @@ NS_ASSUME_NONNULL_BEGIN @class FBNativeAdViewAttributes; /** - @enum FBNativeAdViewType enum - Determines the type of native ad template. Different views are created + Determines the type of native ad template. Different views are created for different values of FBNativeAdViewType */ typedef NS_ENUM(NSInteger, FBNativeAdViewType) { + /// Fixed height view, 100 points (banner equivalent) FBNativeAdViewTypeGenericHeight100 = 1, + /// Fixed height view, 120 points (banner equivalent) FBNativeAdViewTypeGenericHeight120, + /// Fixed height view, 300 points FBNativeAdViewTypeGenericHeight300, + /// Fixed height view, 400 points FBNativeAdViewTypeGenericHeight400, }; diff --git a/iphone/Maps/3party/FBAudienceNetwork.framework/Headers/FBNativeAdsManager.h b/iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK/FBAudienceNetwork.framework/Headers/FBNativeAdsManager.h similarity index 100% rename from iphone/Maps/3party/FBAudienceNetwork.framework/Headers/FBNativeAdsManager.h rename to iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK/FBAudienceNetwork.framework/Headers/FBNativeAdsManager.h diff --git a/iphone/Maps/3party/FBAudienceNetwork.framework/Headers/FBRewardedVideoAd.h b/iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK/FBAudienceNetwork.framework/Headers/FBRewardedVideoAd.h similarity index 99% rename from iphone/Maps/3party/FBAudienceNetwork.framework/Headers/FBRewardedVideoAd.h rename to iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK/FBAudienceNetwork.framework/Headers/FBRewardedVideoAd.h index 72a84118f18..51c48b0bf7f 100644 --- a/iphone/Maps/3party/FBAudienceNetwork.framework/Headers/FBRewardedVideoAd.h +++ b/iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK/FBAudienceNetwork.framework/Headers/FBRewardedVideoAd.h @@ -118,8 +118,6 @@ FB_CLASS_EXPORT FB_SUBCLASSING_RESTRICTED @end /** - @protocol - The methods declared by the FBRewardedVideoAdDelegate protocol allow the adopting delegate to respond to messages from the FBRewardedVideoAd class and thus respond to operations such as whether the ad has been loaded, the person has clicked the ad or closed video/end card. diff --git a/iphone/Maps/3party/FBAudienceNetwork.framework/Info.plist b/iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK/FBAudienceNetwork.framework/Info.plist similarity index 56% rename from iphone/Maps/3party/FBAudienceNetwork.framework/Info.plist rename to iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK/FBAudienceNetwork.framework/Info.plist index 34ae0b0fae5..15de85f04d9 100644 Binary files a/iphone/Maps/3party/FBAudienceNetwork.framework/Info.plist and b/iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK/FBAudienceNetwork.framework/Info.plist differ diff --git a/iphone/Maps/3party/FBAudienceNetwork.framework/Modules/module.modulemap b/iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK/FBAudienceNetwork.framework/Modules/module.modulemap similarity index 100% rename from iphone/Maps/3party/FBAudienceNetwork.framework/Modules/module.modulemap rename to iphone/Maps/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK/FBAudienceNetwork.framework/Modules/module.modulemap diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Banners/MPBannerAdManager.h b/iphone/Maps/3party/MoPubSDK/Internal/Banners/MPBannerAdManager.h new file mode 100644 index 00000000000..40dbdcc579a --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Banners/MPBannerAdManager.h @@ -0,0 +1,26 @@ +// +// MPBannerAdManager.h +// MoPub +// +// Copyright (c) 2013 MoPub. All rights reserved. +// + +#import +#import "MPAdServerCommunicator.h" +#import "MPBaseBannerAdapter.h" + +@protocol MPBannerAdManagerDelegate; + +@interface MPBannerAdManager : NSObject + +@property (nonatomic, weak) id delegate; + +- (id)initWithDelegate:(id)delegate; + +- (void)loadAd; +- (void)forceRefreshAd; +- (void)stopAutomaticallyRefreshingContents; +- (void)startAutomaticallyRefreshingContents; +- (void)rotateToOrientation:(UIInterfaceOrientation)orientation; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Banners/MPBannerAdManager.m b/iphone/Maps/3party/MoPubSDK/Internal/Banners/MPBannerAdManager.m new file mode 100644 index 00000000000..73d6fa19c65 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Banners/MPBannerAdManager.m @@ -0,0 +1,363 @@ +// +// MPBannerAdManager.m +// MoPub +// +// Copyright (c) 2013 MoPub. All rights reserved. +// + +#import "MPBannerAdManager.h" +#import "MPAdServerURLBuilder.h" +#import "MPInstanceProvider.h" +#import "MPCoreInstanceProvider.h" +#import "MPBannerAdManagerDelegate.h" +#import "MPError.h" +#import "MPTimer.h" +#import "MPConstants.h" +#import "MPLogging.h" + +@interface MPBannerAdManager () + +@property (nonatomic, strong) MPAdServerCommunicator *communicator; +@property (nonatomic, strong) MPBaseBannerAdapter *onscreenAdapter; +@property (nonatomic, strong) MPBaseBannerAdapter *requestingAdapter; +@property (nonatomic, strong) UIView *requestingAdapterAdContentView; +@property (nonatomic, strong) MPAdConfiguration *requestingConfiguration; +@property (nonatomic, strong) MPTimer *refreshTimer; +@property (nonatomic, assign) BOOL adActionInProgress; +@property (nonatomic, assign) BOOL automaticallyRefreshesContents; +@property (nonatomic, assign) BOOL hasRequestedAtLeastOneAd; +@property (nonatomic, assign) UIInterfaceOrientation currentOrientation; + +- (void)loadAdWithURL:(NSURL *)URL; +- (void)applicationWillEnterForeground; +- (void)scheduleRefreshTimer; +- (void)refreshTimerDidFire; + +@end + +@implementation MPBannerAdManager + +@synthesize delegate = _delegate; +@synthesize communicator = _communicator; +@synthesize onscreenAdapter = _onscreenAdapter; +@synthesize requestingAdapter = _requestingAdapter; +@synthesize refreshTimer = _refreshTimer; +@synthesize adActionInProgress = _adActionInProgress; +@synthesize currentOrientation = _currentOrientation; + +- (id)initWithDelegate:(id)delegate +{ + self = [super init]; + if (self) { + self.delegate = delegate; + + self.communicator = [[MPCoreInstanceProvider sharedProvider] buildMPAdServerCommunicatorWithDelegate:self]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(applicationWillEnterForeground) + name:UIApplicationWillEnterForegroundNotification + object:[UIApplication sharedApplication]]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(applicationDidEnterBackground) + name:UIApplicationDidEnterBackgroundNotification + object:[UIApplication sharedApplication]]; + + self.automaticallyRefreshesContents = YES; + self.currentOrientation = MPInterfaceOrientation(); + } + return self; +} + +- (void)dealloc +{ + [[NSNotificationCenter defaultCenter] removeObserver:self]; + + [self.communicator cancel]; + [self.communicator setDelegate:nil]; + + [self.refreshTimer invalidate]; + + [self.onscreenAdapter unregisterDelegate]; + + [self.requestingAdapter unregisterDelegate]; + +} + +- (BOOL)loading +{ + return self.communicator.loading || self.requestingAdapter; +} + +- (void)loadAd +{ + if (!self.hasRequestedAtLeastOneAd) { + self.hasRequestedAtLeastOneAd = YES; + } + + if (self.loading) { + MPLogWarn(@"Banner view (%@) is already loading an ad. Wait for previous load to finish.", [self.delegate adUnitId]); + return; + } + + [self loadAdWithURL:nil]; +} + +- (void)forceRefreshAd +{ + [self loadAdWithURL:nil]; +} + +- (void)applicationWillEnterForeground +{ + if (self.automaticallyRefreshesContents && self.hasRequestedAtLeastOneAd) { + [self loadAdWithURL:nil]; + } +} + +- (void)applicationDidEnterBackground +{ + [self pauseRefreshTimer]; +} + +- (void)pauseRefreshTimer +{ + if ([self.refreshTimer isValid]) { + [self.refreshTimer pause]; + } +} + +- (void)stopAutomaticallyRefreshingContents +{ + self.automaticallyRefreshesContents = NO; + + [self pauseRefreshTimer]; +} + +- (void)startAutomaticallyRefreshingContents +{ + self.automaticallyRefreshesContents = YES; + + if ([self.refreshTimer isValid]) { + [self.refreshTimer resume]; + } else if (self.refreshTimer) { + [self scheduleRefreshTimer]; + } +} + +- (void)loadAdWithURL:(NSURL *)URL +{ + URL = [URL copy]; //if this is the URL from the requestingConfiguration, it's about to die... + // Cancel the current request/requesting adapter + self.requestingConfiguration = nil; + [self.requestingAdapter unregisterDelegate]; + self.requestingAdapter = nil; + self.requestingAdapterAdContentView = nil; + + [self.communicator cancel]; + + URL = (URL) ? URL : [MPAdServerURLBuilder URLWithAdUnitID:[self.delegate adUnitId] + keywords:[self.delegate keywords] + location:[self.delegate location] + testing:[self.delegate isTesting]]; + + MPLogInfo(@"Banner view (%@) loading ad with MoPub server URL: %@", [self.delegate adUnitId], URL); + + [self.communicator loadURL:URL]; +} + +- (void)rotateToOrientation:(UIInterfaceOrientation)orientation +{ + self.currentOrientation = orientation; + [self.requestingAdapter rotateToOrientation:orientation]; + [self.onscreenAdapter rotateToOrientation:orientation]; +} + +#pragma mark - Internal + +- (void)scheduleRefreshTimer +{ + [self.refreshTimer invalidate]; + NSTimeInterval timeInterval = self.requestingConfiguration ? self.requestingConfiguration.refreshInterval : DEFAULT_BANNER_REFRESH_INTERVAL; + + if (timeInterval > 0) { + self.refreshTimer = [[MPCoreInstanceProvider sharedProvider] buildMPTimerWithTimeInterval:timeInterval + target:self + selector:@selector(refreshTimerDidFire) + repeats:NO]; + [self.refreshTimer scheduleNow]; + MPLogDebug(@"Scheduled the autorefresh timer to fire in %.1f seconds (%p).", timeInterval, self.refreshTimer); + } +} + +- (void)refreshTimerDidFire +{ + if (!self.loading && self.automaticallyRefreshesContents) { + [self loadAd]; + } +} + +#pragma mark - + +- (void)communicatorDidReceiveAdConfiguration:(MPAdConfiguration *)configuration +{ + self.requestingConfiguration = configuration; + + MPLogInfo(@"Banner ad view is fetching ad network type: %@", self.requestingConfiguration.networkType); + + if (configuration.adType == MPAdTypeUnknown) { + [self didFailToLoadAdapterWithError:[MOPUBError errorWithCode:MOPUBErrorServerError]]; + return; + } + + if (configuration.adType == MPAdTypeInterstitial) { + MPLogWarn(@"Could not load ad: banner object received an interstitial ad unit ID."); + [self didFailToLoadAdapterWithError:[MOPUBError errorWithCode:MOPUBErrorAdapterInvalid]]; + return; + } + + if (configuration.adUnitWarmingUp) { + MPLogInfo(kMPWarmingUpErrorLogFormatWithAdUnitID, self.delegate.adUnitId); + [self didFailToLoadAdapterWithError:[MOPUBError errorWithCode:MOPUBErrorAdUnitWarmingUp]]; + return; + } + + if ([configuration.networkType isEqualToString:kAdTypeClear]) { + MPLogInfo(kMPClearErrorLogFormatWithAdUnitID, self.delegate.adUnitId); + [self didFailToLoadAdapterWithError:[MOPUBError errorWithCode:MOPUBErrorNoInventory]]; + return; + } + + self.requestingAdapter = [[MPInstanceProvider sharedProvider] buildBannerAdapterForConfiguration:configuration + delegate:self]; + if (!self.requestingAdapter) { + [self loadAdWithURL:self.requestingConfiguration.failoverURL]; + return; + } + + [self.requestingAdapter _getAdWithConfiguration:configuration containerSize:self.delegate.containerSize]; +} + +- (void)communicatorDidFailWithError:(NSError *)error +{ + [self didFailToLoadAdapterWithError:error]; +} + +- (void)didFailToLoadAdapterWithError:(NSError *)error +{ + [self.delegate managerDidFailToLoadAd]; + [self scheduleRefreshTimer]; + + MPLogError(@"Banner view (%@) failed. Error: %@", [self.delegate adUnitId], error); +} + +#pragma mark - + +- (MPAdView *)banner +{ + return [self.delegate banner]; +} + +- (id)bannerDelegate +{ + return [self.delegate bannerDelegate]; +} + +- (UIViewController *)viewControllerForPresentingModalView +{ + return [self.delegate viewControllerForPresentingModalView]; +} + +- (MPNativeAdOrientation)allowedNativeAdsOrientation +{ + return [self.delegate allowedNativeAdsOrientation]; +} + +- (CLLocation *)location +{ + return [self.delegate location]; +} + +- (BOOL)requestingAdapterIsReadyToBePresented +{ + return !!self.requestingAdapterAdContentView; +} + +- (void)presentRequestingAdapter +{ + if (!self.adActionInProgress && self.requestingAdapterIsReadyToBePresented) { + [self.onscreenAdapter unregisterDelegate]; + self.onscreenAdapter = self.requestingAdapter; + self.requestingAdapter = nil; + + [self.onscreenAdapter rotateToOrientation:self.currentOrientation]; + [self.delegate managerDidLoadAd:self.requestingAdapterAdContentView]; + [self.onscreenAdapter didDisplayAd]; + + self.requestingAdapterAdContentView = nil; + [self scheduleRefreshTimer]; + } +} + +- (void)adapter:(MPBaseBannerAdapter *)adapter didFinishLoadingAd:(UIView *)ad +{ + if (self.requestingAdapter == adapter) { + self.requestingAdapterAdContentView = ad; + [self presentRequestingAdapter]; + } +} + +- (void)adapter:(MPBaseBannerAdapter *)adapter didFailToLoadAdWithError:(NSError *)error +{ + if (self.requestingAdapter == adapter) { + [self loadAdWithURL:self.requestingConfiguration.failoverURL]; + } + + if (self.onscreenAdapter == adapter) { + // the onscreen adapter has failed. we need to: + // 1) remove it + // 2) tell the delegate + // 3) and note that there can't possibly be a modal on display any more + [self.delegate managerDidFailToLoadAd]; + [self.delegate invalidateContentView]; + [self.onscreenAdapter unregisterDelegate]; + self.onscreenAdapter = nil; + if (self.adActionInProgress) { + [self.delegate userActionDidFinish]; + self.adActionInProgress = NO; + } + if (self.requestingAdapterIsReadyToBePresented) { + [self presentRequestingAdapter]; + } else { + [self loadAd]; + } + } +} + +- (void)userActionWillBeginForAdapter:(MPBaseBannerAdapter *)adapter +{ + if (self.onscreenAdapter == adapter) { + self.adActionInProgress = YES; + [self.delegate userActionWillBegin]; + } +} + +- (void)userActionDidFinishForAdapter:(MPBaseBannerAdapter *)adapter +{ + if (self.onscreenAdapter == adapter) { + [self.delegate userActionDidFinish]; + self.adActionInProgress = NO; + [self presentRequestingAdapter]; + } +} + +- (void)userWillLeaveApplicationFromAdapter:(MPBaseBannerAdapter *)adapter +{ + if (self.onscreenAdapter == adapter) { + [self.delegate userWillLeaveApplication]; + } +} + +@end + + diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Banners/MPBannerAdManagerDelegate.h b/iphone/Maps/3party/MoPubSDK/Internal/Banners/MPBannerAdManagerDelegate.h new file mode 100644 index 00000000000..406d99dbbb3 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Banners/MPBannerAdManagerDelegate.h @@ -0,0 +1,33 @@ +// +// MPBannerAdManagerDelegate.h +// MoPub +// +// Copyright (c) 2013 MoPub. All rights reserved. +// + +#import + +@class MPAdView; +@protocol MPAdViewDelegate; + +@protocol MPBannerAdManagerDelegate + +- (NSString *)adUnitId; +- (MPNativeAdOrientation)allowedNativeAdsOrientation; +- (MPAdView *)banner; +- (id)bannerDelegate; +- (CGSize)containerSize; +- (NSString *)keywords; +- (CLLocation *)location; +- (BOOL)isTesting; +- (UIViewController *)viewControllerForPresentingModalView; + +- (void)invalidateContentView; + +- (void)managerDidLoadAd:(UIView *)ad; +- (void)managerDidFailToLoadAd; +- (void)userActionWillBegin; +- (void)userActionDidFinish; +- (void)userWillLeaveApplication; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Banners/MPBannerCustomEventAdapter.h b/iphone/Maps/3party/MoPubSDK/Internal/Banners/MPBannerCustomEventAdapter.h new file mode 100644 index 00000000000..d8c5dea8b77 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Banners/MPBannerCustomEventAdapter.h @@ -0,0 +1,16 @@ +// +// MPBannerCustomEventAdapter.h +// MoPub +// +// Copyright (c) 2012 MoPub, Inc. All rights reserved. +// + +#import "MPBaseBannerAdapter.h" + +#import "MPPrivateBannerCustomEventDelegate.h" + +@class MPBannerCustomEvent; + +@interface MPBannerCustomEventAdapter : MPBaseBannerAdapter + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Banners/MPBannerCustomEventAdapter.m b/iphone/Maps/3party/MoPubSDK/Internal/Banners/MPBannerCustomEventAdapter.m new file mode 100644 index 00000000000..e3a35ffe27a --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Banners/MPBannerCustomEventAdapter.m @@ -0,0 +1,141 @@ +// +// MPBannerCustomEventAdapter.m +// MoPub +// +// Copyright (c) 2012 MoPub, Inc. All rights reserved. +// + +#import "MPBannerCustomEventAdapter.h" + +#import "MPAdConfiguration.h" +#import "MPBannerCustomEvent.h" +#import "MPInstanceProvider.h" +#import "MPLogging.h" + +@interface MPBannerCustomEventAdapter () + +@property (nonatomic, strong) MPBannerCustomEvent *bannerCustomEvent; +@property (nonatomic, strong) MPAdConfiguration *configuration; +@property (nonatomic, assign) BOOL hasTrackedImpression; +@property (nonatomic, assign) BOOL hasTrackedClick; + +- (void)trackClickOnce; + +@end + +@implementation MPBannerCustomEventAdapter +@synthesize hasTrackedImpression = _hasTrackedImpression; +@synthesize hasTrackedClick = _hasTrackedClick; + +- (void)unregisterDelegate +{ + if ([self.bannerCustomEvent respondsToSelector:@selector(invalidate)]) { + // Secret API to allow us to detach the custom event from (shared instance) routers synchronously + [self.bannerCustomEvent performSelector:@selector(invalidate)]; + } + self.bannerCustomEvent.delegate = nil; + + // make sure the custom event isn't released synchronously as objects owned by the custom event + // may do additional work after a callback that results in unregisterDelegate being called + [[MPCoreInstanceProvider sharedProvider] keepObjectAliveForCurrentRunLoopIteration:_bannerCustomEvent]; + + [super unregisterDelegate]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)getAdWithConfiguration:(MPAdConfiguration *)configuration containerSize:(CGSize)size +{ + MPLogInfo(@"Looking for custom event class named %@.", configuration.customEventClass); + self.configuration = configuration; + + self.bannerCustomEvent = [[MPInstanceProvider sharedProvider] buildBannerCustomEventFromCustomClass:configuration.customEventClass + delegate:self]; + if (self.bannerCustomEvent) { + [self.bannerCustomEvent requestAdWithSize:size customEventInfo:configuration.customEventClassData]; + } else { + [self.delegate adapter:self didFailToLoadAdWithError:nil]; + } +} + +- (void)rotateToOrientation:(UIInterfaceOrientation)newOrientation +{ + [self.bannerCustomEvent rotateToOrientation:newOrientation]; +} + +- (void)didDisplayAd +{ + if ([self.bannerCustomEvent enableAutomaticImpressionAndClickTracking] && !self.hasTrackedImpression) { + self.hasTrackedImpression = YES; + [self trackImpression]; + } + + [self.bannerCustomEvent didDisplayAd]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma mark - MPPrivateBannerCustomEventDelegate + +- (NSString *)adUnitId +{ + return [self.delegate banner].adUnitId; +} + +- (UIViewController *)viewControllerForPresentingModalView +{ + return [self.delegate viewControllerForPresentingModalView]; +} + +- (id)bannerDelegate +{ + return [self.delegate bannerDelegate]; +} + +- (CLLocation *)location +{ + return [self.delegate location]; +} + +- (void)bannerCustomEvent:(MPBannerCustomEvent *)event didLoadAd:(UIView *)ad +{ + [self didStopLoading]; + if (ad) { + [self.delegate adapter:self didFinishLoadingAd:ad]; + } else { + [self.delegate adapter:self didFailToLoadAdWithError:nil]; + } +} + +- (void)bannerCustomEvent:(MPBannerCustomEvent *)event didFailToLoadAdWithError:(NSError *)error +{ + [self didStopLoading]; + [self.delegate adapter:self didFailToLoadAdWithError:error]; +} + +- (void)bannerCustomEventWillBeginAction:(MPBannerCustomEvent *)event +{ + [self trackClickOnce]; + [self.delegate userActionWillBeginForAdapter:self]; +} + +- (void)bannerCustomEventDidFinishAction:(MPBannerCustomEvent *)event +{ + [self.delegate userActionDidFinishForAdapter:self]; +} + +- (void)bannerCustomEventWillLeaveApplication:(MPBannerCustomEvent *)event +{ + [self trackClickOnce]; + [self.delegate userWillLeaveApplicationFromAdapter:self]; +} + +- (void)trackClickOnce +{ + if ([self.bannerCustomEvent enableAutomaticImpressionAndClickTracking] && !self.hasTrackedClick) { + self.hasTrackedClick = YES; + [self trackClick]; + } +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Banners/MPBaseBannerAdapter.h b/iphone/Maps/3party/MoPubSDK/Internal/Banners/MPBaseBannerAdapter.h new file mode 100644 index 00000000000..bc978eafce7 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Banners/MPBaseBannerAdapter.h @@ -0,0 +1,83 @@ +// +// MPBaseBannerAdapter.h +// MoPub +// +// Created by Nafis Jamal on 1/19/11. +// Copyright 2011 MoPub, Inc. All rights reserved. +// + +#import +#import +#import "MPAdView.h" + +@protocol MPBannerAdapterDelegate; +@class MPAdConfiguration; + +@interface MPBaseBannerAdapter : NSObject +{ + id __weak _delegate; +} + +@property (nonatomic, weak) id delegate; +@property (nonatomic, copy) NSURL *impressionTrackingURL; +@property (nonatomic, copy) NSURL *clickTrackingURL; + +- (id)initWithDelegate:(id)delegate; + +/* + * Sets the adapter's delegate to nil. + */ +- (void)unregisterDelegate; + +/* + * -_getAdWithConfiguration creates a strong reference to self before calling + * -getAdWithConfiguration to prevent the adapter from being prematurely deallocated. + */ +- (void)getAdWithConfiguration:(MPAdConfiguration *)configuration containerSize:(CGSize)size; +- (void)_getAdWithConfiguration:(MPAdConfiguration *)configuration containerSize:(CGSize)size; + +- (void)didStopLoading; +- (void)didDisplayAd; + +/* + * Your subclass should implement this method if your native ads vary depending on orientation. + */ +- (void)rotateToOrientation:(UIInterfaceOrientation)newOrientation; + +- (void)trackImpression; + +- (void)trackClick; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@protocol MPBannerAdapterDelegate + +@required + +- (MPAdView *)banner; +- (id)bannerDelegate; +- (UIViewController *)viewControllerForPresentingModalView; +- (MPNativeAdOrientation)allowedNativeAdsOrientation; +- (CLLocation *)location; + +/* + * These callbacks notify you that the adapter (un)successfully loaded an ad. + */ +- (void)adapter:(MPBaseBannerAdapter *)adapter didFailToLoadAdWithError:(NSError *)error; +- (void)adapter:(MPBaseBannerAdapter *)adapter didFinishLoadingAd:(UIView *)ad; + +/* + * These callbacks notify you that the user interacted (or stopped interacting) with the native ad. + */ +- (void)userActionWillBeginForAdapter:(MPBaseBannerAdapter *)adapter; +- (void)userActionDidFinishForAdapter:(MPBaseBannerAdapter *)adapter; + +/* + * This callback notifies you that user has tapped on an ad which will cause them to leave the + * current application (e.g. the ad action opens the iTunes store, Mobile Safari, etc). + */ +- (void)userWillLeaveApplicationFromAdapter:(MPBaseBannerAdapter *)adapter; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Banners/MPBaseBannerAdapter.m b/iphone/Maps/3party/MoPubSDK/Internal/Banners/MPBaseBannerAdapter.m new file mode 100644 index 00000000000..6520564d940 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Banners/MPBaseBannerAdapter.m @@ -0,0 +1,123 @@ +// +// MPBaseBannerAdapter.m +// MoPub +// +// Created by Nafis Jamal on 1/19/11. +// Copyright 2011 MoPub, Inc. All rights reserved. +// + +#import "MPBaseBannerAdapter.h" +#import "MPConstants.h" + +#import "MPAdConfiguration.h" +#import "MPLogging.h" +#import "MPCoreInstanceProvider.h" +#import "MPAnalyticsTracker.h" +#import "MPTimer.h" + +@interface MPBaseBannerAdapter () + +@property (nonatomic, strong) MPAdConfiguration *configuration; +@property (nonatomic, strong) MPTimer *timeoutTimer; + +- (void)startTimeoutTimer; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation MPBaseBannerAdapter + +@synthesize delegate = _delegate; +@synthesize configuration = _configuration; +@synthesize timeoutTimer = _timeoutTimer; + +- (id)initWithDelegate:(id)delegate +{ + if (self = [super init]) { + self.delegate = delegate; + } + return self; +} + +- (void)dealloc +{ + [self unregisterDelegate]; + [self.timeoutTimer invalidate]; +} + +- (void)unregisterDelegate +{ + self.delegate = nil; +} + +#pragma mark - Requesting Ads + +- (void)getAdWithConfiguration:(MPAdConfiguration *)configuration containerSize:(CGSize)size +{ + // To be implemented by subclasses. + [self doesNotRecognizeSelector:_cmd]; +} + +- (void)_getAdWithConfiguration:(MPAdConfiguration *)configuration containerSize:(CGSize)size +{ + self.configuration = configuration; + + [self startTimeoutTimer]; + + MPBaseBannerAdapter *strongSelf = self; + [strongSelf getAdWithConfiguration:configuration containerSize:size]; +} + +- (void)didStopLoading +{ + [self.timeoutTimer invalidate]; +} + +- (void)didDisplayAd +{ + [self trackImpression]; +} + +- (void)startTimeoutTimer +{ + NSTimeInterval timeInterval = (self.configuration && self.configuration.adTimeoutInterval >= 0) ? + self.configuration.adTimeoutInterval : BANNER_TIMEOUT_INTERVAL; + + if (timeInterval > 0) { + self.timeoutTimer = [[MPCoreInstanceProvider sharedProvider] buildMPTimerWithTimeInterval:timeInterval + target:self + selector:@selector(timeout) + repeats:NO]; + + [self.timeoutTimer scheduleNow]; + } +} + +- (void)timeout +{ + [self.delegate adapter:self didFailToLoadAdWithError:nil]; +} + +#pragma mark - Rotation + +- (void)rotateToOrientation:(UIInterfaceOrientation)newOrientation +{ + // Do nothing by default. Subclasses can override. + MPLogDebug(@"rotateToOrientation %d called for adapter %@ (%p)", + newOrientation, NSStringFromClass([self class]), self); +} + +#pragma mark - Metrics + +- (void)trackImpression +{ + [[[MPCoreInstanceProvider sharedProvider] sharedMPAnalyticsTracker] trackImpressionForConfiguration:self.configuration]; +} + +- (void)trackClick +{ + [[[MPCoreInstanceProvider sharedProvider] sharedMPAnalyticsTracker] trackClickForConfiguration:self.configuration]; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Banners/MPPrivateBannerCustomEventDelegate.h b/iphone/Maps/3party/MoPubSDK/Internal/Banners/MPPrivateBannerCustomEventDelegate.h new file mode 100644 index 00000000000..ed97e6ccfe6 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Banners/MPPrivateBannerCustomEventDelegate.h @@ -0,0 +1,19 @@ +// +// MPPrivateBannerCustomEventDelegate.h +// MoPub +// +// Copyright (c) 2013 MoPub. All rights reserved. +// + +#import +#import "MPBannerCustomEventDelegate.h" + +@class MPAdConfiguration; + +@protocol MPPrivateBannerCustomEventDelegate + +- (NSString *)adUnitId; +- (MPAdConfiguration *)configuration; +- (id)bannerDelegate; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Common/AdAlerts/MPAdAlertGestureRecognizer.h b/iphone/Maps/3party/MoPubSDK/Internal/Common/AdAlerts/MPAdAlertGestureRecognizer.h new file mode 100644 index 00000000000..2f34ebddd43 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Common/AdAlerts/MPAdAlertGestureRecognizer.h @@ -0,0 +1,32 @@ +// +// MPAdAlertGestureRecognizer.h +// MoPub +// +// Copyright (c) 2013 MoPub. All rights reserved. +// + +#import + +extern NSInteger const kMPAdAlertGestureMaxAllowedYAxisMovement; + +typedef enum +{ + MPAdAlertGestureRecognizerState_ZigRight1, + MPAdAlertGestureRecognizerState_ZagLeft2, + MPAdAlertGestureRecognizerState_Recognized +} MPAdAlertGestureRecognizerState; + +@interface MPAdAlertGestureRecognizer : UIGestureRecognizer + +// default is 4 +@property (nonatomic, assign) NSInteger numZigZagsForRecognition; + +// default is 100 +@property (nonatomic, assign) CGFloat minTrackedDistanceForZigZag; + +@property (nonatomic, readonly) MPAdAlertGestureRecognizerState currentAlertGestureState; +@property (nonatomic, readonly) CGPoint inflectionPoint; +@property (nonatomic, readonly) BOOL thresholdReached; +@property (nonatomic, readonly) NSInteger curNumZigZags; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Common/AdAlerts/MPAdAlertGestureRecognizer.m b/iphone/Maps/3party/MoPubSDK/Internal/Common/AdAlerts/MPAdAlertGestureRecognizer.m new file mode 100644 index 00000000000..30ab00002f8 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Common/AdAlerts/MPAdAlertGestureRecognizer.m @@ -0,0 +1,229 @@ +// +// MPAdAlertGestureRecognizer.m +// MoPub +// +// Copyright (c) 2013 MoPub. All rights reserved. +// + +#import "MPAdAlertGestureRecognizer.h" + +#import + +#define kMaxRequiredTrackedDistance 100 +#define kDefaultMinTrackedDistance 100 +#define kDefaultNumZigZagsForRecognition 4 + +NSInteger const kMPAdAlertGestureMaxAllowedYAxisMovement = 50; + +@interface MPAdAlertGestureRecognizer () + +@property (nonatomic, assign) MPAdAlertGestureRecognizerState currentAlertGestureState; +@property (nonatomic, assign) CGPoint inflectionPoint; +@property (nonatomic, assign) CGPoint startingPoint; +@property (nonatomic, assign) BOOL thresholdReached; +@property (nonatomic, assign) NSInteger curNumZigZags; + +@end + +@implementation MPAdAlertGestureRecognizer + +@synthesize currentAlertGestureState = _currentAlertGestureState; +@synthesize inflectionPoint = _inflectionPoint; +@synthesize thresholdReached = _thresholdReached; +@synthesize curNumZigZags = _curNumZigZags; +@synthesize numZigZagsForRecognition = _numZigZagsForRecognition; +@synthesize minTrackedDistanceForZigZag = _minTrackedDistanceForZigZag; + +- (id)init +{ + self = [super init]; + if (self != nil) { + [self commonInit]; + } + + return self; +} + +- (id)initWithTarget:(id)target action:(SEL)action +{ + self = [super initWithTarget:target action:action]; + if (self != nil) { + [self commonInit]; + } + + return self; +} + +- (void)commonInit +{ + self.minTrackedDistanceForZigZag = kDefaultMinTrackedDistance; + self.numZigZagsForRecognition = kDefaultNumZigZagsForRecognition; + [self resetToInitialState]; +} + +- (void)setMinTrackedDistanceForZigZag:(CGFloat)minTrackedDistanceForZigZag +{ + if (_minTrackedDistanceForZigZag != minTrackedDistanceForZigZag) { + _minTrackedDistanceForZigZag = MIN(minTrackedDistanceForZigZag, kMaxRequiredTrackedDistance); + } +} + +#pragma mark Required Overrides + +- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event +{ + [super touchesBegan:touches withEvent:event]; + if ([touches count] != 1) { + self.state = UIGestureRecognizerStateFailed; + return; + } + + CGPoint nowPoint = [touches.anyObject locationInView:self.view]; + self.inflectionPoint = nowPoint; + self.startingPoint = nowPoint; +} + +- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event +{ + [super touchesMoved:touches withEvent:event]; + + if (self.state == UIGestureRecognizerStateFailed) { + return; + } + + [self updateAlertGestureStateWithTouches:touches]; +} + +- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event +{ + [super touchesEnded:touches withEvent:event]; + + if ((self.state == UIGestureRecognizerStatePossible) && self.currentAlertGestureState == MPAdAlertGestureRecognizerState_Recognized) { + self.state = UIGestureRecognizerStateRecognized; + } + + [self resetToInitialState]; +} + +- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event +{ + [super touchesCancelled:touches withEvent:event]; + + [self resetToInitialState]; + + self.state = UIGestureRecognizerStateFailed; +} + +- (void)reset +{ + [super reset]; + + [self resetToInitialState]; +} + +- (void)resetToInitialState +{ + self.currentAlertGestureState = MPAdAlertGestureRecognizerState_ZigRight1; + self.inflectionPoint = CGPointZero; + self.startingPoint = CGPointZero; + self.thresholdReached = NO; + self.curNumZigZags = 0; +} + +#pragma mark State Transitions + +- (void)handleZigRightGestureStateWithTouches:(NSSet *)touches +{ + CGPoint nowPoint = [touches.anyObject locationInView:self.view]; + CGPoint prevPoint = [touches.anyObject previousLocationInView:self.view]; + + // first zig must be to the right, x must increase + // if the touch has covered enough distance, then we're ready to move on to the next state + if (nowPoint.x > prevPoint.x && nowPoint.x - self.inflectionPoint.x >= self.minTrackedDistanceForZigZag) { + self.thresholdReached = YES; + } else if (nowPoint.x < prevPoint.x) { + // user has changed touch direction + if (self.thresholdReached) { + self.inflectionPoint = nowPoint; + self.currentAlertGestureState = MPAdAlertGestureRecognizerState_ZagLeft2; + self.thresholdReached = NO; + } else { + // the user changed directions before covering the required distance, fail + self.state = UIGestureRecognizerStateFailed; + } + } + // else remain in the current state and continue tracking finger movement +} + +- (void)handleZagLeftGestureStateWithTouches:(NSSet *)touches +{ + CGPoint nowPoint = [touches.anyObject locationInView:self.view]; + CGPoint prevPoint = [touches.anyObject previousLocationInView:self.view]; + + // zag to the left, x must decrease + // if the touch has covered enough distance, then we're ready to move on to the next state + if (nowPoint.x < prevPoint.x && self.inflectionPoint.x - nowPoint.x >= self.minTrackedDistanceForZigZag) { + BOOL prevThresholdState = self.thresholdReached; + self.thresholdReached = YES; + + // increment once, and only once, after we hit the threshold for the zag + if (prevThresholdState != self.thresholdReached) { + self.curNumZigZags++; + } + + if (self.curNumZigZags >= self.numZigZagsForRecognition) { + self.currentAlertGestureState = MPAdAlertGestureRecognizerState_Recognized; + } + } else if (nowPoint.x > prevPoint.x) { + // user has changed touch direction + if (self.thresholdReached) { + self.inflectionPoint = nowPoint; + self.currentAlertGestureState = MPAdAlertGestureRecognizerState_ZigRight1; + self.thresholdReached = NO; + } else { + // the user changed directions before covering the required distance, fail + self.state = UIGestureRecognizerStateFailed; + } + } + // else remain in the current state and continue tracking finger movement +} + +- (void)updateAlertGestureStateWithTouches:(NSSet *)touches +{ + // fail gesture recognition if the touch moves outside of our defined bounds + if (![self touchIsWithinBoundsForTouches:touches] && self.currentAlertGestureState != MPAdAlertGestureRecognizerState_Recognized) { + self.state = UIGestureRecognizerStateFailed; + return; + } + + switch (self.currentAlertGestureState) { + case MPAdAlertGestureRecognizerState_ZigRight1: + [self handleZigRightGestureStateWithTouches:touches]; + + break; + case MPAdAlertGestureRecognizerState_ZagLeft2: + [self handleZagLeftGestureStateWithTouches:touches]; + + break; + default: + break; + } +} + +- (BOOL)validYAxisMovementForTouches:(NSSet *)touches +{ + CGPoint nowPoint = [touches.anyObject locationInView:self.view]; + + return fabs(nowPoint.y - self.startingPoint.y) <= kMPAdAlertGestureMaxAllowedYAxisMovement; +} + +- (BOOL)touchIsWithinBoundsForTouches:(NSSet *)touches +{ + CGPoint nowPoint = [touches.anyObject locationInView:self.view]; + + // 1. use self.view.bounds because locationInView converts to self.view's coordinate system + // 2. ensure user doesn't stray too far in the Y-axis + return CGRectContainsPoint(self.view.bounds, nowPoint) && [self validYAxisMovementForTouches:touches]; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Common/AdAlerts/MPAdAlertManager.h b/iphone/Maps/3party/MoPubSDK/Internal/Common/AdAlerts/MPAdAlertManager.h new file mode 100644 index 00000000000..0caa34cca34 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Common/AdAlerts/MPAdAlertManager.h @@ -0,0 +1,30 @@ +// +// MPAdAlertManager.h +// MoPub +// +// Copyright (c) 2013 MoPub. All rights reserved. +// + +#import +#import +#import "MPGlobal.h" + +@class CLLocation; +@protocol MPAdAlertManagerDelegate; + +@class MPAdConfiguration; + +@interface MPAdAlertManager : NSObject + +@end + +@protocol MPAdAlertManagerDelegate + +@required +- (UIViewController *)viewControllerForPresentingMailVC; +- (void)adAlertManagerDidTriggerAlert:(MPAdAlertManager *)manager; + +@optional +- (void)adAlertManagerDidProcessAlert:(MPAdAlertManager *)manager; + +@end \ No newline at end of file diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Common/AdAlerts/MPAdAlertManager.m b/iphone/Maps/3party/MoPubSDK/Internal/Common/AdAlerts/MPAdAlertManager.m new file mode 100644 index 00000000000..7342fde46a0 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Common/AdAlerts/MPAdAlertManager.m @@ -0,0 +1,231 @@ +// +// MPAdAlertManager.m +// MoPub +// +// Copyright (c) 2013 MoPub. All rights reserved. +// + +#import "MPAdAlertManager.h" +#import "MPAdConfiguration.h" +#import "MPAdAlertGestureRecognizer.h" +#import "MPLogging.h" +#import "MPIdentityProvider.h" +#import "MPCoreInstanceProvider.h" +#import "MPLastResortDelegate.h" + +#import +#import +#import + +#define kTimestampParamKey @"timestamp" + +@interface MPAdAlertManager () + +@property (nonatomic, assign) BOOL processedAlert; +@property (nonatomic, strong) MPAdAlertGestureRecognizer *adAlertGestureRecognizer; +@property (nonatomic, strong) MFMailComposeViewController *currentOpenMailVC; + +@end + +@implementation MPAdAlertManager + +@synthesize delegate = _delegate; +@synthesize adConfiguration = _adConfiguration; +@synthesize processedAlert = _processedAlert; +@synthesize adAlertGestureRecognizer = _adAlertGestureRecognizer; +@synthesize adUnitId = _adUnitId; +@synthesize targetAdView = _targetAdView; +@synthesize location = _location; +@synthesize currentOpenMailVC = _currentOpenMailVC; + +- (id)init +{ + self = [super init]; + if (self != nil) { + self.adAlertGestureRecognizer = [[MPCoreInstanceProvider sharedProvider] buildMPAdAlertGestureRecognizerWithTarget:self + action:@selector(handleAdAlertGesture)]; + self.adAlertGestureRecognizer.delegate = self; + self.processedAlert = NO; + } + + return self; +} + +- (void)dealloc +{ + [_targetAdView removeGestureRecognizer:_adAlertGestureRecognizer]; + [_adAlertGestureRecognizer removeTarget:self action:nil]; + _adAlertGestureRecognizer.delegate = nil; + _currentOpenMailVC.mailComposeDelegate = [MPLastResortDelegate sharedDelegate]; +} + +- (void)processAdAlert +{ + static NSDateFormatter *dateFormatter = nil; + + MPLogInfo(@"MPAdAlertManager processing ad alert"); + + // don't even try if this device can't send emails + if (![MFMailComposeViewController canSendMail]) { + if ([self.delegate respondsToSelector:@selector(adAlertManagerDidProcessAlert:)]) { + [self.delegate adAlertManagerDidProcessAlert:self]; + } + + return; + } + + // since iOS 4, drawing an image to a graphics context is thread-safe + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + // take screenshot of the ad + UIGraphicsBeginImageContextWithOptions(self.targetAdView.bounds.size, YES, 0.0); + [self.targetAdView.layer renderInContext:UIGraphicsGetCurrentContext()]; + + UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + + dispatch_async(dispatch_get_main_queue(), ^{ + // package additional ad data + NSMutableDictionary *params = [NSMutableDictionary dictionary]; + + [params setValue:@"iOS" forKey:@"platform"]; + [params setValue:[UIDevice currentDevice].systemVersion forKey:@"platform_version"]; + [params setValue:[MPIdentityProvider identifier] forKey:@"device_id"]; + [params setValue:[UIDevice currentDevice].model forKey:@"device_model"]; + [params setValue:[NSNumber numberWithInteger:self.adConfiguration.adType] forKey:@"ad_type"]; + [params setValue:self.adUnitId forKey:@"ad_unit_id"]; + [params setValue:self.adConfiguration.dspCreativeId forKey:@"creative_id"]; + [params setValue:self.adConfiguration.networkType forKey:@"network_type"]; + [params setValue:[[NSLocale currentLocale] localeIdentifier] forKey:@"device_locale"]; + [params setValue:[self.location description] forKey:@"location"]; + [params setValue:MP_SDK_VERSION forKey:@"sdk_version"]; + + if (self.adConfiguration.hasPreferredSize) { + [params setValue:NSStringFromCGSize(self.adConfiguration.preferredSize) forKey:@"ad_size"]; + } + + if (dateFormatter == nil) { + dateFormatter = [[NSDateFormatter alloc] init]; + [dateFormatter setTimeStyle:NSDateFormatterLongStyle]; + [dateFormatter setDateStyle:NSDateFormatterShortStyle]; + } + [params setValue:[dateFormatter stringFromDate:self.adConfiguration.creationTimestamp] forKey:kTimestampParamKey]; + + [self processAdParams:params andScreenshot:image]; + + MPLogInfo(@"MPAdAlertManager finished processing ad alert"); + }); + }); +} + +- (void)handleAdAlertGesture +{ + MPLogInfo(@"MPAdAlertManager alert gesture recognized"); + + [self.delegate adAlertManagerDidTriggerAlert:self]; +} + +- (void)processAdParams:(NSDictionary *)params andScreenshot:(UIImage *)screenshot +{ + NSData *imageData = UIImagePNGRepresentation(screenshot); + NSData *paramData =[[self stringFromDictionary:params] dataUsingEncoding:NSUTF8StringEncoding]; + NSData *markupData = self.adConfiguration.adResponseData; + + self.currentOpenMailVC = [[MFMailComposeViewController alloc] init]; + self.currentOpenMailVC.mailComposeDelegate = self; + + [self.currentOpenMailVC setToRecipients:[NSArray arrayWithObject:@"creative-review@mopub.com"]]; + [self.currentOpenMailVC setSubject:[NSString stringWithFormat:@"New creative violation report - %@", [params objectForKey:kTimestampParamKey]]]; + [self.currentOpenMailVC setMessageBody:@"" isHTML:YES]; + + if (imageData != nil) { + [self.currentOpenMailVC addAttachmentData:imageData mimeType:@"image/png" fileName:@"mp_adalert_screenshot.png"]; + } + + if (paramData != nil) { + [self.currentOpenMailVC addAttachmentData:paramData mimeType:@"text/plain" fileName:@"mp_adalert_parameters.txt"]; + } + + if (markupData != nil) { + [self.currentOpenMailVC addAttachmentData:markupData mimeType:@"text/html" fileName:@"mp_adalert_markup.html"]; + } + + [[self.delegate viewControllerForPresentingMailVC] presentViewController:self.currentOpenMailVC animated:MP_ANIMATED completion:nil]; + + if ([self.delegate respondsToSelector:@selector(adAlertManagerDidProcessAlert:)]) { + [self.delegate adAlertManagerDidProcessAlert:self]; + } +} + +// could just use [dictionary description], but this gives us more control over the output +- (NSString *)stringFromDictionary:(NSDictionary *)dictionary +{ + NSMutableString *result = [NSMutableString string]; + + for (NSString *key in [dictionary allKeys]) { + [result appendFormat:@"%@ : %@\n", key, [dictionary objectForKey:key]]; + } + + return result; +} + +#pragma mark - + +- (void)mailComposeController:(MFMailComposeViewController*)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError*)error +{ + self.currentOpenMailVC = nil; + + // reset processed state to allow the user to alert on this ad again + if (result == MFMailComposeResultCancelled || result == MFMailComposeResultFailed) { + self.processedAlert = NO; + } + + [[self.delegate viewControllerForPresentingMailVC] dismissViewControllerAnimated:MP_ANIMATED completion:nil]; +} + +#pragma mark - Public + +- (void)beginMonitoringAlerts +{ + [self endMonitoringAlerts]; + + [self.targetAdView addGestureRecognizer:self.adAlertGestureRecognizer]; + + // dynamically set minimum tracking distance to account for all ad sizes + self.adAlertGestureRecognizer.minTrackedDistanceForZigZag = self.targetAdView.bounds.size.width / 3; + + self.processedAlert = NO; +} + +- (void)endMonitoringAlerts +{ + [self.targetAdView removeGestureRecognizer:self.adAlertGestureRecognizer]; +} + +- (void)processAdAlertOnce +{ + if (self.processedAlert) { + return; + } + + self.processedAlert = YES; + + [self processAdAlert]; +} + +#pragma mark - + +- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch +{ + if ([touch.view isKindOfClass:[UIButton class]]) { + // we touched a button + return NO; // ignore the touch + } + return YES; // handle the touch +} + +- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer; +{ + return YES; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Common/MPAPIEndpoints.h b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPAPIEndpoints.h new file mode 100644 index 00000000000..0b53eb3f74b --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPAPIEndpoints.h @@ -0,0 +1,24 @@ +// +// MPAPIEndpoints.h +// MoPub +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import + +#define MOPUB_BASE_HOSTNAME @"ads.mopub.com" +#define MOPUB_BASE_HOSTNAME_FOR_TESTING @"testing.ads.mopub.com" + +#define MOPUB_API_PATH_AD_REQUEST @"/m/ad" +#define MOPUB_API_PATH_CONVERSION @"/m/open" +#define MOPUB_API_PATH_NATIVE_POSITIONING @"/m/pos" +#define MOPUB_API_PATH_SESSION @"/m/open" + +@interface MPAPIEndpoints : NSObject + ++ (void)setUsesHTTPS:(BOOL)usesHTTPS; ++ (NSString *)baseURL; ++ (NSString *)baseURLStringWithPath:(NSString *)path testing:(BOOL)testing; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Common/MPAPIEndpoints.m b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPAPIEndpoints.m new file mode 100644 index 00000000000..7ec01768304 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPAPIEndpoints.m @@ -0,0 +1,43 @@ +// +// MPAPIEndpoints.m +// MoPub +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import "MPAPIEndpoints.h" +#import "MPConstants.h" +#import "MPCoreInstanceProvider.h" + +@implementation MPAPIEndpoints + +static BOOL sUsesHTTPS = YES; + ++ (void)setUsesHTTPS:(BOOL)usesHTTPS +{ + sUsesHTTPS = usesHTTPS; +} + ++ (NSString *)baseURL +{ + if ([[MPCoreInstanceProvider sharedProvider] appTransportSecuritySettings] == MPATSSettingEnabled) { + return [@"https://" stringByAppendingString:MOPUB_BASE_HOSTNAME]; + } + + return [@"http://" stringByAppendingString:MOPUB_BASE_HOSTNAME]; +} + ++ (NSString *)baseURLScheme +{ + return sUsesHTTPS ? @"https://" : @"http://"; +} + ++ (NSString *)baseURLStringWithPath:(NSString *)path testing:(BOOL)testing +{ + return [NSString stringWithFormat:@"%@%@%@", + [[self class] baseURLScheme], + testing ? MOPUB_BASE_HOSTNAME_FOR_TESTING : MOPUB_BASE_HOSTNAME, + path]; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Common/MPActivityViewControllerHelper+TweetShare.h b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPActivityViewControllerHelper+TweetShare.h new file mode 100644 index 00000000000..9d8c7341ade --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPActivityViewControllerHelper+TweetShare.h @@ -0,0 +1,31 @@ +// +// MPActivityViewControllerHelper+TweetShare.h +// MoPubSDK +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import +#import "MPActivityViewControllerHelper.h" + +/** + * `TweetShare` category added to MPActivityViewController to add functionality + * for sharing a tweet. + */ + +@interface MPActivityViewControllerHelper (TweetShare) + +/** + * Present the UIActivityViewController as specified by the + * provided URL. + * + * @param URL Instance of NSURL to be used for generating + * the share sheet. Should be of the format: + * mopubshare://tweet?screen_name=&tweet_id= + * + * @return a BOOL indicating whether or not the tweet share url was successfully shown + */ + +- (BOOL)presentActivityViewControllerWithTweetShareURL:(NSURL *)URL; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Common/MPActivityViewControllerHelper+TweetShare.m b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPActivityViewControllerHelper+TweetShare.m new file mode 100644 index 00000000000..dfcfe70c4a2 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPActivityViewControllerHelper+TweetShare.m @@ -0,0 +1,71 @@ +// +// MPActivityViewControllerHelper+TweetShare.m +// MoPubSDK +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import "MPActivityViewControllerHelper+TweetShare.h" +#import "MPLogging.h" +#import "NSURL+MPAdditions.h" + +static NSString * const kShareTweetScreenName = @"screen_name"; +static NSString * const kShareTweetId = @"tweet_id"; +static NSString * const kShareTweetURLTemplate = @"https://twitter.com/%@/status/%@"; +static NSString * const kShareTweetMessageTemplate = @"Check out @%@'s Tweet: %@"; + +/** + * MPSharedTweet parses an NSURL and stores the specified screenName and tweetURL. + */ + +@interface MPSharedTweet : NSObject + +@property (nonatomic, readonly) NSString *screenName; +@property (nonatomic, readonly) NSString *tweetURL; + +- (instancetype)initWithShareURL:(NSURL *)URL; + +@end + +@implementation MPSharedTweet + +- (instancetype)initWithShareURL:(NSURL *)URL +{ + self = [super init]; + if (self) { + NSDictionary *queryParamDict = [URL mp_queryAsDictionary]; + id screenName = [queryParamDict objectForKey:kShareTweetScreenName]; + id tweetId = [queryParamDict objectForKey:kShareTweetId]; + + // Fail initialization if the provided URL is not of the correct format. + // Both parameters are required. + if (screenName && tweetId) { + _screenName = screenName; + _tweetURL = [NSString stringWithFormat:kShareTweetURLTemplate, screenName, tweetId]; + } else { + MPLogDebug(@"MPActivityViewControllerHelper+TweetShare - \ + unable to initWithShareURL for share URL: %@. \ + screen_name or tweet_id missing or of the wrong \ + format", [URL absoluteString]); + return nil; + } + } + return self; +} + +@end + +@implementation MPActivityViewControllerHelper (TweetShare) + +- (BOOL)presentActivityViewControllerWithTweetShareURL:(NSURL *)URL +{ + MPSharedTweet *sharedTweet = [[MPSharedTweet alloc] initWithShareURL:URL]; + if (sharedTweet) { + NSString *tweetMessage = [NSString stringWithFormat:kShareTweetMessageTemplate, + sharedTweet.screenName, sharedTweet.tweetURL]; + return [self presentActivityViewControllerWithSubject:tweetMessage body:tweetMessage]; + } + return NO; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Common/MPActivityViewControllerHelper.h b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPActivityViewControllerHelper.h new file mode 100644 index 00000000000..8b865316c87 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPActivityViewControllerHelper.h @@ -0,0 +1,80 @@ +// +// MPActivityViewControllerHelper.h +// MoPubSDK +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import + +@protocol MPActivityViewControllerHelperDelegate; + +/** + * The MPActivityViewControllerHelper provides a wrapper around a UIActvityViewController + * and provides hooks via the MPActivityViewControllerHelperDelegate to handle the + * lifecycle of the underlying UIActivityViewController. + */ + +@interface MPActivityViewControllerHelper : NSObject + +/** + * The delegate (`MPActivityViewControllerHelperDelegate`) of the + * MPActivityViewControllerHelper. + */ + +@property (nonatomic, weak) id delegate; + +/** + * Initializes the MPActivityViewControllerHelper and stores a weak reference + * to the supplied delegate. + * + * @param delegate + */ +- (instancetype)initWithDelegate:(id)delegate; + +/** + * Instantiates and displays the underlying UIActivityViewController with the + * the specified `subject` and `body`. + * + * @param subject The subject to be displayed in the UIActivityViewController. + * @param body The body to be displayed in the UIActivityViewController. + * + * @return a BOOL indicating whether or not the UIActivityViewController was successfully shown. + */ +- (BOOL)presentActivityViewControllerWithSubject:(NSString *)subject body:(NSString *)body; + +@end + + +/** + * The delegate of a `MPActivityViewController` must adopt the `MPActivityViewController` + * protocol. It must implement `viewControllerForPresentingActivityViewController` to + * provide a root view controller from which to display content. + * + * Optional methods of this protocol allow the delegate to be notified before + * presenting and after dismissal. + */ +@protocol MPActivityViewControllerHelperDelegate + +@required + +/** + * Asks the delegate for a view controller to use for presenting content. + * + * @return A view controller that should be used for presenting content. + */ +- (UIViewController *)viewControllerForPresentingActivityViewController; + +@optional + +/** + * Sent before the UIActivityViewController is presented. + */ +- (void)activityViewControllerWillPresent; + +/** + * Sent after the UIActivityViewController has been dismissed. + */ +- (void)activityViewControllerDidDismiss; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Common/MPActivityViewControllerHelper.m b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPActivityViewControllerHelper.m new file mode 100644 index 00000000000..ea8731c1ef5 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPActivityViewControllerHelper.m @@ -0,0 +1,123 @@ +// +// MPActivityViewControllerHelper.m +// MoPubSDK +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import "MPActivityViewControllerHelper.h" +#import "MPInstanceProvider.h" + + +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 60000 +/** + * MPActivityItemProviderWithSubject subclasses UIActivityItemProvider + * to provide a subject for email activity types. + */ + +@interface MPActivityItemProviderWithSubject : UIActivityItemProvider + +@property (nonatomic, readonly) NSString *subject; +@property (nonatomic, readonly) NSString *body; + +- (instancetype)initWithSubject:(NSString *)subject body:(NSString *)body; + +@end + +@implementation MPActivityItemProviderWithSubject + +- (instancetype)initWithSubject:(NSString *)subject body:(NSString *)body +{ + self = [super initWithPlaceholderItem:body]; + if (self) { + _subject = [subject copy]; + _body = [body copy]; + } + return self; +} + +- (id)item +{ + return self.body; +} + +- (NSString *)activityViewController:(UIActivityViewController *)activityViewController subjectForActivityType:(NSString *)activityType +{ + return self.subject; +} + +@end +#endif + +@interface MPActivityViewControllerHelper() + +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 60000 +- (UIActivityViewController *)initializeActivityViewControllerWithSubject:(NSString *)subject body:(NSString *)body; +#endif + +@end + +@implementation MPActivityViewControllerHelper + +- (instancetype)initWithDelegate:(id)delegate +{ + self = [super init]; + if (self) { + _delegate = delegate; + } + return self; +} + +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 60000 +- (UIActivityViewController *)initializeActivityViewControllerWithSubject:(NSString *)subject body:(NSString *)body +{ + if (NSClassFromString(@"UIActivityViewController") && NSClassFromString(@"UIActivityItemProvider")) { + MPActivityItemProviderWithSubject *activityItemProvider = + [[MPActivityItemProviderWithSubject alloc] initWithSubject:subject body:body]; + UIActivityViewController *activityViewController = + [[UIActivityViewController alloc] initWithActivityItems:@[activityItemProvider] applicationActivities:nil]; + activityViewController.completionHandler = ^ + (NSString* activityType, BOOL completed) { + if ([self.delegate respondsToSelector:@selector(activityViewControllerDidDismiss)]) { + [self.delegate activityViewControllerDidDismiss]; + } + }; + return activityViewController; + } else { + return nil; + } +} +#endif + +- (BOOL)presentActivityViewControllerWithSubject:(NSString *)subject body:(NSString *)body +{ +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 60000 + if (NSClassFromString(@"UIActivityViewController")) { + UIActivityViewController *activityViewController = [self initializeActivityViewControllerWithSubject:subject body:body]; + if (activityViewController) { + if ([self.delegate respondsToSelector:@selector(activityViewControllerWillPresent)]) { + [self.delegate activityViewControllerWillPresent]; + } +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 + UIUserInterfaceIdiom userInterfaceIdiom = [[[MPCoreInstanceProvider sharedProvider] + sharedCurrentDevice] userInterfaceIdiom]; + // iPad must present as popover on iOS >= 8 + if (userInterfaceIdiom == UIUserInterfaceIdiomPad) { + if ([activityViewController respondsToSelector:@selector(popoverPresentationController)]) { + activityViewController.popoverPresentationController.sourceView = + [self.delegate viewControllerForPresentingActivityViewController].view; + } + } +#endif + UIViewController *viewController = [self.delegate viewControllerForPresentingActivityViewController]; + [viewController presentViewController:activityViewController + animated:YES + completion:nil]; + return YES; + } + } +#endif + return NO; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Common/MPAdBrowserController.h b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPAdBrowserController.h new file mode 100644 index 00000000000..931d5c2a7c9 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPAdBrowserController.h @@ -0,0 +1,65 @@ +// +// MPAdBrowserController.h +// MoPub +// +// Created by Nafis Jamal on 1/19/11. +// Copyright 2011 MoPub, Inc. All rights reserved. +// + +#import +#import "MPWebView.h" + +#ifndef CF_RETURNS_RETAINED +#if __has_feature(attribute_cf_returns_retained) +#define CF_RETURNS_RETAINED __attribute__((cf_returns_retained)) +#else +#define CF_RETURNS_RETAINED +#endif +#endif + +@class MPAdConfiguration; + +@protocol MPAdBrowserControllerDelegate; + +@interface MPAdBrowserController : UIViewController + +@property (nonatomic, strong) IBOutlet MPWebView *webView; +@property (nonatomic, strong) IBOutlet UIBarButtonItem *backButton; +@property (nonatomic, strong) IBOutlet UIBarButtonItem *forwardButton; +@property (nonatomic, strong) IBOutlet UIBarButtonItem *refreshButton; +@property (nonatomic, strong) IBOutlet UIBarButtonItem *safariButton; +@property (nonatomic, strong) IBOutlet UIBarButtonItem *doneButton; +@property (nonatomic, strong) IBOutlet UIBarButtonItem *spinnerItem; +@property (nonatomic, strong) UIActivityIndicatorView *spinner; + +@property (nonatomic, weak) id delegate; +@property (nonatomic, copy) NSURL *URL; + +- (instancetype)initWithURL:(NSURL *)URL HTMLString:(NSString *)HTMLString delegate:(id)delegate; +- (instancetype)initWithURL:(NSURL *)URL delegate:(id)delegate; + +// Navigation methods. +- (IBAction)back; +- (IBAction)forward; +- (IBAction)refresh; +- (IBAction)safari; +- (IBAction)done; + +// Drawing methods. +- (CGContextRef)createContext CF_RETURNS_RETAINED; +- (UIImage *)backArrowImage; +- (UIImage *)forwardArrowImage; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@protocol MPAdBrowserControllerDelegate + +- (void)dismissBrowserController:(MPAdBrowserController *)browserController animated:(BOOL)animated; + +@optional + +- (MPAdConfiguration *)adConfiguration; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Common/MPAdBrowserController.m b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPAdBrowserController.m new file mode 100644 index 00000000000..2392c7ccd3f --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPAdBrowserController.m @@ -0,0 +1,354 @@ +// +// MPAdBrowserController.m +// MoPub +// +// Created by Nafis Jamal on 1/19/11. +// Copyright 2011 MoPub, Inc. All rights reserved. +// + +#import "MPAdBrowserController.h" +#import "MPLogging.h" +#import "MPLogEvent.h" +#import "MPLogEventRecorder.h" +#import "MPAdConfiguration.h" +#import "MPAPIEndPoints.h" +#import "NSBundle+MPAdditions.h" + +static NSString * const kAdBrowserControllerNibName = @"MPAdBrowserController"; + +@interface MPAdBrowserController () + +@property (nonatomic, strong) UIActionSheet *actionSheet; +@property (nonatomic, strong) NSString *HTMLString; +@property (nonatomic, assign) int webViewLoadCount; +@property (nonatomic) MPLogEvent *dwellEvent; +@property (nonatomic) BOOL hasAppeared; + +- (void)dismissActionSheet; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation MPAdBrowserController + +@synthesize webView = _webView; +@synthesize backButton = _backButton; +@synthesize forwardButton = _forwardButton; +@synthesize refreshButton = _refreshButton; +@synthesize safariButton = _safariButton; +@synthesize doneButton = _doneButton; +@synthesize spinnerItem = _spinnerItem; +@synthesize spinner = _spinner; +@synthesize actionSheet = _actionSheet; +@synthesize delegate = _delegate; +@synthesize URL = _URL; +@synthesize webViewLoadCount = _webViewLoadCount; +@synthesize HTMLString = _HTMLString; + +#pragma mark - +#pragma mark Lifecycle + +- (instancetype)initWithURL:(NSURL *)URL HTMLString:(NSString *)HTMLString delegate:(id)delegate +{ + if (self = [super initWithNibName:kAdBrowserControllerNibName bundle:[NSBundle resourceBundleForClass:self.class]]) + { + self.delegate = delegate; + self.URL = URL; + self.HTMLString = HTMLString; + + MPLogDebug(@"Ad browser (%p) initialized with URL: %@", self, self.URL); + + self.webView = [[MPWebView alloc] initWithFrame:CGRectZero]; + self.webView.autoresizingMask = UIViewAutoresizingFlexibleWidth | + UIViewAutoresizingFlexibleHeight; + + self.spinner = [[UIActivityIndicatorView alloc] initWithFrame:CGRectZero]; + [self.spinner sizeToFit]; + self.spinner.hidesWhenStopped = YES; + + self.webViewLoadCount = 0; + + _hasAppeared = NO; + } + return self; +} + +- (instancetype)initWithURL:(NSURL *)URL delegate:(id)delegate { + return [self initWithURL:URL + HTMLString:nil + delegate:delegate]; +} + +- (void)dealloc +{ + self.webView.delegate = nil; +} + +- (void)viewDidLoad +{ + [super viewDidLoad]; + + // Set web view delegate + self.webView.delegate = self; + self.webView.scalesPageToFit = YES; + + // Set up toolbar buttons + self.backButton.image = [self backArrowImage]; + self.backButton.title = nil; + self.forwardButton.image = [self forwardArrowImage]; + self.forwardButton.title = nil; + self.spinnerItem.customView = self.spinner; + self.spinnerItem.title = nil; +} + +- (void)viewWillAppear:(BOOL)animated +{ + [super viewWillAppear:animated]; + + // Set button enabled status. + self.backButton.enabled = self.webView.canGoBack; + self.forwardButton.enabled = self.webView.canGoForward; + self.refreshButton.enabled = NO; + self.safariButton.enabled = NO; +} + +- (void)viewDidAppear:(BOOL)animated +{ + [super viewDidAppear:animated]; + + // Track when this view first appears so we can log the time the user stays in the view controller. Creating the event will mark the start of the dwell time. + // Make sure we don't create the event twice. + if (!self.hasAppeared) { + self.dwellEvent = [[MPLogEvent alloc] initWithEventCategory:MPLogEventCategoryAdInteractions eventName:MPLogEventNameClickthroughDwellTime]; + } + + self.hasAppeared = YES; + + NSURL *baseURL = (self.URL != nil) ? self.URL : [NSURL URLWithString:[MPAPIEndpoints baseURL]]; + + if (self.HTMLString) { + [self.webView loadHTMLString:self.HTMLString baseURL:baseURL]; + } else { + [self.webView loadRequest:[NSURLRequest requestWithURL:self.URL]]; + } +} + +- (void)viewWillDisappear:(BOOL)animated +{ + [self.webView stopLoading]; + [super viewWillDisappear:animated]; +} + +#pragma mark - Hidding status bar (iOS 7 and above) + +- (BOOL)prefersStatusBarHidden +{ + return YES; +} + +#pragma mark - +#pragma mark Navigation + +- (IBAction)refresh +{ + [self dismissActionSheet]; + [self.webView reload]; +} + +- (IBAction)done +{ + [self dismissActionSheet]; + if (self.delegate) { + [self.delegate dismissBrowserController:self animated:MP_ANIMATED]; + } else { + [self dismissViewControllerAnimated:MP_ANIMATED completion:nil]; + } + + if ([self.delegate respondsToSelector:@selector(adConfiguration)]) { + MPAdConfiguration *configuration = [self.delegate adConfiguration]; + + if (configuration) { + MPAdConfigurationLogEventProperties *logProperties = [[MPAdConfigurationLogEventProperties alloc] initWithConfiguration:configuration]; + [self.dwellEvent setLogEventProperties:logProperties]; + [self.dwellEvent recordEndTime]; + + MPAddLogEvent(self.dwellEvent); + } + } +} + +- (IBAction)back +{ + [self dismissActionSheet]; + [self.webView goBack]; + self.backButton.enabled = self.webView.canGoBack; + self.forwardButton.enabled = self.webView.canGoForward; +} + +- (IBAction)forward +{ + [self dismissActionSheet]; + [self.webView goForward]; + self.backButton.enabled = self.webView.canGoBack; + self.forwardButton.enabled = self.webView.canGoForward; +} + +- (IBAction)safari +{ + if (self.actionSheet) { + [self dismissActionSheet]; + } else { + self.actionSheet = [[UIActionSheet alloc] initWithTitle:nil + delegate:self + cancelButtonTitle:@"Cancel" + destructiveButtonTitle:nil + otherButtonTitles:@"Open in Safari", nil]; + + if ([UIActionSheet instancesRespondToSelector:@selector(showFromBarButtonItem:animated:)]) { + [self.actionSheet showFromBarButtonItem:self.safariButton animated:YES]; + } else { + [self.actionSheet showInView:self.webView]; + } + } +} + +- (void)dismissActionSheet +{ + [self.actionSheet dismissWithClickedButtonIndex:0 animated:YES]; + +} + +#pragma mark - +#pragma mark UIActionSheetDelegate + +- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex +{ + self.actionSheet = nil; + if (buttonIndex == 0) { + // Open in Safari. + [[UIApplication sharedApplication] openURL:self.URL]; + } +} + +#pragma mark - +#pragma mark MPWebViewDelegate + +- (BOOL)webView:(MPWebView *)webView +shouldStartLoadWithRequest:(NSURLRequest *)request + navigationType:(UIWebViewNavigationType)navigationType +{ + MPLogDebug(@"Ad browser (%p) starting to load URL: %@", self, request.URL); + self.URL = request.URL; + + BOOL appShouldOpenURL = ![self.URL.scheme isEqualToString:@"http"] && ![self.URL.scheme isEqualToString:@"https"]; + + if (appShouldOpenURL) { + [[UIApplication sharedApplication] openURL:self.URL]; + } + + return !appShouldOpenURL; +} + +- (void)webViewDidStartLoad:(MPWebView *)webView +{ + self.refreshButton.enabled = YES; + self.safariButton.enabled = YES; + [self.spinner startAnimating]; + + self.webViewLoadCount++; +} + +- (void)webViewDidFinishLoad:(MPWebView *)webView +{ + self.webViewLoadCount--; + if (self.webViewLoadCount > 0) return; + + self.refreshButton.enabled = YES; + self.safariButton.enabled = YES; + self.backButton.enabled = self.webView.canGoBack; + self.forwardButton.enabled = self.webView.canGoForward; + [self.spinner stopAnimating]; +} + +- (void)webView:(MPWebView *)webView didFailLoadWithError:(NSError *)error +{ + self.webViewLoadCount--; + + self.refreshButton.enabled = YES; + self.safariButton.enabled = YES; + self.backButton.enabled = self.webView.canGoBack; + self.forwardButton.enabled = self.webView.canGoForward; + [self.spinner stopAnimating]; + + // Ignore NSURLErrorDomain error (-999). + if (error.code == NSURLErrorCancelled) return; + + // Ignore "Frame Load Interrupted" errors after navigating to iTunes or the App Store. + if (error.code == 102 && [error.domain isEqual:@"WebKitErrorDomain"]) return; + + MPLogError(@"Ad browser (%p) experienced an error: %@.", self, [error localizedDescription]); +} + +#pragma mark - +#pragma mark Drawing + +- (CGContextRef)createContext +{ + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + CGContextRef context = CGBitmapContextCreate(nil,27,27,8,0, + colorSpace,(CGBitmapInfo)kCGImageAlphaPremultipliedLast); + CFRelease(colorSpace); + return context; +} + +- (UIImage *)backArrowImage +{ + CGContextRef context = [self createContext]; + CGColorRef fillColor = [[UIColor blackColor] CGColor]; + CGContextSetFillColor(context, CGColorGetComponents(fillColor)); + + CGContextBeginPath(context); + CGContextMoveToPoint(context, 8.0f, 13.0f); + CGContextAddLineToPoint(context, 24.0f, 4.0f); + CGContextAddLineToPoint(context, 24.0f, 22.0f); + CGContextClosePath(context); + CGContextFillPath(context); + + CGImageRef imageRef = CGBitmapContextCreateImage(context); + CGContextRelease(context); + + UIImage *image = [[UIImage alloc] initWithCGImage:imageRef]; + CGImageRelease(imageRef); + return image; +} + +- (UIImage *)forwardArrowImage +{ + CGContextRef context = [self createContext]; + CGColorRef fillColor = [[UIColor blackColor] CGColor]; + CGContextSetFillColor(context, CGColorGetComponents(fillColor)); + + CGContextBeginPath(context); + CGContextMoveToPoint(context, 24.0f, 13.0f); + CGContextAddLineToPoint(context, 8.0f, 4.0f); + CGContextAddLineToPoint(context, 8.0f, 22.0f); + CGContextClosePath(context); + CGContextFillPath(context); + + CGImageRef imageRef = CGBitmapContextCreateImage(context); + CGContextRelease(context); + + UIImage *image = [[UIImage alloc] initWithCGImage:imageRef]; + CGImageRelease(imageRef); + return image; +} + +#pragma mark - + +- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation +{ + return YES; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Common/MPAdBrowserController.xib b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPAdBrowserController.xib new file mode 100644 index 00000000000..661f11519b5 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPAdBrowserController.xib @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Common/MPAdConfiguration.h b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPAdConfiguration.h new file mode 100644 index 00000000000..acbe856dcb3 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPAdConfiguration.h @@ -0,0 +1,104 @@ +// +// MPAdConfiguration.h +// MoPub +// +// Copyright (c) 2012 MoPub, Inc. All rights reserved. +// + +#import +#import "MPGlobal.h" + +@class MPRewardedVideoReward; + +enum { + MPAdTypeUnknown = -1, + MPAdTypeBanner = 0, + MPAdTypeInterstitial = 1 +}; +typedef NSUInteger MPAdType; + +extern NSString * const kAdTypeHeaderKey; +extern NSString * const kAdUnitWarmingUpHeaderKey; +extern NSString * const kClickthroughHeaderKey; +extern NSString * const kCreativeIdHeaderKey; +extern NSString * const kCustomSelectorHeaderKey; +extern NSString * const kCustomEventClassNameHeaderKey; +extern NSString * const kCustomEventClassDataHeaderKey; +extern NSString * const kFailUrlHeaderKey; +extern NSString * const kHeightHeaderKey; +extern NSString * const kImpressionTrackerHeaderKey; +extern NSString * const kInterceptLinksHeaderKey; +extern NSString * const kLaunchpageHeaderKey; +extern NSString * const kNativeSDKParametersHeaderKey; +extern NSString * const kNetworkTypeHeaderKey; +extern NSString * const kRefreshTimeHeaderKey; +extern NSString * const kAdTimeoutHeaderKey; +extern NSString * const kScrollableHeaderKey; +extern NSString * const kWidthHeaderKey; +extern NSString * const kDspCreativeIdKey; +extern NSString * const kPrecacheRequiredKey; +extern NSString * const kIsVastVideoPlayerKey; +extern NSString * const kRewardedVideoCurrencyNameHeaderKey; +extern NSString * const kRewardedVideoCurrencyAmountHeaderKey; +extern NSString * const kRewardedVideoCompletionUrlHeaderKey; +extern NSString * const kRewardedCurrenciesHeaderKey; +extern NSString * const kRewardedPlayableDurationHeaderKey; +extern NSString * const kRewardedPlayableRewardOnClickHeaderKey; + +extern NSString * const kInterstitialAdTypeHeaderKey; +extern NSString * const kOrientationTypeHeaderKey; + +extern NSString * const kAdTypeHtml; +extern NSString * const kAdTypeInterstitial; +extern NSString * const kAdTypeMraid; +extern NSString * const kAdTypeClear; +extern NSString * const kAdTypeNative; +extern NSString * const kAdTypeNativeVideo; + +@interface MPAdConfiguration : NSObject + +@property (nonatomic, assign) MPAdType adType; +@property (nonatomic, assign) BOOL adUnitWarmingUp; +@property (nonatomic, copy) NSString *networkType; +@property (nonatomic, assign) CGSize preferredSize; +@property (nonatomic, strong) NSURL *clickTrackingURL; +@property (nonatomic, strong) NSURL *impressionTrackingURL; +@property (nonatomic, strong) NSURL *failoverURL; +@property (nonatomic, strong) NSURL *interceptURLPrefix; +@property (nonatomic, assign) BOOL shouldInterceptLinks; +@property (nonatomic, assign) BOOL scrollable; +@property (nonatomic, assign) NSTimeInterval refreshInterval; +@property (nonatomic, assign) NSTimeInterval adTimeoutInterval; +@property (nonatomic, copy) NSData *adResponseData; +@property (nonatomic, strong) NSDictionary *nativeSDKParameters; +@property (nonatomic, copy) NSString *customSelectorName; +@property (nonatomic, assign) Class customEventClass; +@property (nonatomic, strong) NSDictionary *customEventClassData; +@property (nonatomic, assign) MPInterstitialOrientationType orientationType; +@property (nonatomic, copy) NSString *dspCreativeId; +@property (nonatomic, assign) BOOL precacheRequired; +@property (nonatomic, assign) BOOL isVastVideoPlayer; +@property (nonatomic, strong) NSDate *creationTimestamp; +@property (nonatomic, copy) NSString *creativeId; +@property (nonatomic, copy) NSString *headerAdType; +@property (nonatomic, assign) NSInteger nativeVideoPlayVisiblePercent; +@property (nonatomic, assign) NSInteger nativeVideoPauseVisiblePercent; +@property (nonatomic, assign) NSInteger nativeVideoImpressionMinVisiblePercent; +@property (nonatomic, assign) NSTimeInterval nativeVideoImpressionVisible; +@property (nonatomic, assign) NSTimeInterval nativeVideoMaxBufferingTime; +@property (nonatomic) NSDictionary *nativeVideoTrackers; +@property (nonatomic, readonly) NSArray *availableRewards; +@property (nonatomic, strong) MPRewardedVideoReward *selectedReward; +@property (nonatomic, copy) NSString *rewardedVideoCompletionUrl; +@property (nonatomic, assign) NSTimeInterval rewardedPlayableDuration; +@property (nonatomic, assign) BOOL rewardedPlayableShouldRewardOnClick; +//TODO: Remove `forceUIWebView` once WKWebView is proven +@property (nonatomic, assign) BOOL forceUIWebView; + +- (id)initWithHeaders:(NSDictionary *)headers data:(NSData *)data; + +- (BOOL)hasPreferredSize; +- (NSString *)adResponseHTMLString; +- (NSString *)clickDetectionURLPrefix; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Common/MPAdConfiguration.m b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPAdConfiguration.m new file mode 100644 index 00000000000..038a3ba4e8e --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPAdConfiguration.m @@ -0,0 +1,466 @@ +// +// MPAdConfiguration.m +// MoPub +// +// Copyright (c) 2012 MoPub, Inc. All rights reserved. +// + +#import "MPAdConfiguration.h" + +#import "MPConstants.h" +#import "MPLogging.h" +#import "math.h" +#import "NSJSONSerialization+MPAdditions.h" +#import "MPRewardedVideoReward.h" +#import "MPVASTTrackingEvent.h" + +NSString * const kAdTypeHeaderKey = @"X-Adtype"; +NSString * const kAdUnitWarmingUpHeaderKey = @"X-Warmup"; +NSString * const kClickthroughHeaderKey = @"X-Clickthrough"; +NSString * const kCreativeIdHeaderKey = @"X-CreativeId"; +NSString * const kCustomSelectorHeaderKey = @"X-Customselector"; +NSString * const kCustomEventClassNameHeaderKey = @"X-Custom-Event-Class-Name"; +NSString * const kCustomEventClassDataHeaderKey = @"X-Custom-Event-Class-Data"; +NSString * const kFailUrlHeaderKey = @"X-Failurl"; +NSString * const kHeightHeaderKey = @"X-Height"; +NSString * const kImpressionTrackerHeaderKey = @"X-Imptracker"; +NSString * const kInterceptLinksHeaderKey = @"X-Interceptlinks"; +NSString * const kLaunchpageHeaderKey = @"X-Launchpage"; +NSString * const kNativeSDKParametersHeaderKey = @"X-Nativeparams"; +NSString * const kNetworkTypeHeaderKey = @"X-Networktype"; +NSString * const kRefreshTimeHeaderKey = @"X-Refreshtime"; +NSString * const kAdTimeoutHeaderKey = @"X-AdTimeout"; +NSString * const kScrollableHeaderKey = @"X-Scrollable"; +NSString * const kWidthHeaderKey = @"X-Width"; +NSString * const kDspCreativeIdKey = @"X-DspCreativeid"; +NSString * const kPrecacheRequiredKey = @"X-PrecacheRequired"; +NSString * const kIsVastVideoPlayerKey = @"X-VastVideoPlayer"; +//TODO: Remove `kForceUIWebViewKey` once WKWebView is proven +NSString * const kForceUIWebViewKey = @"X-ForceUIWebView"; + +NSString * const kInterstitialAdTypeHeaderKey = @"X-Fulladtype"; +NSString * const kOrientationTypeHeaderKey = @"X-Orientation"; + +NSString * const kNativeVideoPlayVisiblePercentHeaderKey = @"X-Play-Visible-Percent"; +NSString * const kNativeVideoPauseVisiblePercentHeaderKey = @"X-Pause-Visible-Percent"; +NSString * const kNativeVideoImpressionMinVisiblePercentHeaderKey = @"X-Impression-Min-Visible-Percent"; +NSString * const kNativeVideoImpressionVisibleMsHeaderKey = @"X-Impression-Visible-Ms"; +NSString * const kNativeVideoMaxBufferingTimeMsHeaderKey = @"X-Max-Buffer-Ms"; +NSString * const kNativeVideoTrackersHeaderKey = @"X-Video-Trackers"; + +NSString * const kAdTypeHtml = @"html"; +NSString * const kAdTypeInterstitial = @"interstitial"; +NSString * const kAdTypeMraid = @"mraid"; +NSString * const kAdTypeClear = @"clear"; +NSString * const kAdTypeNative = @"json"; +NSString * const kAdTypeNativeVideo = @"json_video"; + +// rewarded video +NSString * const kRewardedVideoCurrencyNameHeaderKey = @"X-Rewarded-Video-Currency-Name"; +NSString * const kRewardedVideoCurrencyAmountHeaderKey = @"X-Rewarded-Video-Currency-Amount"; +NSString * const kRewardedVideoCompletionUrlHeaderKey = @"X-Rewarded-Video-Completion-Url"; +NSString * const kRewardedCurrenciesHeaderKey = @"X-Rewarded-Currencies"; + +// rewarded playables +NSString * const kRewardedPlayableDurationHeaderKey = @"X-Rewarded-Duration"; +NSString * const kRewardedPlayableRewardOnClickHeaderKey = @"X-Should-Reward-On-Click"; + +// native video +NSString * const kNativeVideoTrackerUrlMacro = @"%%VIDEO_EVENT%%"; +NSString * const kNativeVideoTrackerEventsHeaderKey = @"events"; +NSString * const kNativeVideoTrackerUrlsHeaderKey = @"urls"; +NSString * const kNativeVideoTrackerEventDictionaryKey = @"event"; +NSString * const kNativeVideoTrackerTextDictionaryKey = @"text"; + + +@interface MPAdConfiguration () + +@property (nonatomic, copy) NSString *adResponseHTMLString; +@property (nonatomic, strong, readwrite) NSArray *availableRewards; + +- (MPAdType)adTypeFromHeaders:(NSDictionary *)headers; +- (NSString *)networkTypeFromHeaders:(NSDictionary *)headers; +- (NSTimeInterval)refreshIntervalFromHeaders:(NSDictionary *)headers; +- (NSDictionary *)dictionaryFromHeaders:(NSDictionary *)headers forKey:(NSString *)key; +- (NSURL *)URLFromHeaders:(NSDictionary *)headers forKey:(NSString *)key; +- (Class)setUpCustomEventClassFromHeaders:(NSDictionary *)headers; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation MPAdConfiguration + +- (id)initWithHeaders:(NSDictionary *)headers data:(NSData *)data +{ + self = [super init]; + if (self) { + self.adResponseData = data; + + self.adType = [self adTypeFromHeaders:headers]; + + self.adUnitWarmingUp = [[headers objectForKey:kAdUnitWarmingUpHeaderKey] boolValue]; + + self.networkType = [self networkTypeFromHeaders:headers]; + self.networkType = self.networkType ? self.networkType : @""; + + self.preferredSize = CGSizeMake([[headers objectForKey:kWidthHeaderKey] floatValue], + [[headers objectForKey:kHeightHeaderKey] floatValue]); + + self.clickTrackingURL = [self URLFromHeaders:headers + forKey:kClickthroughHeaderKey]; + self.impressionTrackingURL = [self URLFromHeaders:headers + forKey:kImpressionTrackerHeaderKey]; + self.failoverURL = [self URLFromHeaders:headers + forKey:kFailUrlHeaderKey]; + self.interceptURLPrefix = [self URLFromHeaders:headers + forKey:kLaunchpageHeaderKey]; + + NSNumber *shouldInterceptLinks = [headers objectForKey:kInterceptLinksHeaderKey]; + self.shouldInterceptLinks = shouldInterceptLinks ? [shouldInterceptLinks boolValue] : YES; + self.scrollable = [[headers objectForKey:kScrollableHeaderKey] boolValue]; + self.refreshInterval = [self refreshIntervalFromHeaders:headers]; + self.adTimeoutInterval = [self timeIntervalFromHeaders:headers forKey:kAdTimeoutHeaderKey]; + + + self.nativeSDKParameters = [self dictionaryFromHeaders:headers + forKey:kNativeSDKParametersHeaderKey]; + self.customSelectorName = [headers objectForKey:kCustomSelectorHeaderKey]; + + self.orientationType = [self orientationTypeFromHeaders:headers]; + + self.customEventClass = [self setUpCustomEventClassFromHeaders:headers]; + + self.customEventClassData = [self customEventClassDataFromHeaders:headers]; + + self.dspCreativeId = [headers objectForKey:kDspCreativeIdKey]; + + self.precacheRequired = [[headers objectForKey:kPrecacheRequiredKey] boolValue]; + + self.isVastVideoPlayer = [[headers objectForKey:kIsVastVideoPlayerKey] boolValue]; + + self.forceUIWebView = [[headers objectForKey:kForceUIWebViewKey] boolValue]; + + self.creationTimestamp = [NSDate date]; + + self.creativeId = [headers objectForKey:kCreativeIdHeaderKey]; + + self.headerAdType = [headers objectForKey:kAdTypeHeaderKey]; + + self.nativeVideoPlayVisiblePercent = [self percentFromHeaders:headers forKey:kNativeVideoPlayVisiblePercentHeaderKey]; + + self.nativeVideoPauseVisiblePercent = [self percentFromHeaders:headers forKey:kNativeVideoPauseVisiblePercentHeaderKey]; + + self.nativeVideoImpressionMinVisiblePercent = [self percentFromHeaders:headers forKey:kNativeVideoImpressionMinVisiblePercentHeaderKey]; + + self.nativeVideoImpressionVisible = [self timeIntervalFromMsHeaders:headers forKey:kNativeVideoImpressionVisibleMsHeaderKey]; + + self.nativeVideoMaxBufferingTime = [self timeIntervalFromMsHeaders:headers forKey:kNativeVideoMaxBufferingTimeMsHeaderKey]; + + self.nativeVideoTrackers = [self nativeVideoTrackersFromHeaders:headers key:kNativeVideoTrackersHeaderKey]; + + + // rewarded video + + // Attempt to parse the multiple currency header first since this will take + // precedence over the older single currency approach. + self.availableRewards = [self parseAvailableRewardsFromHeaders:headers]; + if (self.availableRewards != nil) { + // Multiple currencies exist. We will select the first entry in the list + // as the default selected reward. + if (self.availableRewards.count > 0) { + self.selectedReward = self.availableRewards[0]; + } + // In the event that the list of available currencies is empty, we will + // follow the behavior from the single currency approach and create an unspecified reward. + else { + MPRewardedVideoReward * defaultReward = [[MPRewardedVideoReward alloc] initWithCurrencyType:kMPRewardedVideoRewardCurrencyTypeUnspecified amount:@(kMPRewardedVideoRewardCurrencyAmountUnspecified)]; + self.availableRewards = [NSArray arrayWithObject:defaultReward]; + self.selectedReward = defaultReward; + } + } + // Multiple currencies are not available; attempt to process single currency + // headers. + else { + NSString *currencyName = [headers objectForKey:kRewardedVideoCurrencyNameHeaderKey] ?: kMPRewardedVideoRewardCurrencyTypeUnspecified; + + NSNumber *currencyAmount = [self adAmountFromHeaders:headers key:kRewardedVideoCurrencyAmountHeaderKey]; + if (currencyAmount.integerValue <= 0) { + currencyAmount = @(kMPRewardedVideoRewardCurrencyAmountUnspecified); + } + + MPRewardedVideoReward * reward = [[MPRewardedVideoReward alloc] initWithCurrencyType:currencyName amount:currencyAmount]; + self.availableRewards = [NSArray arrayWithObject:reward]; + self.selectedReward = reward; + } + + self.rewardedVideoCompletionUrl = [headers objectForKey:kRewardedVideoCompletionUrlHeaderKey]; + + // rewarded playables + self.rewardedPlayableDuration = [self timeIntervalFromHeaders:headers forKey:kRewardedPlayableDurationHeaderKey]; + self.rewardedPlayableShouldRewardOnClick = [[headers objectForKey:kRewardedPlayableRewardOnClickHeaderKey] boolValue]; + } + return self; +} + +- (Class)setUpCustomEventClassFromHeaders:(NSDictionary *)headers +{ + NSString *customEventClassName = [headers objectForKey:kCustomEventClassNameHeaderKey]; + + NSMutableDictionary *convertedCustomEvents = [NSMutableDictionary dictionary]; + if (self.adType == MPAdTypeBanner) { + [convertedCustomEvents setObject:@"MPGoogleAdMobBannerCustomEvent" forKey:@"admob_native"]; + [convertedCustomEvents setObject:@"MPMillennialBannerCustomEvent" forKey:@"millennial_native"]; + [convertedCustomEvents setObject:@"MPHTMLBannerCustomEvent" forKey:@"html"]; + [convertedCustomEvents setObject:@"MPMRAIDBannerCustomEvent" forKey:@"mraid"]; + [convertedCustomEvents setObject:@"MOPUBNativeVideoCustomEvent" forKey:@"json_video"]; + [convertedCustomEvents setObject:@"MPMoPubNativeCustomEvent" forKey:@"json"]; + } else if (self.adType == MPAdTypeInterstitial) { + [convertedCustomEvents setObject:@"MPGoogleAdMobInterstitialCustomEvent" forKey:@"admob_full"]; + [convertedCustomEvents setObject:@"MPMillennialInterstitialCustomEvent" forKey:@"millennial_full"]; + [convertedCustomEvents setObject:@"MPHTMLInterstitialCustomEvent" forKey:@"html"]; + [convertedCustomEvents setObject:@"MPMRAIDInterstitialCustomEvent" forKey:@"mraid"]; + [convertedCustomEvents setObject:@"MPMoPubRewardedVideoCustomEvent" forKey:@"rewarded_video"]; + [convertedCustomEvents setObject:@"MPMoPubRewardedPlayableCustomEvent" forKey:@"rewarded_playable"]; + } + if ([convertedCustomEvents objectForKey:self.networkType]) { + customEventClassName = [convertedCustomEvents objectForKey:self.networkType]; + } + + Class customEventClass = NSClassFromString(customEventClassName); + + if (customEventClassName && !customEventClass) { + MPLogWarn(@"Could not find custom event class named %@", customEventClassName); + } + + return customEventClass; +} + + + +- (NSDictionary *)customEventClassDataFromHeaders:(NSDictionary *)headers +{ + NSDictionary *result = [self dictionaryFromHeaders:headers forKey:kCustomEventClassDataHeaderKey]; + if (!result) { + result = [self dictionaryFromHeaders:headers forKey:kNativeSDKParametersHeaderKey]; + } + return result; +} + + +- (BOOL)hasPreferredSize +{ + return (self.preferredSize.width > 0 && self.preferredSize.height > 0); +} + +- (NSString *)adResponseHTMLString +{ + if (!_adResponseHTMLString) { + self.adResponseHTMLString = [[NSString alloc] initWithData:self.adResponseData + encoding:NSUTF8StringEncoding]; + } + + return _adResponseHTMLString; +} + +- (NSString *)clickDetectionURLPrefix +{ + return self.interceptURLPrefix.absoluteString ? self.interceptURLPrefix.absoluteString : @""; +} + +#pragma mark - Private + +- (MPAdType)adTypeFromHeaders:(NSDictionary *)headers +{ + NSString *adTypeString = [headers objectForKey:kAdTypeHeaderKey]; + + if ([adTypeString isEqualToString:@"interstitial"] || [adTypeString isEqualToString:@"rewarded_video"] || [adTypeString isEqualToString:@"rewarded_playable"]) { + return MPAdTypeInterstitial; + } else if (adTypeString && + [headers objectForKey:kOrientationTypeHeaderKey]) { + return MPAdTypeInterstitial; + } else if (adTypeString) { + return MPAdTypeBanner; + } else { + return MPAdTypeUnknown; + } +} + +- (NSString *)networkTypeFromHeaders:(NSDictionary *)headers +{ + NSString *adTypeString = [headers objectForKey:kAdTypeHeaderKey]; + if ([adTypeString isEqualToString:@"interstitial"]) { + return [headers objectForKey:kInterstitialAdTypeHeaderKey]; + } else { + return adTypeString; + } +} + +- (NSURL *)URLFromHeaders:(NSDictionary *)headers forKey:(NSString *)key +{ + NSString *URLString = [headers objectForKey:key]; + return URLString ? [NSURL URLWithString:URLString] : nil; +} + +- (NSDictionary *)dictionaryFromHeaders:(NSDictionary *)headers forKey:(NSString *)key +{ + NSData *data = [(NSString *)[headers objectForKey:key] dataUsingEncoding:NSUTF8StringEncoding]; + NSDictionary *JSONFromHeaders = nil; + if (data) { + JSONFromHeaders = [NSJSONSerialization mp_JSONObjectWithData:data options:NSJSONReadingMutableContainers clearNullObjects:YES error:nil]; + } + return JSONFromHeaders; +} + +- (NSTimeInterval)refreshIntervalFromHeaders:(NSDictionary *)headers +{ + NSString *intervalString = [headers objectForKey:kRefreshTimeHeaderKey]; + NSTimeInterval interval = -1; + if (intervalString) { + interval = [intervalString doubleValue]; + if (interval < MINIMUM_REFRESH_INTERVAL) { + interval = MINIMUM_REFRESH_INTERVAL; + } + } + return interval; +} + +- (NSTimeInterval)timeIntervalFromHeaders:(NSDictionary *)headers forKey:(NSString *)key +{ + NSString *intervalString = [headers objectForKey:key]; + NSTimeInterval interval = -1; + if (intervalString) { + int parsedInt = -1; + BOOL isNumber = [[NSScanner scannerWithString:intervalString] scanInt:&parsedInt]; + if (isNumber && parsedInt >= 0) { + interval = parsedInt; + } + } + + return interval; +} + +- (NSTimeInterval)timeIntervalFromMsHeaders:(NSDictionary *)headers forKey:(NSString *)key +{ + NSString *msString = [headers objectForKey:key]; + NSTimeInterval interval = -1; + if (msString) { + int parsedInt = -1; + BOOL isNumber = [[NSScanner scannerWithString:msString] scanInt:&parsedInt]; + if (isNumber && parsedInt >= 0) { + interval = parsedInt / 1000.0f; + } + } + + return interval; +} + +- (NSInteger)percentFromHeaders:(NSDictionary *)headers forKey:(NSString *)key +{ + NSString *percentString = [headers objectForKey:key]; + NSInteger percent = -1; + if (percentString) { + int parsedInt = -1; + BOOL isNumber = [[NSScanner scannerWithString:percentString] scanInt:&parsedInt]; + if (isNumber && parsedInt >= 0 && parsedInt <= 100) { + percent = parsedInt; + } + } + + return percent; +} + +- (NSNumber *)adAmountFromHeaders:(NSDictionary *)headers key:(NSString *)key +{ + NSString *amountString = [headers objectForKey:key]; + NSNumber *amount = @(-1); + if (amountString) { + int parsedInt = -1; + BOOL isNumber = [[NSScanner scannerWithString:amountString] scanInt:&parsedInt]; + if (isNumber && parsedInt >= 0) { + amount = @(parsedInt); + } + } + + return amount; +} + +- (MPInterstitialOrientationType)orientationTypeFromHeaders:(NSDictionary *)headers +{ + NSString *orientation = [headers objectForKey:kOrientationTypeHeaderKey]; + if ([orientation isEqualToString:@"p"]) { + return MPInterstitialOrientationTypePortrait; + } else if ([orientation isEqualToString:@"l"]) { + return MPInterstitialOrientationTypeLandscape; + } else { + return MPInterstitialOrientationTypeAll; + } +} + +- (NSDictionary *)nativeVideoTrackersFromHeaders:(NSDictionary *)headers key:(NSString *)key +{ + NSDictionary *dictFromHeader = [self dictionaryFromHeaders:headers forKey:key]; + if (!dictFromHeader) { + return nil; + } + NSMutableDictionary *videoTrackerDict = [NSMutableDictionary new]; + NSArray *events = dictFromHeader[kNativeVideoTrackerEventsHeaderKey]; + NSArray *urls = dictFromHeader[kNativeVideoTrackerUrlsHeaderKey]; + NSSet *supportedEvents = [NSSet setWithObjects:MPVASTTrackingEventTypeStart, MPVASTTrackingEventTypeFirstQuartile, MPVASTTrackingEventTypeMidpoint, MPVASTTrackingEventTypeThirdQuartile, MPVASTTrackingEventTypeComplete, nil]; + for (NSString *event in events) { + if (![supportedEvents containsObject:event]) { + continue; + } + [self setVideoTrackers:videoTrackerDict event:event urls:urls]; + } + if (videoTrackerDict.count == 0) { + return nil; + } + return videoTrackerDict; +} + +- (void)setVideoTrackers:(NSMutableDictionary *)videoTrackerDict event:(NSString *)event urls:(NSArray *)urls { + NSMutableArray *trackers = [NSMutableArray new]; + for (NSString *url in urls) { + if ([url rangeOfString:kNativeVideoTrackerUrlMacro].location != NSNotFound) { + NSString *trackerUrl = [url stringByReplacingOccurrencesOfString:kNativeVideoTrackerUrlMacro withString:event]; + NSDictionary *dict = @{kNativeVideoTrackerEventDictionaryKey:event, kNativeVideoTrackerTextDictionaryKey:trackerUrl}; + MPVASTTrackingEvent *tracker = [[MPVASTTrackingEvent alloc] initWithDictionary:dict]; + [trackers addObject:tracker]; + } + } + if (trackers.count > 0) { + videoTrackerDict[event] = trackers; + } +} + +- (NSArray *)parseAvailableRewardsFromHeaders:(NSDictionary *)headers { + // The X-Rewarded-Currencies header key doesn't exist. This is probably + // not a rewarded ad. + NSDictionary * currencies = [self dictionaryFromHeaders:headers forKey:kRewardedCurrenciesHeaderKey]; + if (currencies == nil) { + return nil; + } + + // Either the list of available rewards doesn't exist or is empty. + // This is an error. + NSArray * rewards = [currencies objectForKey:@"rewards"]; + if (rewards.count == 0) { + MPLogError(@"No available rewards found."); + return nil; + } + + // Parse the list of JSON rewards into objects. + NSMutableArray * availableRewards = [NSMutableArray arrayWithCapacity:rewards.count]; + [rewards enumerateObjectsUsingBlock:^(NSDictionary * rewardDict, NSUInteger idx, BOOL * _Nonnull stop) { + NSString * name = rewardDict[@"name"] ?: kMPRewardedVideoRewardCurrencyTypeUnspecified; + NSNumber * amount = rewardDict[@"amount"] ?: @(kMPRewardedVideoRewardCurrencyAmountUnspecified); + + MPRewardedVideoReward * reward = [[MPRewardedVideoReward alloc] initWithCurrencyType:name amount:amount]; + [availableRewards addObject:reward]; + }]; + + return availableRewards; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Common/MPAdDestinationDisplayAgent.h b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPAdDestinationDisplayAgent.h new file mode 100644 index 00000000000..ff7b3fb543b --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPAdDestinationDisplayAgent.h @@ -0,0 +1,41 @@ +// +// MPAdDestinationDisplayAgent.h +// MoPub +// +// Copyright (c) 2013 MoPub. All rights reserved. +// + +#import +#import "MPActivityViewControllerHelper+TweetShare.h" +#import "MPURLResolver.h" +#import "MPProgressOverlayView.h" +#import "MPAdBrowserController.h" +#import "MPStoreKitProvider.h" + +@protocol MPAdDestinationDisplayAgentDelegate; + +@interface MPAdDestinationDisplayAgent : NSObject + +@property (nonatomic, weak) id delegate; + ++ (MPAdDestinationDisplayAgent *)agentWithDelegate:(id)delegate; +- (void)displayDestinationForURL:(NSURL *)URL; +- (void)cancel; + +@end + +@protocol MPAdDestinationDisplayAgentDelegate + +- (UIViewController *)viewControllerForPresentingModalView; +- (void)displayAgentWillPresentModal; +- (void)displayAgentWillLeaveApplication; +- (void)displayAgentDidDismissModal; + +@optional + +- (MPAdConfiguration *)adConfiguration; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Common/MPAdDestinationDisplayAgent.m b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPAdDestinationDisplayAgent.m new file mode 100644 index 00000000000..4ef1a35aaed --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPAdDestinationDisplayAgent.m @@ -0,0 +1,372 @@ +// +// MPAdDestinationDisplayAgent.m +// MoPub +// +// Copyright (c) 2013 MoPub. All rights reserved. +// + +#import "MPAdDestinationDisplayAgent.h" +#import "MPCoreInstanceProvider.h" +#import "MPLastResortDelegate.h" +#import "MPLogging.h" +#import "NSURL+MPAdditions.h" +#import "MPCoreInstanceProvider.h" +#import "MPAnalyticsTracker.h" + +static NSString * const kDisplayAgentErrorDomain = @"com.mopub.displayagent"; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface MPAdDestinationDisplayAgent () + +@property (nonatomic, strong) MPURLResolver *resolver; +@property (nonatomic, strong) MPURLResolver *enhancedDeeplinkFallbackResolver; +@property (nonatomic, strong) MPProgressOverlayView *overlayView; +@property (nonatomic, assign) BOOL isLoadingDestination; + +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= MP_IOS_6_0 +@property (nonatomic, strong) SKStoreProductViewController *storeKitController; +#endif + +@property (nonatomic, strong) MPAdBrowserController *browserController; +@property (nonatomic, strong) MPTelephoneConfirmationController *telephoneConfirmationController; +@property (nonatomic, strong) MPActivityViewControllerHelper *activityViewControllerHelper; + +- (void)presentStoreKitControllerWithItemIdentifier:(NSString *)identifier fallbackURL:(NSURL *)URL; +- (void)hideOverlay; +- (void)hideModalAndNotifyDelegate; +- (void)dismissAllModalContent; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation MPAdDestinationDisplayAgent + +@synthesize delegate = _delegate; +@synthesize resolver = _resolver; +@synthesize isLoadingDestination = _isLoadingDestination; + ++ (MPAdDestinationDisplayAgent *)agentWithDelegate:(id)delegate +{ + MPAdDestinationDisplayAgent *agent = [[MPAdDestinationDisplayAgent alloc] init]; + agent.delegate = delegate; + agent.overlayView = [[MPProgressOverlayView alloc] initWithDelegate:agent]; + agent.activityViewControllerHelper = [[MPActivityViewControllerHelper alloc] initWithDelegate:agent]; + return agent; +} + +- (void)dealloc +{ + [self dismissAllModalContent]; + + self.overlayView.delegate = nil; +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= MP_IOS_6_0 + // XXX: If this display agent is deallocated while a StoreKit controller is still on-screen, + // nil-ing out the controller's delegate would leave us with no way to dismiss the controller + // in the future. Therefore, we change the controller's delegate to a singleton object which + // implements SKStoreProductViewControllerDelegate and is always around. + self.storeKitController.delegate = [MPLastResortDelegate sharedDelegate]; +#endif + self.browserController.delegate = nil; + +} + +- (void)dismissAllModalContent +{ + [self.overlayView hide]; +} + +- (void)displayDestinationForURL:(NSURL *)URL +{ + if (self.isLoadingDestination) return; + self.isLoadingDestination = YES; + + [self.delegate displayAgentWillPresentModal]; + [self.overlayView show]; + + [self.resolver cancel]; + [self.enhancedDeeplinkFallbackResolver cancel]; + + __weak typeof(self) weakSelf = self; + self.resolver = [[MPCoreInstanceProvider sharedProvider] buildMPURLResolverWithURL:URL completion:^(MPURLActionInfo *suggestedAction, NSError *error) { + typeof(self) strongSelf = weakSelf; + if (strongSelf) { + if (error) { + [strongSelf failedToResolveURLWithError:error]; + } else { + [strongSelf handleSuggestedURLAction:suggestedAction isResolvingEnhancedDeeplink:NO]; + } + } + }]; + + [self.resolver start]; +} + +- (void)cancel +{ + if (self.isLoadingDestination) { + [self.resolver cancel]; + [self.enhancedDeeplinkFallbackResolver cancel]; + [self hideOverlay]; + [self completeDestinationLoading]; + } +} + +- (BOOL)handleSuggestedURLAction:(MPURLActionInfo *)actionInfo isResolvingEnhancedDeeplink:(BOOL)isResolvingEnhancedDeeplink +{ + if (actionInfo == nil) { + [self failedToResolveURLWithError:[NSError errorWithDomain:kDisplayAgentErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Invalid URL action"}]]; + return NO; + } + + BOOL success = YES; + + switch (actionInfo.actionType) { + case MPURLActionTypeStoreKit: + [self showStoreKitProductWithParameter:actionInfo.iTunesItemIdentifier + fallbackURL:actionInfo.iTunesStoreFallbackURL]; + break; + case MPURLActionTypeGenericDeeplink: + [self openURLInApplication:actionInfo.deeplinkURL]; + break; + case MPURLActionTypeEnhancedDeeplink: + if (isResolvingEnhancedDeeplink) { + // We end up here if we encounter a nested enhanced deeplink. We'll simply disallow + // this to avoid getting into cycles. + [self failedToResolveURLWithError:[NSError errorWithDomain:kDisplayAgentErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Cannot resolve an enhanced deeplink that is nested within another enhanced deeplink."}]]; + success = NO; + } else { + [self handleEnhancedDeeplinkRequest:actionInfo.enhancedDeeplinkRequest]; + } + break; + case MPURLActionTypeOpenInSafari: + [self openURLInApplication:actionInfo.safariDestinationURL]; + break; + case MPURLActionTypeOpenInWebView: + [self showWebViewWithHTMLString:actionInfo.HTTPResponseString + baseURL:actionInfo.webViewBaseURL]; + break; + case MPURLActionTypeOpenURLInWebView: + [self showWebViewWithURL:actionInfo.originalURL]; + break; + case MPURLActionTypeShare: + [self openShareURL:actionInfo.shareURL]; + break; + default: + [self failedToResolveURLWithError:[NSError errorWithDomain:kDisplayAgentErrorDomain code:-2 userInfo:@{NSLocalizedDescriptionKey: @"Unrecognized URL action type."}]]; + success = NO; + break; + } + + return success; +} + +- (void)handleEnhancedDeeplinkRequest:(MPEnhancedDeeplinkRequest *)request +{ + BOOL didOpenSuccessfully = [[UIApplication sharedApplication] openURL:request.primaryURL]; + if (didOpenSuccessfully) { + [self hideOverlay]; + [self.delegate displayAgentWillLeaveApplication]; + [self completeDestinationLoading]; + [[[MPCoreInstanceProvider sharedProvider] sharedMPAnalyticsTracker] sendTrackingRequestForURLs:request.primaryTrackingURLs]; + } else if (request.fallbackURL) { + [self handleEnhancedDeeplinkFallbackForRequest:request]; + } else { + [self openURLInApplication:request.originalURL]; + } +} + +- (void)handleEnhancedDeeplinkFallbackForRequest:(MPEnhancedDeeplinkRequest *)request; +{ + __weak typeof(self) weakSelf = self; + [self.enhancedDeeplinkFallbackResolver cancel]; + self.enhancedDeeplinkFallbackResolver = [[MPCoreInstanceProvider sharedProvider] buildMPURLResolverWithURL:request.fallbackURL completion:^(MPURLActionInfo *actionInfo, NSError *error) { + typeof(self) strongSelf = weakSelf; + if (strongSelf) { + if (error) { + // If the resolver fails, just treat the entire original URL as a regular deeplink. + [strongSelf openURLInApplication:request.originalURL]; + } else { + // Otherwise, the resolver will return us a URL action. We process that action + // normally with one exception: we don't follow any nested enhanced deeplinks. + BOOL success = [strongSelf handleSuggestedURLAction:actionInfo isResolvingEnhancedDeeplink:YES]; + if (success) { + [[[MPCoreInstanceProvider sharedProvider] sharedMPAnalyticsTracker] sendTrackingRequestForURLs:request.fallbackTrackingURLs]; + } + } + } + }]; + [self.enhancedDeeplinkFallbackResolver start]; +} + +- (void)showWebViewWithHTMLString:(NSString *)HTMLString baseURL:(NSURL *)URL +{ + self.browserController = [[MPAdBrowserController alloc] initWithURL:URL + HTMLString:HTMLString + delegate:self]; + [self showAdBrowserController:self.browserController]; +} + +- (void)showWebViewWithURL:(NSURL *)URL { + self.browserController = [[MPAdBrowserController alloc] initWithURL:URL + delegate:self]; + [self showAdBrowserController:self.browserController]; +} + +- (void)showAdBrowserController:(MPAdBrowserController *)adBrowserController { + [self hideOverlay]; + + self.browserController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve; + [[self.delegate viewControllerForPresentingModalView] presentViewController:self.browserController + animated:MP_ANIMATED + completion:nil]; +} + +- (void)showStoreKitProductWithParameter:(NSString *)parameter fallbackURL:(NSURL *)URL +{ + if ([MPStoreKitProvider deviceHasStoreKit]) { + [self presentStoreKitControllerWithItemIdentifier:parameter fallbackURL:URL]; + } else { + [self openURLInApplication:URL]; + } +} + +- (void)openURLInApplication:(NSURL *)URL +{ + [self hideOverlay]; + + if ([URL mp_hasTelephoneScheme] || [URL mp_hasTelephonePromptScheme]) { + [self interceptTelephoneURL:URL]; + } else { + BOOL didOpenSuccessfully = [[UIApplication sharedApplication] openURL:URL]; + if (didOpenSuccessfully) { + [self.delegate displayAgentWillLeaveApplication]; + } + [self completeDestinationLoading]; + } +} + +- (BOOL)openShareURL:(NSURL *)URL +{ + MPLogDebug(@"MPAdDestinationDisplayAgent - loading Share URL: %@", URL); + MPMoPubShareHostCommand command = [URL mp_MoPubShareHostCommand]; + switch (command) { + case MPMoPubShareHostCommandTweet: + return [self.activityViewControllerHelper presentActivityViewControllerWithTweetShareURL:URL]; + default: + MPLogWarn(@"MPAdDestinationDisplayAgent - unsupported Share URL: %@", [URL absoluteString]); + return NO; + } +} + +- (void)interceptTelephoneURL:(NSURL *)URL +{ + __weak MPAdDestinationDisplayAgent *weakSelf = self; + self.telephoneConfirmationController = [[MPTelephoneConfirmationController alloc] initWithURL:URL clickHandler:^(NSURL *targetTelephoneURL, BOOL confirmed) { + MPAdDestinationDisplayAgent *strongSelf = weakSelf; + if (strongSelf) { + if (confirmed) { + [strongSelf.delegate displayAgentWillLeaveApplication]; + [[UIApplication sharedApplication] openURL:targetTelephoneURL]; + } + [strongSelf completeDestinationLoading]; + } + }]; + + [self.telephoneConfirmationController show]; +} + +- (void)failedToResolveURLWithError:(NSError *)error +{ + [self hideOverlay]; + [self completeDestinationLoading]; +} + +- (void)completeDestinationLoading +{ + self.isLoadingDestination = NO; + [self.delegate displayAgentDidDismissModal]; +} + +- (void)presentStoreKitControllerWithItemIdentifier:(NSString *)identifier fallbackURL:(NSURL *)URL +{ +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= MP_IOS_6_0 + self.storeKitController = [MPStoreKitProvider buildController]; + self.storeKitController.delegate = self; + + NSDictionary *parameters = [NSDictionary dictionaryWithObject:identifier + forKey:SKStoreProductParameterITunesItemIdentifier]; + [self.storeKitController loadProductWithParameters:parameters completionBlock:nil]; + + [self hideOverlay]; + [[self.delegate viewControllerForPresentingModalView] presentViewController:self.storeKitController animated:MP_ANIMATED completion:nil]; +#endif +} + +#pragma mark - + +- (void)productViewControllerDidFinish:(SKStoreProductViewController *)viewController +{ + self.isLoadingDestination = NO; + [self hideModalAndNotifyDelegate]; +} + +#pragma mark - + +- (void)dismissBrowserController:(MPAdBrowserController *)browserController animated:(BOOL)animated +{ + self.isLoadingDestination = NO; + [self hideModalAndNotifyDelegate]; +} + +- (MPAdConfiguration *)adConfiguration +{ + if ([self.delegate respondsToSelector:@selector(adConfiguration)]) { + return [self.delegate adConfiguration]; + } + + return nil; +} + +#pragma mark - + +- (void)overlayCancelButtonPressed +{ + [self cancel]; +} + +#pragma mark - Convenience Methods + +- (void)hideModalAndNotifyDelegate +{ + [[self.delegate viewControllerForPresentingModalView] dismissViewControllerAnimated:MP_ANIMATED completion:^{ + [self.delegate displayAgentDidDismissModal]; + }]; +} + +- (void)hideOverlay +{ + [self.overlayView hide]; +} + +#pragma mark + +- (UIViewController *)viewControllerForPresentingActivityViewController +{ + return self.delegate.viewControllerForPresentingModalView; +} + +- (void)activityViewControllerWillPresent +{ + [self hideOverlay]; + self.isLoadingDestination = NO; + [self.delegate displayAgentWillPresentModal]; +} + +- (void)activityViewControllerDidDismiss +{ + [self.delegate displayAgentDidDismissModal]; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Common/MPAdServerCommunicator.h b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPAdServerCommunicator.h new file mode 100644 index 00000000000..ce4bdf016dc --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPAdServerCommunicator.h @@ -0,0 +1,37 @@ +// +// MPAdServerCommunicator.h +// MoPub +// +// Copyright (c) 2012 MoPub, Inc. All rights reserved. +// + +#import + +#import "MPAdConfiguration.h" +#import "MPGlobal.h" + +@protocol MPAdServerCommunicatorDelegate; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface MPAdServerCommunicator : NSObject + +@property (nonatomic, weak) id delegate; +@property (nonatomic, assign, readonly) BOOL loading; + +- (id)initWithDelegate:(id)delegate; + +- (void)loadURL:(NSURL *)URL; +- (void)cancel; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@protocol MPAdServerCommunicatorDelegate + +@required +- (void)communicatorDidReceiveAdConfiguration:(MPAdConfiguration *)configuration; +- (void)communicatorDidFailWithError:(NSError *)error; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Common/MPAdServerCommunicator.m b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPAdServerCommunicator.m new file mode 100644 index 00000000000..74837e168b2 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPAdServerCommunicator.m @@ -0,0 +1,165 @@ +// +// MPAdServerCommunicator.m +// MoPub +// +// Copyright (c) 2012 MoPub, Inc. All rights reserved. +// + +#import "MPAdServerCommunicator.h" + +#import "MPAdConfiguration.h" +#import "MPLogging.h" +#import "MPCoreInstanceProvider.h" +#import "MPLogEvent.h" +#import "MPLogEventRecorder.h" + +const NSTimeInterval kRequestTimeoutInterval = 10.0; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface MPAdServerCommunicator () + +@property (nonatomic, assign, readwrite) BOOL loading; +@property (nonatomic, copy) NSURL *URL; +@property (nonatomic, strong) NSURLConnection *connection; +@property (nonatomic, strong) NSMutableData *responseData; +@property (nonatomic, strong) NSDictionary *responseHeaders; +@property (nonatomic, strong) MPLogEvent *adRequestLatencyEvent; + +- (NSError *)errorForStatusCode:(NSInteger)statusCode; +- (NSURLRequest *)adRequestForURL:(NSURL *)URL; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation MPAdServerCommunicator + +@synthesize delegate = _delegate; +@synthesize URL = _URL; +@synthesize connection = _connection; +@synthesize responseData = _responseData; +@synthesize responseHeaders = _responseHeaders; +@synthesize loading = _loading; + +- (id)initWithDelegate:(id)delegate +{ + self = [super init]; + if (self) { + self.delegate = delegate; + } + return self; +} + +- (void)dealloc +{ + [self.connection cancel]; + +} + +#pragma mark - Public + +- (void)loadURL:(NSURL *)URL +{ + [self cancel]; + self.URL = URL; + + // Start tracking how long it takes to successfully or unsuccessfully retrieve an ad. + self.adRequestLatencyEvent = [[MPLogEvent alloc] initWithEventCategory:MPLogEventCategoryRequests eventName:MPLogEventNameAdRequest]; + self.adRequestLatencyEvent.requestURI = URL.absoluteString; + + self.connection = [NSURLConnection connectionWithRequest:[self adRequestForURL:URL] + delegate:self]; + self.loading = YES; +} + +- (void)cancel +{ + self.adRequestLatencyEvent = nil; + self.loading = NO; + [self.connection cancel]; + self.connection = nil; + self.responseData = nil; + self.responseHeaders = nil; +} + +#pragma mark - NSURLConnection delegate (NSURLConnectionDataDelegate in iOS 5.0+) + +- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response +{ + if ([response respondsToSelector:@selector(statusCode)]) { + NSInteger statusCode = [(NSHTTPURLResponse *)response statusCode]; + if (statusCode >= 400) { + // Do not record a logging event if we failed to make a connection. + self.adRequestLatencyEvent = nil; + + [connection cancel]; + self.loading = NO; + [self.delegate communicatorDidFailWithError:[self errorForStatusCode:statusCode]]; + return; + } + } + + self.responseData = [NSMutableData data]; + self.responseHeaders = [(NSHTTPURLResponse *)response allHeaderFields]; +} + +- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data +{ + [self.responseData appendData:data]; +} + +- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error +{ + // Do not record a logging event if we failed to make a connection. + self.adRequestLatencyEvent = nil; + + self.loading = NO; + [self.delegate communicatorDidFailWithError:error]; +} + +- (void)connectionDidFinishLoading:(NSURLConnection *)connection +{ + [self.adRequestLatencyEvent recordEndTime]; + self.adRequestLatencyEvent.requestStatusCode = 200; + + MPAdConfiguration *configuration = [[MPAdConfiguration alloc] + initWithHeaders:self.responseHeaders + data:self.responseData]; + MPAdConfigurationLogEventProperties *logEventProperties = + [[MPAdConfigurationLogEventProperties alloc] initWithConfiguration:configuration]; + + // Do not record ads that are warming up. + if (configuration.adUnitWarmingUp) { + self.adRequestLatencyEvent = nil; + } else { + [self.adRequestLatencyEvent setLogEventProperties:logEventProperties]; + MPAddLogEvent(self.adRequestLatencyEvent); + } + + self.loading = NO; + [self.delegate communicatorDidReceiveAdConfiguration:configuration]; +} + +#pragma mark - Internal + +- (NSError *)errorForStatusCode:(NSInteger)statusCode +{ + NSString *errorMessage = [NSString stringWithFormat: + NSLocalizedString(@"MoPub returned status code %d.", + @"Status code error"), + statusCode]; + NSDictionary *errorInfo = [NSDictionary dictionaryWithObject:errorMessage + forKey:NSLocalizedDescriptionKey]; + return [NSError errorWithDomain:@"mopub.com" code:statusCode userInfo:errorInfo]; +} + +- (NSURLRequest *)adRequestForURL:(NSURL *)URL +{ + NSMutableURLRequest *request = [[MPCoreInstanceProvider sharedProvider] buildConfiguredURLRequestWithURL:URL]; + [request setCachePolicy:NSURLRequestReloadIgnoringCacheData]; + [request setTimeoutInterval:kRequestTimeoutInterval]; + return request; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Common/MPAdServerURLBuilder.h b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPAdServerURLBuilder.h new file mode 100644 index 00000000000..2e11954eaae --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPAdServerURLBuilder.h @@ -0,0 +1,35 @@ +// +// MPAdServerURLBuilder.h +// MoPub +// +// Copyright (c) 2012 MoPub. All rights reserved. +// + +#import +#import + +@interface MPAdServerURLBuilder : NSObject + ++ (NSURL *)URLWithAdUnitID:(NSString *)adUnitID + keywords:(NSString *)keywords + location:(CLLocation *)location + testing:(BOOL)testing; + ++ (NSURL *)URLWithAdUnitID:(NSString *)adUnitID + keywords:(NSString *)keywords + location:(CLLocation *)location + versionParameterName:(NSString *)versionParameterName + version:(NSString *)version + testing:(BOOL)testing + desiredAssets:(NSArray *)assets; + ++ (NSURL *)URLWithAdUnitID:(NSString *)adUnitID + keywords:(NSString *)keywords + location:(CLLocation *)location + versionParameterName:(NSString *)versionParameterName + version:(NSString *)version + testing:(BOOL)testing + desiredAssets:(NSArray *)assets + adSequence:(NSInteger)adSequence; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Common/MPAdServerURLBuilder.m b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPAdServerURLBuilder.m new file mode 100644 index 00000000000..a9864622342 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPAdServerURLBuilder.m @@ -0,0 +1,306 @@ +// +// MPAdServerURLBuilder.m +// MoPub +// +// Copyright (c) 2012 MoPub. All rights reserved. +// + +#import "MPAdServerURLBuilder.h" + +#import "MPConstants.h" +#import "MPGeolocationProvider.h" +#import "MPGlobal.h" +#import "MPKeywordProvider.h" +#import "MPIdentityProvider.h" +#import "MPCoreInstanceProvider.h" +#import "MPReachability.h" +#import "MPAPIEndpoints.h" + +static NSString * const kMoPubInterfaceOrientationPortrait = @"p"; +static NSString * const kMoPubInterfaceOrientationLandscape = @"l"; +static NSInteger const kAdSequenceNone = -1; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface MPAdServerURLBuilder () + ++ (NSString *)queryParameterForKeywords:(NSString *)keywords; ++ (NSString *)queryParameterForOrientation; ++ (NSString *)queryParameterForScaleFactor; ++ (NSString *)queryParameterForTimeZone; ++ (NSString *)queryParameterForLocation:(CLLocation *)location; ++ (NSString *)queryParameterForMRAID; ++ (NSString *)queryParameterForDNT; ++ (NSString *)queryParameterForConnectionType; ++ (NSString *)queryParameterForApplicationVersion; ++ (NSString *)queryParameterForCarrierName; ++ (NSString *)queryParameterForISOCountryCode; ++ (NSString *)queryParameterForMobileNetworkCode; ++ (NSString *)queryParameterForMobileCountryCode; ++ (NSString *)queryParameterForDeviceName; ++ (NSString *)queryParameterForDesiredAdAssets:(NSArray *)assets; ++ (NSString *)queryParameterForAdSequence:(NSInteger)adSequence; ++ (NSString *)queryParameterForPhysicalScreenSize; ++ (NSString *)queryParameterForBundleIdentifier; ++ (NSString *)queryParameterForAppTransportSecurity; ++ (BOOL)advertisingTrackingEnabled; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation MPAdServerURLBuilder + ++ (NSURL *)URLWithAdUnitID:(NSString *)adUnitID + keywords:(NSString *)keywords + location:(CLLocation *)location + testing:(BOOL)testing +{ + return [self URLWithAdUnitID:adUnitID + keywords:keywords + location:location + versionParameterName:@"nv" + version:MP_SDK_VERSION + testing:testing + desiredAssets:nil]; +} + ++ (NSURL *)URLWithAdUnitID:(NSString *)adUnitID + keywords:(NSString *)keywords + location:(CLLocation *)location + versionParameterName:(NSString *)versionParameterName + version:(NSString *)version + testing:(BOOL)testing + desiredAssets:(NSArray *)assets +{ + + + return [self URLWithAdUnitID:adUnitID + keywords:keywords + location:location + versionParameterName:versionParameterName + version:version + testing:testing + desiredAssets:assets + adSequence:kAdSequenceNone]; +} + ++ (NSURL *)URLWithAdUnitID:(NSString *)adUnitID + keywords:(NSString *)keywords + location:(CLLocation *)location + versionParameterName:(NSString *)versionParameterName + version:(NSString *)version + testing:(BOOL)testing + desiredAssets:(NSArray *)assets + adSequence:(NSInteger)adSequence +{ + NSString *URLString = [NSString stringWithFormat:@"%@?v=%@&udid=%@&id=%@&%@=%@", + [MPAPIEndpoints baseURLStringWithPath:MOPUB_API_PATH_AD_REQUEST testing:testing], + MP_SERVER_VERSION, + [MPIdentityProvider identifier], + [adUnitID stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding], + versionParameterName, version]; + + URLString = [URLString stringByAppendingString:[self queryParameterForKeywords:keywords]]; + URLString = [URLString stringByAppendingString:[self queryParameterForOrientation]]; + URLString = [URLString stringByAppendingString:[self queryParameterForScaleFactor]]; + URLString = [URLString stringByAppendingString:[self queryParameterForTimeZone]]; + URLString = [URLString stringByAppendingString:[self queryParameterForLocation:location]]; + URLString = [URLString stringByAppendingString:[self queryParameterForMRAID]]; + URLString = [URLString stringByAppendingString:[self queryParameterForDNT]]; + URLString = [URLString stringByAppendingString:[self queryParameterForConnectionType]]; + URLString = [URLString stringByAppendingString:[self queryParameterForApplicationVersion]]; + URLString = [URLString stringByAppendingString:[self queryParameterForCarrierName]]; + URLString = [URLString stringByAppendingString:[self queryParameterForISOCountryCode]]; + URLString = [URLString stringByAppendingString:[self queryParameterForMobileNetworkCode]]; + URLString = [URLString stringByAppendingString:[self queryParameterForMobileCountryCode]]; + URLString = [URLString stringByAppendingString:[self queryParameterForDeviceName]]; + URLString = [URLString stringByAppendingString:[self queryParameterForDesiredAdAssets:assets]]; + URLString = [URLString stringByAppendingString:[self queryParameterForAdSequence:adSequence]]; + URLString = [URLString stringByAppendingString:[self queryParameterForPhysicalScreenSize]]; + URLString = [URLString stringByAppendingString:[self queryParameterForBundleIdentifier]]; + URLString = [URLString stringByAppendingString:[self queryParameterForAppTransportSecurity]]; + + return [NSURL URLWithString:URLString]; +} + + ++ (NSString *)queryParameterForKeywords:(NSString *)keywords +{ + NSMutableArray *keywordsArray = [NSMutableArray array]; + NSString *trimmedKeywords = [keywords stringByTrimmingCharactersInSet: + [NSCharacterSet whitespaceCharacterSet]]; + if ([trimmedKeywords length] > 0) { + [keywordsArray addObject:trimmedKeywords]; + } + + // Append the Facebook attribution keyword (if available). + Class fbKeywordProviderClass = NSClassFromString(@"MPFacebookKeywordProvider"); + if ([fbKeywordProviderClass conformsToProtocol:@protocol(MPKeywordProvider)]) + { + NSString *fbAttributionKeyword = [(Class) fbKeywordProviderClass keyword]; + if ([fbAttributionKeyword length] > 0) { + [keywordsArray addObject:fbAttributionKeyword]; + } + } + + if ([keywordsArray count] == 0) { + return @""; + } else { + NSString *keywords = [[keywordsArray componentsJoinedByString:@","] + stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; + return [NSString stringWithFormat:@"&q=%@", keywords]; + } +} + ++ (NSString *)queryParameterForOrientation +{ + UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation; + NSString *orientString = UIInterfaceOrientationIsPortrait(orientation) ? + kMoPubInterfaceOrientationPortrait : kMoPubInterfaceOrientationLandscape; + return [NSString stringWithFormat:@"&o=%@", orientString]; +} + ++ (NSString *)queryParameterForScaleFactor +{ + return [NSString stringWithFormat:@"&sc=%.1f", MPDeviceScaleFactor()]; +} + ++ (NSString *)queryParameterForTimeZone +{ + static NSDateFormatter *formatter; + @synchronized(self) + { + if (!formatter) formatter = [[NSDateFormatter alloc] init]; + } + [formatter setDateFormat:@"Z"]; + NSDate *today = [NSDate date]; + return [NSString stringWithFormat:@"&z=%@", [formatter stringFromDate:today]]; +} + ++ (NSString *)queryParameterForLocation:(CLLocation *)location +{ + NSString *result = @""; + + CLLocation *bestLocation = location; + CLLocation *locationFromProvider = [[[MPCoreInstanceProvider sharedProvider] sharedMPGeolocationProvider] lastKnownLocation]; + + if (locationFromProvider) { + bestLocation = locationFromProvider; + } + + if (bestLocation && bestLocation.horizontalAccuracy >= 0) { + result = [NSString stringWithFormat:@"&ll=%@,%@", + [NSNumber numberWithDouble:bestLocation.coordinate.latitude], + [NSNumber numberWithDouble:bestLocation.coordinate.longitude]]; + + if (bestLocation.horizontalAccuracy) { + result = [result stringByAppendingFormat:@"&lla=%@", + [NSNumber numberWithDouble:bestLocation.horizontalAccuracy]]; + } + + if (bestLocation == locationFromProvider) { + result = [result stringByAppendingString:@"&llsdk=1"]; + } + + NSTimeInterval locationLastUpdatedMillis = [[NSDate date] timeIntervalSinceDate:bestLocation.timestamp] * 1000.0; + + result = [result stringByAppendingFormat:@"&llf=%.0f", locationLastUpdatedMillis]; + } + + return result; +} + ++ (NSString *)queryParameterForMRAID +{ + if (NSClassFromString(@"MPMRAIDBannerCustomEvent") && + NSClassFromString(@"MPMRAIDInterstitialCustomEvent")) { + return @"&mr=1"; + } else { + return @""; + } +} + ++ (NSString *)queryParameterForDNT +{ + return [self advertisingTrackingEnabled] ? @"" : @"&dnt=1"; +} + ++ (NSString *)queryParameterForConnectionType +{ + return [[[MPCoreInstanceProvider sharedProvider] sharedMPReachability] hasWifi] ? @"&ct=2" : @"&ct=3"; +} + ++ (NSString *)queryParameterForApplicationVersion +{ + NSString *applicationVersion = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"]; + return [NSString stringWithFormat:@"&av=%@", + [applicationVersion mp_URLEncodedString]]; +} + ++ (NSString *)queryParameterForCarrierName +{ + NSString *carrierName = [[[MPCoreInstanceProvider sharedProvider] sharedCarrierInfo] objectForKey:@"carrierName"]; + return carrierName ? [NSString stringWithFormat:@"&cn=%@", + [carrierName mp_URLEncodedString]] : @""; +} + ++ (NSString *)queryParameterForISOCountryCode +{ + NSString *code = [[[MPCoreInstanceProvider sharedProvider] sharedCarrierInfo] objectForKey:@"isoCountryCode"]; + return code ? [NSString stringWithFormat:@"&iso=%@", [code mp_URLEncodedString]] : @""; +} + ++ (NSString *)queryParameterForMobileNetworkCode +{ + NSString *code = [[[MPCoreInstanceProvider sharedProvider] sharedCarrierInfo] objectForKey:@"mobileNetworkCode"]; + return code ? [NSString stringWithFormat:@"&mnc=%@", [code mp_URLEncodedString]] : @""; +} + ++ (NSString *)queryParameterForMobileCountryCode +{ + NSString *code = [[[MPCoreInstanceProvider sharedProvider] sharedCarrierInfo] objectForKey:@"mobileCountryCode"]; + return code ? [NSString stringWithFormat:@"&mcc=%@", [code mp_URLEncodedString]] : @""; +} + ++ (NSString *)queryParameterForDeviceName +{ + NSString *deviceName = [[UIDevice currentDevice] mp_hardwareDeviceName]; + return deviceName ? [NSString stringWithFormat:@"&dn=%@", [deviceName mp_URLEncodedString]] : @""; +} + ++ (NSString *)queryParameterForDesiredAdAssets:(NSArray *)assets +{ + NSString *concatenatedAssets = [assets componentsJoinedByString:@","]; + return [concatenatedAssets length] ? [NSString stringWithFormat:@"&assets=%@", concatenatedAssets] : @""; +} + ++ (NSString *)queryParameterForAdSequence:(NSInteger)adSequence +{ + return (adSequence >= 0) ? [NSString stringWithFormat:@"&seq=%ld", (long)adSequence] : @""; +} + ++ (NSString *)queryParameterForPhysicalScreenSize +{ + CGSize screenSize = MPScreenResolution(); + + return [NSString stringWithFormat:@"&w=%.0f&h=%.0f", screenSize.width, screenSize.height]; +} + ++ (NSString *)queryParameterForBundleIdentifier +{ + NSString *bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier]; + return bundleIdentifier ? [NSString stringWithFormat:@"&bundle=%@", [bundleIdentifier mp_URLEncodedString]] : @""; +} + ++ (NSString *)queryParameterForAppTransportSecurity +{ + return [NSString stringWithFormat:@"&ats=%@", @([[MPCoreInstanceProvider sharedProvider] appTransportSecuritySettings])]; +} + ++ (BOOL)advertisingTrackingEnabled +{ + return [MPIdentityProvider advertisingTrackingEnabled]; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Common/MPClosableView.h b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPClosableView.h new file mode 100644 index 00000000000..d683d1abd9b --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPClosableView.h @@ -0,0 +1,62 @@ +// +// MPClosableView.h +// MoPubSDK +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import + +enum { + MPClosableViewCloseButtonLocationTopRight, + MPClosableViewCloseButtonLocationTopLeft, + MPClosableViewCloseButtonLocationTopCenter, + MPClosableViewCloseButtonLocationBottomRight, + MPClosableViewCloseButtonLocationBottomLeft, + MPClosableViewCloseButtonLocationBottomCenter, + MPClosableViewCloseButtonLocationCenter +}; +typedef NSInteger MPClosableViewCloseButtonLocation; + +enum { + MPClosableViewCloseButtonTypeNone, + MPClosableViewCloseButtonTypeTappableWithoutImage, + MPClosableViewCloseButtonTypeTappableWithImage, +}; +typedef NSInteger MPClosableViewCloseButtonType; + +CGRect MPClosableViewCustomCloseButtonFrame(CGSize size, MPClosableViewCloseButtonLocation closeButtonLocation); + +@protocol MPClosableViewDelegate; + +/** + * `MPClosableView` is composed of a content view and a close button. The close button can + * be positioned at any corner, the center of top and bottom edges, and the center of the view. + * The close button can either be a button that is tappable with image, tappable without an image, + * and completely disabled altogether. Finally, `MPClosableView` keeps track as to whether or not + * it has been tapped. + */ +@interface MPClosableView : UIView + +@property (nonatomic, weak) id delegate; +@property (nonatomic, assign) MPClosableViewCloseButtonType closeButtonType; +@property (nonatomic, assign) MPClosableViewCloseButtonLocation closeButtonLocation; +@property (nonatomic, readonly) BOOL wasTapped; + +- (instancetype)initWithFrame:(CGRect)frame closeButtonType:(MPClosableViewCloseButtonType)closeButtonType; + +@end + +/** + * `MPClosableViewDelegate` allows an object that implements `MPClosableViewDelegate` to + * be notified when the close button of the `MPClosableView` has been tapped. + */ +@protocol MPClosableViewDelegate + +- (void)closeButtonPressed:(MPClosableView *)closableView; + +@optional + +- (void)closableView:(MPClosableView *)closableView didMoveToWindow:(UIWindow *)window; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Common/MPClosableView.m b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPClosableView.m new file mode 100644 index 00000000000..0b7719ab581 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPClosableView.m @@ -0,0 +1,163 @@ +// +// MPClosableView.m +// MoPubSDK +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import "MPClosableView.h" +#import "MPInstanceProvider.h" +#import "MPUserInteractionGestureRecognizer.h" + +static CGFloat kCloseRegionWidth = 50.0f; +static CGFloat kCloseRegionHeight = 50.0f; +static NSString *const kExpandableCloseButtonImageName = @"MPCloseButtonX.png"; + +CGRect MPClosableViewCustomCloseButtonFrame(CGSize size, MPClosableViewCloseButtonLocation closeButtonLocation) +{ + CGFloat width = size.width; + CGFloat height = size.height; + CGRect closeButtonFrame = CGRectMake(0.0f, 0.0f, kCloseRegionWidth, kCloseRegionHeight); + + switch (closeButtonLocation) { + case MPClosableViewCloseButtonLocationTopRight: + closeButtonFrame.origin = CGPointMake(width-kCloseRegionWidth, 0.0f); + break; + case MPClosableViewCloseButtonLocationTopLeft: + closeButtonFrame.origin = CGPointMake(0.0f, 0.0f); + break; + case MPClosableViewCloseButtonLocationTopCenter: + closeButtonFrame.origin = CGPointMake((width-kCloseRegionWidth) / 2.0f, 0.0f); + break; + case MPClosableViewCloseButtonLocationBottomRight: + closeButtonFrame.origin = CGPointMake(width-kCloseRegionWidth, height-kCloseRegionHeight); + break; + case MPClosableViewCloseButtonLocationBottomLeft: + closeButtonFrame.origin = CGPointMake(0.0f, height-kCloseRegionHeight); + break; + case MPClosableViewCloseButtonLocationBottomCenter: + closeButtonFrame.origin = CGPointMake((width-kCloseRegionWidth) / 2.0f, height-kCloseRegionHeight); + break; + case MPClosableViewCloseButtonLocationCenter: + closeButtonFrame.origin = CGPointMake((width-kCloseRegionWidth) / 2.0f, (height-kCloseRegionHeight) / 2.0f); + break; + default: + closeButtonFrame.origin = CGPointMake(width-kCloseRegionWidth, 0.0f); + break; + } + + return closeButtonFrame; +} + +@interface MPClosableView () + +@property (nonatomic, strong) UIButton *closeButton; +@property (nonatomic, strong) UIImage *closeButtonImage; +@property (nonatomic, strong) MPUserInteractionGestureRecognizer *userInteractionRecognizer; +@property (nonatomic, assign) BOOL wasTapped; + +@end + +@implementation MPClosableView + +- (instancetype)initWithFrame:(CGRect)frame closeButtonType:(MPClosableViewCloseButtonType)closeButtonType +{ + self = [super initWithFrame:frame]; + + if (self) { + self.backgroundColor = [UIColor clearColor]; + self.opaque = NO; + + _closeButtonLocation = MPClosableViewCloseButtonLocationTopRight; + + _userInteractionRecognizer = [[MPUserInteractionGestureRecognizer alloc] initWithTarget:self action:@selector(handleInteraction:)]; + _userInteractionRecognizer.cancelsTouchesInView = NO; + [self addGestureRecognizer:_userInteractionRecognizer]; + _userInteractionRecognizer.delegate = self; + + _closeButtonImage = [UIImage imageNamed:MPResourcePathForResource(kExpandableCloseButtonImageName)]; + + _closeButton = [UIButton buttonWithType:UIButtonTypeCustom]; + _closeButton.backgroundColor = [UIColor clearColor]; + _closeButton.accessibilityLabel = @"Close Interstitial Ad"; + + [_closeButton addTarget:self action:@selector(closeButtonPressed) forControlEvents:UIControlEventTouchUpInside]; + + [self setCloseButtonType:closeButtonType]; + + [self addSubview:_closeButton]; + } + + return self; +} + +- (void)dealloc +{ + _userInteractionRecognizer.delegate = nil; + [self.userInteractionRecognizer removeTarget:self action:nil]; +} + +- (void)layoutSubviews +{ + self.closeButton.frame = MPClosableViewCustomCloseButtonFrame(self.bounds.size, self.closeButtonLocation); + [self bringSubviewToFront:self.closeButton]; +} + +- (void)didMoveToWindow +{ + if ([self.delegate respondsToSelector:@selector(closableView:didMoveToWindow:)]) { + [self.delegate closableView:self didMoveToWindow:self.window]; + } +} + +- (void)setCloseButtonType:(MPClosableViewCloseButtonType)closeButtonType +{ + _closeButtonType = closeButtonType; + + switch (closeButtonType) { + case MPClosableViewCloseButtonTypeNone: + self.closeButton.hidden = YES; + break; + case MPClosableViewCloseButtonTypeTappableWithoutImage: + self.closeButton.hidden = NO; + [self.closeButton setImage:nil forState:UIControlStateNormal]; + break; + case MPClosableViewCloseButtonTypeTappableWithImage: + self.closeButton.hidden = NO; + [self.closeButton setImage:self.closeButtonImage forState:UIControlStateNormal]; + break; + default: + self.closeButton.hidden = NO; + [self.closeButton setImage:self.closeButtonImage forState:UIControlStateNormal]; + break; + } +} + +- (void)setCloseButtonLocation:(MPClosableViewCloseButtonLocation)closeButtonLocation +{ + _closeButtonLocation = closeButtonLocation; + [self setNeedsLayout]; +} + +- (void)handleInteraction:(UIGestureRecognizer *)gestureRecognizer +{ + if (gestureRecognizer.state == UIGestureRecognizerStateEnded) { + self.wasTapped = YES; + } +} + +#pragma mark - + +- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer; +{ + return YES; +} + +#pragma mark - + +- (void)closeButtonPressed +{ + [self.delegate closeButtonPressed:self]; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Common/MPCountdownTimerView.h b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPCountdownTimerView.h new file mode 100644 index 00000000000..c4a6aff689e --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPCountdownTimerView.h @@ -0,0 +1,58 @@ +// +// MPCountdownTimerView.h +// MoPubSDK +// +// Copyright © 2016 MoPub. All rights reserved. +// + +#import + +/** + * A view that will display a countdown timer and invoke a completion block once + * the timer has elapsed. + */ +@interface MPCountdownTimerView : UIView + +/** + * Flag indicating if the timer is active. + */ +@property (nonatomic, readonly) BOOL isActive; + +/** + * Flag indicating if the timer is currently paused. + */ +@property (nonatomic, readonly) BOOL isPaused; + +/** + * Initializes a countdown timer view. The timer is not automatically started. + * + * @param frame Frame of the view. + * @param seconds Duration of the timer in seconds. This value must be greater than zero. + * @returns An initialized timer if successful; otherwise nil. + */ +- (instancetype)initWithFrame:(CGRect)frame duration:(NSTimeInterval)seconds; + +/** + * Starts the countdown timer. If the timer has already started, calling this method again will do nothing. + * + * @param completion Completion block that is fired when the timer elapses or is stopped. + */ +- (void)startWithTimerCompletion:(void(^)(BOOL hasElapsed))completion; + +/** + * Stops the timer and optionally invokes the completion block from `startWithTimerCompletion:`. + * If the timer hasn't started, calling this method will do nothing. + */ +- (void)stopAndSignalCompletion:(BOOL)shouldSignalCompletion; + +/** + * Pauses the timer. If the timer hasn't started, calling this method will do nothing. + */ +- (void)pause; + +/** + * Resumes the timer. If the timer isn't paused, calling this method will do nothing. + */ +- (void)resume; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Common/MPCountdownTimerView.m b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPCountdownTimerView.m new file mode 100644 index 00000000000..f0df837d510 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPCountdownTimerView.m @@ -0,0 +1,195 @@ +// +// MPCountdownTimerView.m +// MoPubSDK +// +// Copyright © 2016 MoPub. All rights reserved. +// + +#import "MPCountdownTimerView.h" +#import "MPLogging.h" +#import "MPTimer.h" +#import "NSBundle+MPAdditions.h" + +// The frequency at which the internal timer is fired in seconds. +// This value matches the step size of the jQuery knob in MPCountdownTimer.html. +static const NSTimeInterval kCountdownTimerInterval = 0.05; + +@interface MPCountdownTimerView() +@property (nonatomic, assign, readwrite) BOOL isPaused; + +@property (nonatomic, copy) void(^completionBlock)(BOOL); +@property (nonatomic, assign) NSTimeInterval currentSeconds; +@property (nonatomic, strong) MPTimer * timer; +@property (nonatomic, strong) UIWebView * timerView; +@end + +@implementation MPCountdownTimerView + +#pragma mark - Initialization + +- (instancetype)initWithFrame:(CGRect)frame duration:(NSTimeInterval)seconds { + if (self = [super initWithFrame:frame]) { + // Duration should be non-negative. + if (seconds < 0) { + MPLogDebug(@"Attempted to initialize MPCountdownTimerView with a negative duration. No timer will be created."); + return nil; + } + + _completionBlock = nil; + _currentSeconds = seconds; + _isPaused = NO; + _timer = nil; + _timerView = ({ + UIWebView * view = [[UIWebView alloc] initWithFrame:self.bounds]; + view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + view.backgroundColor = [UIColor clearColor]; + view.delegate = self; + view.opaque = NO; + view.userInteractionEnabled = NO; + view; + }); + self.userInteractionEnabled = NO; + + [self addSubview:_timerView]; + [_timerView loadHTMLString:self.timerHtml baseURL:self.timerBaseUrl]; + } + + return self; +} + +- (void)dealloc { + // Stop the timer if the view is going away, but do not notify the completion block + // if there is one. + _completionBlock = nil; + [self stopAndSignalCompletion:NO]; +} + +#pragma mark - Timer + +- (BOOL)isActive { + return (self.timer != nil); +} + +- (void)startWithTimerCompletion:(void(^)(BOOL hasElapsed))completion { + if (self.isActive) { + return; + } + + // Reset any internal state that may be dirty from a previous timer run. + self.isPaused = NO; + + // Copy the completion block and set up the initial state of the timer. + self.completionBlock = completion; + + // Start the timer now. + self.timer = [MPTimer timerWithTimeInterval:kCountdownTimerInterval + target:self + selector:@selector(onTimerUpdate:) + repeats:YES]; + [self.timer scheduleNow]; + + MPLogInfo(@"MPCountdownTimerView started"); +} + +- (void)stopAndSignalCompletion:(BOOL)shouldSignalCompletion { + if (!self.isActive) { + return; + } + + // Invalidate and clear the timer to stop it completely. + [self.timer invalidate]; + self.timer = nil; + + MPLogInfo(@"MPCountdownTimerView stopped"); + + // Notify the completion block and clear it out once it's handling has finished. + if (shouldSignalCompletion && self.completionBlock != nil) { + BOOL hasElapsed = (self.currentSeconds <= 0); + self.completionBlock(hasElapsed); + + MPLogInfo(@"MPCountdownTimerView completion block notified"); + } + + // Clear out the completion block since the timer has stopped and it is + // no longer needed for this instance. + self.completionBlock = nil; +} + +- (void)pause { + if (!self.isActive) { + return; + } + + self.isPaused = [self.timer pause]; + if (self.isPaused) { + MPLogInfo(@"MPCountdownTimerView paused"); + } +} + +- (void)resume { + if (!self.isActive) { + return; + } + + if ([self.timer resume]) { + self.isPaused = NO; + MPLogInfo(@"MPCountdownTimerView resumed"); + } +} + +#pragma mark - Resources + +- (NSString *)timerHtml { + // Save the contents of the HTML into a static string since it will not change + // across instances. + static NSString * html = nil; + + if (html == nil) { + NSBundle * parentBundle = [NSBundle resourceBundleForClass:self.class]; + NSString * filepath = [parentBundle pathForResource:@"MPCountdownTimer" ofType:@"html"]; + html = [NSString stringWithContentsOfFile:filepath encoding:NSUTF8StringEncoding error:nil]; + + if (html == nil) { + MPLogError(@"Could not find MPCountdownTimer.html in bundle %@", parentBundle.bundlePath); + } + } + + return html; +} + +- (NSURL *)timerBaseUrl { + // Save the base URL into a static string since it will not change + // across instances. + static NSURL * baseUrl = nil; + + if (baseUrl == nil) { + NSBundle * parentBundle = [NSBundle resourceBundleForClass:self.class]; + baseUrl = [NSURL fileURLWithPath:parentBundle.bundlePath]; + } + + return baseUrl; +} + +#pragma mark - MPTimer + +- (void)onTimerUpdate:(MPTimer *)sender { + // Update the count. + self.currentSeconds -= kCountdownTimerInterval; + + NSString * jsToEvaluate = [NSString stringWithFormat:@"setCounterValue(%f);", self.currentSeconds]; + [self.timerView stringByEvaluatingJavaScriptFromString:jsToEvaluate]; + + // Stop the timer and notify the completion block. + if (self.currentSeconds <= 0) { + [self stopAndSignalCompletion:YES]; + } +} + +#pragma mark - UIWebViewDelegate + +- (void)webViewDidFinishLoad:(UIWebView *)webView { + NSString * jsToEvaluate = [NSString stringWithFormat:@"setMaxCounterValue(%f); setCounterValue(%f);", self.currentSeconds, self.currentSeconds]; + [self.timerView stringByEvaluatingJavaScriptFromString:jsToEvaluate]; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Common/MPEnhancedDeeplinkRequest.h b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPEnhancedDeeplinkRequest.h new file mode 100644 index 00000000000..d8c12311edb --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPEnhancedDeeplinkRequest.h @@ -0,0 +1,22 @@ +// +// MPEnhancedDeeplinkRequest.h +// MoPub +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import + +@interface MPEnhancedDeeplinkRequest : NSObject + +@property (copy) NSURL *originalURL; + +// Request components derived from the original URL. +@property (copy) NSURL *primaryURL; +@property (strong) NSArray *primaryTrackingURLs; +@property (copy) NSURL *fallbackURL; +@property (strong) NSArray *fallbackTrackingURLs; + +- (instancetype)initWithURL:(NSURL *)URL; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Common/MPEnhancedDeeplinkRequest.m b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPEnhancedDeeplinkRequest.m new file mode 100644 index 00000000000..317eb52dfe2 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPEnhancedDeeplinkRequest.m @@ -0,0 +1,56 @@ +// +// MPEnhancedDeeplinkRequest.m +// MoPub +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import "MPEnhancedDeeplinkRequest.h" +#import "NSURL+MPAdditions.h" + +static NSString * const kRequiredHostname = @"navigate"; +static NSString * const kPrimaryURLKey = @"primaryUrl"; +static NSString * const kPrimaryTrackingURLKey = @"primaryTrackingUrl"; +static NSString * const kFallbackURLKey = @"fallbackUrl"; +static NSString * const kFallbackTrackingURLKey = @"fallbackTrackingUrl"; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation MPEnhancedDeeplinkRequest + +- (instancetype)initWithURL:(NSURL *)URL +{ + self = [super init]; + if (self) { + if (![[[URL host] lowercaseString] isEqualToString:kRequiredHostname]) { + return nil; + } + + NSString *primaryURLString = [URL mp_queryParameterForKey:kPrimaryURLKey]; + if (![primaryURLString length]) { + return nil; + } + _primaryURL = [NSURL URLWithString:primaryURLString]; + _originalURL = [URL copy]; + + NSMutableArray *primaryTrackingURLs = [NSMutableArray array]; + NSArray *primaryTrackingURLStrings = [URL mp_queryParametersForKey:kPrimaryTrackingURLKey]; + for (NSString *URLString in primaryTrackingURLStrings) { + [primaryTrackingURLs addObject:[NSURL URLWithString:URLString]]; + } + _primaryTrackingURLs = [NSArray arrayWithArray:primaryTrackingURLs]; + + NSString *fallbackURLString = [URL mp_queryParameterForKey:kFallbackURLKey]; + _fallbackURL = [NSURL URLWithString:fallbackURLString]; + + NSMutableArray *fallbackTrackingURLs = [NSMutableArray array]; + NSArray *fallbackTrackingURLStrings = [URL mp_queryParametersForKey:kFallbackTrackingURLKey]; + for (NSString *URLString in fallbackTrackingURLStrings) { + [fallbackTrackingURLs addObject:[NSURL URLWithString:URLString]]; + } + _fallbackTrackingURLs = [NSArray arrayWithArray:fallbackTrackingURLs]; + } + return self; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Common/MPFacebookKeywordProvider.h b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPFacebookKeywordProvider.h new file mode 100644 index 00000000000..7e92b65a180 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPFacebookKeywordProvider.h @@ -0,0 +1,21 @@ +// +// MPFacebookAttributionIdProvider.h +// MoPub +// +// Copyright (c) 2012 MoPub, Inc. All rights reserved. +// + +#import +#import "MPKeywordProvider.h" + +/* + * This class enables the MoPub SDK to deliver targeted ads from Facebook via MoPub Marketplace + * (MoPub's real-time bidding ad exchange) as part of a test program. This class sends an identifier + * to Facebook so Facebook can select the ad MoPub will serve in your app through MoPub Marketplace. + * If this class is removed from the SDK, your application will not receive targeted ads from + * Facebook. + */ + +@interface MPFacebookKeywordProvider : NSObject + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Common/MPFacebookKeywordProvider.m b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPFacebookKeywordProvider.m new file mode 100644 index 00000000000..de1a01f37ab --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPFacebookKeywordProvider.m @@ -0,0 +1,28 @@ +// +// MPFacebookAttributionIdProvider.m +// MoPub +// +// Copyright (c) 2012 MoPub, Inc. All rights reserved. +// + +#import "MPFacebookKeywordProvider.h" +#import + +static NSString *kFacebookAttributionIdPasteboardKey = @"fb_app_attribution"; +static NSString *kFacebookAttributionIdPrefix = @"FBATTRID:"; + +@implementation MPFacebookKeywordProvider + +#pragma mark - MPKeywordProvider + ++ (NSString *)keyword { + NSString *facebookAttributionId = [[UIPasteboard pasteboardWithName:kFacebookAttributionIdPasteboardKey + create:NO] string]; + if (!facebookAttributionId) { + return nil; + } + + return [NSString stringWithFormat:@"%@%@", kFacebookAttributionIdPrefix, facebookAttributionId]; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Common/MPKeywordProvider.h b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPKeywordProvider.h new file mode 100644 index 00000000000..fa96647c643 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPKeywordProvider.h @@ -0,0 +1,14 @@ +// +// MPKeywordProvider.h +// MoPub +// +// Copyright (c) 2012 MoPub, Inc. All rights reserved. +// + +#import + +@protocol MPKeywordProvider + ++ (NSString *)keyword; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Common/MPLastResortDelegate.h b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPLastResortDelegate.h new file mode 100644 index 00000000000..8f277d806aa --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPLastResortDelegate.h @@ -0,0 +1,18 @@ +// +// MPLastResortDelegate.h +// MoPub +// +// Copyright (c) 2013 MoPub. All rights reserved. +// + +#import +#import + +@interface MPLastResortDelegate : NSObject +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 60000 + +#endif + ++ (id)sharedDelegate; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Common/MPLastResortDelegate.m b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPLastResortDelegate.m new file mode 100644 index 00000000000..dfaacc8e365 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPLastResortDelegate.m @@ -0,0 +1,37 @@ +// +// MPLastResortDelegate.m +// MoPub +// +// Copyright (c) 2013 MoPub. All rights reserved. +// + +#import "MPLastResortDelegate.h" +#import "MPGlobal.h" + +@class MFMailComposeViewController; + +@implementation MPLastResortDelegate + ++ (id)sharedDelegate +{ + static MPLastResortDelegate *lastResortDelegate; + static dispatch_once_t once; + dispatch_once(&once, ^{ + lastResortDelegate = [[self alloc] init]; + }); + return lastResortDelegate; +} + +- (void)mailComposeController:(MFMailComposeViewController*)controller didFinishWithResult:(NSInteger)result error:(NSError*)error +{ + [(UIViewController *)controller dismissViewControllerAnimated:MP_ANIMATED completion:nil]; +} + +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 60000 +- (void)productViewControllerDidFinish:(SKStoreProductViewController *)viewController +{ + [viewController dismissViewControllerAnimated:MP_ANIMATED completion:nil]; +} +#endif + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Common/MPProgressOverlayView.h b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPProgressOverlayView.h new file mode 100644 index 00000000000..3d3692f5a18 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPProgressOverlayView.h @@ -0,0 +1,39 @@ +// +// MPProgressOverlayView.h +// MoPub +// +// Created by Andrew He on 7/18/12. +// Copyright 2012 MoPub, Inc. All rights reserved. +// + +#import + +@protocol MPProgressOverlayViewDelegate; + +@interface MPProgressOverlayView : UIView { + id __weak _delegate; + UIView *_outerContainer; + UIView *_innerContainer; + UIActivityIndicatorView *_activityIndicator; + UIButton *_closeButton; + CGPoint _closeButtonPortraitCenter; +} + +@property (nonatomic, weak) id delegate; +@property (nonatomic, strong) UIButton *closeButton; + +- (id)initWithDelegate:(id)delegate; +- (void)show; +- (void)hide; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@protocol MPProgressOverlayViewDelegate + +@optional +- (void)overlayCancelButtonPressed; +- (void)overlayDidAppear; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Common/MPProgressOverlayView.m b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPProgressOverlayView.m new file mode 100644 index 00000000000..803996c267c --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPProgressOverlayView.m @@ -0,0 +1,325 @@ +// +// MPProgressOverlayView.m +// MoPub +// +// Created by Andrew He on 7/18/12. +// Copyright 2012 MoPub, Inc. All rights reserved. +// + +#import "MPProgressOverlayView.h" +#import "MPGlobal.h" +#import "MPLogging.h" +#import + +static NSString * const kCloseButtonXImageName = @"MPCloseButtonX.png"; + +@interface MPProgressOverlayView () + +- (void)updateCloseButtonPosition; +- (void)registerForDeviceOrientationNotifications; +- (void)unregisterForDeviceOrientationNotifications; +- (void)deviceOrientationDidChange:(NSNotification *)notification; +- (void)setTransformForCurrentOrientationAnimated:(BOOL)animated; +- (void)setTransformForAllSubviews:(CGAffineTransform)transform; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#define kProgressOverlaySide 60.0 +#define kProgressOverlayBorderWidth 1.0 +#define kProgressOverlayCornerRadius 8.0 +#define kProgressOverlayShadowOpacity 0.8 +#define kProgressOverlayShadowRadius 8.0 +#define kProgressOverlayCloseButtonDelay 4.0 + +static void exponentialDecayInterpolation(void *info, const CGFloat *input, CGFloat *output); + +@implementation MPProgressOverlayView + +@synthesize delegate = _delegate; +@synthesize closeButton = _closeButton; + +- (id)initWithDelegate:(id)delegate +{ + self = [self initWithFrame:MPKeyWindow().bounds]; + if (self) { + self.delegate = delegate; + } + return self; +} + +- (id)initWithFrame:(CGRect)frame +{ + self = [super initWithFrame:frame]; + if (self) { + self.alpha = 0.0; + self.opaque = NO; + + // Close button. + _closeButton = [UIButton buttonWithType:UIButtonTypeCustom]; + _closeButton.alpha = 0.0; + _closeButton.hidden = YES; + [_closeButton addTarget:self + action:@selector(closeButtonPressed) + forControlEvents:UIControlEventTouchUpInside]; + UIImage *image = [UIImage imageNamed:MPResourcePathForResource(kCloseButtonXImageName)]; + [_closeButton setImage:image forState:UIControlStateNormal]; + [_closeButton sizeToFit]; + + _closeButtonPortraitCenter = + CGPointMake(self.bounds.size.width - 6.0 - CGRectGetMidX(_closeButton.bounds), + 6.0 + CGRectGetMidY(_closeButton.bounds)); + + _closeButton.center = _closeButtonPortraitCenter; + [self addSubview:_closeButton]; + + // Progress indicator container. + CGRect outerFrame = CGRectMake(0, 0, kProgressOverlaySide, kProgressOverlaySide); + _outerContainer = [[UIView alloc] initWithFrame:outerFrame]; + _outerContainer.alpha = 0.6; + _outerContainer.backgroundColor = [UIColor whiteColor]; + _outerContainer.center = self.center; + _outerContainer.frame = CGRectIntegral(_outerContainer.frame); + _outerContainer.opaque = NO; + _outerContainer.layer.cornerRadius = kProgressOverlayCornerRadius; + if ([_outerContainer.layer respondsToSelector:@selector(setShadowColor:)]) { + _outerContainer.layer.shadowColor = [UIColor blackColor].CGColor; + _outerContainer.layer.shadowOffset = CGSizeMake(0.0f, kProgressOverlayShadowRadius - 2.0f); + _outerContainer.layer.shadowOpacity = kProgressOverlayShadowOpacity; + _outerContainer.layer.shadowRadius = kProgressOverlayShadowRadius; + } + [self addSubview:_outerContainer]; + + CGFloat innerSide = kProgressOverlaySide - 2 * kProgressOverlayBorderWidth; + CGRect innerFrame = CGRectMake(0, 0, innerSide, innerSide); + _innerContainer = [[UIView alloc] initWithFrame:innerFrame]; + _innerContainer.backgroundColor = [UIColor blackColor]; + _innerContainer.center = CGPointMake(CGRectGetMidX(_outerContainer.bounds), + CGRectGetMidY(_outerContainer.bounds)); + _innerContainer.frame = CGRectIntegral(_innerContainer.frame); + _innerContainer.layer.cornerRadius = + kProgressOverlayCornerRadius - kProgressOverlayBorderWidth; + _innerContainer.opaque = NO; + [_outerContainer addSubview:_innerContainer]; + + // Progress indicator. + + _activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle: + UIActivityIndicatorViewStyleWhiteLarge]; + [_activityIndicator sizeToFit]; + [_activityIndicator startAnimating]; + _activityIndicator.center = self.center; + _activityIndicator.frame = CGRectIntegral(_activityIndicator.frame); + [self addSubview:_activityIndicator]; + + [self registerForDeviceOrientationNotifications]; + } + return self; +} + +- (void)dealloc +{ + [self unregisterForDeviceOrientationNotifications]; +} + +#pragma mark - Public Methods + +- (void)show +{ + [MPKeyWindow() addSubview:self]; + + [self setTransformForCurrentOrientationAnimated:NO]; + + if (MP_ANIMATED) { + [UIView animateWithDuration:0.2 animations:^{ + self.alpha = 1.0; + } completion:^(BOOL finished) { + if ([self.delegate respondsToSelector:@selector(overlayDidAppear)]) { + [self.delegate overlayDidAppear]; + } + }]; + } else { + self.alpha = 1.0; + if ([self.delegate respondsToSelector:@selector(overlayDidAppear)]) { + [self.delegate overlayDidAppear]; + } + } + + [self performSelector:@selector(enableCloseButton) + withObject:nil + afterDelay:kProgressOverlayCloseButtonDelay]; +} + +- (void)hide +{ + [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(enableCloseButton) object:nil]; + + self.closeButton.hidden = YES; + self.closeButton.alpha = 0.0f; + + if (MP_ANIMATED) { + [UIView animateWithDuration:0.2 animations:^{ + self.alpha = 0.0; + } completion:^(BOOL finished) { + [self removeFromSuperview]; + }]; + } else { + self.alpha = 0.0; + [self removeFromSuperview]; + } +} + +#pragma mark - Drawing and Layout + +- (void)drawRect:(CGRect)rect +{ + CGContextRef context = UIGraphicsGetCurrentContext(); + + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + + static const CGFloat input_value_range[2] = {0, 1}; + static const CGFloat output_value_range[8] = {0, 1, 0, 1, 0, 1, 0, 1}; + CGFunctionCallbacks callbacks = {0, exponentialDecayInterpolation, NULL}; + + CGFunctionRef shadingFunction = CGFunctionCreate((__bridge void *)(self), 1, input_value_range, 4, + output_value_range, &callbacks); + + CGPoint startPoint = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds)); + CGFloat startRadius = 0.0; + CGPoint endPoint = startPoint; + CGFloat endRadius = MAX(CGRectGetWidth(self.bounds), CGRectGetHeight(self.bounds)) / 2; + + CGShadingRef shading = CGShadingCreateRadial(colorSpace, startPoint, startRadius, endPoint, + endRadius, shadingFunction, + YES, // extend shading beyond starting circle + YES); // extend shading beyond ending circle + CGContextDrawShading(context, shading); + + CGShadingRelease(shading); + CGFunctionRelease(shadingFunction); + CGColorSpaceRelease(colorSpace); +} + +#define kGradientMaximumAlphaValue 0.90 +#define kGradientAlphaDecayFactor 1.1263 + +static void exponentialDecayInterpolation(void *info, const CGFloat *input, CGFloat *output) +{ + // output is an RGBA array corresponding to the color black with an alpha value somewhere on + // our exponential decay curve. + CGFloat progress = *input; + output[0] = 0.0; + output[1] = 0.0; + output[2] = 0.0; + output[3] = kGradientMaximumAlphaValue - exp(-progress / kGradientAlphaDecayFactor); +} + +- (void)layoutSubviews +{ + [self updateCloseButtonPosition]; +} + +- (void)updateCloseButtonPosition +{ + // Ensure that the close button is anchored to the top-right corner of the screen. + + CGPoint originalCenter = _closeButtonPortraitCenter; + CGPoint center = originalCenter; + BOOL statusBarHidden = [UIApplication sharedApplication].statusBarHidden; + CGFloat statusBarOffset = (statusBarHidden) ? 0.0 : 20.0; + + UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation]; + switch (orientation) { + case UIInterfaceOrientationLandscapeLeft: + center.x = CGRectGetMaxX(self.bounds) - originalCenter.x + statusBarOffset; + center.y = originalCenter.y; + break; + case UIInterfaceOrientationLandscapeRight: + center.x = originalCenter.x - statusBarOffset; + center.y = CGRectGetMaxY(self.bounds) - originalCenter.y; + break; + case UIInterfaceOrientationPortraitUpsideDown: + center.x = CGRectGetMaxX(self.bounds) - originalCenter.x; + center.y = CGRectGetMaxY(self.bounds) - originalCenter.y - statusBarOffset; + break; + default: + center.y = originalCenter.y + statusBarOffset; + break; + } + + _closeButton.center = center; +} + +#pragma mark - Internal + +- (void)registerForDeviceOrientationNotifications +{ + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(deviceOrientationDidChange:) + name:UIDeviceOrientationDidChangeNotification + object:nil]; +} + +- (void)unregisterForDeviceOrientationNotifications +{ + [[NSNotificationCenter defaultCenter] removeObserver:self + name:UIDeviceOrientationDidChangeNotification + object:nil]; +} + +- (void)deviceOrientationDidChange:(NSNotification *)notification +{ + [self setTransformForCurrentOrientationAnimated:YES]; + [self setNeedsLayout]; +} + +- (void)enableCloseButton +{ + _closeButton.hidden = NO; + + [UIView beginAnimations:nil context:nil]; + _closeButton.alpha = 1.0; + [UIView commitAnimations]; +} + +- (void)closeButtonPressed +{ + if ([_delegate respondsToSelector:@selector(overlayCancelButtonPressed)]) { + [_delegate overlayCancelButtonPressed]; + } +} + +- (void)setTransformForCurrentOrientationAnimated:(BOOL)animated +{ + UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation; + float angle = 0; + if (UIInterfaceOrientationIsPortrait(orientation)) { + if (orientation == UIInterfaceOrientationPortraitUpsideDown) { + angle = M_PI; + } + } else { + if (orientation == UIInterfaceOrientationLandscapeLeft) { + angle = -M_PI_2; + } else { + angle = M_PI_2; + } + } + + if (animated) { + [UIView beginAnimations:nil context:nil]; + [self setTransformForAllSubviews:CGAffineTransformMakeRotation(angle)]; + [UIView commitAnimations]; + } else { + [self setTransformForAllSubviews:CGAffineTransformMakeRotation(angle)]; + } +} + +- (void)setTransformForAllSubviews:(CGAffineTransform)transform +{ + for (UIView *view in self.subviews) { + view.transform = transform; + } +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Common/MPURLActionInfo.h b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPURLActionInfo.h new file mode 100644 index 00000000000..a487bc4d217 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPURLActionInfo.h @@ -0,0 +1,47 @@ +// +// MPURLActionInfo.h +// MoPub +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import + +#import "MPEnhancedDeeplinkRequest.h" + +#ifndef NS_ENUM +#define NS_ENUM(_type, _name) enum _name : _type _name; enum _name : _type +#endif + +typedef NS_ENUM(NSUInteger, MPURLActionType) { + MPURLActionTypeStoreKit, + MPURLActionTypeGenericDeeplink, + MPURLActionTypeEnhancedDeeplink, + MPURLActionTypeOpenInSafari, + MPURLActionTypeOpenURLInWebView, + MPURLActionTypeOpenInWebView, + MPURLActionTypeShare +}; + +@interface MPURLActionInfo : NSObject + +@property (nonatomic, readonly) MPURLActionType actionType; +@property (nonatomic, readonly, copy) NSURL *originalURL; +@property (nonatomic, readonly, copy) NSString *iTunesItemIdentifier; +@property (nonatomic, readonly, copy) NSURL *iTunesStoreFallbackURL; +@property (nonatomic, readonly, copy) NSURL *safariDestinationURL; +@property (nonatomic, readonly, copy) NSString *HTTPResponseString; +@property (nonatomic, readonly, copy) NSURL *webViewBaseURL; +@property (nonatomic, readonly, copy) NSURL *deeplinkURL; +@property (nonatomic, readonly, strong) MPEnhancedDeeplinkRequest *enhancedDeeplinkRequest; +@property (nonatomic, readonly, copy) NSURL *shareURL; + ++ (instancetype)infoWithURL:(NSURL *)URL iTunesItemIdentifier:(NSString *)identifier iTunesStoreFallbackURL:(NSURL *)URL; ++ (instancetype)infoWithURL:(NSURL *)URL safariDestinationURL:(NSURL *)safariDestinationURL; ++ (instancetype)infoWithURL:(NSURL *)URL HTTPResponseString:(NSString *)responseString webViewBaseURL:(NSURL *)baseURL; ++ (instancetype)infoWithURL:(NSURL *)URL webViewBaseURL:(NSURL *)baseURL; ++ (instancetype)infoWithURL:(NSURL *)URL deeplinkURL:(NSURL *)deeplinkURL; ++ (instancetype)infoWithURL:(NSURL *)URL enhancedDeeplinkRequest:(MPEnhancedDeeplinkRequest *)request; ++ (instancetype)infoWithURL:(NSURL *)URL shareURL:(NSURL *)shareURL; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Common/MPURLActionInfo.m b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPURLActionInfo.m new file mode 100644 index 00000000000..228eb488d9b --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPURLActionInfo.m @@ -0,0 +1,94 @@ +// +// MPURLActionInfo.m +// MoPub +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import "MPURLActionInfo.h" + +@interface MPURLActionInfo () + +@property (nonatomic, readwrite) MPURLActionType actionType; +@property (nonatomic, readwrite, copy) NSURL *originalURL; +@property (nonatomic, readwrite, copy) NSString *iTunesItemIdentifier; +@property (nonatomic, readwrite, copy) NSURL *iTunesStoreFallbackURL; +@property (nonatomic, readwrite, copy) NSURL *safariDestinationURL; +@property (nonatomic, readwrite, copy) NSString *HTTPResponseString; +@property (nonatomic, readwrite, copy) NSURL *webViewBaseURL; +@property (nonatomic, readwrite, copy) NSURL *deeplinkURL; +@property (nonatomic, readwrite, strong) MPEnhancedDeeplinkRequest *enhancedDeeplinkRequest; +@property (nonatomic, readwrite, copy) NSURL *shareURL; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation MPURLActionInfo + ++ (instancetype)infoWithURL:(NSURL *)URL iTunesItemIdentifier:(NSString *)identifier iTunesStoreFallbackURL:(NSURL *)fallbackURL +{ + MPURLActionInfo *info = [[[self class] alloc] init]; + info.actionType = MPURLActionTypeStoreKit; + info.originalURL = URL; + info.iTunesItemIdentifier = identifier; + info.iTunesStoreFallbackURL = fallbackURL; + return info; +} + ++ (instancetype)infoWithURL:(NSURL *)URL safariDestinationURL:(NSURL *)safariDestinationURL +{ + MPURLActionInfo *info = [[[self class] alloc] init]; + info.actionType = MPURLActionTypeOpenInSafari; + info.originalURL = URL; + info.safariDestinationURL = safariDestinationURL; + return info; +} + ++ (instancetype)infoWithURL:(NSURL *)URL HTTPResponseString:(NSString *)responseString webViewBaseURL:(NSURL *)baseURL +{ + MPURLActionInfo *info = [[[self class] alloc] init]; + info.actionType = MPURLActionTypeOpenInWebView; + info.originalURL = URL; + info.HTTPResponseString = responseString; + info.webViewBaseURL = baseURL; + return info; +} + ++ (instancetype)infoWithURL:(NSURL *)URL webViewBaseURL:(NSURL *)baseURL +{ + MPURLActionInfo *info = [[[self class] alloc] init]; + info.actionType = MPURLActionTypeOpenURLInWebView; + info.originalURL = URL; + info.webViewBaseURL = baseURL; + return info; +} + ++ (instancetype)infoWithURL:(NSURL *)URL deeplinkURL:(NSURL *)deeplinkURL +{ + MPURLActionInfo *info = [[[self class] alloc] init]; + info.actionType = MPURLActionTypeGenericDeeplink; + info.originalURL = URL; + info.deeplinkURL = deeplinkURL; + return info; +} + ++ (instancetype)infoWithURL:(NSURL *)URL enhancedDeeplinkRequest:(MPEnhancedDeeplinkRequest *)request +{ + MPURLActionInfo *info = [[[self class] alloc] init]; + info.actionType = MPURLActionTypeEnhancedDeeplink; + info.originalURL = URL; + info.enhancedDeeplinkRequest = request; + return info; +} + ++ (instancetype)infoWithURL:(NSURL *)URL shareURL:(NSURL *)shareURL +{ + MPURLActionInfo *info = [[[self class] alloc] init]; + info.actionType = MPURLActionTypeShare; + info.originalURL = URL; + info.shareURL = shareURL; + return info; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Common/MPURLResolver.h b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPURLResolver.h new file mode 100644 index 00000000000..a162b42a2ae --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPURLResolver.h @@ -0,0 +1,20 @@ +// +// MPURLResolver.h +// MoPub +// +// Copyright (c) 2013 MoPub. All rights reserved. +// + +#import +#import "MPGlobal.h" +#import "MPURLActionInfo.h" + +typedef void (^MPURLResolverCompletionBlock)(MPURLActionInfo *actionInfo, NSError *error); + +@interface MPURLResolver : NSObject + ++ (instancetype)resolverWithURL:(NSURL *)URL completion:(MPURLResolverCompletionBlock)completion; +- (void)start; +- (void)cancel; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Common/MPURLResolver.m b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPURLResolver.m new file mode 100644 index 00000000000..141bd1f1a4d --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPURLResolver.m @@ -0,0 +1,286 @@ +// +// MPURLResolver.m +// MoPub +// +// Copyright (c) 2013 MoPub. All rights reserved. +// + +#import +#import "MPURLResolver.h" +#import "NSURL+MPAdditions.h" +#import "NSHTTPURLResponse+MPAdditions.h" +#import "MPInstanceProvider.h" +#import "MPLogging.h" +#import "MPCoreInstanceProvider.h" + +static NSString * const kMoPubSafariScheme = @"mopubnativebrowser"; +static NSString * const kMoPubSafariNavigateHost = @"navigate"; +static NSString * const kResolverErrorDomain = @"com.mopub.resolver"; + +@interface MPURLResolver () + +@property (nonatomic, strong) NSURL *originalURL; +@property (nonatomic, strong) NSURL *currentURL; +@property (nonatomic, strong) NSURLConnection *connection; +@property (nonatomic, strong) NSMutableData *responseData; +@property (nonatomic, assign) NSStringEncoding responseEncoding; +@property (nonatomic, copy) MPURLResolverCompletionBlock completion; + +- (MPURLActionInfo *)actionInfoFromURL:(NSURL *)URL error:(NSError **)error; +- (NSString *)storeItemIdentifierForURL:(NSURL *)URL; +- (BOOL)URLShouldOpenInApplication:(NSURL *)URL; +- (BOOL)URLIsHTTPOrHTTPS:(NSURL *)URL; +- (BOOL)URLPointsToAMap:(NSURL *)URL; +- (NSStringEncoding)stringEncodingFromContentType:(NSString *)contentType; + +@end + +@implementation MPURLResolver + +@synthesize originalURL = _originalURL; +@synthesize currentURL = _currentURL; +@synthesize connection = _connection; +@synthesize responseData = _responseData; +@synthesize completion = _completion; + ++ (instancetype)resolverWithURL:(NSURL *)URL completion:(MPURLResolverCompletionBlock)completion +{ + return [[MPURLResolver alloc] initWithURL:URL completion:completion]; +} + +- (instancetype)initWithURL:(NSURL *)URL completion:(MPURLResolverCompletionBlock)completion +{ + self = [super init]; + if (self) { + _originalURL = [URL copy]; + _completion = [completion copy]; + } + return self; +} + +- (void)start +{ + [self.connection cancel]; + self.currentURL = self.originalURL; + + NSError *error = nil; + MPURLActionInfo *info = [self actionInfoFromURL:self.originalURL error:&error]; + + if (info) { + [self safeInvokeAndNilCompletionBlock:info error:nil]; + } else if (error) { + [self safeInvokeAndNilCompletionBlock:nil error:error]; + } else { + NSURLRequest *request = [[MPCoreInstanceProvider sharedProvider] buildConfiguredURLRequestWithURL:self.originalURL]; + self.responseData = [NSMutableData data]; + self.responseEncoding = NSUTF8StringEncoding; + self.connection = [NSURLConnection connectionWithRequest:request delegate:self]; + } +} + +- (void)cancel +{ + [self.connection cancel]; + self.connection = nil; + self.completion = nil; +} + +- (void)safeInvokeAndNilCompletionBlock:(MPURLActionInfo *)info error:(NSError *)error +{ + if (self.completion != nil) { + self.completion(info, error); + self.completion = nil; + } +} + +#pragma mark - Handling Application/StoreKit URLs + +/* + * Parses the provided URL for actions to perform (opening StoreKit, opening Safari, etc.). + * If the URL represents an action, this method will return an info object containing data that is + * relevant to the suggested action. + */ +- (MPURLActionInfo *)actionInfoFromURL:(NSURL *)URL error:(NSError **)error; +{ + MPURLActionInfo *actionInfo = nil; + + if (URL == nil) { + if (error) { + *error = [NSError errorWithDomain:kResolverErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey: @"URL is nil"}]; + } + return nil; + } + + if ([self storeItemIdentifierForURL:URL]) { + actionInfo = [MPURLActionInfo infoWithURL:self.originalURL iTunesItemIdentifier:[self storeItemIdentifierForURL:URL] iTunesStoreFallbackURL:URL]; + } else if ([self URLHasDeeplinkPlusScheme:URL]) { + MPEnhancedDeeplinkRequest *request = [[MPEnhancedDeeplinkRequest alloc] initWithURL:URL]; + if (request) { + actionInfo = [MPURLActionInfo infoWithURL:self.originalURL enhancedDeeplinkRequest:request]; + } else { + actionInfo = [MPURLActionInfo infoWithURL:self.originalURL deeplinkURL:URL]; + } + } else if ([self safariURLForURL:URL]) { + actionInfo = [MPURLActionInfo infoWithURL:self.originalURL safariDestinationURL:[NSURL URLWithString:[self safariURLForURL:URL]]]; + } else if ([URL mp_isMoPubShareScheme]) { + actionInfo = [MPURLActionInfo infoWithURL:self.originalURL shareURL:URL]; + } else if ([self URLShouldOpenInApplication:URL]) { + actionInfo = [MPURLActionInfo infoWithURL:self.originalURL deeplinkURL:URL]; + } else if ([URL.scheme isEqualToString:@"http"]) { // handle HTTP requests in particular to get around ATS settings + // As a note: `appTransportSecuritySettings` returns what makes sense for the iOS version. I.e., if the device + // is running iOS 8, this method will always return `MPATSSettingAllowsArbitraryLoads`. If the device is running + // iOS 9, this method will never give us `MPATSSettingAllowsArbitraryLoadsInWebContent`. As a result, we don't + // have to do OS checks here; we can just trust these settings. + MPATSSetting settings = [[MPCoreInstanceProvider sharedProvider] appTransportSecuritySettings]; + + if ((settings & MPATSSettingAllowsArbitraryLoads) != 0) { // opens as normal if ATS is disabled + // don't do anything + } else if ((settings & MPATSSettingAllowsArbitraryLoadsInWebContent) != 0) { // opens in WKWebView if ATS is disabled for arbitrary web content + actionInfo = [MPURLActionInfo infoWithURL:self.originalURL + webViewBaseURL:self.currentURL]; + } else { // opens in Mobile Safari if no other option is available + actionInfo = [MPURLActionInfo infoWithURL:self.originalURL + safariDestinationURL:self.currentURL]; + } + } + + return actionInfo; +} + +#pragma mark Identifying Application URLs + +- (BOOL)URLShouldOpenInApplication:(NSURL *)URL +{ + return ![self URLIsHTTPOrHTTPS:URL] || [self URLPointsToAMap:URL]; +} + +- (BOOL)URLIsHTTPOrHTTPS:(NSURL *)URL +{ + return [URL.scheme isEqualToString:@"http"] || [URL.scheme isEqualToString:@"https"]; +} + +- (BOOL)URLHasDeeplinkPlusScheme:(NSURL *)URL +{ + return [[URL.scheme lowercaseString] isEqualToString:@"deeplink+"]; +} + +- (BOOL)URLPointsToAMap:(NSURL *)URL +{ + return [URL.host hasSuffix:@"maps.google.com"] || [URL.host hasSuffix:@"maps.apple.com"]; +} + +#pragma mark Extracting StoreItem Identifiers + +- (NSString *)storeItemIdentifierForURL:(NSURL *)URL +{ + NSString *itemIdentifier = nil; + if ([URL.host hasSuffix:@"itunes.apple.com"]) { + NSString *lastPathComponent = [[URL path] lastPathComponent]; + if ([lastPathComponent hasPrefix:@"id"]) { + itemIdentifier = [lastPathComponent substringFromIndex:2]; + } else { + itemIdentifier = [URL.mp_queryAsDictionary objectForKey:@"id"]; + } + } else if ([URL.host hasSuffix:@"phobos.apple.com"]) { + itemIdentifier = [URL.mp_queryAsDictionary objectForKey:@"id"]; + } + + NSCharacterSet *nonIntegers = [[NSCharacterSet decimalDigitCharacterSet] invertedSet]; + if (itemIdentifier && itemIdentifier.length > 0 && [itemIdentifier rangeOfCharacterFromSet:nonIntegers].location == NSNotFound) { + return itemIdentifier; + } + + return nil; +} + +#pragma mark - Identifying URLs to open in Safari + +- (NSString *)safariURLForURL:(NSURL *)URL +{ + NSString *safariURL = nil; + + if ([[URL scheme] isEqualToString:kMoPubSafariScheme] && + [[URL host] isEqualToString:kMoPubSafariNavigateHost]) { + safariURL = [URL.mp_queryAsDictionary objectForKey:@"url"]; + } + + return safariURL; +} + +#pragma mark - Identifying NSStringEncoding from NSURLResponse Content-Type header + +- (NSStringEncoding)stringEncodingFromContentType:(NSString *)contentType +{ + NSStringEncoding encoding = NSUTF8StringEncoding; + + if (![contentType length]) { + MPLogWarn(@"Attempting to set string encoding from nil %@", kMoPubHTTPHeaderContentType); + return encoding; + } + + NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"(?<=charset=)[^;]*" options:kNilOptions error:nil]; + + NSTextCheckingResult *charsetResult = [regex firstMatchInString:contentType options:kNilOptions range:NSMakeRange(0, [contentType length])]; + if (charsetResult && charsetResult.range.location != NSNotFound) { + NSString *charset = [contentType substringWithRange:[charsetResult range]]; + + // ensure that charset is not deallocated early + CFStringRef cfCharset = CFBridgingRetain(charset); + CFStringEncoding cfEncoding = CFStringConvertIANACharSetNameToEncoding(cfCharset); + CFBridgingRelease(cfCharset); + + if (cfEncoding == kCFStringEncodingInvalidId) { + return encoding; + } + encoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding); + } + + return encoding; +} + +#pragma mark - + +- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data +{ + [self.responseData appendData:data]; +} + +- (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)response +{ + // First, check to see if the redirect URL matches any of our suggested actions. + NSError *error = nil; + MPURLActionInfo *info = [self actionInfoFromURL:request.URL error:&error]; + + if (info) { + [connection cancel]; + [self safeInvokeAndNilCompletionBlock:info error:nil]; + return nil; + } else { + // The redirected URL didn't match any actions, so we should continue with loading the URL. + self.currentURL = request.URL; + return request; + } +} + +- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response +{ + NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; + + NSDictionary *headers = [httpResponse allHeaderFields]; + NSString *contentType = [headers objectForKey:kMoPubHTTPHeaderContentType]; + self.responseEncoding = [httpResponse stringEncodingFromContentType:contentType]; +} + +- (void)connectionDidFinishLoading:(NSURLConnection *)connection +{ + NSString *responseString = [[NSString alloc] initWithData:self.responseData encoding:self.responseEncoding]; + MPURLActionInfo *info = [MPURLActionInfo infoWithURL:self.originalURL HTTPResponseString:responseString webViewBaseURL:self.currentURL]; + [self safeInvokeAndNilCompletionBlock:info error:nil]; +} + +- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error +{ + [self safeInvokeAndNilCompletionBlock:nil error:error]; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Common/MPVideoConfig.h b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPVideoConfig.h new file mode 100644 index 00000000000..7a2e23ef7ae --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPVideoConfig.h @@ -0,0 +1,49 @@ +// +// MPVideoConfig.h +// MoPub +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import +#import "MPVASTResponse.h" + +@interface MPVideoConfig : NSObject + +@property (nonatomic, readonly) NSURL *mediaURL; +@property (nonatomic, readonly) NSURL *clickThroughURL; +@property (nonatomic, readonly) NSArray *clickTrackingURLs; +@property (nonatomic, readonly) NSArray *errorURLs; +@property (nonatomic, readonly) NSArray *impressionURLs; + +/** @name Tracking Events */ + +@property (nonatomic, readonly) NSArray *creativeViewTrackers; +@property (nonatomic, readonly) NSArray *startTrackers; +@property (nonatomic, readonly) NSArray *firstQuartileTrackers; +@property (nonatomic, readonly) NSArray *midpointTrackers; +@property (nonatomic, readonly) NSArray *thirdQuartileTrackers; +@property (nonatomic, readonly) NSArray *completionTrackers; +@property (nonatomic, readonly) NSArray *muteTrackers; +@property (nonatomic, readonly) NSArray *unmuteTrackers; +@property (nonatomic, readonly) NSArray *pauseTrackers; +@property (nonatomic, readonly) NSArray *rewindTrackers; +@property (nonatomic, readonly) NSArray *resumeTrackers; +@property (nonatomic, readonly) NSArray *fullscreenTrackers; +@property (nonatomic, readonly) NSArray *exitFullscreenTrackers; +@property (nonatomic, readonly) NSArray *expandTrackers; +@property (nonatomic, readonly) NSArray *collapseTrackers; +@property (nonatomic, readonly) NSArray *acceptInvitationLinearTrackers; +@property (nonatomic, readonly) NSArray *closeLinearTrackers; +@property (nonatomic, readonly) NSArray *skipTrackers; +@property (nonatomic, readonly) NSArray *otherProgressTrackers; + +/** @name Viewability */ + +@property (nonatomic, readonly) NSTimeInterval minimumViewabilityTimeInterval; +@property (nonatomic, readonly) double minimumFractionOfVideoVisible; +@property (nonatomic, readonly) NSURL *viewabilityTrackingURL; + +- (instancetype)initWithVASTResponse:(MPVASTResponse *)response additionalTrackers:(NSDictionary *)additionalTrackers; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Common/MPVideoConfig.m b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPVideoConfig.m new file mode 100644 index 00000000000..f5c2c4b9aea --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPVideoConfig.m @@ -0,0 +1,300 @@ +// +// MPVideoConfig.m +// MoPub +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import "MPVideoConfig.h" +#import "MPLogging.h" +#import "MPVASTStringUtilities.h" + +@interface MPVideoPlaybackCandidate : NSObject + +@property (nonatomic, readwrite) MPVASTLinearAd *linearAd; +@property (nonatomic, readwrite) NSArray *errorURLs; +@property (nonatomic, readwrite) NSArray *impressionURLs; +@property (nonatomic, readwrite) NSTimeInterval minimumViewabilityTimeInterval; +@property (nonatomic, readwrite) double minimumFractionOfVideoVisible; +@property (nonatomic, readwrite) NSURL *viewabilityTrackingURL; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation MPVideoPlaybackCandidate + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface MPVideoConfig () + +@property (nonatomic, readwrite) NSURL *mediaURL; +@property (nonatomic, readwrite) NSURL *clickThroughURL; +@property (nonatomic, readwrite) NSArray *clickTrackingURLs; +@property (nonatomic, readwrite) NSArray *errorURLs; +@property (nonatomic, readwrite) NSArray *impressionURLs; +@property (nonatomic, readwrite) NSArray *startTrackers; +@property (nonatomic, readwrite) NSArray *firstQuartileTrackers; +@property (nonatomic, readwrite) NSArray *midpointTrackers; +@property (nonatomic, readwrite) NSArray *thirdQuartileTrackers; +@property (nonatomic, readwrite) NSArray *completionTrackers; +@property (nonatomic, readwrite) NSArray *muteTrackers; +@property (nonatomic, readwrite) NSArray *unmuteTrackers; +@property (nonatomic, readwrite) NSArray *pauseTrackers; +@property (nonatomic, readwrite) NSArray *rewindTrackers; +@property (nonatomic, readwrite) NSArray *resumeTrackers; +@property (nonatomic, readwrite) NSArray *fullscreenTrackers; +@property (nonatomic, readwrite) NSArray *exitFullscreenTrackers; +@property (nonatomic, readwrite) NSArray *expandTrackers; +@property (nonatomic, readwrite) NSArray *collapseTrackers; +@property (nonatomic, readwrite) NSArray *acceptInvitationLinearTrackers; +@property (nonatomic, readwrite) NSArray *closeLinearTrackers; +@property (nonatomic, readwrite) NSArray *skipTrackers; +@property (nonatomic, readwrite) NSArray *otherProgressTrackers; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface MPVASTLinearAd (MPVideoConfig) + +@property (nonatomic, readwrite) NSArray *clickTrackingURLs; +@property (nonatomic, readwrite) NSArray *customClickURLs; +@property (nonatomic, readwrite) NSArray *industryIcons; +@property (nonatomic, readwrite) NSDictionary *trackingEvents; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation MPVideoConfig + +- (instancetype)initWithVASTResponse:(MPVASTResponse *)response additionalTrackers:(NSDictionary *)additionalTrackers +{ + self = [super init]; + if (self) { + [self commonInit:response additionalTrackers:additionalTrackers]; + } + return self; +} + +- (void)commonInit:(MPVASTResponse *)response additionalTrackers:(NSDictionary *)additionalTrackers +{ + NSArray *candidates = [self playbackCandidatesFromVASTResponse:response]; + + if (candidates.count == 0) { + return; + } + + MPVideoPlaybackCandidate *candidate = candidates[0]; + MPVASTMediaFile *mediaFile = candidate.linearAd.highestBitrateMediaFile; + + _mediaURL = mediaFile.URL; + _clickThroughURL = candidate.linearAd.clickThroughURL; + _clickTrackingURLs = candidate.linearAd.clickTrackingURLs; + _errorURLs = candidate.errorURLs; + _impressionURLs = candidate.impressionURLs; + + NSDictionary *trackingEvents = candidate.linearAd.trackingEvents; + _creativeViewTrackers = trackingEvents[MPVASTTrackingEventTypeCreativeView]; + _startTrackers = [self trackersByMergingOriginalTrackers:trackingEvents additionalTrackers:additionalTrackers name:MPVASTTrackingEventTypeStart]; + _firstQuartileTrackers = [self trackersByMergingOriginalTrackers:trackingEvents additionalTrackers:additionalTrackers name:MPVASTTrackingEventTypeFirstQuartile]; + _midpointTrackers = [self trackersByMergingOriginalTrackers:trackingEvents additionalTrackers:additionalTrackers name:MPVASTTrackingEventTypeMidpoint]; + _thirdQuartileTrackers = [self trackersByMergingOriginalTrackers:trackingEvents additionalTrackers:additionalTrackers name:MPVASTTrackingEventTypeThirdQuartile]; + _completionTrackers = [self trackersByMergingOriginalTrackers:trackingEvents additionalTrackers:additionalTrackers name:MPVASTTrackingEventTypeComplete]; + _muteTrackers = trackingEvents[MPVASTTrackingEventTypeMute]; + _unmuteTrackers = trackingEvents[MPVASTTrackingEventTypeUnmute]; + _pauseTrackers = trackingEvents[MPVASTTrackingEventTypePause]; + _rewindTrackers = trackingEvents[MPVASTTrackingEventTypeRewind]; + _resumeTrackers = trackingEvents[MPVASTTrackingEventTypeResume]; + _fullscreenTrackers = trackingEvents[MPVASTTrackingEventTypeFullscreen]; + _exitFullscreenTrackers = trackingEvents[MPVASTTrackingEventTypeExitFullscreen]; + _expandTrackers = trackingEvents[MPVASTTrackingEventTypeExpand]; + _collapseTrackers = trackingEvents[MPVASTTrackingEventTypeCollapse]; + _acceptInvitationLinearTrackers = trackingEvents[MPVASTTrackingEventTypeAcceptInvitationLinear]; + _closeLinearTrackers = trackingEvents[MPVASTTrackingEventTypeCloseLinear]; + _skipTrackers = trackingEvents[MPVASTTrackingEventTypeSkip]; + _otherProgressTrackers = trackingEvents[MPVASTTrackingEventTypeProgress]; + + _minimumViewabilityTimeInterval = candidate.minimumViewabilityTimeInterval; + _minimumFractionOfVideoVisible = candidate.minimumFractionOfVideoVisible; + _viewabilityTrackingURL = candidate.viewabilityTrackingURL; +} + +- (NSArray *)playbackCandidatesFromVASTResponse:(MPVASTResponse *)response +{ + NSMutableArray *candidates = [NSMutableArray array]; + + for (MPVASTAd *ad in response.ads) { + if (ad.inlineAd) { + MPVASTInline *inlineAd = ad.inlineAd; + NSArray *creatives = inlineAd.creatives; + for (MPVASTCreative *creative in creatives) { + if (creative.linearAd && [creative.linearAd.mediaFiles count]) { + MPVideoPlaybackCandidate *candidate = [[MPVideoPlaybackCandidate alloc] init]; + candidate.linearAd = creative.linearAd; + candidate.errorURLs = inlineAd.errorURLs; + candidate.impressionURLs = inlineAd.impressionURLs; + + NSDictionary *viewabilityExt = [self extensionFromInlineAd:inlineAd forKey:@"MoPubViewabilityTracker"]; + if (viewabilityExt) { + NSURL *viewabilityTrackingURL = [NSURL URLWithString:viewabilityExt[@"text"]]; + BOOL valid = [MPVASTStringUtilities stringRepresentsNonNegativeDuration:viewabilityExt[@"viewablePlaytime"]]&& + [MPVASTStringUtilities stringRepresentsNonNegativePercentage:viewabilityExt[@"percentViewable"]] && + viewabilityTrackingURL; + + if (valid) { + candidate.minimumViewabilityTimeInterval = [MPVASTStringUtilities timeIntervalFromString:viewabilityExt[@"viewablePlaytime"]]; + candidate.minimumFractionOfVideoVisible = [MPVASTStringUtilities percentageFromString:viewabilityExt[@"percentViewable"]] / 100.0; + candidate.viewabilityTrackingURL = viewabilityTrackingURL; + } + } + + [candidates addObject:candidate]; + } + } + } else if (ad.wrapper) { + NSArray *candidatesFromWrapper = [self playbackCandidatesFromVASTResponse:ad.wrapper.wrappedVASTResponse]; + + // Merge any wrapper-level tracking URLs into each of the candidates. + for (MPVideoPlaybackCandidate *candidate in candidatesFromWrapper) { + candidate.errorURLs = [candidate.errorURLs arrayByAddingObjectsFromArray:ad.wrapper.errorURLs]; + candidate.impressionURLs = [candidate.impressionURLs arrayByAddingObjectsFromArray:ad.wrapper.impressionURLs]; + + candidate.linearAd.trackingEvents = [self dictionaryByMergingTrackingDictionaries:@[candidate.linearAd.trackingEvents, [self trackingEventsFromWrapper:ad.wrapper]]]; + candidate.linearAd.clickTrackingURLs = [candidate.linearAd.clickTrackingURLs arrayByAddingObjectsFromArray:[self clickTrackingURLsFromWrapper:ad.wrapper]]; + candidate.linearAd.customClickURLs = [candidate.linearAd.customClickURLs arrayByAddingObjectsFromArray:[self customClickURLsFromWrapper:ad.wrapper]]; + candidate.linearAd.industryIcons = [candidate.linearAd.industryIcons arrayByAddingObjectsFromArray:[self industryIconsFromWrapper:ad.wrapper]]; + } + + [candidates addObjectsFromArray:candidatesFromWrapper]; + } + } + + return candidates; +} + +- (NSDictionary *)trackingEventsFromWrapper:(MPVASTWrapper *)wrapper +{ + NSMutableArray *trackingEventDictionaries = [NSMutableArray array]; + + for (MPVASTCreative *creative in wrapper.creatives) { + [trackingEventDictionaries addObject:creative.linearAd.trackingEvents]; + } + + return [self dictionaryByMergingTrackingDictionaries:trackingEventDictionaries]; +} + +- (NSArray *)clickTrackingURLsFromWrapper:(MPVASTWrapper *)wrapper +{ + NSMutableArray *clickTrackingURLs = [NSMutableArray array]; + for (MPVASTCreative *creative in wrapper.creatives) { + [clickTrackingURLs addObjectsFromArray:creative.linearAd.clickTrackingURLs]; + } + + return clickTrackingURLs; +} + +- (NSArray *)customClickURLsFromWrapper:(MPVASTWrapper *)wrapper +{ + NSMutableArray *customClickURLs = [NSMutableArray array]; + for (MPVASTCreative *creative in wrapper.creatives) { + [customClickURLs addObjectsFromArray:creative.linearAd.customClickURLs]; + } + + return customClickURLs; +} + +- (NSArray *)industryIconsFromWrapper:(MPVASTWrapper *)wrapper +{ + NSMutableArray *industryIcons = [NSMutableArray array]; + for (MPVASTCreative *creative in wrapper.creatives) { + [industryIcons addObjectsFromArray:creative.linearAd.industryIcons]; + } + + return industryIcons; +} + +- (NSDictionary *)extensionFromInlineAd:(MPVASTInline *)inlineAd forKey:(NSString *)key +{ + NSDictionary *extensions = inlineAd.extensions; + id extensionObject = [extensions objectForKey:@"Extension"]; + + if ([extensionObject isKindOfClass:[NSDictionary class]]) { + // Case 1: "Extensions" element with only one "Extension" child. + NSDictionary *extensionChildNode = extensionObject; + id extension = [self firstObjectForKey:key inDictionary:extensionChildNode]; + if ([extension isKindOfClass:[NSDictionary class]]) { + return extension; + } + } else if ([extensionObject isKindOfClass:[NSArray class]]) { + // Case 2: "Extensions" element with multiple "Extension" children. + NSArray *extensionChildNodes = extensionObject; + for (id node in extensionChildNodes) { + if (![node isKindOfClass:[NSDictionary class]]) { + continue; + } + + id extension = [self firstObjectForKey:key inDictionary:node]; + if ([extension isKindOfClass:[NSDictionary class]]) { + return extension; + } + } + } + + return nil; +} + +// When dealing with VAST, we will often have dictionaries where a key can map either to a single +// value or an array of values. For example, the dictionary containing VAST extensions might contain +// one or more nodes. This method is useful when we simply want the first value matching +// a given key. It is equivalent to calling [dictionary objectForKey:key] when the key maps to a +// single value. When the key maps to an NSArray, it returns the first value in the array. +- (id)firstObjectForKey:(NSString *)key inDictionary:(NSDictionary *)dictionary +{ + id value = [dictionary objectForKey:key]; + if ([value isKindOfClass:[NSArray class]]) { + return [value firstObject]; + } else { + return value; + } +} + +- (NSArray *)trackersByMergingOriginalTrackers:(NSDictionary *)originalTrackers additionalTrackers:(NSDictionary *)additionalTrackers name:(NSString *)trackerName +{ + if (![originalTrackers[trackerName] isKindOfClass:[NSArray class]]) { + return additionalTrackers[trackerName]; + } + if (![additionalTrackers[trackerName] isKindOfClass:[NSArray class]]) { + return originalTrackers[trackerName]; + } + NSMutableArray *mergedTrackers = [NSMutableArray new]; + [mergedTrackers addObjectsFromArray:originalTrackers[trackerName]]; + [mergedTrackers addObjectsFromArray:additionalTrackers[trackerName]]; + return mergedTrackers; +} + +- (NSDictionary *)dictionaryByMergingTrackingDictionaries:(NSArray *)dictionaries +{ + NSMutableDictionary *mergedDictionary = [NSMutableDictionary dictionary]; + for (NSDictionary *dictionary in dictionaries) { + for (NSString *key in [dictionary allKeys]) { + if ([dictionary[key] isKindOfClass:[NSArray class]]) { + if (!mergedDictionary[key]) { + mergedDictionary[key] = [NSMutableArray array]; + } + + [mergedDictionary[key] addObjectsFromArray:dictionary[key]]; + } else { + MPLogError(@"TrackingEvents dictionary expected an array object for key '%@' " + @"but got an instance of %@ instead.", + key, NSStringFromClass([dictionary[key] class])); + } + } + } + return mergedDictionary; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Common/MPXMLParser.h b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPXMLParser.h new file mode 100644 index 00000000000..c246979f6b4 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPXMLParser.h @@ -0,0 +1,14 @@ +// +// MPXMLParser.h +// MoPub +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import + +@interface MPXMLParser : NSObject + +- (NSDictionary *)dictionaryWithData:(NSData *)data error:(NSError **)error; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Common/MPXMLParser.m b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPXMLParser.m new file mode 100644 index 00000000000..c38b620899e --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Common/MPXMLParser.m @@ -0,0 +1,88 @@ +// +// MPXMLParser.m +// MoPub +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import "MPXMLParser.h" + +@interface MPXMLParser () + +@property (nonatomic) NSMutableArray *elementStack; +@property (nonatomic) NSMutableString *currentTextContent; +@property (nonatomic) NSError *parseError; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation MPXMLParser + +- (instancetype)init +{ + if (self = [super init]) { + _elementStack = [NSMutableArray array]; + + // Create a "root" dictionary. + [_elementStack addObject:[NSMutableDictionary dictionary]]; + + _currentTextContent = [NSMutableString string]; + } + return self; +} + +- (NSDictionary *)dictionaryWithData:(NSData *)data error:(NSError *__autoreleasing *)error +{ + NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data]; + [parser setDelegate:self]; + [parser parse]; + return self.elementStack[0]; +} + +#pragma mark - + +- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict +{ + NSMutableDictionary *parentElement = [self.elementStack lastObject]; + NSMutableDictionary *currentElement = [NSMutableDictionary dictionary]; + [currentElement addEntriesFromDictionary:attributeDict]; + + if (parentElement[elementName] && [parentElement[elementName] isKindOfClass:[NSArray class]]) { + [parentElement[elementName] addObject:currentElement]; + } else if (parentElement[elementName]) { + NSMutableDictionary *previousElement = parentElement[elementName]; + NSMutableArray *elementsArray = [NSMutableArray array]; + [elementsArray addObject:previousElement]; + [elementsArray addObject:currentElement]; + parentElement[elementName] = elementsArray; + } else { + parentElement[elementName] = currentElement; + } + + [self.elementStack addObject:currentElement]; +} + +- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName +{ + NSMutableDictionary *currentElement = [self.elementStack lastObject]; + NSString *trimmedContent = [self.currentTextContent stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; + if ([trimmedContent length]) { + currentElement[@"text"] = trimmedContent; + } + + self.currentTextContent = [NSMutableString string]; + [self.elementStack removeLastObject]; +} + +- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string +{ + [self.currentTextContent appendString:string]; +} + +- (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError +{ + self.parseError = parseError; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Event Logging/MPLogEvent+NativeVideo.h b/iphone/Maps/3party/MoPubSDK/Internal/Event Logging/MPLogEvent+NativeVideo.h new file mode 100644 index 00000000000..28ec396027e --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Event Logging/MPLogEvent+NativeVideo.h @@ -0,0 +1,25 @@ +// +// MPLogEvent+NativeVideo.h +// MoPubSDK +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import + +#import "MPLogEvent.h" + +typedef NS_ENUM(NSInteger, MPNativeVideoEventType) { + MPNativeVideoEventTypeDownloadStart, + MPNativeVideoEventTypeVideoReady, + MPNativeVideoEventTypeBuffering, + MPNativeVideoEventTypeDownloadFinished, + MPNativeVideoEventTypeErrorFailedToPlay, + MPNativeVideoEventTypeErrorDuringPlayback, +}; + +@interface MPLogEvent (NativeVideo) + +- (instancetype)initWithLogEventProperties:(MPAdConfigurationLogEventProperties *)logEventProperties nativeVideoEventType:(MPNativeVideoEventType)nativeVideoEventType; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Event Logging/MPLogEvent+NativeVideo.m b/iphone/Maps/3party/MoPubSDK/Internal/Event Logging/MPLogEvent+NativeVideo.m new file mode 100644 index 00000000000..9fd1281b810 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Event Logging/MPLogEvent+NativeVideo.m @@ -0,0 +1,39 @@ +// +// MPLogEvent+NativeVideo.m +// MoPubSDK +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import "MPLogEvent+NativeVideo.h" + +static NSDictionary *nativeVideoLogEventTypeToString = nil; + +NSString *const MPNativeVideoEventTypeDownloadStartString = @"download_start"; +NSString *const MPNativeVideoEventTypeVideoReadyString = @"download_video_ready"; +NSString *const MPNativeVideoEventTypeBufferingString = @"download_buffering"; +NSString *const MPNativeVideoEventTypeDownloadFinishedString = @"download_finished"; +NSString *const MPNativeVideoEventTypeErrorFailedToPlayString = @"error_failed_to_play"; +NSString *const MPNativeVideoEventTypeErrorDuringPlaybackString = @"error_during_playback"; + +@implementation MPLogEvent (NativeVideo) + +- (instancetype)initWithLogEventProperties:(MPAdConfigurationLogEventProperties *)logEventProperties nativeVideoEventType:(MPNativeVideoEventType)nativeVideoEventType +{ + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + nativeVideoLogEventTypeToString = @{@(MPNativeVideoEventTypeDownloadStart): MPNativeVideoEventTypeDownloadStartString, + @(MPNativeVideoEventTypeVideoReady): MPNativeVideoEventTypeVideoReadyString, + @(MPNativeVideoEventTypeBuffering): MPNativeVideoEventTypeBufferingString, + @(MPNativeVideoEventTypeDownloadFinished): MPNativeVideoEventTypeDownloadFinishedString, + @(MPNativeVideoEventTypeErrorDuringPlayback): MPNativeVideoEventTypeErrorDuringPlaybackString, + @(MPNativeVideoEventTypeErrorFailedToPlay): MPNativeVideoEventTypeErrorFailedToPlayString}; + }); + + if (self = [[MPLogEvent alloc] initWithEventCategory:MPLogEventCategoryNativeVideo eventName:[nativeVideoLogEventTypeToString objectForKey:@(nativeVideoEventType)]]) { + [self setLogEventProperties:logEventProperties]; + } + return self; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Event Logging/MPLogEvent.h b/iphone/Maps/3party/MoPubSDK/Internal/Event Logging/MPLogEvent.h new file mode 100644 index 00000000000..20ce150e161 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Event Logging/MPLogEvent.h @@ -0,0 +1,260 @@ +// +// MPLogEvent.h +// MoPubSDK +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import +#import "MPAdConfiguration.h" + +#ifndef NS_ENUM +#define NS_ENUM(_type, _name) enum _name : _type _name; enum _name : _type +#endif + +typedef NS_ENUM(NSInteger, MPLogEventScribeCategory) { + MPExchangeClientEventCategory, + MPExchangeClientErrorCategory, +}; + +typedef NS_ENUM(NSInteger, MPLogEventNetworkType) { + MPLogEventNetworkTypeUnknown, + MPLogEventNetworkTypeEthernet, + MPLogEventNetworkTypeWifi, + MPLogEventNetworkTypeMobile, +}; + +/* + * Event names. + */ +extern NSString *const MPLogEventNameAdRequest; +extern NSString *const MPLogEventNameClickthroughDwellTime; + +/* + * Event categories. + */ +extern NSString *const MPLogEventCategoryRequests; +extern NSString *const MPLogEventCategoryNativeVideo; +extern NSString *const MPLogEventCategoryAdInteractions; + +/* MPAdConfigurationLogEventProperties + * + * Convenience struct class for keeping track of the log event properties + * that are parsed off the MPAdConfiguration. + */ +@interface MPAdConfigurationLogEventProperties : NSObject + +@property (nonatomic, copy) NSString *adType; +@property (nonatomic, copy) NSString *adCreativeId; +@property (nonatomic, copy) NSString *dspCreativeId; +@property (nonatomic, copy) NSString *adNetworkType; +@property (nonatomic) CGSize adSize; +@property (nonatomic, copy) NSString *requestId; +@property (nonatomic, copy) NSString *adUnitId; + +- (instancetype)initWithConfiguration:(MPAdConfiguration *)configuration; + +@end + +@interface MPLogEvent : NSObject + +#pragma mark - Public properties + +/* Event details */ + +/** + * The event or error name. Examples include "ad_request", "ad_impression", "ad_click", + * "json_missing_required_fields", "invalid_url", "invalid_positioning_data". + */ +@property (nonatomic, copy) NSString *eventName; + +/** + * The event or error category. This is typically set to the component in which the event + * or error occurred. Examples include "network", "mraid_video", "mopub_native", "facebook_banner", + * "server_positioning", "client_positioning". + */ +@property (nonatomic, copy) NSString *eventCategory; + +/** + * Creation time of the object. + */ +@property (nonatomic, readonly) NSDate *timestamp; + +/** + * The scribe category that the log event will be bucketed into. + */ +@property (nonatomic, readonly) MPLogEventScribeCategory scribeCategory; + + +/* Ad details */ + +/** + * The MoPub AdUnit ID string that is associated with the event. + */ +@property (nonatomic, copy) NSString *adUnitId; + +/** + * The MoPub Creative ID string that is associated with the event. + */ +@property (nonatomic, copy) NSString *adCreativeId; + +/** + * The DSP Creative ID string that is associated with the event. + */ +@property (nonatomic, copy) NSString *dspCreativeId; + +/** + * Identifier for a class of ad. Examples include "html", "mraid", "interstitial", "json", + * "custom", "clear". + */ +@property (nonatomic, copy) NSString *adType; + +/** + * Identifier for an ad network type. Examples include "admob", "custom", "custom_native", "mojiva", + * "huntmads", "millennial". + */ +@property (nonatomic, copy) NSString *adNetworkType; + +/** + * The ad's size in device independent pixels (dips). + */ +@property (nonatomic) CGSize adSize; + +/* App details */ + +/** + * The name of the app the SDK is being included in + */ +@property (nonatomic, copy) NSString *appName; + +/** + * The app store identifier for the app the SDK is being included in + */ +@property (nonatomic, copy) NSString *appStoreId; + +/** + * The app store identifier for the app the SDK is being included in + */ +@property (nonatomic, copy) NSString *appBundleId; + +/** + * The version of the client app the SDK is included in + */ +@property (nonatomic, copy) NSString *appVersion; + + +/* Performance details */ + +/** + * Time taken in ms to perform a request or action + */ +@property (nonatomic, readonly) NSUInteger performanceDurationMs; + + +/* Request details */ + +/** + * The unique ID created by the MoPub ad server for the ad request + */ +@property (nonatomic, copy) NSString *requestId; + +/** + * The status code of the request from the MoPub ad server + */ +@property (nonatomic) NSUInteger requestStatusCode; + +/** + * The full URI for the request to the adserver + */ +@property (nonatomic, copy) NSString *requestURI; + +/** + * The number of retries made in the request. + */ +@property (nonatomic) NSUInteger requestRetries; + + +/** SDK details */ + +/** + * The current MoPub SDK version, e.g. "3.1.0" + */ +@property (nonatomic, copy, readonly) NSString *sdkVersion; + + +/* Device details */ + +/** + * The name of the device, e.g. iPhone, iPad, iPod + */ +@property (nonatomic, copy, readonly) NSString *deviceModel; + +/** + * The version of iOS running on the device, e.g. 8.0.1, 6.2.1, etc + */ +@property (nonatomic, copy, readonly) NSString *deviceOSVersion; + +/** + * The device's screen size in device independent pixels (dips). + */ +@property (nonatomic, readonly) CGSize deviceSize; + +/* Geo details */ + +/** + * Latitude and longitude + */ +@property (nonatomic, readonly) double geoLat; +@property (nonatomic, readonly) double geoLon; + +/** + * The radius of the circle in meters of the estimated accuracy of the latitude and longitude + */ +@property (nonatomic, readonly) double geoAccuracy; + + +/* Network/Carrier details */ + +@property (nonatomic, readonly) MPLogEventNetworkType networkType; +@property (nonatomic, copy, readonly) NSString *networkOperatorCode; +@property (nonatomic, copy, readonly) NSString *networkOperatorName; +@property (nonatomic, copy, readonly) NSString *networkISOCountryCode; +@property (nonatomic, copy, readonly) NSString *networkSIMCode; +@property (nonatomic, copy, readonly) NSString *networkSIMOperatorName; +@property (nonatomic, copy, readonly) NSString *networkSimISOCountryCode; + +/* Client details */ + +/** + * User specific, unique, resettable ID for advertising, i.e. IFA. + */ +@property (nonatomic, copy, readonly) NSString *clientAdvertisingId; + +/** + * Whether or not the user has limited ad tracking. + */ + +@property (nonatomic, readonly) BOOL clientDoNotTrack; + +- (instancetype)initWithEventCategory:(NSString *)eventCategory eventName:(NSString *)eventName; + +- (NSString *)serialize; +- (NSDictionary *)asDictionary; + +/** + * Current timestamp in ms since January 1, 1970 00:00:00.0 UTC (aka epoch time) + */ +- (NSUInteger)timestampAsEpoch; + +/** + * Record the end time for a timed event. The start time will be the event's timestamp, which is + * set on object creation. This will set the performanceDurationMs property with the timestamp - end time. + */ +- (void)recordEndTime; + +/** + * Convenience method to set common properties with the MPAdConfigurationLogEventProperties. + */ +- (void)setLogEventProperties:(MPAdConfigurationLogEventProperties *)logEventProperties; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Event Logging/MPLogEvent.m b/iphone/Maps/3party/MoPubSDK/Internal/Event Logging/MPLogEvent.m new file mode 100644 index 00000000000..66a8a632e95 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Event Logging/MPLogEvent.m @@ -0,0 +1,294 @@ +// +// MPLogEvent.m +// MoPubSDK +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import "MPLogEvent.h" +#import "MPIdentityProvider.h" +#import "MPInternalUtils.h" +#import "MPCoreInstanceProvider.h" +#import "MPConstants.h" +#import "MPReachability.h" +#import "MPGeolocationProvider.h" + +NSUInteger const IOS_APP_PLATFORM_ID = 1; +NSUInteger const IOS_SDK_PRODUCT_ID = 0; +NSString * const IOS_MANUFACTURER_NAME = @"Apple"; + +/* + * Event names. + */ +NSString * const MPLogEventNameAdRequest = @"ad_request"; +NSString * const MPLogEventNameClickthroughDwellTime = @"clickthrough_dwell_time"; + +/* + * Event categories. + */ +NSString * const MPLogEventCategoryRequests = @"requests"; +NSString * const MPLogEventCategoryNativeVideo = @"native_video"; +NSString * const MPLogEventCategoryAdInteractions = @"ad_interactions"; + +@interface MPAdConfigurationLogEventProperties () + +@end + +@implementation MPAdConfigurationLogEventProperties + +- (instancetype)initWithConfiguration:(MPAdConfiguration *)configuration +{ + if (self = [super init]) { + _adType = configuration.headerAdType; + _adCreativeId = configuration.creativeId; + _dspCreativeId = configuration.dspCreativeId; + _adNetworkType = configuration.networkType; + _adSize = configuration.preferredSize; + + NSDictionary *failURLQueryParameters = MPDictionaryFromQueryString([configuration.failoverURL query]); + _requestId = failURLQueryParameters[@"request_id"]; + _adUnitId = failURLQueryParameters[@"id"]; + } + return self; +} + +@end + + +#pragma mark - Private properties + +@interface MPLogEvent () + +@property (nonatomic, readwrite) NSDate *timestamp; +@property (nonatomic, readwrite) MPLogEventScribeCategory scribeCategory; +@property (nonatomic, copy, readwrite) NSString *sdkVersion; +@property (nonatomic, copy, readwrite) NSString *deviceModel; +@property (nonatomic, copy, readwrite) NSString *deviceOSVersion; +@property (nonatomic, readwrite) CGSize deviceSize; +@property (nonatomic, readwrite) double geoLat; +@property (nonatomic, readwrite) double geoLon; +@property (nonatomic, readwrite) double geoAccuracy; +@property (nonatomic, readwrite) MPLogEventNetworkType networkType; +@property (nonatomic, copy, readwrite) NSString *networkOperatorCode; +@property (nonatomic, copy, readwrite) NSString *networkOperatorName; +@property (nonatomic, copy, readwrite) NSString *networkISOCountryCode; +@property (nonatomic, readwrite) NSUInteger performanceDurationMs; +@property (nonatomic, copy, readwrite) NSString *networkSIMCode; +@property (nonatomic, copy, readwrite) NSString *networkSIMOperatorName; +@property (nonatomic, copy, readwrite) NSString *networkSimISOCountryCode; +@property (nonatomic, copy, readwrite) NSString *clientAdvertisingId; +@property (nonatomic, readwrite) BOOL clientDoNotTrack; + +@end + + +@implementation MPLogEvent + +- (instancetype)initWithEventCategory:(NSString *)eventCategory eventName:(NSString *)eventName +{ + if (self = [super init]) { + + MPCoreInstanceProvider *provider = [MPCoreInstanceProvider sharedProvider]; + + [self setEventName:eventName]; + [self setEventCategory:eventCategory]; + + [self setTimestamp:[NSDate date]]; + [self setScribeCategory:MPExchangeClientEventCategory]; + + // SDK Info + [self setSdkVersion:MP_SDK_VERSION]; + + // Device info + UIDevice *device = [UIDevice currentDevice]; + [self setDeviceModel:[device model]]; + [self setDeviceOSVersion:[device systemVersion]]; + + [self setDeviceSize:MPScreenBounds().size]; + + // Geolocation info + CLLocation *location = [[[MPCoreInstanceProvider sharedProvider] sharedMPGeolocationProvider] lastKnownLocation]; + [self setGeoLat:location.coordinate.latitude]; + [self setGeoLon:location.coordinate.longitude]; + [self setGeoAccuracy:location.horizontalAccuracy]; + + // Client info + [self setClientDoNotTrack:![MPIdentityProvider advertisingTrackingEnabled]]; + // Note: we've chosen at this time to never send the real IDFA. + [self setClientAdvertisingId:[MPIdentityProvider obfuscatedIdentifier]]; + + + // Network/Carrier info + if ([provider sharedMPReachability].hasWifi) { + [self setNetworkType:MPLogEventNetworkTypeWifi]; + } else if ([provider sharedMPReachability].hasCellular) { + [self setNetworkType:MPLogEventNetworkTypeMobile]; + + NSDictionary *carrierInfo = [[MPCoreInstanceProvider sharedProvider] sharedCarrierInfo]; + NSString *networkOperatorName = carrierInfo[@"carrierName"]; + NSString *mcc = carrierInfo[@"mobileCountryCode"]; + NSString *mnc = carrierInfo[@"mobileNetworkCode"]; + NSString *networkOperatorCode = [NSString stringWithFormat:@"%@%@", mcc, mnc]; + NSString *isoCountryCode = [[NSLocale currentLocale] objectForKey:NSLocaleCountryCode]; + + [self setNetworkOperatorName:networkOperatorName]; + [self setNetworkSIMOperatorName:networkOperatorName]; + + [self setNetworkSIMCode:networkOperatorCode]; + [self setNetworkOperatorCode:networkOperatorCode]; + + [self setNetworkISOCountryCode:isoCountryCode]; + [self setNetworkSimISOCountryCode:isoCountryCode]; + + } else { + [self setNetworkType:MPLogEventNetworkTypeUnknown]; + } + + } + + return self; +} + +- (void)setRequestURI:(NSString *)requestURI +{ + // We don't pass up the advertising identifier so we obfuscate it. + _requestURI = [requestURI stringByReplacingOccurrencesOfString:[MPIdentityProvider identifier] + withString:[MPIdentityProvider obfuscatedIdentifier]]; +} + +- (NSUInteger)appPlatform +{ + return IOS_APP_PLATFORM_ID; +} + +- (NSUInteger)sdkProduct +{ + return IOS_SDK_PRODUCT_ID; +} + +- (NSString *)deviceManufacturer +{ + return IOS_MANUFACTURER_NAME; +} + +/** + * Current timestamp in ms since January 1, 1970 00:00:00.0 UTC (aka epoch time) + */ +- (NSUInteger)timestampAsEpoch +{ + return [[self timestamp] timeIntervalSince1970]; +} + +- (NSString *)eventCategoryAsString +{ + return @"exchange_client_event"; +} + +- (NSUInteger)networkTypeAsInteger +{ + switch ([self networkType]) { + case MPLogEventNetworkTypeUnknown: + return 0; + break; + case MPLogEventNetworkTypeEthernet: + return 1; + break; + case MPLogEventNetworkTypeWifi: + return 2; + break; + case MPLogEventNetworkTypeMobile: + return 3; + break; + default: + return 0; + break; + } +} + +- (NSDictionary *)asDictionary +{ + NSMutableDictionary *d = [[NSMutableDictionary alloc] init]; + + [d mp_safeSetObject:[self eventCategoryAsString] forKey:@"_category_"]; + [d mp_safeSetObject:[self eventName] forKey:@"name"]; + [d mp_safeSetObject:[self eventCategory] forKey:@"name_category"]; + + [d mp_safeSetObject:@([self sdkProduct]) forKey:@"sdk_product"]; + [d mp_safeSetObject:[self sdkVersion] forKey:@"sdk_version"]; + + [d mp_safeSetObject:[self adUnitId] forKey:@"ad_unit_id"]; + [d mp_safeSetObject:[self adCreativeId] forKey:@"ad_creative_id"]; + [d mp_safeSetObject:[self dspCreativeId] forKey:@"dsp_creative_id"]; + [d mp_safeSetObject:[self adType] forKey:@"ad_type"]; + [d mp_safeSetObject:[self adNetworkType] forKey:@"ad_network_type"]; + [d mp_safeSetObject:@(self.adSize.width) forKey:@"ad_width_px"]; + [d mp_safeSetObject:@(self.adSize.height) forKey:@"ad_height_px"]; + + [d mp_safeSetObject:@([self appPlatform]) forKey:@"app_platform"]; + [d mp_safeSetObject:[self appName] forKey:@"app_name"]; + [d mp_safeSetObject:[self appStoreId] forKey:@"app_appstore_id"]; + [d mp_safeSetObject:[self appBundleId] forKey:@"app_bundle_id"]; + [d mp_safeSetObject:[self appVersion] forKey:@"app_version"]; + + [d mp_safeSetObject:[self clientAdvertisingId] forKey:@"client_advertising_id"]; + [d mp_safeSetObject:@([self clientDoNotTrack]) forKey:@"client_do_not_track"]; + + [d mp_safeSetObject:[self deviceManufacturer] forKey:@"device_manufacturer"]; + [d mp_safeSetObject:[self deviceModel] forKey:@"device_model"]; + [d mp_safeSetObject:[self deviceModel] forKey:@"device_product"]; + [d mp_safeSetObject:[self deviceOSVersion] forKey:@"device_os_version"]; + [d mp_safeSetObject:@(self.deviceSize.width) forKey:@"device_screen_width_px"]; + [d mp_safeSetObject:@(self.deviceSize.height) forKey:@"device_screen_height_px"]; + + [d mp_safeSetObject:@([self geoLat]) forKey:@"geo_lat"]; + [d mp_safeSetObject:@([self geoLon]) forKey:@"geo_lon"]; + [d mp_safeSetObject:@([self geoAccuracy]) forKey:@"geo_accuracy_radius_meters"]; + + [d mp_safeSetObject:@([self performanceDurationMs]) forKey:@"perf_duration_ms"]; + + [d mp_safeSetObject:@([self networkType]) forKey:@"network_type"]; + [d mp_safeSetObject:[self networkOperatorCode] forKey:@"network_operator_code"]; + [d mp_safeSetObject:[self networkOperatorName] forKey:@"network_operator_name"]; + [d mp_safeSetObject:[self networkISOCountryCode] forKey:@"network_iso_country_code"]; + [d mp_safeSetObject:[self networkSimISOCountryCode] forKey:@"network_sim_iso_country_code"]; + + [d mp_safeSetObject:[self requestId] forKey:@"req_id"]; + [d mp_safeSetObject:@([self requestStatusCode]) forKey:@"req_status_code"]; + [d mp_safeSetObject:[self requestURI] forKey:@"req_uri"]; + [d mp_safeSetObject:@([self requestRetries]) forKey:@"req_retries"]; + + [d mp_safeSetObject:@([self timestampAsEpoch]) forKey:@"timestamp_client"]; + + return d; +} + +- (NSString *)serialize +{ + NSData *data = [NSJSONSerialization dataWithJSONObject:[self asDictionary] options:0 error:nil]; + NSString *jsonString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + + return jsonString; +} + +- (void)recordEndTime +{ + NSDate *currentTime = [NSDate date]; + NSTimeInterval durationMs = [currentTime timeIntervalSinceDate:self.timestamp] * 1000.0; + + self.performanceDurationMs = durationMs; +} + +- (void)setLogEventProperties:(MPAdConfigurationLogEventProperties *)logEventProperties +{ + if (logEventProperties) { + [self setAdType:logEventProperties.adType]; + [self setAdCreativeId:logEventProperties.adCreativeId]; + [self setDspCreativeId:logEventProperties.dspCreativeId]; + [self setAdNetworkType:logEventProperties.adNetworkType]; + [self setAdSize:logEventProperties.adSize]; + [self setRequestId:logEventProperties.requestId]; + [self setAdUnitId:logEventProperties.adUnitId]; + } +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Event Logging/MPLogEventCommunicator.h b/iphone/Maps/3party/MoPubSDK/Internal/Event Logging/MPLogEventCommunicator.h new file mode 100644 index 00000000000..5f3995a3dd2 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Event Logging/MPLogEventCommunicator.h @@ -0,0 +1,15 @@ +// +// MPLogEventCommunicator.h +// MoPubSDK +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import + +@interface MPLogEventCommunicator : NSObject + +- (void)sendEvents:(NSArray *)events; +- (BOOL)isOverLimit; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Event Logging/MPLogEventCommunicator.m b/iphone/Maps/3party/MoPubSDK/Internal/Event Logging/MPLogEventCommunicator.m new file mode 100644 index 00000000000..af9ec75ac2c --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Event Logging/MPLogEventCommunicator.m @@ -0,0 +1,94 @@ +// +// MPLogEventCommunicator.m +// MoPubSDK +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import "MPLogEventCommunicator.h" +#import "MPLogging.h" +#import "MPLogEvent.h" +#import "MPRetryingHTTPOperation.h" +#import "MPNetworkManager.h" +#import "MPCoreInstanceProvider.h" + +static NSString *const kAnalyticsURL = @"https://analytics.mopub.com/i/jot/exchange_client_event"; + +static const NSInteger MAX_CONCURRENT_CONNECTIONS = 1; + +@interface MPLogEventCommunicator () + +#if !OS_OBJECT_USE_OBJC +@property (nonatomic, assign) dispatch_queue_t eventProcessingQueue; +#else +@property (nonatomic, strong) dispatch_queue_t eventProcessingQueue; +#endif + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation MPLogEventCommunicator + +- (instancetype)init +{ + if (self = [super init]) { + _eventProcessingQueue = dispatch_queue_create("com.mopub.eventProcessingQueue", DISPATCH_QUEUE_SERIAL); + } + + return self; +} + +- (void)dealloc +{ +#if !OS_OBJECT_USE_OBJC + dispatch_release(_eventProcessingQueue); +#endif +} + +- (void)sendEvents:(NSArray *)events +{ + if (events && [events count]) { + dispatch_async(self.eventProcessingQueue, ^{ + NSURLRequest *request = [self buildRequestWithEvents:events]; + MPRetryingHTTPOperation *operation = [[MPRetryingHTTPOperation alloc] initWithRequest:request]; + [[[MPCoreInstanceProvider sharedProvider] sharedNetworkManager] addNetworkTransferOperation:operation]; + }); + } +} + +- (BOOL)isOverLimit +{ + if ([[[MPCoreInstanceProvider sharedProvider] sharedNetworkManager] networkTransferOperationCount] >= MAX_CONCURRENT_CONNECTIONS) { + return YES; + } + return NO; +} + +- (NSURLRequest *)buildRequestWithEvents:(NSArray *)events +{ + NSURL *URL = [NSURL URLWithString:kAnalyticsURL]; + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL]; + [request setHTTPMethod:@"POST"]; + + NSString *POSTBodyString = [self makeParamStringForEvents:events]; + [request setHTTPBody:[POSTBodyString dataUsingEncoding:NSUTF8StringEncoding]]; + + return request; +} + +- (NSString *)makeParamStringForEvents:(NSArray *)events +{ + NSMutableArray *serializedEvents = [[NSMutableArray alloc] init]; + for (id event in events) { + [serializedEvents addObject:[event asDictionary]]; + } + NSData *jsonData = [NSJSONSerialization dataWithJSONObject:serializedEvents options:0 error:nil]; + + NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; + NSString *paramString = [NSString stringWithFormat:@"log=%@", [jsonString mp_URLEncodedString]]; + + return paramString; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Event Logging/MPLogEventRecorder.h b/iphone/Maps/3party/MoPubSDK/Internal/Event Logging/MPLogEventRecorder.h new file mode 100644 index 00000000000..d32f3baddc1 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Event Logging/MPLogEventRecorder.h @@ -0,0 +1,18 @@ +// +// MPLogEventRecorder.h +// MoPubSDK +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import + +@class MPLogEvent; + +void MPAddLogEvent(MPLogEvent *event); + +@interface MPLogEventRecorder : NSObject + +- (void)addEvent:(MPLogEvent *)event; + +@end \ No newline at end of file diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Event Logging/MPLogEventRecorder.m b/iphone/Maps/3party/MoPubSDK/Internal/Event Logging/MPLogEventRecorder.m new file mode 100644 index 00000000000..164bb06be95 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Event Logging/MPLogEventRecorder.m @@ -0,0 +1,204 @@ +// +// MPLogEventRecorder.m +// MoPubSDK + +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#include +#import "MPLogEventRecorder.h" +#import "MPLogEvent.h" +#import "MPLogging.h" +#import "MPLogEventCommunicator.h" +#import "MPCoreInstanceProvider.h" +#import "MPTimer.h" + +void MPAddLogEvent(MPLogEvent *event) +{ + [[[MPCoreInstanceProvider sharedProvider] sharedLogEventRecorder] addEvent:event]; +} + +/** + * The max number of events allowed in the event queue. + */ +static const NSInteger QUEUE_LIMIT = 1000; + +/** + * The max number of events sent per request. + */ +static const NSInteger EVENT_SEND_THRESHOLD = 25; + +/** + * A number between 0 and 1 that represents the ratio at which events will be added + * to the event queue. For example, if SAMPLE_RATE is set to 0.2, then 20% of all + * events reported to the MPLogEventRecorder will be communicated to scribe. + */ +static const double SAMPLE_RATE = 0.1; + +/** + * The delay in seconds to wait until sending the next batch of events. + */ +static const NSTimeInterval POLL_DELAY_INTERVAL = 2 * 60; + +/** + * The maximum size of the requestIDLoggingCache. Note: since this is an + * NSCache, this is not a strict limit. + */ +static const NSInteger MAX_REQUEST_ID_CACHE_SIZE = 100; + +/////////////////////////////////////////////////////////////////////////// + +@interface MPLogEventRecorder () + +/** + * IMPORTANT: All access to self.events should be performed inside a block on self.dispatchQueue. + * This is to prevent concurrent access issues to the event array. + */ +@property (nonatomic) dispatch_queue_t dispatchQueue; +@property (nonatomic) NSMutableArray *events; +@property (nonatomic) NSCache *requestIDLoggingCache; + +@property (nonatomic) MPLogEventCommunicator *communicator; +@property (nonatomic) MPTimer *sendTimer; + +@end + +/////////////////////////////////////////////////////////////////////////// + +@implementation MPLogEventRecorder + +#pragma mark - Public methods + +- (instancetype)init +{ + if (self = [super init]) { + _events = [NSMutableArray array]; + _dispatchQueue = dispatch_queue_create("com.mopub.MPLogEventRecorder", NULL); + _communicator = [[MPLogEventCommunicator alloc] init]; + _sendTimer = [MPTimer timerWithTimeInterval:POLL_DELAY_INTERVAL + target:self + selector:@selector(sendEvents) + repeats:YES]; + [_sendTimer scheduleNow]; + _requestIDLoggingCache = [[NSCache alloc] init]; + _requestIDLoggingCache.countLimit = MAX_REQUEST_ID_CACHE_SIZE; + } + + return self; +} + +- (void)dealloc +{ + [self.sendTimer invalidate]; +} + +- (void)addEvent:(MPLogEvent *)event +{ + if (event) { + dispatch_async(self.dispatchQueue, ^{ + + // We only add the event to the queue if it's been selected for sampling. + if (![self sampleWithLogEvent:event]) { + MPLogDebug(@"RECORDER: Skipped adding log event to the queue because it failed the sample test."); + return; + } + + if ([self overQueueLimit]) { + MPLogDebug(@"RECORDER: Skipped adding log event to the queue because the event queue is over its size limit."); + return; + } + + [self.events addObject:event]; + MPLogDebug([NSString stringWithFormat:@"RECORDER: Event added. There are now %lu events in the queue.", (unsigned long)[self.events count]]); + }); + } +} + +#pragma mark - Private methods + +- (void)sendEvents +{ + dispatch_async(self.dispatchQueue, ^{ + MPLogDebug([NSString stringWithFormat:@"RECORDER: -sendEvents dispatched with %lu events in the queue.", (unsigned long)[self.events count]]); + + if ([self.communicator isOverLimit]) { + MPLogDebug(@"RECORDER: Skipped sending events because the communicator has too many active connections."); + return; + } + + if ([self.events count] == 0) { + return; + } + + if ([self.events count] > EVENT_SEND_THRESHOLD) { + MPLogDebug(@"RECORDER: Enqueueing a portion of events to be scribed."); + + // If we have more events than we can send at once, then send only the first slice. + NSRange sendRange; + sendRange.location = 0; + sendRange.length = EVENT_SEND_THRESHOLD; + NSArray *eventsToSend = [self.events subarrayWithRange:sendRange]; + + // Don't flush the event array in this case, because we'll have more to send in the + // future. + NSRange unSentRange; + unSentRange.location = sendRange.length; + unSentRange.length = [self.events count] - sendRange.length; + NSArray *unSentEvents = [self.events subarrayWithRange:unSentRange]; + + [self.communicator sendEvents:eventsToSend]; + self.events = [unSentEvents mutableCopy]; + } else { + MPLogDebug(@"RECORDER: Enqueueing all events to be scribed."); + [self.communicator sendEvents:[NSArray arrayWithArray:self.events]]; + [self.events removeAllObjects]; + } + + MPLogDebug([NSString stringWithFormat:@"RECORDER: There are now %lu events in the queue.", (unsigned long)[self.events count]]); + }); +} + +/** + * IMPORTANT: This method should only be called inside a block on the object's dispatch queue. + */ +- (BOOL)overQueueLimit +{ + return [self.events count] >= QUEUE_LIMIT; +} + +/* + Using this sampling method will ensure sampling decision remains constant for each request id. + */ +- (BOOL)sampleWithLogEvent:(MPLogEvent *)event +{ + BOOL samplingResult; + if (!event.requestId) { + samplingResult = [self sample]; + } else { + NSNumber *existingSampleAsNumber = [self.requestIDLoggingCache objectForKey:event.requestId]; + if (existingSampleAsNumber) { + samplingResult = [existingSampleAsNumber boolValue]; + } else { + samplingResult = [self sample]; + [self.requestIDLoggingCache setObject:[NSNumber numberWithBool:samplingResult] forKey:event.requestId]; + } + } + return samplingResult; +} + +- (BOOL)sample +{ + NSUInteger diceRoll = arc4random_uniform(100); + return [self shouldSampleForRate:SAMPLE_RATE diceRoll:diceRoll]; +} + +/** + * IMPORTANT: This method takes a sample rate between 0 and 1, and the diceRoll is intended to be between 0 and 100. It has been separated from the -(BOOL)sample method for easier testing. + */ +- (BOOL)shouldSampleForRate:(CGFloat)sampleRate diceRoll:(NSUInteger)diceRoll +{ + NSUInteger sample = (NSUInteger)(sampleRate*100); + return diceRoll < sample; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Event Logging/MPNetworkManager.h b/iphone/Maps/3party/MoPubSDK/Internal/Event Logging/MPNetworkManager.h new file mode 100644 index 00000000000..55e955864a8 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Event Logging/MPNetworkManager.h @@ -0,0 +1,35 @@ +// +// MPNetworkManager.h +// MoPub +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import + +// Adapted from NetworkManager in Apple's MVCNetworking sample code. + +// The shared instance of this class provides a way for clients to execute network-related +// operations while minimizing the impact to tasks executing on the main thread. In order to do +// this, it manages a single dedicated networking thread. + +@interface MPNetworkManager : NSObject + +@property (assign, readonly) NSUInteger networkTransferOperationCount; + +// Returns the network manager shared instance. ++ (instancetype)sharedNetworkManager; + +// Adds the specified operation object to an internal operation queue reserved for network transfer +// operations. +// +// If the specified operation supports the `runLoopThread` property and the value of that property +// is nil, this method sets the run loop thread of the operation to the dedicated networking thread. +// Any callbacks from an asynchronous network request will then run on the networking thread's +// run loop, rather than the main run loop. +- (void)addNetworkTransferOperation:(NSOperation *)operation; + +// Cancels the specified operation. +- (void)cancelOperation:(NSOperation *)operation; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Event Logging/MPNetworkManager.m b/iphone/Maps/3party/MoPubSDK/Internal/Event Logging/MPNetworkManager.m new file mode 100644 index 00000000000..478ef6c284f --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Event Logging/MPNetworkManager.m @@ -0,0 +1,93 @@ +// +// MPNetworkManager.m +// MoPub +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import "MPNetworkManager.h" + +#import "MPCoreInstanceProvider.h" +#import "MPRetryingHTTPOperation.h" + +static const double kNetworkThreadPriority = 0.3; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface MPNetworkManager () + +@property (strong) NSThread *networkThread; +@property (strong, readwrite) NSOperationQueue *networkTransferQueue; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation MPNetworkManager + ++ (instancetype)sharedNetworkManager +{ + static MPNetworkManager *sNetworkManager = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sNetworkManager = [[[self class] alloc] init]; + [sNetworkManager startNetworkThread]; + }); + return sNetworkManager; +} + +- (instancetype)init +{ + self = [super init]; + if (self) { + _networkThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkThreadMain) object:nil]; + _networkThread.name = @"com.mopub.MPNetworkManager"; + _networkThread.threadPriority = kNetworkThreadPriority; + + _networkTransferQueue = [[NSOperationQueue alloc] init]; + _networkTransferQueue.maxConcurrentOperationCount = 1; + } + return self; +} + +- (void)startNetworkThread +{ + [self.networkThread start]; +} + +- (void)networkThreadMain +{ + NSAssert(![NSThread isMainThread], @"The network thread should not be the main thread."); + + @autoreleasepool { + // Add a dummy input source to prevent the run loop from exiting immediately. + NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; + [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode]; + [runLoop run]; + } +} + +#pragma mark - Public + +- (void)addNetworkTransferOperation:(NSOperation *)operation +{ + if ([operation respondsToSelector:@selector(setRunLoopThread:)]) { + if (![(id)operation runLoopThread]) { + [(id)operation setRunLoopThread:self.networkThread]; + } + } + + [self.networkTransferQueue addOperation:operation]; +} + +- (void)cancelOperation:(NSOperation *)operation +{ + [operation cancel]; +} + +- (NSUInteger)networkTransferOperationCount +{ + return [self.networkTransferQueue operationCount]; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Event Logging/MPQRunLoopOperation.h b/iphone/Maps/3party/MoPubSDK/Internal/Event Logging/MPQRunLoopOperation.h new file mode 100644 index 00000000000..f57e4cfa211 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Event Logging/MPQRunLoopOperation.h @@ -0,0 +1,117 @@ +// +// MPQRunLoopOperation.h +// MoPub +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +/* + File: QRunLoopOperation.h + Contains: An abstract subclass of NSOperation for async run loop based operations. + Written by: DTS + Copyright: Copyright (c) 2010 Apple Inc. All Rights Reserved. + Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Inc. + ("Apple") in consideration of your agreement to the following + terms, and your use, installation, modification or + redistribution of this Apple software constitutes acceptance of + these terms. If you do not agree with these terms, please do + not use, install, modify or redistribute this Apple software. + In consideration of your agreement to abide by the following + terms, and subject to these terms, Apple grants you a personal, + non-exclusive license, under Apple's copyrights in this + original Apple software (the "Apple Software"), to use, + reproduce, modify and redistribute the Apple Software, with or + without modifications, in source and/or binary forms; provided + that if you redistribute the Apple Software in its entirety and + without modifications, you must retain this notice and the + following text and disclaimers in all such redistributions of + the Apple Software. Neither the name, trademarks, service marks + or logos of Apple Inc. may be used to endorse or promote + products derived from the Apple Software without specific prior + written permission from Apple. Except as expressly stated in + this notice, no other rights or licenses, express or implied, + are granted by Apple herein, including but not limited to any + patent rights that may be infringed by your derivative works or + by other works in which the Apple Software may be incorporated. + The Apple Software is provided by Apple on an "AS IS" basis. + APPLE MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING + WITHOUT LIMITATION THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, + MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING + THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN + COMBINATION WITH YOUR PRODUCTS. + IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, + INCIDENTAL OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY + OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION + OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY + OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR + OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. +*/ + +#import + +typedef enum { + MPQRunLoopOperationStateInited, + MPQRunLoopOperationStateExecuting, + MPQRunLoopOperationStateFinished +} MPQRunLoopOperationState; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +// Adapted from QRunLoopOperation in Apple's MVCNetworking sample code. + +@interface MPQRunLoopOperation : NSOperation + +// Things you can configure before queuing the operation. + +// IMPORTANT: Do not change these after queuing the operation; it's very likely that +// bad things will happen if you do. + +@property (strong) NSThread *runLoopThread; +@property (copy) NSSet *runLoopModes; + +// Things that are only meaningful after the operation is finished. + +@property (copy, readonly) NSError *error; + +// Things you can only alter implicitly. + +@property (assign, readonly) MPQRunLoopOperationState state; +@property (strong, readonly) NSThread *actualRunLoopThread; +@property (assign, readonly) BOOL isActualRunLoopThread; +@property (copy, readonly) NSSet *actualRunLoopModes; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface MPQRunLoopOperation (SubClassSupport) + +// Override points + +// A subclass will probably need to override -operationDidStart and -operationWillFinish +// to set up and tear down its run loop sources, respectively. These are always called +// on the actual run loop thread. +// +// Note that -operationWillFinish will be called even if the operation is cancelled. +// +// -operationWillFinish can check the error property to see whether the operation was +// successful. error will be NSCocoaErrorDomain/NSUserCancelledError on cancellation. +// +// -operationDidStart is allowed to call -finishWithError:. + +- (void)operationDidStart; +- (void)operationWillFinish; + +// Support methods + +// A subclass should call finishWithError: when the operation is complete, passing nil +// for no error and an error otherwise. It must call this on the actual run loop thread. +// +// Note that this will call -operationWillFinish before returning. + +- (void)finishWithError:(NSError *)error; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Event Logging/MPQRunLoopOperation.m b/iphone/Maps/3party/MoPubSDK/Internal/Event Logging/MPQRunLoopOperation.m new file mode 100644 index 00000000000..e431d97274a --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Event Logging/MPQRunLoopOperation.m @@ -0,0 +1,265 @@ +// +// MPQRunLoopOperation.m +// MoPub +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +/* + File: QRunLoopOperation.m + Contains: An abstract subclass of NSOperation for async run loop based operations. + Written by: DTS + Copyright: Copyright (c) 2010 Apple Inc. All Rights Reserved. + Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Inc. + ("Apple") in consideration of your agreement to the following + terms, and your use, installation, modification or + redistribution of this Apple software constitutes acceptance of + these terms. If you do not agree with these terms, please do + not use, install, modify or redistribute this Apple software. + In consideration of your agreement to abide by the following + terms, and subject to these terms, Apple grants you a personal, + non-exclusive license, under Apple's copyrights in this + original Apple software (the "Apple Software"), to use, + reproduce, modify and redistribute the Apple Software, with or + without modifications, in source and/or binary forms; provided + that if you redistribute the Apple Software in its entirety and + without modifications, you must retain this notice and the + following text and disclaimers in all such redistributions of + the Apple Software. Neither the name, trademarks, service marks + or logos of Apple Inc. may be used to endorse or promote + products derived from the Apple Software without specific prior + written permission from Apple. Except as expressly stated in + this notice, no other rights or licenses, express or implied, + are granted by Apple herein, including but not limited to any + patent rights that may be infringed by your derivative works or + by other works in which the Apple Software may be incorporated. + The Apple Software is provided by Apple on an "AS IS" basis. + APPLE MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING + WITHOUT LIMITATION THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, + MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING + THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN + COMBINATION WITH YOUR PRODUCTS. + IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, + INCIDENTAL OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY + OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION + OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY + OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR + OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. +*/ + +#import "MPQRunLoopOperation.h" + +@interface MPQRunLoopOperation () + +@property (assign, readwrite) MPQRunLoopOperationState state; +@property (copy, readwrite) NSError *error; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation MPQRunLoopOperation + +// Necessary since auto-synthesize doesn't happen when we manually implement both getter / setter. +@synthesize state = _state; + +- (instancetype)init +{ + self = [super init]; + if (self) { + NSAssert(_state == MPQRunLoopOperationStateInited, @"MPQRunLoopOperation must be in the inited state upon initialization."); + } + return self; +} + +#pragma mark - Properties + +// Returns the effective run loop thread, that is, the one set by the user +// or, if that's not set, the main thread. +- (NSThread *)actualRunLoopThread +{ + return self.runLoopThread ?: [NSThread mainThread]; +} + +// Returns YES if the current thread is the actual run loop thread. +- (BOOL)isActualRunLoopThread +{ + return [[NSThread currentThread] isEqual:[self actualRunLoopThread]]; +} + +// Returns the run loop modes in which this operation can be executed. If the user provides a set +// of run loop modes, this method will return that set; otherwise, it will return a set containing +// the default run loop mode. +- (NSSet *)actualRunLoopModes +{ + return ([self.runLoopModes count] != 0) ? self.runLoopModes : [NSSet setWithObject:NSDefaultRunLoopMode]; +} + +#pragma mark - Core state transitions + +- (MPQRunLoopOperationState)state +{ + return _state; +} + +- (void)setState:(MPQRunLoopOperationState)newState +{ + @synchronized(self) { + MPQRunLoopOperationState oldState; + + // The following check is really important. The state can only go forward, and there + // should be no redundant changes to the state (that is, newState must never be + // equal to _state). + + assert(newState > _state); + + // inited -> executing = update isExecuting + // inited -> finished = update isFinished + // executing -> finished = update both isExecuting and isFinished + + oldState = _state; + + if ((newState == MPQRunLoopOperationStateExecuting) || (oldState == MPQRunLoopOperationStateExecuting)) { + [self willChangeValueForKey:@"isExecuting"]; + } + if (newState == MPQRunLoopOperationStateFinished) { + [self willChangeValueForKey:@"isFinished"]; + } + + _state = newState; + + if (newState == MPQRunLoopOperationStateFinished) { + [self didChangeValueForKey:@"isFinished"]; + } + if ((newState == MPQRunLoopOperationStateExecuting) || (oldState == MPQRunLoopOperationStateExecuting)) { + [self didChangeValueForKey:@"isExecuting"]; + } + } +} + +- (void)startOnRunLoopThread +{ + NSAssert([self isActualRunLoopThread], @"-startOnRunLoopThread must be called on the run loop thread."); + NSAssert(self.state == MPQRunLoopOperationStateExecuting, @"The operation should be in the executing state."); + + if (self.isCancelled) { + // We were cancelled before we even got running. Flip the finished state immediately. + [self finishWithError:[NSError errorWithDomain:NSCocoaErrorDomain code:NSUserCancelledError userInfo:nil]]; + } else { + [self operationDidStart]; + } +} + +- (void)cancelOnRunLoopThread +{ + NSAssert([self isActualRunLoopThread], @"-cancelOnRunLoopThread must be called on the run loop thread."); + + // We know that a) state was kQRunLoopOperationStateExecuting when we were + // scheduled (that's enforced by -cancel), and b) the state can't go + // backwards (that's enforced by -setState), so we know the state must + // either be kQRunLoopOperationStateExecuting or kQRunLoopOperationStateFinished. + // We also know that the transition from executing to finished always + // happens on the run loop thread. Thus, we don't need to lock here. + // We can look at state and, if we're executing, trigger a cancellation. + + if (self.state == MPQRunLoopOperationStateExecuting) { + [self finishWithError:[NSError errorWithDomain:NSCocoaErrorDomain code:NSUserCancelledError userInfo:nil]]; + } +} + +- (void)finishWithError:(NSError *)error +{ + NSAssert([self isActualRunLoopThread], @"-finishWithError: must be called on the run loop thread."); + + // `error` may be nil, since this method serves as the "exit" point for the operation and will + // get called in both the success and the failure cases. + + if (!self.error) { + self.error = error; + } + + [self operationWillFinish]; + + self.state = MPQRunLoopOperationStateFinished; +} + +#pragma mark - Subclass override points + +- (void)operationDidStart +{ + NSAssert([self isActualRunLoopThread], @"-operationDidStart must be called on the run loop thread."); +} + +- (void)operationWillFinish +{ + NSAssert([self isActualRunLoopThread], @"-operationWillFinish must be called on the run loop thread."); +} + +#pragma mark - NSOperation overrides + +/* + * All of the following overridden methods must be thread-safe. + */ + +- (BOOL)isConcurrent +{ + return YES; +} + +- (BOOL)isExecuting +{ + return self.state == MPQRunLoopOperationStateExecuting; +} + +- (BOOL)isFinished +{ + return self.state == MPQRunLoopOperationStateFinished; +} + +- (void)start +{ + NSAssert(self.state == MPQRunLoopOperationStateInited, @"-start cannot be called on an executing or finished operation."); + + // We have to change the state here, otherwise isExecuting won't necessarily return + // true by the time we return from -start. Also, we don't test for cancellation + // here because that would a) result in us sending isFinished notifications on a + // thread that isn't our run loop thread, and b) confuse the core cancellation code, + // which expects to run on our run loop thread. Finally, we don't have to worry + // about races with other threads calling -start. Only one thread is allowed to + // start us at a time + + self.state = MPQRunLoopOperationStateExecuting; + [self performSelector:@selector(startOnRunLoopThread) onThread:self.actualRunLoopThread withObject:nil waitUntilDone:NO modes:[self.actualRunLoopModes allObjects]]; +} + +- (void)cancel +{ + BOOL shouldRunCancelOnRunLoopThread; + BOOL wasAlreadyCancelled; + + // We need to synchronise here to avoid state changes to isCancelled and state + // while we're running. + + @synchronized(self) { + wasAlreadyCancelled = self.isCancelled; + + // Call our super class so that isCancelled starts returning true immediately. + [super cancel]; + + // If we were the one to set isCancelled (that is, we won the race with regards + // other threads calling -cancel) and we're actually running (that is, we lost + // the race with other threads calling -start and the run loop thread finishing), + // we schedule to run on the run loop thread. + + shouldRunCancelOnRunLoopThread = !wasAlreadyCancelled && self.state == MPQRunLoopOperationStateExecuting; + } + + if (shouldRunCancelOnRunLoopThread) { + [self performSelector:@selector(cancelOnRunLoopThread) onThread:[self actualRunLoopThread] withObject:nil waitUntilDone:NO modes:[self.actualRunLoopModes allObjects]]; + } +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Event Logging/MPRetryingHTTPOperation.h b/iphone/Maps/3party/MoPubSDK/Internal/Event Logging/MPRetryingHTTPOperation.h new file mode 100644 index 00000000000..0cde2fbe971 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Event Logging/MPRetryingHTTPOperation.h @@ -0,0 +1,34 @@ +// +// MPRetryingHTTPOperation.h +// MoPub +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import + +#import "MPQRunLoopOperation.h" + +extern NSString * const MPRetryingHTTPOperationErrorDomain; + +typedef enum { + MPRetryingHTTPOperationReceivedNonRetryResponse = -1000, + MPRetryingHTTPOperationExceededRetryLimit = -1001 +} MPRetryingHTTPOperationErrorCode; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +// Adapted from QHTTPOperation / RetryingHTTPOperation in Apple's MVCNetworking sample code. + +@interface MPRetryingHTTPOperation : MPQRunLoopOperation + +// Things that are configured via the init method and can't be changed. +@property (copy, readonly) NSURLRequest *request; + +// Things that are only meaningful after the operation is finished. +@property (copy, readonly) NSHTTPURLResponse *lastResponse; +@property (strong, readonly) NSMutableData *lastReceivedData; + +- (instancetype)initWithRequest:(NSURLRequest *)request; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Event Logging/MPRetryingHTTPOperation.m b/iphone/Maps/3party/MoPubSDK/Internal/Event Logging/MPRetryingHTTPOperation.m new file mode 100644 index 00000000000..49a55c638b4 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Event Logging/MPRetryingHTTPOperation.m @@ -0,0 +1,147 @@ +// +// MPRetryingHTTPOperation.m +// MoPub +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import "MPRetryingHTTPOperation.h" + +#import "MPLogging.h" + +NSString * const MPRetryingHTTPOperationErrorDomain = @"com.mopub.MPRetryingHTTPOperation"; +static const NSUInteger kMaximumFailedRetryAttempts = 5; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface MPRetryingHTTPOperation () + +@property (copy, readwrite) NSURLRequest *request; +@property (strong) NSURLConnection *connection; +@property (copy, readwrite) NSHTTPURLResponse *lastResponse; +@property (strong, readwrite) NSMutableData *lastReceivedData; +@property (assign) NSUInteger failedRetryAttempts; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation MPRetryingHTTPOperation + +- (instancetype)initWithRequest:(NSURLRequest *)request +{ + NSAssert(request != nil, @"-initWithRequest: cannot take a nil request."); + NSAssert([request URL] != nil, @"-initWithRequest: cannot take a request whose URL is nil."); + + NSString *scheme = [[[request URL] scheme] lowercaseString]; + NSAssert([scheme isEqualToString:@"http"] || [scheme isEqualToString:@"https"], @"-initWithRequest: can only take a request whose URL has an HTTP/HTTPS scheme."); + + self = [super init]; + if (self) { + _request = [request copy]; + _connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO]; + } + return self; +} + +#pragma mark - MPQRunLoopOperation overrides + +- (void)operationDidStart +{ + [super operationDidStart]; + + MPLogDebug(@"Starting request: %@.", self.request); + [self.connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; + [self.connection start]; +} + +- (void)operationWillFinish +{ + [super operationWillFinish]; + + [self.connection cancel]; + self.connection = nil; +} + +#pragma mark - Internal + +- (BOOL)shouldRetryForResponse:(NSHTTPURLResponse *)response +{ + return response.statusCode == 503 || response.statusCode == 504; +} + +- (NSTimeInterval)retryDelayForFailedAttempts:(NSUInteger)failedAttempts +{ + if (failedAttempts == 0) { + // Return a short delay if this method is called when there have been no failed retries. + return 1; + } else { + return pow(2, failedAttempts - 1) * 60; + } +} + +- (void)retry +{ + NSAssert([self isActualRunLoopThread], @"Retries should occur on the run loop thread."); + + MPLogDebug(@"Retrying request: %@.", self.request); + + [self.lastReceivedData setLength:0]; + + self.connection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:NO]; + [self.connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; + [self.connection start]; +} + +#pragma mark - + +- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response +{ + NSAssert([self isActualRunLoopThread], @"NSURLConnection callbacks should occur on the run loop thread."); + NSAssert([response isKindOfClass:[NSHTTPURLResponse class]], @"Response must be of type NSHTTPURLResponse."); + + self.lastResponse = (NSHTTPURLResponse *)response; +} + +- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data +{ + NSAssert([self isActualRunLoopThread], @"NSURLConnection callbacks should occur on the run loop thread."); + + if (!self.lastReceivedData) { + self.lastReceivedData = [NSMutableData data]; + } + + [self.lastReceivedData appendData:data]; +} + +- (void)connectionDidFinishLoading:(NSURLConnection *)connection +{ + NSAssert([self isActualRunLoopThread], @"NSURLConnection callbacks should occur on the run loop thread."); + + if (self.lastResponse.statusCode == 200) { + MPLogDebug(@"Successful request: %@.", self.request); + [self finishWithError:nil]; + } else if (self.failedRetryAttempts > kMaximumFailedRetryAttempts) { + MPLogDebug(@"Too many failed attempts for this request: %@.", self.request); + [self finishWithError:[NSError errorWithDomain:MPRetryingHTTPOperationErrorDomain code:MPRetryingHTTPOperationExceededRetryLimit userInfo:nil]]; + } else if ([self shouldRetryForResponse:self.lastResponse]) { + self.failedRetryAttempts++; + NSTimeInterval retryDelay = [self retryDelayForFailedAttempts:self.failedRetryAttempts]; + MPLogDebug(@"Server error during attempt #%@ for request: %@.", @(self.failedRetryAttempts), self.request); + MPLogDebug(@"Backing off: %.1f", retryDelay); + [self performSelector:@selector(retry) withObject:nil afterDelay:retryDelay]; + } else { + MPLogDebug(@"%@", [[NSString alloc] initWithData:self.request.HTTPBody encoding:NSUTF8StringEncoding]); + MPLogDebug(@"Failed request: %@, status code: %ld, error: %@.", self.request, self.lastResponse.statusCode, self.error); + [self finishWithError:[NSError errorWithDomain:MPRetryingHTTPOperationErrorDomain code:MPRetryingHTTPOperationReceivedNonRetryResponse userInfo:nil]]; + } +} + +- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error +{ + NSAssert([self isActualRunLoopThread], @"NSURLConnection callbacks should occur on the run loop thread."); + + [self finishWithError:error]; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/HTML/MPAdWebViewAgent.h b/iphone/Maps/3party/MoPubSDK/Internal/HTML/MPAdWebViewAgent.h new file mode 100644 index 00000000000..1d086166e90 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/HTML/MPAdWebViewAgent.h @@ -0,0 +1,51 @@ +// +// MPAdWebViewAgent.h +// MoPub +// +// Copyright (c) 2013 MoPub. All rights reserved. +// + +#import +#import "MPAdDestinationDisplayAgent.h" +#import "MPWebView.h" + +enum { + MPAdWebViewEventAdDidAppear = 0, + MPAdWebViewEventAdDidDisappear = 1 +}; +typedef NSUInteger MPAdWebViewEvent; + +@protocol MPAdWebViewAgentDelegate; + +@class MPAdConfiguration; +@class CLLocation; + +@interface MPAdWebViewAgent : NSObject + +@property (nonatomic, strong) MPWebView *view; +@property (nonatomic, weak) id delegate; + +- (id)initWithAdWebViewFrame:(CGRect)frame delegate:(id)delegate; +- (void)loadConfiguration:(MPAdConfiguration *)configuration; +- (void)rotateToOrientation:(UIInterfaceOrientation)orientation; +- (void)invokeJavaScriptForEvent:(MPAdWebViewEvent)event; +- (void)forceRedraw; + +- (void)enableRequestHandling; +- (void)disableRequestHandling; + +@end + +@protocol MPAdWebViewAgentDelegate + +- (NSString *)adUnitId; +- (CLLocation *)location; +- (UIViewController *)viewControllerForPresentingModalView; +- (void)adDidClose:(MPWebView *)ad; +- (void)adDidFinishLoadingAd:(MPWebView *)ad; +- (void)adDidFailToLoadAd:(MPWebView *)ad; +- (void)adActionWillBegin:(MPWebView *)ad; +- (void)adActionWillLeaveApplication:(MPWebView *)ad; +- (void)adActionDidFinish:(MPWebView *)ad; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/HTML/MPAdWebViewAgent.m b/iphone/Maps/3party/MoPubSDK/Internal/HTML/MPAdWebViewAgent.m new file mode 100644 index 00000000000..1e53f91383b --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/HTML/MPAdWebViewAgent.m @@ -0,0 +1,356 @@ +// +// MPAdWebViewAgent.m +// MoPub +// +// Copyright (c) 2013 MoPub. All rights reserved. +// + +#import "MPAdWebViewAgent.h" +#import "MPAdConfiguration.h" +#import "MPGlobal.h" +#import "MPLogging.h" +#import "MPAdDestinationDisplayAgent.h" +#import "NSURL+MPAdditions.h" +#import "UIWebView+MPAdditions.h" +#import "MPWebView.h" +#import "MPInstanceProvider.h" +#import "MPCoreInstanceProvider.h" +#import "MPUserInteractionGestureRecognizer.h" +#import "NSJSONSerialization+MPAdditions.h" +#import "NSURL+MPAdditions.h" +#import "MPInternalUtils.h" +#import "MPAPIEndPoints.h" + +#ifndef NSFoundationVersionNumber_iOS_6_1 +#define NSFoundationVersionNumber_iOS_6_1 993.00 +#endif + +#define MPOffscreenWebViewNeedsRenderingWorkaround() (floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_6_1) + +@interface MPAdWebViewAgent () + +@property (nonatomic, strong) MPAdConfiguration *configuration; +@property (nonatomic, strong) MPAdDestinationDisplayAgent *destinationDisplayAgent; +@property (nonatomic, assign) BOOL shouldHandleRequests; +@property (nonatomic, strong) id adAlertManager; +@property (nonatomic, assign) BOOL userInteractedWithWebView; +@property (nonatomic, strong) MPUserInteractionGestureRecognizer *userInteractionRecognizer; +@property (nonatomic, assign) CGRect frame; + +- (void)performActionForMoPubSpecificURL:(NSURL *)URL; +- (BOOL)shouldIntercept:(NSURL *)URL navigationType:(UIWebViewNavigationType)navigationType; +- (void)interceptURL:(NSURL *)URL; + +@end + +@implementation MPAdWebViewAgent + +@synthesize configuration = _configuration; +@synthesize delegate = _delegate; +@synthesize destinationDisplayAgent = _destinationDisplayAgent; +@synthesize shouldHandleRequests = _shouldHandleRequests; +@synthesize view = _view; +@synthesize adAlertManager = _adAlertManager; +@synthesize userInteractedWithWebView = _userInteractedWithWebView; +@synthesize userInteractionRecognizer = _userInteractionRecognizer; + +- (id)initWithAdWebViewFrame:(CGRect)frame delegate:(id)delegate; +{ + self = [super init]; + if (self) { + _frame = frame; + + self.destinationDisplayAgent = [[MPCoreInstanceProvider sharedProvider] buildMPAdDestinationDisplayAgentWithDelegate:self]; + self.delegate = delegate; + self.shouldHandleRequests = YES; + self.adAlertManager = [[MPCoreInstanceProvider sharedProvider] buildMPAdAlertManagerWithDelegate:self]; + + self.userInteractionRecognizer = [[MPUserInteractionGestureRecognizer alloc] initWithTarget:self action:@selector(handleInteraction:)]; + self.userInteractionRecognizer.cancelsTouchesInView = NO; + self.userInteractionRecognizer.delegate = self; + } + return self; +} + +- (void)dealloc +{ + self.userInteractionRecognizer.delegate = nil; + [self.userInteractionRecognizer removeTarget:self action:nil]; + [self.destinationDisplayAgent cancel]; + [self.destinationDisplayAgent setDelegate:nil]; + self.view.delegate = nil; +} + +- (void)handleInteraction:(UITapGestureRecognizer *)sender +{ + if (sender.state == UIGestureRecognizerStateEnded) { + self.userInteractedWithWebView = YES; + } +} + +#pragma mark - + +- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer; +{ + return YES; +} + +#pragma mark - + +- (UIViewController *)viewControllerForPresentingMailVC +{ + return [self.delegate viewControllerForPresentingModalView]; +} + +- (void)adAlertManagerDidTriggerAlert:(MPAdAlertManager *)manager +{ + [self.adAlertManager processAdAlertOnce]; +} + +#pragma mark - Public + +- (void)loadConfiguration:(MPAdConfiguration *)configuration +{ + self.configuration = configuration; + + // Initialize web view + if (self.view != nil) { + self.view.delegate = nil; + [self.view removeFromSuperview]; + self.view = nil; + } + self.view = [[MPWebView alloc] initWithFrame:self.frame forceUIWebView:self.configuration.forceUIWebView]; + self.view.delegate = self; + [self.view addGestureRecognizer:self.userInteractionRecognizer]; + + // Ignore server configuration size for interstitials. At this point our web view + // is sized correctly for the device's screen. Currently the server sends down values for a 3.5in + // screen, and they do not size correctly on a 4in screen. + if (configuration.adType != MPAdTypeInterstitial) { + if ([configuration hasPreferredSize]) { + CGRect frame = self.view.frame; + frame.size.width = configuration.preferredSize.width; + frame.size.height = configuration.preferredSize.height; + self.view.frame = frame; + } + } + + // excuse interstitials from user tapped check since it's already a takeover experience + // and certain videos may delay tap gesture recognition + if (configuration.adType == MPAdTypeInterstitial) { + self.userInteractedWithWebView = YES; + } + + [self.view mp_setScrollable:configuration.scrollable]; + [self.view disableJavaScriptDialogs]; + + [self.view loadHTMLString:[configuration adResponseHTMLString] + baseURL:[NSURL URLWithString:[MPAPIEndpoints baseURL]] + ]; + + [self initAdAlertManager]; +} + +- (void)invokeJavaScriptForEvent:(MPAdWebViewEvent)event +{ + switch (event) { + case MPAdWebViewEventAdDidAppear: + [self.view stringByEvaluatingJavaScriptFromString:@"webviewDidAppear();"]; + break; + case MPAdWebViewEventAdDidDisappear: + [self.view stringByEvaluatingJavaScriptFromString:@"webviewDidClose();"]; + break; + default: + break; + } +} + +- (void)disableRequestHandling +{ + self.shouldHandleRequests = NO; + [self.destinationDisplayAgent cancel]; +} + +- (void)enableRequestHandling +{ + self.shouldHandleRequests = YES; +} + +#pragma mark - + +- (UIViewController *)viewControllerForPresentingModalView +{ + return [self.delegate viewControllerForPresentingModalView]; +} + +- (void)displayAgentWillPresentModal +{ + [self.delegate adActionWillBegin:self.view]; +} + +- (void)displayAgentWillLeaveApplication +{ + [self.delegate adActionWillLeaveApplication:self.view]; +} + +- (void)displayAgentDidDismissModal +{ + [self.delegate adActionDidFinish:self.view]; +} + +- (MPAdConfiguration *)adConfiguration +{ + return self.configuration; +} + +#pragma mark - + +- (BOOL)webView:(MPWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request + navigationType:(UIWebViewNavigationType)navigationType +{ + if (!self.shouldHandleRequests) { + return NO; + } + + NSURL *URL = [request URL]; + if ([URL mp_isMoPubScheme]) { + [self performActionForMoPubSpecificURL:URL]; + return NO; + } else if ([self shouldIntercept:URL navigationType:navigationType]) { + + // Disable intercept without user interaction + if (!self.userInteractedWithWebView) { + MPLogInfo(@"Redirect without user interaction detected"); + return NO; + } + + [self interceptURL:URL]; + return NO; + } else { + // don't handle any deep links without user interaction + return self.userInteractedWithWebView || [URL mp_isSafeForLoadingWithoutUserAction]; + } +} + +- (void)webViewDidStartLoad:(MPWebView *)webView +{ + [self.view disableJavaScriptDialogs]; +} + + +#pragma mark - MoPub-specific URL handlers +- (void)performActionForMoPubSpecificURL:(NSURL *)URL +{ + MPLogDebug(@"MPAdWebView - loading MoPub URL: %@", URL); + MPMoPubHostCommand command = [URL mp_mopubHostCommand]; + switch (command) { + case MPMoPubHostCommandClose: + [self.delegate adDidClose:self.view]; + break; + case MPMoPubHostCommandFinishLoad: + [self.delegate adDidFinishLoadingAd:self.view]; + break; + case MPMoPubHostCommandFailLoad: + [self.delegate adDidFailToLoadAd:self.view]; + break; + default: + MPLogWarn(@"MPAdWebView - unsupported MoPub URL: %@", [URL absoluteString]); + break; + } +} + +#pragma mark - URL Interception +- (BOOL)shouldIntercept:(NSURL *)URL navigationType:(UIWebViewNavigationType)navigationType +{ + if ([URL mp_hasTelephoneScheme] || [URL mp_hasTelephonePromptScheme]) { + return YES; + } else if (!(self.configuration.shouldInterceptLinks)) { + return NO; + } else if (navigationType == UIWebViewNavigationTypeLinkClicked) { + return YES; + } else if (navigationType == UIWebViewNavigationTypeOther) { + return [[URL absoluteString] hasPrefix:[self.configuration clickDetectionURLPrefix]]; + } else { + return NO; + } +} + +- (void)interceptURL:(NSURL *)URL +{ + NSURL *redirectedURL = URL; + if (self.configuration.clickTrackingURL) { + NSString *path = [NSString stringWithFormat:@"%@&r=%@", + self.configuration.clickTrackingURL.absoluteString, + [[URL absoluteString] mp_URLEncodedString]]; + redirectedURL = [NSURL URLWithString:path]; + } + + [self.destinationDisplayAgent displayDestinationForURL:redirectedURL]; +} + +#pragma mark - Utility + +- (void)initAdAlertManager +{ + self.adAlertManager.adConfiguration = self.configuration; + self.adAlertManager.adUnitId = [self.delegate adUnitId]; + self.adAlertManager.targetAdView = self.view; + self.adAlertManager.location = [self.delegate location]; + [self.adAlertManager beginMonitoringAlerts]; +} + +- (void)rotateToOrientation:(UIInterfaceOrientation)orientation +{ + [self forceRedraw]; +} + +- (void)forceRedraw +{ + UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation]; + int angle = -1; + switch (orientation) { + case UIInterfaceOrientationPortrait: angle = 0; break; + case UIInterfaceOrientationLandscapeLeft: angle = 90; break; + case UIInterfaceOrientationLandscapeRight: angle = -90; break; + case UIInterfaceOrientationPortraitUpsideDown: angle = 180; break; + default: break; + } + + if (angle == -1) return; + + // UIWebView doesn't seem to fire the 'orientationchange' event upon rotation, so we do it here. + NSString *orientationEventScript = [NSString stringWithFormat: + @"window.__defineGetter__('orientation',function(){return %d;});" + @"(function(){ var evt = document.createEvent('Events');" + @"evt.initEvent('orientationchange',true,true);window.dispatchEvent(evt);})();", + angle]; + [self.view stringByEvaluatingJavaScriptFromString:orientationEventScript]; + + // XXX: If the UIWebView is rotated off-screen (which may happen with interstitials), its + // content may render off-center upon display. We compensate by setting the viewport meta tag's + // 'width' attribute to be the size of the webview. + NSString *viewportUpdateScript = [NSString stringWithFormat: + @"document.querySelector('meta[name=viewport]')" + @".setAttribute('content', 'width=%f;', false);", + self.view.frame.size.width]; + [self.view stringByEvaluatingJavaScriptFromString:viewportUpdateScript]; + + // XXX: In iOS 7, off-screen UIWebViews will fail to render certain image creatives. + // Specifically, creatives that only contain an tag whose src attribute uses a 302 + // redirect will not be rendered at all. One workaround is to temporarily change the web view's + // internal contentInset property; this seems to force the web view to re-draw. + if (MPOffscreenWebViewNeedsRenderingWorkaround()) { + if ([self.view respondsToSelector:@selector(scrollView)]) { + UIScrollView *scrollView = self.view.scrollView; + UIEdgeInsets originalInsets = scrollView.contentInset; + UIEdgeInsets newInsets = UIEdgeInsetsMake(originalInsets.top + 1, + originalInsets.left, + originalInsets.bottom, + originalInsets.right); + scrollView.contentInset = newInsets; + scrollView.contentInset = originalInsets; + } + } +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/HTML/MPHTMLBannerCustomEvent.h b/iphone/Maps/3party/MoPubSDK/Internal/HTML/MPHTMLBannerCustomEvent.h new file mode 100644 index 00000000000..d6784f2b45e --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/HTML/MPHTMLBannerCustomEvent.h @@ -0,0 +1,19 @@ +// +// MPHTMLBannerCustomEvent.h +// MoPub +// +// Copyright (c) 2013 MoPub. All rights reserved. +// + +#import "MPBannerCustomEvent.h" +#import "MPAdWebViewAgent.h" +#import "MPPrivateBannerCustomEventDelegate.h" + +@interface MPHTMLBannerCustomEvent : MPBannerCustomEvent + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wobjc-property-synthesis" +@property (nonatomic, weak) id delegate; +#pragma clang diagnostic pop + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/HTML/MPHTMLBannerCustomEvent.m b/iphone/Maps/3party/MoPubSDK/Internal/HTML/MPHTMLBannerCustomEvent.m new file mode 100644 index 00000000000..af30a10e79e --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/HTML/MPHTMLBannerCustomEvent.m @@ -0,0 +1,103 @@ +// +// MPHTMLBannerCustomEvent.m +// MoPub +// +// Copyright (c) 2013 MoPub. All rights reserved. +// + +#import "MPHTMLBannerCustomEvent.h" +#import "MPWebView.h" +#import "MPLogging.h" +#import "MPAdConfiguration.h" +#import "MPInstanceProvider.h" + +@interface MPHTMLBannerCustomEvent () + +@property (nonatomic, strong) MPAdWebViewAgent *bannerAgent; + +@end + +@implementation MPHTMLBannerCustomEvent + +@synthesize bannerAgent = _bannerAgent; + +- (BOOL)enableAutomaticImpressionAndClickTracking +{ + return NO; +} + +- (void)requestAdWithSize:(CGSize)size customEventInfo:(NSDictionary *)info +{ + MPLogInfo(@"Loading MoPub HTML banner"); + MPLogTrace(@"Loading banner with HTML source: %@", [[self.delegate configuration] adResponseHTMLString]); + + CGRect adWebViewFrame = CGRectMake(0, 0, size.width, size.height); + self.bannerAgent = [[MPInstanceProvider sharedProvider] buildMPAdWebViewAgentWithAdWebViewFrame:adWebViewFrame + delegate:self]; + [self.bannerAgent loadConfiguration:[self.delegate configuration]]; +} + +- (void)dealloc +{ + self.bannerAgent.delegate = nil; +} + +- (void)rotateToOrientation:(UIInterfaceOrientation)newOrientation +{ + [self.bannerAgent rotateToOrientation:newOrientation]; +} + +#pragma mark - MPAdWebViewAgentDelegate + +- (CLLocation *)location +{ + return [self.delegate location]; +} + +- (NSString *)adUnitId +{ + return [self.delegate adUnitId]; +} + +- (UIViewController *)viewControllerForPresentingModalView +{ + return [self.delegate viewControllerForPresentingModalView]; +} + +- (void)adDidFinishLoadingAd:(MPWebView *)ad +{ + MPLogInfo(@"MoPub HTML banner did load"); + [self.delegate bannerCustomEvent:self didLoadAd:ad]; +} + +- (void)adDidFailToLoadAd:(MPWebView *)ad +{ + MPLogInfo(@"MoPub HTML banner did fail"); + [self.delegate bannerCustomEvent:self didFailToLoadAdWithError:nil]; +} + +- (void)adDidClose:(MPWebView *)ad +{ + //don't care +} + +- (void)adActionWillBegin:(MPWebView *)ad +{ + MPLogInfo(@"MoPub HTML banner will begin action"); + [self.delegate bannerCustomEventWillBeginAction:self]; +} + +- (void)adActionDidFinish:(MPWebView *)ad +{ + MPLogInfo(@"MoPub HTML banner did finish action"); + [self.delegate bannerCustomEventDidFinishAction:self]; +} + +- (void)adActionWillLeaveApplication:(MPWebView *)ad +{ + MPLogInfo(@"MoPub HTML banner will leave application"); + [self.delegate bannerCustomEventWillLeaveApplication:self]; +} + + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/HTML/MPHTMLInterstitialCustomEvent.h b/iphone/Maps/3party/MoPubSDK/Internal/HTML/MPHTMLInterstitialCustomEvent.h new file mode 100644 index 00000000000..1d767b74d3f --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/HTML/MPHTMLInterstitialCustomEvent.h @@ -0,0 +1,19 @@ +// +// MPHTMLInterstitialCustomEvent.h +// MoPub +// +// Copyright (c) 2013 MoPub. All rights reserved. +// + +#import "MPInterstitialCustomEvent.h" +#import "MPHTMLInterstitialViewController.h" +#import "MPPrivateInterstitialCustomEventDelegate.h" + +@interface MPHTMLInterstitialCustomEvent : MPInterstitialCustomEvent + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wobjc-property-synthesis" +@property (nonatomic, weak) id delegate; +#pragma clang diagnostic pop + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/HTML/MPHTMLInterstitialCustomEvent.m b/iphone/Maps/3party/MoPubSDK/Internal/HTML/MPHTMLInterstitialCustomEvent.m new file mode 100644 index 00000000000..bc39e41285d --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/HTML/MPHTMLInterstitialCustomEvent.m @@ -0,0 +1,119 @@ +// +// MPHTMLInterstitialCustomEvent.m +// MoPub +// +// Copyright (c) 2013 MoPub. All rights reserved. +// + +#import "MPHTMLInterstitialCustomEvent.h" +#import "MPLogging.h" +#import "MPAdConfiguration.h" +#import "MPInstanceProvider.h" + +@interface MPHTMLInterstitialCustomEvent () + +@property (nonatomic, strong) MPHTMLInterstitialViewController *interstitial; +@property (nonatomic, assign) BOOL trackedImpression; + +@end + +@implementation MPHTMLInterstitialCustomEvent + +@synthesize interstitial = _interstitial; + +- (BOOL)enableAutomaticImpressionAndClickTracking +{ + // An HTML interstitial tracks its own clicks. Turn off automatic tracking to prevent the tap event callback + // from generating an additional click. + // However, an HTML interstitial does not track its own impressions so we must manually do it in this class. + // See interstitialDidAppear: + return NO; +} + +- (void)requestInterstitialWithCustomEventInfo:(NSDictionary *)info +{ + MPLogInfo(@"Loading MoPub HTML interstitial"); + MPAdConfiguration *configuration = [self.delegate configuration]; + MPLogTrace(@"Loading HTML interstitial with source: %@", [configuration adResponseHTMLString]); + + self.interstitial = [[MPInstanceProvider sharedProvider] buildMPHTMLInterstitialViewControllerWithDelegate:self + orientationType:configuration.orientationType]; + [self.interstitial loadConfiguration:configuration]; +} + +- (void)showInterstitialFromRootViewController:(UIViewController *)rootViewController +{ + [self.interstitial presentInterstitialFromViewController:rootViewController]; +} + +#pragma mark - MPInterstitialViewControllerDelegate + +- (CLLocation *)location +{ + return [self.delegate location]; +} + +- (NSString *)adUnitId +{ + return [self.delegate adUnitId]; +} + +- (void)interstitialDidLoadAd:(MPInterstitialViewController *)interstitial +{ + MPLogInfo(@"MoPub HTML interstitial did load"); + [self.delegate interstitialCustomEvent:self didLoadAd:self.interstitial]; +} + +- (void)interstitialDidFailToLoadAd:(MPInterstitialViewController *)interstitial +{ + MPLogInfo(@"MoPub HTML interstitial did fail"); + [self.delegate interstitialCustomEvent:self didFailToLoadAdWithError:nil]; +} + +- (void)interstitialWillAppear:(MPInterstitialViewController *)interstitial +{ + MPLogInfo(@"MoPub HTML interstitial will appear"); + [self.delegate interstitialCustomEventWillAppear:self]; +} + +- (void)interstitialDidAppear:(MPInterstitialViewController *)interstitial +{ + MPLogInfo(@"MoPub HTML interstitial did appear"); + [self.delegate interstitialCustomEventDidAppear:self]; + + if (!self.trackedImpression) { + self.trackedImpression = YES; + [self.delegate trackImpression]; + } +} + +- (void)interstitialWillDisappear:(MPInterstitialViewController *)interstitial +{ + MPLogInfo(@"MoPub HTML interstitial will disappear"); + [self.delegate interstitialCustomEventWillDisappear:self]; +} + +- (void)interstitialDidDisappear:(MPInterstitialViewController *)interstitial +{ + MPLogInfo(@"MoPub HTML interstitial did disappear"); + [self.delegate interstitialCustomEventDidDisappear:self]; + + // Deallocate the interstitial as we don't need it anymore. If we don't deallocate the interstitial after dismissal, + // then the html in the webview will continue to run which could lead to bugs such as continuing to play the sound of an inline + // video since the app may hold onto the interstitial ad controller. Moreover, we keep an array of controllers around as well. + self.interstitial = nil; +} + +- (void)interstitialDidReceiveTapEvent:(MPInterstitialViewController *)interstitial +{ + MPLogInfo(@"MoPub HTML interstitial did receive tap event"); + [self.delegate interstitialCustomEventDidReceiveTapEvent:self]; +} + +- (void)interstitialWillLeaveApplication:(MPInterstitialViewController *)interstitial +{ + MPLogInfo(@"MoPub HTML interstitial will leave application"); + [self.delegate interstitialCustomEventWillLeaveApplication:self]; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/HTML/MPHTMLInterstitialViewController.h b/iphone/Maps/3party/MoPubSDK/Internal/HTML/MPHTMLInterstitialViewController.h new file mode 100644 index 00000000000..f15ebfe6145 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/HTML/MPHTMLInterstitialViewController.h @@ -0,0 +1,21 @@ +// +// MPHTMLInterstitialViewController.h +// MoPub +// +// Copyright (c) 2012 MoPub, Inc. All rights reserved. +// + +#import + +#import "MPAdWebViewAgent.h" +#import "MPInterstitialViewController.h" + +@class MPAdConfiguration; + +@interface MPHTMLInterstitialViewController : MPInterstitialViewController + +@property (nonatomic, strong) MPAdWebViewAgent *backingViewAgent; + +- (void)loadConfiguration:(MPAdConfiguration *)configuration; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/HTML/MPHTMLInterstitialViewController.m b/iphone/Maps/3party/MoPubSDK/Internal/HTML/MPHTMLInterstitialViewController.m new file mode 100644 index 00000000000..0c9eae2532f --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/HTML/MPHTMLInterstitialViewController.m @@ -0,0 +1,150 @@ +// +// MPHTMLInterstitialViewController.m +// MoPub +// +// Copyright (c) 2012 MoPub, Inc. All rights reserved. +// + +#import "MPHTMLInterstitialViewController.h" +#import "MPWebView.h" +#import "MPAdDestinationDisplayAgent.h" +#import "MPInstanceProvider.h" + +@interface MPHTMLInterstitialViewController () + +@property (nonatomic, strong) MPWebView *backingView; + +@end + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation MPHTMLInterstitialViewController + +@synthesize delegate = _delegate; +@synthesize backingViewAgent = _backingViewAgent; +@synthesize backingView = _backingView; + +- (void)dealloc +{ + self.backingViewAgent.delegate = nil; + + self.backingView.delegate = nil; +} + +- (void)viewDidLoad +{ + [super viewDidLoad]; + + self.view.backgroundColor = [UIColor blackColor]; + self.backingViewAgent = [[MPInstanceProvider sharedProvider] buildMPAdWebViewAgentWithAdWebViewFrame:self.view.bounds + delegate:self]; +} + +#pragma mark - Public + +- (void)loadConfiguration:(MPAdConfiguration *)configuration +{ + [self view]; + [self.backingViewAgent loadConfiguration:configuration]; + + self.backingView = self.backingViewAgent.view; + self.backingView.frame = self.view.bounds; + self.backingView.autoresizingMask = UIViewAutoresizingFlexibleWidth | + UIViewAutoresizingFlexibleHeight; + [self.view addSubview:self.backingView]; +} + +- (void)willPresentInterstitial +{ + self.backingView.alpha = 0.0; + [self.delegate interstitialWillAppear:self]; +} + +- (void)didPresentInterstitial +{ + [self.backingViewAgent enableRequestHandling]; + [self.backingViewAgent invokeJavaScriptForEvent:MPAdWebViewEventAdDidAppear]; + + // XXX: In certain cases, UIWebView's content appears off-center due to rotation / auto- + // resizing while off-screen. -forceRedraw corrects this issue, but there is always a brief + // instant when the old content is visible. We mask this using a short fade animation. + [self.backingViewAgent forceRedraw]; + + [UIView beginAnimations:nil context:nil]; + [UIView setAnimationDuration:0.3]; + self.backingView.alpha = 1.0; + [UIView commitAnimations]; + + [self.delegate interstitialDidAppear:self]; +} + +- (void)willDismissInterstitial +{ + [self.backingViewAgent disableRequestHandling]; + [self.delegate interstitialWillDisappear:self]; +} + +- (void)didDismissInterstitial +{ + [self.backingViewAgent invokeJavaScriptForEvent:MPAdWebViewEventAdDidDisappear]; + [self.delegate interstitialDidDisappear:self]; +} + +#pragma mark - Autorotation + +- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation +{ + [super didRotateFromInterfaceOrientation:fromInterfaceOrientation]; + + [self.backingViewAgent rotateToOrientation:self.interfaceOrientation]; +} + +#pragma mark - MPAdWebViewAgentDelegate + +- (CLLocation *)location +{ + return [self.delegate location]; +} + +- (NSString *)adUnitId +{ + return [self.delegate adUnitId]; +} + +- (UIViewController *)viewControllerForPresentingModalView +{ + return self; +} + +- (void)adDidFinishLoadingAd:(MPWebView *)ad +{ + [self.delegate interstitialDidLoadAd:self]; +} + +- (void)adDidFailToLoadAd:(MPWebView *)ad +{ + [self.delegate interstitialDidFailToLoadAd:self]; +} + +- (void)adActionWillBegin:(MPWebView *)ad +{ + [self.delegate interstitialDidReceiveTapEvent:self]; +} + +- (void)adActionWillLeaveApplication:(MPWebView *)ad +{ + [self.delegate interstitialWillLeaveApplication:self]; + [self dismissInterstitialAnimated:NO]; +} + +- (void)adActionDidFinish:(MPWebView *)ad +{ + //NOOP: the landing page is going away, but not the interstitial. +} + +- (void)adDidClose:(MPWebView *)ad +{ + //NOOP: the ad is going away, but not the interstitial. +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/HTML/MPWebView.h b/iphone/Maps/3party/MoPubSDK/Internal/HTML/MPWebView.h new file mode 100644 index 00000000000..0bb0ab2ccb5 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/HTML/MPWebView.h @@ -0,0 +1,102 @@ +// +// MPWebView.h +// MoPubSDK +// +// Copyright © 2016 MoPub. All rights reserved. +// + +/*** + * MPWebView + * This class is a wrapper class for WKWebView and UIWebView. Internally, it utilizes WKWebView when possible, and + * falls back on UIWebView only when WKWebView isn't available (i.e., in iOS 7). MPWebView's interface is meant to + * imitate UIWebView, and, in many cases, MPWebView can act as a drop-in replacement for UIWebView. MPWebView also + * blocks all JavaScript text boxes from appearing. + * + * While `stringByEvaluatingJavaScriptFromString:` does exist for UIWebView compatibility reasons, it's highly + * recommended that the caller uses `evaluateJavaScript:completionHandler:` whenever code can be reworked + * to make use of completion blocks to keep the advantages of asynchronicity. It solely fires off the javascript + * execution within WKWebView and does not wait or return. + * + * MPWebView currently does not support a few other features of UIWebView -- such as pagination -- as WKWebView also + * does not contain support. + ***/ + +#import + +@class MPWebView; + +@protocol MPWebViewDelegate + +@optional + +- (BOOL)webView:(MPWebView *)webView +shouldStartLoadWithRequest:(NSURLRequest *)request + navigationType:(UIWebViewNavigationType)navigationType; + +- (void)webViewDidStartLoad:(MPWebView *)webView; + +- (void)webViewDidFinishLoad:(MPWebView *)webView; + +- (void)webView:(MPWebView *)webView +didFailLoadWithError:(NSError *)error; + +@end + +typedef void (^MPWebViewJavascriptEvaluationCompletionHandler)(id result, NSError *error); + +@interface MPWebView : UIView + +// If you -need- UIWebView for some reason, use this method to init and send `YES` to `forceUIWebView` to be sure +// you're using UIWebView regardless of OS. If any other `init` method is used, or if `NO` is used as the forceUIWebView +// parameter, WKWebView will be used when available. +- (instancetype)initWithFrame:(CGRect)frame forceUIWebView:(BOOL)forceUIWebView; + +@property (weak, nonatomic) id delegate; + +@property (nonatomic, readonly, getter=isLoading) BOOL loading; + +// These methods and properties are non-functional below iOS 9. If you call or try to set them, they'll do nothing. +// For the properties, if you try to access them, you'll get `NO` 100% of the time. They are entirely hidden when +// compiling with iOS 8 SDK or below. +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 90000 +- (void)loadData:(NSData *)data + MIMEType:(NSString *)MIMEType +textEncodingName:(NSString *)encodingName + baseURL:(NSURL *)baseURL; + +@property (nonatomic) BOOL allowsLinkPreview; +@property (nonatomic, readonly) BOOL allowsPictureInPictureMediaPlayback; +#endif + +- (void)loadHTMLString:(NSString *)string + baseURL:(NSURL *)baseURL; + +- (void)loadRequest:(NSURLRequest *)request; +- (void)stopLoading; +- (void)reload; + +@property (nonatomic, readonly) BOOL canGoBack; +@property (nonatomic, readonly) BOOL canGoForward; +- (void)goBack; +- (void)goForward; + +@property (nonatomic) BOOL scalesPageToFit; +@property (nonatomic, readonly) UIScrollView *scrollView; + +- (void)evaluateJavaScript:(NSString *)javaScriptString + completionHandler:(MPWebViewJavascriptEvaluationCompletionHandler)completionHandler; + +// When using WKWebView, always returns @"" and solely fires the javascript execution without waiting on it. +// If you need a guaranteed return value from `stringByEvaluatingJavaScriptFromString:`, please use +// `evaluateJavaScript:completionHandler:` instead. +- (NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)javaScriptString; + +@property (nonatomic, readonly) BOOL allowsInlineMediaPlayback; +@property (nonatomic, readonly) BOOL mediaPlaybackRequiresUserAction; +@property (nonatomic, readonly) BOOL mediaPlaybackAllowsAirPlay; + +// UIWebView+MPAdditions methods +- (void)mp_setScrollable:(BOOL)scrollable; +- (void)disableJavaScriptDialogs; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/HTML/MPWebView.m b/iphone/Maps/3party/MoPubSDK/Internal/HTML/MPWebView.m new file mode 100644 index 00000000000..71f45aa0528 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/HTML/MPWebView.m @@ -0,0 +1,605 @@ +// +// MPWebView.m +// MoPubSDK +// +// Copyright © 2016 MoPub. All rights reserved. +// + +#import "MPWebView.h" + +#import + +static BOOL const kMoPubAllowsInlineMediaPlaybackDefault = YES; +static BOOL const kMoPubRequiresUserActionForMediaPlaybackDefault = NO; + +// Set defaults for this as its default differs between UIWebView and WKWebView +static BOOL const kMoPubAllowsLinkPreviewDefault = NO; + +static NSString *const kMoPubJavaScriptDisableDialogScript = @"window.alert = function() { }; window.prompt = function() { }; window.confirm = function() { };"; +static NSString *const kMoPubScalesPageToFitScript = @"var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', 'width=device-width, initial-scale=1.0, user-scalable=no'); document.getElementsByTagName('head')[0].appendChild(meta);"; + +static NSString *const kMoPubFrameKeyPathString = @"frame"; + +@interface MPWebView () + +@property (weak, nonatomic) WKWebView *wkWebView; +@property (weak, nonatomic) UIWebView *uiWebView; + +@end + +@implementation MPWebView + +- (instancetype)init { + if (self = [super init]) { + [self setUpStepsForceUIWebView:NO]; + } + + return self; +} + +- (instancetype)initWithCoder:(NSCoder *)aDecoder { + if (self = [super initWithCoder:aDecoder]) { + [self setUpStepsForceUIWebView:NO]; + } + + return self; +} + +- (instancetype)initWithFrame:(CGRect)frame { + if (self = [super initWithFrame:frame]) { + [self setUpStepsForceUIWebView:NO]; + } + + return self; +} + +- (instancetype)initWithFrame:(CGRect)frame forceUIWebView:(BOOL)forceUIWebView { + if (self = [super initWithFrame:frame]) { + [self setUpStepsForceUIWebView:forceUIWebView]; + } + + return self; +} + +- (void)setUpStepsForceUIWebView:(BOOL)forceUIWebView { + // set up web view + UIView *webView; + + if (!forceUIWebView && [WKWebView class]) { + WKUserContentController *contentController = [[WKUserContentController alloc] init]; + WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init]; + config.allowsInlineMediaPlayback = kMoPubAllowsInlineMediaPlaybackDefault; + if ([config respondsToSelector:@selector(requiresUserActionForMediaPlayback)]) { + config.requiresUserActionForMediaPlayback = kMoPubRequiresUserActionForMediaPlaybackDefault; + } else { + config.mediaPlaybackRequiresUserAction = kMoPubRequiresUserActionForMediaPlaybackDefault; + } + config.userContentController = contentController; + + WKWebView *wkWebView = [[WKWebView alloc] initWithFrame:self.bounds configuration:config]; + + wkWebView.UIDelegate = self; + wkWebView.navigationDelegate = self; + + webView = wkWebView; + + self.wkWebView = wkWebView; + + // Put WKWebView onto the offscreen view so any loading will complete correctly; see comment below. + [self retainWKWebViewOffscreen:wkWebView]; + } else { + UIWebView *uiWebView = [[UIWebView alloc] initWithFrame:self.bounds]; + + uiWebView.allowsInlineMediaPlayback = kMoPubAllowsInlineMediaPlaybackDefault; + uiWebView.mediaPlaybackRequiresUserAction = kMoPubRequiresUserActionForMediaPlaybackDefault; + + uiWebView.delegate = self; + + webView = uiWebView; + + self.uiWebView = uiWebView; + + [self addSubview:webView]; + } + + webView.backgroundColor = [UIColor clearColor]; + webView.opaque = NO; + + webView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + + // set default scalesPageToFit + self.scalesPageToFit = NO; + + // configure like the old MPAdWebView + self.backgroundColor = [UIColor clearColor]; + self.opaque = NO; + + // set default for allowsLinkPreview as they're different between WKWebView and UIWebView + self.allowsLinkPreview = kMoPubAllowsLinkPreviewDefault; + + // set up KVO to adjust the frame of the WKWebView to avoid white screens + if (self.wkWebView) { + [self addObserver:self + forKeyPath:kMoPubFrameKeyPathString + options:NSKeyValueObservingOptionOld + context:NULL]; + } +} + +// WKWebView won't load/execute javascript unless it's on the view hierarchy. Because the MoPub SDK uses a lot of +// javascript before adding the view to the hierarchy, let's stick the WKWebView into an offscreen-but-on-the-window +// view, and move it to self when self gets a window. +static UIView *gOffscreenView = nil; + +- (void)retainWKWebViewOffscreen:(WKWebView *)webView { + if (!gOffscreenView) { + gOffscreenView = [self constructOffscreenView]; + } + [gOffscreenView addSubview:webView]; +} + +- (void)cleanUpOffscreenView { + if (gOffscreenView.subviews.count == 0) { + [gOffscreenView removeFromSuperview]; + gOffscreenView = nil; + } +} + +- (UIView *)constructOffscreenView { + UIView *view = [[UIView alloc] initWithFrame:CGRectZero]; + view.clipsToBounds = YES; + + UIWindow *appWindow = [[UIApplication sharedApplication] keyWindow]; + [appWindow addSubview:view]; + + return view; +} + +- (void)didMoveToWindow { + // If using WKWebView, and if MPWebView is in the view hierarchy, and if the WKWebView is in the offscreen view currently, + // move our WKWebView to self and deallocate OffscreenView if no other MPWebView is using it. + if (self.wkWebView + && self.window != nil + && [self.wkWebView.superview isEqual:gOffscreenView]) { + self.wkWebView.frame = self.bounds; + [self addSubview:self.wkWebView]; + + // Don't keep OffscreenView if we don't need it; it can always be re-allocated again later + [self cleanUpOffscreenView]; + } else if (self.wkWebView + && self.window == nil + && [self.wkWebView.superview isEqual:self]) { + [self retainWKWebViewOffscreen:self.wkWebView]; + } +} + +// Occasionally, we encounter an issue where, when MPWebView is initialized at a different frame size than when it's shown, +// the WKWebView shows as all white because it doesn't have a chance to get redrawn at the new size before getting shown. +// This makes sure WKWebView is always already rendered at the correct size when it gets moved to the window. +- (void)observeValueForKeyPath:(NSString *)keyPath + ofObject:(id)object + change:(NSDictionary *)change + context:(void *)context { + // Only keep the wkWebView up-to-date if its superview is the offscreen view. + // If it's attached to self, the autoresizing mask should come into play & this is just extra work. + if ([keyPath isEqualToString:kMoPubFrameKeyPathString] + && [self.wkWebView.superview isEqual:gOffscreenView]) { + self.wkWebView.frame = self.bounds; + } +} + +- (void)dealloc { + // Remove KVO observer + if (self.wkWebView) { + [self removeObserver:self forKeyPath:kMoPubFrameKeyPathString]; + } + + // Avoids EXC_BAD_INSTRUCTION crash + self.wkWebView.scrollView.delegate = nil; + + // Be sure our WKWebView doesn't stay stuck to the static OffscreenView + [self.wkWebView removeFromSuperview]; + // Deallocate OffscreenView if needed + [self cleanUpOffscreenView]; +} + +- (BOOL)isLoading { + return self.uiWebView ? self.uiWebView.isLoading : self.wkWebView.isLoading; +} + +- (void)loadData:(NSData *)data + MIMEType:(NSString *)MIMEType +textEncodingName:(NSString *)encodingName + baseURL:(NSURL *)baseURL { + if (self.uiWebView) { + [self.uiWebView loadData:data + MIMEType:MIMEType + textEncodingName:encodingName + baseURL:baseURL]; + } else { +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 90000 + if ([self.wkWebView respondsToSelector:@selector(loadData:MIMEType:characterEncodingName:baseURL:)]) { + [self.wkWebView loadData:data + MIMEType:MIMEType + characterEncodingName:encodingName + baseURL:baseURL]; + } +#endif + } +} + +- (void)loadHTMLString:(NSString *)string + baseURL:(NSURL *)baseURL { + if (self.uiWebView) { + [self.uiWebView loadHTMLString:string + baseURL:baseURL]; + } else { + [self.wkWebView loadHTMLString:string + baseURL:baseURL]; + } +} + +- (void)loadRequest:(NSURLRequest *)request { + if (self.uiWebView) { + [self.uiWebView loadRequest:request]; + } else { + [self.wkWebView loadRequest:request]; + } +} + +- (void)stopLoading { + if (self.uiWebView) { + [self.uiWebView stopLoading]; + } else { + [self.wkWebView stopLoading]; + } +} + +- (void)reload { + if (self.uiWebView) { + [self.uiWebView reload]; + } else { + [self.wkWebView reload]; + } +} + +- (BOOL)canGoBack { + return self.uiWebView ? self.uiWebView.canGoBack : self.wkWebView.canGoBack; +} + +- (BOOL)canGoForward { + return self.uiWebView ? self.uiWebView.canGoForward : self.wkWebView.canGoForward; +} + +- (void)goBack { + if (self.uiWebView) { + [self.uiWebView goBack]; + } else { + [self.wkWebView goBack]; + } +} + +- (void)goForward { + if (self.uiWebView) { + [self.uiWebView goForward]; + } else { + [self.wkWebView goForward]; + } +} + +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 90000 +- (void)setAllowsLinkPreview:(BOOL)allowsLinkPreview { + if (self.uiWebView) { + if ([self.uiWebView respondsToSelector:@selector(setAllowsLinkPreview:)]) { + self.uiWebView.allowsLinkPreview = allowsLinkPreview; + } + } else { + if ([self.wkWebView respondsToSelector:@selector(setAllowsLinkPreview:)]) { + self.wkWebView.allowsLinkPreview = allowsLinkPreview; + } + } +} + +- (BOOL)allowsLinkPreview { + if (self.uiWebView) { + if ([self.uiWebView respondsToSelector:@selector(allowsLinkPreview)]) { + return self.uiWebView.allowsLinkPreview; + } + } else { + if ([self.wkWebView respondsToSelector:@selector(allowsLinkPreview)]) { + return self.wkWebView.allowsLinkPreview; + } + } + + return NO; +} +#endif + +- (void)setScalesPageToFit:(BOOL)scalesPageToFit { + if (self.uiWebView) { + self.uiWebView.scalesPageToFit = scalesPageToFit; + } else { + if (scalesPageToFit) { + self.wkWebView.scrollView.delegate = nil; + + [self.wkWebView.configuration.userContentController removeAllUserScripts]; + } else { + // Make sure the scroll view can't scroll (prevent double tap to zoom) + self.wkWebView.scrollView.delegate = self; + + // Inject the user script to scale the page if needed + if (self.wkWebView.configuration.userContentController.userScripts.count == 0) { + WKUserScript *viewportScript = [[WKUserScript alloc] initWithSource:kMoPubScalesPageToFitScript + injectionTime:WKUserScriptInjectionTimeAtDocumentEnd + forMainFrameOnly:YES]; + [self.wkWebView.configuration.userContentController addUserScript:viewportScript]; + } + } + } +} + +- (BOOL)scalesPageToFit { + return self.uiWebView ? self.uiWebView.scalesPageToFit : self.scrollView.delegate == nil; +} + +- (UIScrollView *)scrollView { + return self.uiWebView ? self.uiWebView.scrollView : self.wkWebView.scrollView; +} + +- (void)evaluateJavaScript:(NSString *)javaScriptString + completionHandler:(MPWebViewJavascriptEvaluationCompletionHandler)completionHandler { + if (self.uiWebView) { + NSString *resultString = [self.uiWebView stringByEvaluatingJavaScriptFromString:javaScriptString]; + + if (completionHandler) { + completionHandler(resultString, nil); + } + } else { + [self.wkWebView evaluateJavaScript:javaScriptString + completionHandler:completionHandler]; + } +} + +- (NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)javaScriptString { + if (self.uiWebView) { + return [self.uiWebView stringByEvaluatingJavaScriptFromString:javaScriptString]; + } else { + // There is no way to reliably wait for `evaluateJavaScript:completionHandler:` to finish without risk of + // deadlocking the main thread. This method is called on the main thread and the completion block is also + // called on the main thread. + // Instead of waiting, just fire and return an empty string. + + // Methods attempted: + // libdispatch dispatch groups + // http://stackoverflow.com/questions/17920169/how-to-wait-for-method-that-has-completion-block-all-on-main-thread + + [self.wkWebView evaluateJavaScript:javaScriptString completionHandler:nil]; + return @""; + } +} + +- (BOOL)allowsInlineMediaPlayback { + return self.uiWebView ? self.uiWebView.allowsInlineMediaPlayback : self.wkWebView.configuration.allowsInlineMediaPlayback; +} + +- (BOOL)mediaPlaybackRequiresUserAction { + if (self.uiWebView) { + return self.uiWebView.mediaPlaybackRequiresUserAction; + } else { +#if __IPHONE_OS_VERSION_MIN_REQUIRED < 90000 + if (![self.wkWebView.configuration respondsToSelector:@selector(requiresUserActionForMediaPlayback)]) { + return self.wkWebView.configuration.mediaPlaybackRequiresUserAction; + } +#endif + +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 90000 + return self.wkWebView.configuration.requiresUserActionForMediaPlayback; +#else + return NO; // avoid compiler error under 8.4 SDK +#endif + } +} + +- (BOOL)mediaPlaybackAllowsAirPlay { + if (self.uiWebView) { + return self.uiWebView.mediaPlaybackAllowsAirPlay; + } else { +#if __IPHONE_OS_VERSION_MIN_REQUIRED < 90000 + if (![self.wkWebView.configuration respondsToSelector:@selector(allowsAirPlayForMediaPlayback)]) { + return self.wkWebView.configuration.mediaPlaybackAllowsAirPlay; + } +#endif + +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 90000 + return self.wkWebView.configuration.allowsAirPlayForMediaPlayback; +#else + return NO; // avoid compiler error under 8.4 SDK +#endif + } +} + +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 90000 +- (BOOL)allowsPictureInPictureMediaPlayback { + if (self.uiWebView) { + if ([self.uiWebView respondsToSelector:@selector(allowsPictureInPictureMediaPlayback)]) { + return self.uiWebView.allowsPictureInPictureMediaPlayback; + } + } else { + if ([self.wkWebView.configuration respondsToSelector:@selector(allowsPictureInPictureMediaPlayback)]) { + return self.wkWebView.configuration.allowsPictureInPictureMediaPlayback; + } + } + + return NO; +} +#endif + +#pragma mark - UIWebViewDelegate + +- (BOOL)webView:(UIWebView *)webView +shouldStartLoadWithRequest:(NSURLRequest *)request + navigationType:(UIWebViewNavigationType)navigationType { + if ([self.delegate respondsToSelector:@selector(webView:shouldStartLoadWithRequest:navigationType:)]) { + return [self.delegate webView:self + shouldStartLoadWithRequest:request + navigationType:navigationType]; + } + + return YES; +} + +- (void)webViewDidStartLoad:(UIWebView *)webView { + if ([self.delegate respondsToSelector:@selector(webViewDidStartLoad:)]) { + [self.delegate webViewDidStartLoad:self]; + } +} + +- (void)webViewDidFinishLoad:(UIWebView *)webView { + if ([self.delegate respondsToSelector:@selector(webViewDidFinishLoad:)]) { + [self.delegate webViewDidFinishLoad:self]; + } +} + +- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error { + if ([self.delegate respondsToSelector:@selector(webView:didFailLoadWithError:)]) { + [self.delegate webView:self didFailLoadWithError:error]; + } +} + +#pragma mark - UIWebView+MPAdditions methods + +/// Find all subviews that are UIScrollViews or subclasses and set their scrolling and bounce. +- (void)mp_setScrollable:(BOOL)scrollable { + UIScrollView *scrollView = self.scrollView; + scrollView.scrollEnabled = scrollable; + scrollView.bounces = scrollable; +} + +/// Redefine alert, prompt, and confirm to do nothing +- (void)disableJavaScriptDialogs +{ + if (self.uiWebView) { // Only redefine on UIWebView, as the WKNavigationDelegate for WKWebView takes care of this. + [self stringByEvaluatingJavaScriptFromString:kMoPubJavaScriptDisableDialogScript]; + } +} + +#pragma mark - WKNavigationDelegate + +- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation { + if ([self.delegate respondsToSelector:@selector(webViewDidStartLoad:)]) { + [self.delegate webViewDidStartLoad:self]; + } +} + +- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation { + if ([self.delegate respondsToSelector:@selector(webViewDidFinishLoad:)]) { + [self.delegate webViewDidFinishLoad:self]; + } +} + +- (void)webView:(WKWebView *)webView +didFailNavigation:(WKNavigation *)navigation + withError:(NSError *)error { + if ([self.delegate respondsToSelector:@selector(webView:didFailLoadWithError:)]) { + [self.delegate webView:self didFailLoadWithError:error]; + } +} + +- (void)webView:(WKWebView *)webView +didFailProvisionalNavigation:(WKNavigation *)navigation + withError:(NSError *)error { + if ([self.delegate respondsToSelector:@selector(webView:didFailLoadWithError:)]) { + [self.delegate webView:self didFailLoadWithError:error]; + } +} + +- (void)webView:(WKWebView *)webView +decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction +decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler { + WKNavigationActionPolicy policy = WKNavigationActionPolicyAllow; + + if ([self.delegate respondsToSelector:@selector(webView:shouldStartLoadWithRequest:navigationType:)]) { + NSURLRequest *request = navigationAction.request; + UIWebViewNavigationType navType; + switch (navigationAction.navigationType) { + case WKNavigationTypeLinkActivated: + navType = UIWebViewNavigationTypeLinkClicked; + break; + case WKNavigationTypeFormSubmitted: + navType = UIWebViewNavigationTypeFormSubmitted; + break; + case WKNavigationTypeBackForward: + navType = UIWebViewNavigationTypeBackForward; + break; + case WKNavigationTypeReload: + navType = UIWebViewNavigationTypeReload; + break; + case WKNavigationTypeFormResubmitted: + navType = UIWebViewNavigationTypeFormResubmitted; + break; + default: + navType = UIWebViewNavigationTypeOther; + break; + } + + policy = [self.delegate webView:self + shouldStartLoadWithRequest:request + navigationType:navType] ? WKNavigationActionPolicyAllow : WKNavigationActionPolicyCancel; + } + + decisionHandler(policy); +} + +- (WKWebView *)webView:(WKWebView *)webView +createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration +forNavigationAction:(WKNavigationAction *)navigationAction +windowFeatures:(WKWindowFeatures *)windowFeatures { + // Open any links to new windows in the current WKWebView rather than create a new one + if (!navigationAction.targetFrame.isMainFrame) { + [webView loadRequest:navigationAction.request]; + } + + return nil; +} + +#pragma mark - UIScrollViewDelegate + +// Avoid double tap to zoom in WKWebView +// Delegate is only set when scalesPagesToFit is set to NO +- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView { + return nil; +} + +#pragma mark - WKUIDelegate + +// WKUIDelegate method implementations makes it so that, if a WKWebView is being used, javascript dialog boxes can +// never show. They're programatically dismissed with the "Cancel" option (if there is any such option) before showing +// a view. + +- (void)webView:(WKWebView *)webView +runJavaScriptAlertPanelWithMessage:(NSString *)message +initiatedByFrame:(WKFrameInfo *)frame +#if __IPHONE_OS_VERSION_MAX_ALLOWED < 90000 // This pre-processor code is to be sure we can compile under both iOS 8 and 9 SDKs +completionHandler:(void (^)())completionHandler { +#else +completionHandler:(void (^)(void))completionHandler { +#endif + completionHandler(); +} + +- (void)webView:(WKWebView *)webView +runJavaScriptConfirmPanelWithMessage:(NSString *)message +initiatedByFrame:(WKFrameInfo *)frame +completionHandler:(void (^)(BOOL))completionHandler { + completionHandler(NO); +} + +- (void)webView:(WKWebView *)webView +runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt + defaultText:(NSString *)defaultText +initiatedByFrame:(WKFrameInfo *)frame +completionHandler:(void (^)(NSString *result))completionHandler { + completionHandler(nil); +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Interstitials/MPBaseInterstitialAdapter.h b/iphone/Maps/3party/MoPubSDK/Internal/Interstitials/MPBaseInterstitialAdapter.h new file mode 100644 index 00000000000..0807ed304c3 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Interstitials/MPBaseInterstitialAdapter.h @@ -0,0 +1,69 @@ +// +// MPBaseInterstitialAdapter.h +// MoPub +// +// Created by Nafis Jamal on 4/27/11. +// Copyright 2011 MoPub. All rights reserved. +// + +#import +#import + +@class MPAdConfiguration, CLLocation; + +@protocol MPInterstitialAdapterDelegate; + +@interface MPBaseInterstitialAdapter : NSObject + +@property (nonatomic, weak) id delegate; + +/* + * Creates an adapter with a reference to an MPInterstitialAdManager. + */ +- (id)initWithDelegate:(id)delegate; + +/* + * Sets the adapter's delegate to nil. + */ +- (void)unregisterDelegate; + +- (void)getAdWithConfiguration:(MPAdConfiguration *)configuration; +- (void)_getAdWithConfiguration:(MPAdConfiguration *)configuration; + +- (void)didStopLoading; + +/* + * Presents the interstitial from the specified view controller. + */ +- (void)showInterstitialFromViewController:(UIViewController *)controller; + +@end + +@interface MPBaseInterstitialAdapter (ProtectedMethods) + +- (void)trackImpression; +- (void)trackClick; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@class MPInterstitialAdController; + +@protocol MPInterstitialAdapterDelegate + +- (MPInterstitialAdController *)interstitialAdController; +- (id)interstitialDelegate; +- (CLLocation *)location; + +- (void)adapterDidFinishLoadingAd:(MPBaseInterstitialAdapter *)adapter; +- (void)adapter:(MPBaseInterstitialAdapter *)adapter didFailToLoadAdWithError:(NSError *)error; +- (void)interstitialWillAppearForAdapter:(MPBaseInterstitialAdapter *)adapter; +- (void)interstitialDidAppearForAdapter:(MPBaseInterstitialAdapter *)adapter; +- (void)interstitialWillDisappearForAdapter:(MPBaseInterstitialAdapter *)adapter; +- (void)interstitialDidDisappearForAdapter:(MPBaseInterstitialAdapter *)adapter; +- (void)interstitialDidExpireForAdapter:(MPBaseInterstitialAdapter *)adapter; +- (void)interstitialDidReceiveTapEventForAdapter:(MPBaseInterstitialAdapter *)adapter; +- (void)interstitialWillLeaveApplicationForAdapter:(MPBaseInterstitialAdapter *)adapter; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Interstitials/MPBaseInterstitialAdapter.m b/iphone/Maps/3party/MoPubSDK/Internal/Interstitials/MPBaseInterstitialAdapter.m new file mode 100644 index 00000000000..d31c8fc5478 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Interstitials/MPBaseInterstitialAdapter.m @@ -0,0 +1,115 @@ +// +// MPBaseInterstitialAdapter.m +// MoPub +// +// Created by Nafis Jamal on 4/27/11. +// Copyright 2011 MoPub. All rights reserved. +// + +#import "MPBaseInterstitialAdapter.h" +#import "MPAdConfiguration.h" +#import "MPGlobal.h" +#import "MPAnalyticsTracker.h" +#import "MPCoreInstanceProvider.h" +#import "MPTimer.h" +#import "MPConstants.h" + +@interface MPBaseInterstitialAdapter () + +@property (nonatomic, strong) MPAdConfiguration *configuration; +@property (nonatomic, strong) MPTimer *timeoutTimer; + +- (void)startTimeoutTimer; + +@end + +@implementation MPBaseInterstitialAdapter + +@synthesize delegate = _delegate; +@synthesize configuration = _configuration; +@synthesize timeoutTimer = _timeoutTimer; + +- (id)initWithDelegate:(id)delegate +{ + self = [super init]; + if (self) { + self.delegate = delegate; + } + return self; +} + +- (void)dealloc +{ + [self unregisterDelegate]; + + [self.timeoutTimer invalidate]; + +} + +- (void)unregisterDelegate +{ + self.delegate = nil; +} + +- (void)getAdWithConfiguration:(MPAdConfiguration *)configuration +{ + // To be implemented by subclasses. + [self doesNotRecognizeSelector:_cmd]; +} + +- (void)_getAdWithConfiguration:(MPAdConfiguration *)configuration +{ + self.configuration = configuration; + + [self startTimeoutTimer]; + + MPBaseInterstitialAdapter *strongSelf = self; + [strongSelf getAdWithConfiguration:configuration]; +} + +- (void)startTimeoutTimer +{ + NSTimeInterval timeInterval = (self.configuration && self.configuration.adTimeoutInterval >= 0) ? + self.configuration.adTimeoutInterval : INTERSTITIAL_TIMEOUT_INTERVAL; + + if (timeInterval > 0) { + self.timeoutTimer = [[MPCoreInstanceProvider sharedProvider] buildMPTimerWithTimeInterval:timeInterval + target:self + selector:@selector(timeout) + repeats:NO]; + + [self.timeoutTimer scheduleNow]; + } +} + +- (void)didStopLoading +{ + [self.timeoutTimer invalidate]; +} + +- (void)timeout +{ + [self.delegate adapter:self didFailToLoadAdWithError:nil]; +} + +#pragma mark - Presentation + +- (void)showInterstitialFromViewController:(UIViewController *)controller +{ + [self doesNotRecognizeSelector:_cmd]; +} + +#pragma mark - Metrics + +- (void)trackImpression +{ + [[[MPCoreInstanceProvider sharedProvider] sharedMPAnalyticsTracker] trackImpressionForConfiguration:self.configuration]; +} + +- (void)trackClick +{ + [[[MPCoreInstanceProvider sharedProvider] sharedMPAnalyticsTracker] trackClickForConfiguration:self.configuration]; +} + +@end + diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Interstitials/MPInterstitialAdManager.h b/iphone/Maps/3party/MoPubSDK/Internal/Interstitials/MPInterstitialAdManager.h new file mode 100644 index 00000000000..1d044fad997 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Interstitials/MPInterstitialAdManager.h @@ -0,0 +1,28 @@ +// +// MPInterstitialAdManager.h +// MoPub +// +// Copyright (c) 2012 MoPub, Inc. All rights reserved. +// + +#import "MPAdServerCommunicator.h" +#import "MPBaseInterstitialAdapter.h" + +@class CLLocation; +@protocol MPInterstitialAdManagerDelegate; + +@interface MPInterstitialAdManager : NSObject + +@property (nonatomic, weak) id delegate; +@property (nonatomic, assign, readonly) BOOL ready; + +- (id)initWithDelegate:(id)delegate; + +- (void)loadInterstitialWithAdUnitID:(NSString *)ID + keywords:(NSString *)keywords + location:(CLLocation *)location + testing:(BOOL)testing; +- (void)presentInterstitialFromViewController:(UIViewController *)controller; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Interstitials/MPInterstitialAdManager.m b/iphone/Maps/3party/MoPubSDK/Internal/Interstitials/MPInterstitialAdManager.m new file mode 100644 index 00000000000..a3340b6daee --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Interstitials/MPInterstitialAdManager.m @@ -0,0 +1,227 @@ +// +// MPInterstitialAdManager.m +// MoPub +// +// Copyright (c) 2012 MoPub, Inc. All rights reserved. +// + +#import + +#import "MPInterstitialAdManager.h" + +#import "MPAdServerURLBuilder.h" +#import "MPInterstitialAdController.h" +#import "MPInterstitialCustomEventAdapter.h" +#import "MPInstanceProvider.h" +#import "MPCoreInstanceProvider.h" +#import "MPInterstitialAdManagerDelegate.h" +#import "MPLogging.h" +#import "MPError.h" + +@interface MPInterstitialAdManager () + +@property (nonatomic, assign) BOOL loading; +@property (nonatomic, assign, readwrite) BOOL ready; +@property (nonatomic, strong) MPBaseInterstitialAdapter *adapter; +@property (nonatomic, strong) MPAdServerCommunicator *communicator; +@property (nonatomic, strong) MPAdConfiguration *configuration; + +- (void)setUpAdapterWithConfiguration:(MPAdConfiguration *)configuration; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation MPInterstitialAdManager + +@synthesize loading = _loading; +@synthesize ready = _ready; +@synthesize delegate = _delegate; +@synthesize communicator = _communicator; +@synthesize adapter = _adapter; +@synthesize configuration = _configuration; + +- (id)initWithDelegate:(id)delegate +{ + self = [super init]; + if (self) { + self.communicator = [[MPCoreInstanceProvider sharedProvider] buildMPAdServerCommunicatorWithDelegate:self]; + self.delegate = delegate; + } + return self; +} + +- (void)dealloc +{ + [self.communicator cancel]; + [self.communicator setDelegate:nil]; + + self.adapter = nil; +} + +- (void)setAdapter:(MPBaseInterstitialAdapter *)adapter +{ + if (self.adapter != adapter) { + [self.adapter unregisterDelegate]; + _adapter = adapter; + } +} + +#pragma mark - Public + +- (void)loadAdWithURL:(NSURL *)URL +{ + if (self.loading) { + MPLogWarn(@"Interstitial controller is already loading an ad. " + @"Wait for previous load to finish."); + return; + } + + MPLogInfo(@"Interstitial controller is loading ad with MoPub server URL: %@", URL); + + self.loading = YES; + [self.communicator loadURL:URL]; +} + + +- (void)loadInterstitialWithAdUnitID:(NSString *)ID keywords:(NSString *)keywords location:(CLLocation *)location testing:(BOOL)testing +{ + if (self.ready) { + [self.delegate managerDidLoadInterstitial:self]; + } else { + [self loadAdWithURL:[MPAdServerURLBuilder URLWithAdUnitID:ID + keywords:keywords + location:location + testing:testing]]; + } +} + +- (void)presentInterstitialFromViewController:(UIViewController *)controller +{ + if (self.ready) { + [self.adapter showInterstitialFromViewController:controller]; + } +} + +- (CLLocation *)location +{ + return [self.delegate location]; +} + +- (MPInterstitialAdController *)interstitialAdController +{ + return [self.delegate interstitialAdController]; +} + +- (id)interstitialDelegate +{ + return [self.delegate interstitialDelegate]; +} + +#pragma mark - MPAdServerCommunicatorDelegate + +- (void)communicatorDidReceiveAdConfiguration:(MPAdConfiguration *)configuration +{ + self.configuration = configuration; + + MPLogInfo(@"Interstitial ad view is fetching ad network type: %@", self.configuration.networkType); + + if (self.configuration.adUnitWarmingUp) { + MPLogInfo(kMPWarmingUpErrorLogFormatWithAdUnitID, self.delegate.interstitialAdController.adUnitId); + self.loading = NO; + [self.delegate manager:self didFailToLoadInterstitialWithError:[MOPUBError errorWithCode:MOPUBErrorAdUnitWarmingUp]]; + return; + } + + if ([self.configuration.networkType isEqualToString:kAdTypeClear]) { + MPLogInfo(kMPClearErrorLogFormatWithAdUnitID, self.delegate.interstitialAdController.adUnitId); + self.loading = NO; + [self.delegate manager:self didFailToLoadInterstitialWithError:[MOPUBError errorWithCode:MOPUBErrorNoInventory]]; + return; + } + + if (self.configuration.adType != MPAdTypeInterstitial) { + MPLogWarn(@"Could not load ad: interstitial object received a non-interstitial ad unit ID."); + self.loading = NO; + [self.delegate manager:self didFailToLoadInterstitialWithError:[MOPUBError errorWithCode:MOPUBErrorAdapterInvalid]]; + return; + } + + [self setUpAdapterWithConfiguration:self.configuration]; +} + +- (void)communicatorDidFailWithError:(NSError *)error +{ + self.ready = NO; + self.loading = NO; + + [self.delegate manager:self didFailToLoadInterstitialWithError:error]; +} + +- (void)setUpAdapterWithConfiguration:(MPAdConfiguration *)configuration; +{ + MPBaseInterstitialAdapter *adapter = [[MPInstanceProvider sharedProvider] buildInterstitialAdapterForConfiguration:configuration + delegate:self]; + if (!adapter) { + [self adapter:nil didFailToLoadAdWithError:nil]; + return; + } + + self.adapter = adapter; + [self.adapter _getAdWithConfiguration:configuration]; +} + +#pragma mark - MPInterstitialAdapterDelegate + +- (void)adapterDidFinishLoadingAd:(MPBaseInterstitialAdapter *)adapter +{ + self.ready = YES; + self.loading = NO; + [self.delegate managerDidLoadInterstitial:self]; +} + +- (void)adapter:(MPBaseInterstitialAdapter *)adapter didFailToLoadAdWithError:(NSError *)error +{ + self.ready = NO; + self.loading = NO; + [self loadAdWithURL:self.configuration.failoverURL]; +} + +- (void)interstitialWillAppearForAdapter:(MPBaseInterstitialAdapter *)adapter +{ + [self.delegate managerWillPresentInterstitial:self]; +} + +- (void)interstitialDidAppearForAdapter:(MPBaseInterstitialAdapter *)adapter +{ + [self.delegate managerDidPresentInterstitial:self]; +} + +- (void)interstitialWillDisappearForAdapter:(MPBaseInterstitialAdapter *)adapter +{ + [self.delegate managerWillDismissInterstitial:self]; +} + +- (void)interstitialDidDisappearForAdapter:(MPBaseInterstitialAdapter *)adapter +{ + self.ready = NO; + [self.delegate managerDidDismissInterstitial:self]; +} + +- (void)interstitialDidExpireForAdapter:(MPBaseInterstitialAdapter *)adapter +{ + self.ready = NO; + [self.delegate managerDidExpireInterstitial:self]; +} + +- (void)interstitialDidReceiveTapEventForAdapter:(MPBaseInterstitialAdapter *)adapter +{ + [self.delegate managerDidReceiveTapEventFromInterstitial:self]; +} + +- (void)interstitialWillLeaveApplicationForAdapter:(MPBaseInterstitialAdapter *)adapter +{ + //noop +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Interstitials/MPInterstitialAdManagerDelegate.h b/iphone/Maps/3party/MoPubSDK/Internal/Interstitials/MPInterstitialAdManagerDelegate.h new file mode 100644 index 00000000000..b06fb1c1d4e --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Interstitials/MPInterstitialAdManagerDelegate.h @@ -0,0 +1,29 @@ +// +// MPInterstitialAdManagerDelegate.h +// MoPub +// +// Copyright (c) 2013 MoPub. All rights reserved. +// + +#import + +@class MPInterstitialAdManager; +@class MPInterstitialAdController; +@class CLLocation; + +@protocol MPInterstitialAdManagerDelegate + +- (MPInterstitialAdController *)interstitialAdController; +- (CLLocation *)location; +- (id)interstitialDelegate; +- (void)managerDidLoadInterstitial:(MPInterstitialAdManager *)manager; +- (void)manager:(MPInterstitialAdManager *)manager +didFailToLoadInterstitialWithError:(NSError *)error; +- (void)managerWillPresentInterstitial:(MPInterstitialAdManager *)manager; +- (void)managerDidPresentInterstitial:(MPInterstitialAdManager *)manager; +- (void)managerWillDismissInterstitial:(MPInterstitialAdManager *)manager; +- (void)managerDidDismissInterstitial:(MPInterstitialAdManager *)manager; +- (void)managerDidExpireInterstitial:(MPInterstitialAdManager *)manager; +- (void)managerDidReceiveTapEventFromInterstitial:(MPInterstitialAdManager *)manager; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Interstitials/MPInterstitialCustomEventAdapter.h b/iphone/Maps/3party/MoPubSDK/Internal/Interstitials/MPInterstitialCustomEventAdapter.h new file mode 100644 index 00000000000..71534c94ae3 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Interstitials/MPInterstitialCustomEventAdapter.h @@ -0,0 +1,14 @@ +// +// MPInterstitialCustomEventAdapter.h +// MoPub +// +// Copyright (c) 2012 MoPub, Inc. All rights reserved. +// + +#import "MPBaseInterstitialAdapter.h" + +#import "MPPrivateInterstitialCustomEventDelegate.h" + +@interface MPInterstitialCustomEventAdapter : MPBaseInterstitialAdapter + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Interstitials/MPInterstitialCustomEventAdapter.m b/iphone/Maps/3party/MoPubSDK/Internal/Interstitials/MPInterstitialCustomEventAdapter.m new file mode 100644 index 00000000000..293f1a27952 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Interstitials/MPInterstitialCustomEventAdapter.m @@ -0,0 +1,139 @@ +// +// MPInterstitialCustomEventAdapter.m +// MoPub +// +// Copyright (c) 2012 MoPub, Inc. All rights reserved. +// + +#import "MPInterstitialCustomEventAdapter.h" + +#import "MPAdConfiguration.h" +#import "MPLogging.h" +#import "MPInstanceProvider.h" +#import "MPInterstitialCustomEvent.h" +#import "MPInterstitialAdController.h" + +@interface MPInterstitialCustomEventAdapter () + +@property (nonatomic, strong) MPInterstitialCustomEvent *interstitialCustomEvent; +@property (nonatomic, strong) MPAdConfiguration *configuration; +@property (nonatomic, assign) BOOL hasTrackedImpression; +@property (nonatomic, assign) BOOL hasTrackedClick; + +@end + +@implementation MPInterstitialCustomEventAdapter +@synthesize hasTrackedImpression = _hasTrackedImpression; +@synthesize hasTrackedClick = _hasTrackedClick; + +@synthesize interstitialCustomEvent = _interstitialCustomEvent; + +- (void)dealloc +{ + if ([self.interstitialCustomEvent respondsToSelector:@selector(invalidate)]) { + // Secret API to allow us to detach the custom event from (shared instance) routers synchronously + // See the chartboost interstitial custom event for an example use case. + [self.interstitialCustomEvent performSelector:@selector(invalidate)]; + } + self.interstitialCustomEvent.delegate = nil; + + // make sure the custom event isn't released synchronously as objects owned by the custom event + // may do additional work after a callback that results in dealloc being called + [[MPCoreInstanceProvider sharedProvider] keepObjectAliveForCurrentRunLoopIteration:_interstitialCustomEvent]; +} + +- (void)getAdWithConfiguration:(MPAdConfiguration *)configuration +{ + MPLogInfo(@"Looking for custom event class named %@.", configuration.customEventClass); + self.configuration = configuration; + + self.interstitialCustomEvent = [[MPInstanceProvider sharedProvider] buildInterstitialCustomEventFromCustomClass:configuration.customEventClass delegate:self]; + + if (self.interstitialCustomEvent) { + [self.interstitialCustomEvent requestInterstitialWithCustomEventInfo:configuration.customEventClassData]; + } else { + [self.delegate adapter:self didFailToLoadAdWithError:nil]; + } +} + +- (void)showInterstitialFromViewController:(UIViewController *)controller +{ + [self.interstitialCustomEvent showInterstitialFromRootViewController:controller]; +} + +#pragma mark - MPInterstitialCustomEventDelegate + +- (NSString *)adUnitId +{ + return [self.delegate interstitialAdController].adUnitId; +} + +- (CLLocation *)location +{ + return [self.delegate location]; +} + +- (id)interstitialDelegate +{ + return [self.delegate interstitialDelegate]; +} + +- (void)interstitialCustomEvent:(MPInterstitialCustomEvent *)customEvent + didLoadAd:(id)ad +{ + [self didStopLoading]; + [self.delegate adapterDidFinishLoadingAd:self]; +} + +- (void)interstitialCustomEvent:(MPInterstitialCustomEvent *)customEvent + didFailToLoadAdWithError:(NSError *)error +{ + [self didStopLoading]; + [self.delegate adapter:self didFailToLoadAdWithError:error]; +} + +- (void)interstitialCustomEventWillAppear:(MPInterstitialCustomEvent *)customEvent +{ + [self.delegate interstitialWillAppearForAdapter:self]; +} + +- (void)interstitialCustomEventDidAppear:(MPInterstitialCustomEvent *)customEvent +{ + if ([self.interstitialCustomEvent enableAutomaticImpressionAndClickTracking] && !self.hasTrackedImpression) { + self.hasTrackedImpression = YES; + [self trackImpression]; + } + [self.delegate interstitialDidAppearForAdapter:self]; +} + +- (void)interstitialCustomEventWillDisappear:(MPInterstitialCustomEvent *)customEvent +{ + [self.delegate interstitialWillDisappearForAdapter:self]; +} + +- (void)interstitialCustomEventDidDisappear:(MPInterstitialCustomEvent *)customEvent +{ + [self.delegate interstitialDidDisappearForAdapter:self]; +} + +- (void)interstitialCustomEventDidExpire:(MPInterstitialCustomEvent *)customEvent +{ + [self.delegate interstitialDidExpireForAdapter:self]; +} + +- (void)interstitialCustomEventDidReceiveTapEvent:(MPInterstitialCustomEvent *)customEvent +{ + if ([self.interstitialCustomEvent enableAutomaticImpressionAndClickTracking] && !self.hasTrackedClick) { + self.hasTrackedClick = YES; + [self trackClick]; + } + + [self.delegate interstitialDidReceiveTapEventForAdapter:self]; +} + +- (void)interstitialCustomEventWillLeaveApplication:(MPInterstitialCustomEvent *)customEvent +{ + [self.delegate interstitialWillLeaveApplicationForAdapter:self]; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Interstitials/MPInterstitialViewController.h b/iphone/Maps/3party/MoPubSDK/Internal/Interstitials/MPInterstitialViewController.h new file mode 100644 index 00000000000..c91e3b1ef24 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Interstitials/MPInterstitialViewController.h @@ -0,0 +1,53 @@ +// +// MPInterstitialViewController.h +// MoPub +// +// Copyright (c) 2012 MoPub, Inc. All rights reserved. +// + +#import +#import "MPGlobal.h" + +@class CLLocation; + +@protocol MPInterstitialViewControllerDelegate; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface MPInterstitialViewController : UIViewController + +@property (nonatomic, assign) MPInterstitialCloseButtonStyle closeButtonStyle; +@property (nonatomic, assign) MPInterstitialOrientationType orientationType; +@property (nonatomic, strong) UIButton *closeButton; +@property (nonatomic, weak) id delegate; + +- (void)presentInterstitialFromViewController:(UIViewController *)controller; +- (void)dismissInterstitialAnimated:(BOOL)animated; +- (BOOL)shouldDisplayCloseButton; +- (void)willPresentInterstitial; +- (void)didPresentInterstitial; +- (void)willDismissInterstitial; +- (void)didDismissInterstitial; +- (void)layoutCloseButton; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@protocol MPInterstitialViewControllerDelegate + +- (NSString *)adUnitId; +- (void)interstitialDidLoadAd:(MPInterstitialViewController *)interstitial; +- (void)interstitialDidFailToLoadAd:(MPInterstitialViewController *)interstitial; +- (void)interstitialWillAppear:(MPInterstitialViewController *)interstitial; +- (void)interstitialDidAppear:(MPInterstitialViewController *)interstitial; +- (void)interstitialWillDisappear:(MPInterstitialViewController *)interstitial; +- (void)interstitialDidDisappear:(MPInterstitialViewController *)interstitial; +- (void)interstitialDidReceiveTapEvent:(MPInterstitialViewController *)interstitial; +- (void)interstitialWillLeaveApplication:(MPInterstitialViewController *)interstitial; + +@optional +- (CLLocation *)location; +- (void)interstitialRewardedVideoEnded; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Interstitials/MPInterstitialViewController.m b/iphone/Maps/3party/MoPubSDK/Internal/Interstitials/MPInterstitialViewController.m new file mode 100644 index 00000000000..d3f8dcbd290 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Interstitials/MPInterstitialViewController.m @@ -0,0 +1,272 @@ +// +// MPInterstitialViewController.m +// MoPub +// +// Copyright (c) 2012 MoPub, Inc. All rights reserved. +// + +#import "MPInterstitialViewController.h" + +#import "MPGlobal.h" +#import "MPLogging.h" +#import "UIButton+MPAdditions.h" + +static const CGFloat kCloseButtonPadding = 5.0; +static const CGFloat kCloseButtonEdgeInset = 5.0; +static NSString * const kCloseButtonXImageName = @"MPCloseButtonX.png"; + +@interface MPInterstitialViewController () + +@property (nonatomic, assign) BOOL applicationHasStatusBar; + +- (void)setCloseButtonImageWithImageNamed:(NSString *)imageName; +- (void)setCloseButtonStyle:(MPInterstitialCloseButtonStyle)style; +- (void)closeButtonPressed; +- (void)dismissInterstitialAnimated:(BOOL)animated; +- (void)setApplicationStatusBarHidden:(BOOL)hidden; + +@end + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation MPInterstitialViewController + +@synthesize closeButton = _closeButton; +@synthesize closeButtonStyle = _closeButtonStyle; +@synthesize orientationType = _orientationType; +@synthesize applicationHasStatusBar = _applicationHasStatusBar; +@synthesize delegate = _delegate; + + +- (void)viewDidLoad +{ + [super viewDidLoad]; + + self.view.backgroundColor = [UIColor blackColor]; +} + +#pragma mark - Public + +- (void)presentInterstitialFromViewController:(UIViewController *)controller +{ + if (self.presentingViewController) { + MPLogWarn(@"Cannot present an interstitial that is already on-screen."); + return; + } + + [self willPresentInterstitial]; + + self.applicationHasStatusBar = !([UIApplication sharedApplication].isStatusBarHidden); + [self setApplicationStatusBarHidden:YES]; + + [self layoutCloseButton]; + + [controller presentViewController:self animated:MP_ANIMATED completion:^{ + [self didPresentInterstitial]; + }]; +} + +- (void)willPresentInterstitial +{ + +} + +- (void)didPresentInterstitial +{ + +} + +- (void)willDismissInterstitial +{ + +} + +- (void)didDismissInterstitial +{ + +} + +- (BOOL)shouldDisplayCloseButton +{ + return YES; +} + +#pragma mark - Close Button + +- (UIButton *)closeButton +{ + if (!_closeButton) { + _closeButton = [UIButton buttonWithType:UIButtonTypeCustom]; + _closeButton.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | + UIViewAutoresizingFlexibleBottomMargin; + + UIImage *closeButtonImage = [UIImage imageNamed:MPResourcePathForResource(kCloseButtonXImageName)]; + [_closeButton setImage:closeButtonImage forState:UIControlStateNormal]; + [_closeButton sizeToFit]; + + [_closeButton addTarget:self + action:@selector(closeButtonPressed) + forControlEvents:UIControlEventTouchUpInside]; + _closeButton.accessibilityLabel = @"Close Interstitial Ad"; + } + + return _closeButton; +} + +- (void)layoutCloseButton +{ + CGFloat originX = self.view.bounds.size.width - kCloseButtonPadding - + self.closeButton.bounds.size.width; + self.closeButton.frame = CGRectMake(originX, + kCloseButtonPadding, + self.closeButton.bounds.size.width, + self.closeButton.bounds.size.height); + self.closeButton.mp_TouchAreaInsets = UIEdgeInsetsMake(kCloseButtonEdgeInset, kCloseButtonEdgeInset, kCloseButtonEdgeInset, kCloseButtonEdgeInset); + [self setCloseButtonStyle:self.closeButtonStyle]; + [self.view addSubview:self.closeButton]; + [self.view bringSubviewToFront:self.closeButton]; +} + +- (void)setCloseButtonImageWithImageNamed:(NSString *)imageName +{ + UIImage *image = [UIImage imageNamed:imageName]; + [self.closeButton setImage:image forState:UIControlStateNormal]; + [self.closeButton sizeToFit]; +} + +- (void)setCloseButtonStyle:(MPInterstitialCloseButtonStyle)style +{ + _closeButtonStyle = style; + switch (style) { + case MPInterstitialCloseButtonStyleAlwaysVisible: + self.closeButton.hidden = NO; + break; + case MPInterstitialCloseButtonStyleAlwaysHidden: + self.closeButton.hidden = YES; + break; + case MPInterstitialCloseButtonStyleAdControlled: + self.closeButton.hidden = ![self shouldDisplayCloseButton]; + break; + default: + self.closeButton.hidden = NO; + break; + } +} + +- (void)closeButtonPressed +{ + [self dismissInterstitialAnimated:YES]; +} + +- (void)dismissInterstitialAnimated:(BOOL)animated +{ + [self setApplicationStatusBarHidden:!self.applicationHasStatusBar]; + + [self willDismissInterstitial]; + + UIViewController *presentingViewController = self.presentingViewController; + // TODO: Is this check necessary? + if (presentingViewController.presentedViewController == self) { + [presentingViewController dismissViewControllerAnimated:MP_ANIMATED completion:^{ + [self didDismissInterstitial]; + }]; + } else { + [self didDismissInterstitial]; + } +} + +#pragma mark - Hidding status bar (pre-iOS 7) + +- (void)setApplicationStatusBarHidden:(BOOL)hidden +{ + [[UIApplication sharedApplication] mp_preIOS7setApplicationStatusBarHidden:hidden]; +} + +#pragma mark - Hidding status bar (iOS 7 and above) + +- (BOOL)prefersStatusBarHidden +{ + return YES; +} + +#pragma mark - Autorotation (iOS 6.0 and above) + +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= MP_IOS_6_0 +- (BOOL)shouldAutorotate +{ + return YES; +} + +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= MP_IOS_9_0 +- (UIInterfaceOrientationMask)supportedInterfaceOrientations +#else +- (NSUInteger)supportedInterfaceOrientations +#endif +{ + NSUInteger applicationSupportedOrientations = + [[UIApplication sharedApplication] supportedInterfaceOrientationsForWindow:MPKeyWindow()]; + NSUInteger interstitialSupportedOrientations = applicationSupportedOrientations; + NSString *orientationDescription = @"any"; + + // Using the _orientationType, narrow down the supported interface orientations. + + if (_orientationType == MPInterstitialOrientationTypePortrait) { + interstitialSupportedOrientations &= + (UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskPortraitUpsideDown); + orientationDescription = @"portrait"; + } else if (_orientationType == MPInterstitialOrientationTypeLandscape) { + interstitialSupportedOrientations &= UIInterfaceOrientationMaskLandscape; + orientationDescription = @"landscape"; + } + + // If the application does not support any of the orientations given by _orientationType, + // just return the application's supported orientations. + + if (!interstitialSupportedOrientations) { + MPLogError(@"Your application does not support this interstitial's desired orientation " + @"(%@).", orientationDescription); + return applicationSupportedOrientations; + } else { + return interstitialSupportedOrientations; + } +} + +- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation +{ + NSUInteger supportedInterfaceOrientations = [self supportedInterfaceOrientations]; + UIInterfaceOrientation currentInterfaceOrientation = MPInterfaceOrientation(); + NSUInteger currentInterfaceOrientationMask = (1 << currentInterfaceOrientation); + + // First, try to display the interstitial using the current interface orientation. If the + // current interface orientation is unsupported, just use any of the supported orientations. + + if (supportedInterfaceOrientations & currentInterfaceOrientationMask) { + return currentInterfaceOrientation; + } else if (supportedInterfaceOrientations & UIInterfaceOrientationMaskPortrait) { + return UIInterfaceOrientationPortrait; + } else if (supportedInterfaceOrientations & UIInterfaceOrientationMaskPortraitUpsideDown) { + return UIInterfaceOrientationPortraitUpsideDown; + } else if (supportedInterfaceOrientations & UIInterfaceOrientationMaskLandscapeLeft) { + return UIInterfaceOrientationLandscapeLeft; + } else { + return UIInterfaceOrientationLandscapeRight; + } +} +#endif + +#pragma mark - Autorotation (before iOS 6.0) + +- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation +{ + if (_orientationType == MPInterstitialOrientationTypePortrait) { + return (interfaceOrientation == UIInterfaceOrientationPortrait || + interfaceOrientation == UIInterfaceOrientationPortraitUpsideDown); + } else if (_orientationType == MPInterstitialOrientationTypeLandscape) { + return (interfaceOrientation == UIInterfaceOrientationLandscapeLeft || + interfaceOrientation == UIInterfaceOrientationLandscapeRight); + } else { + return YES; + } +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Interstitials/MPPrivateInterstitialCustomEventDelegate.h b/iphone/Maps/3party/MoPubSDK/Internal/Interstitials/MPPrivateInterstitialCustomEventDelegate.h new file mode 100644 index 00000000000..cb12c1d2704 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Interstitials/MPPrivateInterstitialCustomEventDelegate.h @@ -0,0 +1,20 @@ +// +// MPPrivateInterstitialcustomEventDelegate.h +// MoPub +// +// Copyright (c) 2013 MoPub. All rights reserved. +// + +#import +#import "MPInterstitialCustomEventDelegate.h" + +@class MPAdConfiguration; +@class CLLocation; + +@protocol MPPrivateInterstitialCustomEventDelegate + +- (NSString *)adUnitId; +- (MPAdConfiguration *)configuration; +- (id)interstitialDelegate; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/MPCoreInstanceProvider.h b/iphone/Maps/3party/MoPubSDK/Internal/MPCoreInstanceProvider.h new file mode 100644 index 00000000000..3d6af00e9d7 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/MPCoreInstanceProvider.h @@ -0,0 +1,78 @@ +// +// MPCoreInstanceProvider.h +// MoPub +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import +#import +#import "MPGlobal.h" +#import "MPURLResolver.h" + +@class MPAdConfiguration; + +// Fetching Ads +@class MPAdServerCommunicator; +@protocol MPAdServerCommunicatorDelegate; + +// URL Handling +@class MPAdDestinationDisplayAgent; +@protocol MPAdDestinationDisplayAgentDelegate; + +// Utilities +@class MPAdAlertManager, MPAdAlertGestureRecognizer; +@class MPAnalyticsTracker; +@class MPReachability; +@class MPTimer; +@class MPGeolocationProvider; +@class CLLocationManager; +@class MPLogEventRecorder; +@class MPNetworkManager; + +typedef id(^MPSingletonProviderBlock)(); + + +typedef NS_OPTIONS(NSUInteger, MPATSSetting) { + MPATSSettingEnabled = 0, + MPATSSettingAllowsArbitraryLoads = (1 << 0), + MPATSSettingAllowsArbitraryLoadsForMedia = (1 << 1), + MPATSSettingAllowsArbitraryLoadsInWebContent = (1 << 2), + MPATSSettingRequiresCertificateTransparency = (1 << 3), + MPATSSettingAllowsLocalNetworking = (1 << 4), +}; + +@interface MPCoreInstanceProvider : NSObject + ++ (instancetype)sharedProvider; +- (id)singletonForClass:(Class)klass provider:(MPSingletonProviderBlock)provider; + +- (void)keepObjectAliveForCurrentRunLoopIteration:(id)anObject; + +#pragma mark - Fetching Ads +- (NSMutableURLRequest *)buildConfiguredURLRequestWithURL:(NSURL *)URL; +- (MPAdServerCommunicator *)buildMPAdServerCommunicatorWithDelegate:(id)delegate; + +#pragma mark - URL Handling +- (MPURLResolver *)buildMPURLResolverWithURL:(NSURL *)URL completion:(MPURLResolverCompletionBlock)completion; +- (MPAdDestinationDisplayAgent *)buildMPAdDestinationDisplayAgentWithDelegate:(id)delegate; + +#pragma mark - Utilities +- (UIDevice *)sharedCurrentDevice; +- (MPGeolocationProvider *)sharedMPGeolocationProvider; +- (CLLocationManager *)buildCLLocationManager; +- (id)buildMPAdAlertManagerWithDelegate:(id)delegate; +- (MPAdAlertGestureRecognizer *)buildMPAdAlertGestureRecognizerWithTarget:(id)target action:(SEL)action; +- (NSOperationQueue *)sharedOperationQueue; +- (MPAnalyticsTracker *)sharedMPAnalyticsTracker; +- (MPReachability *)sharedMPReachability; +- (MPLogEventRecorder *)sharedLogEventRecorder; +- (MPNetworkManager *)sharedNetworkManager; +- (MPATSSetting)appTransportSecuritySettings; + +// This call may return nil and may not update if the user hot-swaps the device's sim card. +- (NSDictionary *)sharedCarrierInfo; + +- (MPTimer *)buildMPTimerWithTimeInterval:(NSTimeInterval)seconds target:(id)target selector:(SEL)selector repeats:(BOOL)repeats; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/MPCoreInstanceProvider.m b/iphone/Maps/3party/MoPubSDK/Internal/MPCoreInstanceProvider.m new file mode 100644 index 00000000000..3db8b09e4dc --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/MPCoreInstanceProvider.m @@ -0,0 +1,333 @@ +// +// MPCoreInstanceProvider.m +// MoPub +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import "MPCoreInstanceProvider.h" + +#import +#import + +#import "MPAdServerCommunicator.h" +#import "MPURLResolver.h" +#import "MPAdDestinationDisplayAgent.h" +#import "MPReachability.h" +#import "MPTimer.h" +#import "MPAnalyticsTracker.h" +#import "MPGeolocationProvider.h" +#import "MPLogEventRecorder.h" +#import "MPNetworkManager.h" + +#define MOPUB_CARRIER_INFO_DEFAULTS_KEY @"com.mopub.carrierinfo" + +#define SYSTEM_VERSION_LESS_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedAscending) +#define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending) + +static NSString *const kMoPubAppTransportSecurityDictionaryKey = @"NSAppTransportSecurity"; +static NSString *const kMoPubAppTransportSecurityAllowsArbitraryLoadsKey = @"NSAllowsArbitraryLoads"; +static NSString *const kMoPubAppTransportSecurityAllowsArbitraryLoadsForMediaKey = @"NSAllowsArbitraryLoadsForMedia"; +static NSString *const kMoPubAppTransportSecurityAllowsArbitraryLoadsInWebContentKey = @"NSAllowsArbitraryLoadsInWebContent"; +static NSString *const kMoPubAppTransportSecurityRequiresCertificateTransparencyKey = @"NSRequiresCertificateTransparency"; +static NSString *const kMoPubAppTransportSecurityAllowsLocalNetworkingKey = @"NSAllowsLocalNetworking"; + +typedef enum +{ + MPTwitterDeepLinkNotChecked, + MPTwitterDeepLinkEnabled, + MPTwitterDeepLinkDisabled +} MPTwitterDeepLink; + +@interface MPCoreInstanceProvider () + +@property (nonatomic, copy) NSString *userAgent; +@property (nonatomic, strong) NSMutableDictionary *singletons; +@property (nonatomic, strong) NSMutableDictionary *carrierInfo; +@property (nonatomic, assign) MPTwitterDeepLink twitterDeepLinkStatus; + +@end + +@implementation MPCoreInstanceProvider + +@synthesize userAgent = _userAgent; +@synthesize singletons = _singletons; +@synthesize carrierInfo = _carrierInfo; +@synthesize twitterDeepLinkStatus = _twitterDeepLinkStatus; + +static MPCoreInstanceProvider *sharedProvider = nil; + ++ (instancetype)sharedProvider +{ + static dispatch_once_t once; + dispatch_once(&once, ^{ + sharedProvider = [[self alloc] init]; + }); + + return sharedProvider; +} + +- (id)init +{ + self = [super init]; + if (self) { + self.singletons = [NSMutableDictionary dictionary]; + + [self initializeCarrierInfo]; + } + return self; +} + + +- (id)singletonForClass:(Class)klass provider:(MPSingletonProviderBlock)provider +{ + id singleton = [self.singletons objectForKey:klass]; + if (!singleton) { + singleton = provider(); + [self.singletons setObject:singleton forKey:(id)klass]; + } + return singleton; +} + +// This method ensures that "anObject" is retained until the next runloop iteration when +// performNoOp: is executed. +// +// This is useful in situations where, potentially due to a callback chain reaction, an object +// is synchronously deallocated as it's trying to do more work, especially invoking self, after +// the callback. +- (void)keepObjectAliveForCurrentRunLoopIteration:(id)anObject +{ + [self performSelector:@selector(performNoOp:) withObject:anObject afterDelay:0]; +} + +- (void)performNoOp:(id)anObject +{ + ; // noop +} + +#pragma mark - Initializing Carrier Info + +- (void)initializeCarrierInfo +{ + self.carrierInfo = [NSMutableDictionary dictionary]; + + // check if we have a saved copy + NSDictionary *saved = [[NSUserDefaults standardUserDefaults] dictionaryForKey:MOPUB_CARRIER_INFO_DEFAULTS_KEY]; + if (saved != nil) { + [self.carrierInfo addEntriesFromDictionary:saved]; + } + + // now asynchronously load a fresh copy + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + CTTelephonyNetworkInfo *networkInfo = [[CTTelephonyNetworkInfo alloc] init]; + [self performSelectorOnMainThread:@selector(updateCarrierInfoForCTCarrier:) withObject:networkInfo.subscriberCellularProvider waitUntilDone:NO]; + }); +} + +- (void)updateCarrierInfoForCTCarrier:(CTCarrier *)ctCarrier +{ + // use setValue instead of setObject here because ctCarrier could be nil, and any of its properties could be nil + [self.carrierInfo setValue:ctCarrier.carrierName forKey:@"carrierName"]; + [self.carrierInfo setValue:ctCarrier.isoCountryCode forKey:@"isoCountryCode"]; + [self.carrierInfo setValue:ctCarrier.mobileCountryCode forKey:@"mobileCountryCode"]; + [self.carrierInfo setValue:ctCarrier.mobileNetworkCode forKey:@"mobileNetworkCode"]; + + [[NSUserDefaults standardUserDefaults] setObject:self.carrierInfo forKey:MOPUB_CARRIER_INFO_DEFAULTS_KEY]; + [[NSUserDefaults standardUserDefaults] synchronize]; +} + +#pragma mark - Fetching Ads +- (NSMutableURLRequest *)buildConfiguredURLRequestWithURL:(NSURL *)URL +{ + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL]; + [request setHTTPShouldHandleCookies:YES]; + [request setValue:self.userAgent forHTTPHeaderField:@"User-Agent"]; + return request; +} + +- (NSString *)userAgent +{ + if (!_userAgent) { + self.userAgent = [[[UIWebView alloc] init] stringByEvaluatingJavaScriptFromString:@"navigator.userAgent"]; + } + + return _userAgent; +} + +- (MPAdServerCommunicator *)buildMPAdServerCommunicatorWithDelegate:(id)delegate +{ + return [(MPAdServerCommunicator *)[MPAdServerCommunicator alloc] initWithDelegate:delegate]; +} + + +#pragma mark - URL Handling + +- (MPURLResolver *)buildMPURLResolverWithURL:(NSURL *)URL completion:(MPURLResolverCompletionBlock)completion; +{ + return [MPURLResolver resolverWithURL:URL completion:completion]; +} + +- (MPAdDestinationDisplayAgent *)buildMPAdDestinationDisplayAgentWithDelegate:(id)delegate +{ + return [MPAdDestinationDisplayAgent agentWithDelegate:delegate]; +} + +#pragma mark - Utilities + +- (UIDevice *)sharedCurrentDevice +{ + return [UIDevice currentDevice]; +} + +- (MPGeolocationProvider *)sharedMPGeolocationProvider +{ + return [self singletonForClass:[MPGeolocationProvider class] provider:^id{ + return [MPGeolocationProvider sharedProvider]; + }]; +} + +- (CLLocationManager *)buildCLLocationManager +{ + return [[CLLocationManager alloc] init]; +} + +- (id)buildMPAdAlertManagerWithDelegate:(id)delegate +{ + id adAlertManager = nil; + + Class adAlertManagerClass = NSClassFromString(@"MPAdAlertManager"); + if (adAlertManagerClass != nil) { + adAlertManager = [[adAlertManagerClass alloc] init]; + [adAlertManager performSelector:@selector(setDelegate:) withObject:delegate]; + } + + return adAlertManager; +} + +- (MPAdAlertGestureRecognizer *)buildMPAdAlertGestureRecognizerWithTarget:(id)target action:(SEL)action +{ + MPAdAlertGestureRecognizer *gestureRecognizer = nil; + + Class gestureRecognizerClass = NSClassFromString(@"MPAdAlertGestureRecognizer"); + if (gestureRecognizerClass != nil) { + gestureRecognizer = [[gestureRecognizerClass alloc] initWithTarget:target action:action]; + } + + return gestureRecognizer; +} + +- (NSOperationQueue *)sharedOperationQueue +{ + static NSOperationQueue *sharedOperationQueue = nil; + static dispatch_once_t pred; + + dispatch_once(&pred, ^{ + sharedOperationQueue = [[NSOperationQueue alloc] init]; + }); + + return sharedOperationQueue; +} + +- (MPAnalyticsTracker *)sharedMPAnalyticsTracker +{ + return [self singletonForClass:[MPAnalyticsTracker class] provider:^id{ + return [MPAnalyticsTracker tracker]; + }]; +} + +- (MPReachability *)sharedMPReachability +{ + return [self singletonForClass:[MPReachability class] provider:^id{ + return [MPReachability reachabilityForLocalWiFi]; + }]; +} + +- (MPLogEventRecorder *)sharedLogEventRecorder +{ + return [self singletonForClass:[MPLogEventRecorder class] provider:^id{ + MPLogEventRecorder *recorder = [[MPLogEventRecorder alloc] init]; + return recorder; + }]; +} + +- (MPNetworkManager *)sharedNetworkManager +{ + return [self singletonForClass:[MPNetworkManager class] provider:^id{ + return [MPNetworkManager sharedNetworkManager]; + }]; +} + +- (MPATSSetting)appTransportSecuritySettings +{ + // Keep track of ATS settings statically, as they'll never change in the lifecycle of the application. + // This way, the setting value only gets assembled once. + static BOOL gCheckedAppTransportSettings = NO; + static MPATSSetting gSetting = MPATSSettingEnabled; + + // If we've already checked ATS settings, just use what we have + if (gCheckedAppTransportSettings) { + return gSetting; + } + + // Otherwise, figure out ATS settings + + // App Transport Security was introduced in iOS 9; if the system version is less than 9, then arbirtrary loads are fine. + if (SYSTEM_VERSION_LESS_THAN(@"9.0")) { + gSetting = MPATSSettingAllowsArbitraryLoads; + gCheckedAppTransportSettings = YES; + return gSetting; + } + + // Start with the assumption that ATS is enabled + gSetting = MPATSSettingEnabled; + + // Grab the ATS dictionary from the Info.plist + NSDictionary *atsSettingsDictionary = [NSBundle mainBundle].infoDictionary[kMoPubAppTransportSecurityDictionaryKey]; + + // Check if ATS is entirely disabled, and if so, add that to the setting value + if ([atsSettingsDictionary[kMoPubAppTransportSecurityAllowsArbitraryLoadsKey] boolValue]) { + gSetting |= MPATSSettingAllowsArbitraryLoads; + } + + // New App Transport Security keys were introduced in iOS 10. Only send settings for these keys if we're running iOS 10 or greater. + // They may exist in the dictionary if we're running iOS 9, but they won't do anything, so the server shouldn't know about them. + if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"10.0")) { + // In iOS 10, NSAllowsArbitraryLoads gets ignored if ANY keys of NSAllowsArbitraryLoadsForMedia, + // NSAllowsArbitraryLoadsInWebContent, or NSAllowsLocalNetworking are PRESENT (i.e., they can be set to `false`) + // See: https://developer.apple.com/library/content/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html#//apple_ref/doc/uid/TP40009251-SW34 + // If needed, flip NSAllowsArbitraryLoads back to 0 if any of these keys are present. + if (atsSettingsDictionary[kMoPubAppTransportSecurityAllowsArbitraryLoadsForMediaKey] != nil + || atsSettingsDictionary[kMoPubAppTransportSecurityAllowsArbitraryLoadsInWebContentKey] != nil + || atsSettingsDictionary[kMoPubAppTransportSecurityAllowsLocalNetworkingKey] != nil) { + gSetting &= (~MPATSSettingAllowsArbitraryLoads); + } + + if ([atsSettingsDictionary[kMoPubAppTransportSecurityAllowsArbitraryLoadsForMediaKey] boolValue]) { + gSetting |= MPATSSettingAllowsArbitraryLoadsForMedia; + } + if ([atsSettingsDictionary[kMoPubAppTransportSecurityAllowsArbitraryLoadsInWebContentKey] boolValue]) { + gSetting |= MPATSSettingAllowsArbitraryLoadsInWebContent; + } + if ([atsSettingsDictionary[kMoPubAppTransportSecurityRequiresCertificateTransparencyKey] boolValue]) { + gSetting |= MPATSSettingRequiresCertificateTransparency; + } + if ([atsSettingsDictionary[kMoPubAppTransportSecurityAllowsLocalNetworkingKey] boolValue]) { + gSetting |= MPATSSettingAllowsLocalNetworking; + } + } + + gCheckedAppTransportSettings = YES; + return gSetting; +} + +- (NSDictionary *)sharedCarrierInfo +{ + return self.carrierInfo; +} + +- (MPTimer *)buildMPTimerWithTimeInterval:(NSTimeInterval)seconds target:(id)target selector:(SEL)selector repeats:(BOOL)repeats +{ + return [MPTimer timerWithTimeInterval:seconds target:target selector:selector repeats:repeats]; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/MPInstanceProvider.h b/iphone/Maps/3party/MoPubSDK/Internal/MPInstanceProvider.h new file mode 100644 index 00000000000..3b0b7c4d9a4 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/MPInstanceProvider.h @@ -0,0 +1,124 @@ +// +// MPInstanceProvider.h +// MoPub +// +// Copyright (c) 2013 MoPub. All rights reserved. +// + +#import "MPGlobal.h" +#import "MPCoreInstanceProvider.h" + +// MPWebView +@class MPWebView; +@protocol MPWebViewDelegate; + +// Banners +@class MPBannerAdManager; +@protocol MPBannerAdManagerDelegate; +@class MPBaseBannerAdapter; +@protocol MPBannerAdapterDelegate; +@class MPBannerCustomEvent; +@protocol MPBannerCustomEventDelegate; + +// Interstitials +@class MPInterstitialAdManager; +@protocol MPInterstitialAdManagerDelegate; +@class MPBaseInterstitialAdapter; +@protocol MPInterstitialAdapterDelegate; +@class MPInterstitialCustomEvent; +@protocol MPInterstitialCustomEventDelegate; +@class MPHTMLInterstitialViewController; +@class MPMRAIDInterstitialViewController; +@protocol MPInterstitialViewControllerDelegate; + +// Rewarded Video +@class MPRewardedVideoAdManager; +@class MPRewardedVideoAdapter; +@class MPRewardedVideoCustomEvent; +@protocol MPRewardedVideoAdapterDelegate; +@protocol MPRewardedVideoCustomEventDelegate; +@protocol MPRewardedVideoAdManagerDelegate; + +// HTML Ads +@class MPAdWebViewAgent; +@protocol MPAdWebViewAgentDelegate; + +// MRAID +@class MRController; +@protocol MRControllerDelegate; +@class MPClosableView; +@class MRBridge; +@protocol MRBridgeDelegate; +@protocol MPClosableViewDelegate; +@class MRBundleManager; +@class MRVideoPlayerManager; +@protocol MRVideoPlayerManagerDelegate; +@class MPMoviePlayerViewController; +@class MRNativeCommandHandler; +@protocol MRNativeCommandHandlerDelegate; + +//Native +@protocol MPNativeCustomEventDelegate; +@class MPNativeCustomEvent; +@class MPNativeAdSource; +@protocol MPNativeAdSourceDelegate; +@class MPNativePositionSource; +@class MPStreamAdPlacementData; +@class MPStreamAdPlacer; +@class MPAdPositioning; +@protocol MPNativeAdRenderer; + +@interface MPInstanceProvider : NSObject + ++(instancetype)sharedProvider; +- (id)singletonForClass:(Class)klass provider:(MPSingletonProviderBlock)provider; +- (id)singletonForClass:(Class)klass provider:(MPSingletonProviderBlock)provider context:(id)context; + +#pragma mark - Banners +- (MPBannerAdManager *)buildMPBannerAdManagerWithDelegate:(id)delegate; +- (MPBaseBannerAdapter *)buildBannerAdapterForConfiguration:(MPAdConfiguration *)configuration + delegate:(id)delegate; +- (MPBannerCustomEvent *)buildBannerCustomEventFromCustomClass:(Class)customClass + delegate:(id)delegate; + +#pragma mark - Interstitials +- (MPInterstitialAdManager *)buildMPInterstitialAdManagerWithDelegate:(id)delegate; +- (MPBaseInterstitialAdapter *)buildInterstitialAdapterForConfiguration:(MPAdConfiguration *)configuration + delegate:(id)delegate; +- (MPInterstitialCustomEvent *)buildInterstitialCustomEventFromCustomClass:(Class)customClass + delegate:(id)delegate; +- (MPHTMLInterstitialViewController *)buildMPHTMLInterstitialViewControllerWithDelegate:(id)delegate + orientationType:(MPInterstitialOrientationType)type; +- (MPMRAIDInterstitialViewController *)buildMPMRAIDInterstitialViewControllerWithDelegate:(id)delegate + configuration:(MPAdConfiguration *)configuration; + +#pragma mark - Rewarded Video +- (MPRewardedVideoAdManager *)buildRewardedVideoAdManagerWithAdUnitID:(NSString *)adUnitID delegate:(id)delegate; +- (MPRewardedVideoAdapter *)buildRewardedVideoAdapterWithDelegate:(id)delegate; +- (MPRewardedVideoCustomEvent *)buildRewardedVideoCustomEventFromCustomClass:(Class)customClass delegate:(id)delegate; + + +#pragma mark - HTML Ads +- (MPAdWebViewAgent *)buildMPAdWebViewAgentWithAdWebViewFrame:(CGRect)frame + delegate:(id)delegate; + +#pragma mark - MRAID +- (MPClosableView *)buildMRAIDMPClosableViewWithFrame:(CGRect)frame webView:(MPWebView *)webView delegate:(id)delegate; +- (MRBundleManager *)buildMRBundleManager; +- (MRController *)buildBannerMRControllerWithFrame:(CGRect)frame delegate:(id)delegate; +- (MRController *)buildInterstitialMRControllerWithFrame:(CGRect)frame delegate:(id)delegate; +- (MRBridge *)buildMRBridgeWithWebView:(MPWebView *)webView delegate:(id)delegate; +- (MRVideoPlayerManager *)buildMRVideoPlayerManagerWithDelegate:(id)delegate; +- (MPMoviePlayerViewController *)buildMPMoviePlayerViewControllerWithURL:(NSURL *)URL; +- (MRNativeCommandHandler *)buildMRNativeCommandHandlerWithDelegate:(id)delegate; + +#pragma mark - Native + +- (MPNativeCustomEvent *)buildNativeCustomEventFromCustomClass:(Class)customClass + delegate:(id)delegate; +- (MPNativeAdSource *)buildNativeAdSourceWithDelegate:(id)delegate; +- (MPNativePositionSource *)buildNativePositioningSource; +- (MPStreamAdPlacementData *)buildStreamAdPlacementDataWithPositioning:(MPAdPositioning *)positioning; +- (MPStreamAdPlacer *)buildStreamAdPlacerWithViewController:(UIViewController *)controller adPositioning:(MPAdPositioning *)positioning rendererConfigurations:(NSArray *)rendererConfigurations; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/MPInstanceProvider.m b/iphone/Maps/3party/MoPubSDK/Internal/MPInstanceProvider.m new file mode 100644 index 00000000000..763e25f0d55 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/MPInstanceProvider.m @@ -0,0 +1,319 @@ +// +// MPInstanceProvider.m +// MoPub +// +// Copyright (c) 2013 MoPub. All rights reserved. +// + +#import "MPInstanceProvider.h" +#import "MPWebView.h" +#import "MPAdWebViewAgent.h" +#import "MPInterstitialAdManager.h" +#import "MPInterstitialCustomEventAdapter.h" +#import "MPHTMLInterstitialViewController.h" +#import "MPMRAIDInterstitialViewController.h" +#import "MPInterstitialCustomEvent.h" +#import "MPBaseBannerAdapter.h" +#import "MPBannerCustomEventAdapter.h" +#import "MPBannerCustomEvent.h" +#import "MPBannerAdManager.h" +#import "MPLogging.h" +#import "MRBundleManager.h" +#import "MRVideoPlayerManager.h" +#import +#import "MRNativeCommandHandler.h" +#import "MRBridge.h" +#import "MRController.h" +#import "MPClosableView.h" +#import "MPRewardedVideoAdManager.h" +#import "MPRewardedVideoAdapter.h" +#import "MPRewardedVideoCustomEvent.h" + +#if MP_HAS_NATIVE_PACKAGE +#import "MPNativeCustomEvent.h" +#import "MPNativeAdSource.h" +#import "MPNativePositionSource.h" +#import "MPStreamAdPlacementData.h" +#import "MPStreamAdPlacer.h" +#endif + +@interface MPInstanceProvider () + +// A nested dictionary. The top-level dictionary maps Class objects to second-level dictionaries. +// The second level dictionaries map identifiers to singleton objects. +// +// An example: +// { +// SomeClass: +// { +// @"default": +// @"other_context": +// } +// } +// +@property (nonatomic, strong) NSMutableDictionary *singletons; + +@end + + +@implementation MPInstanceProvider + +static MPInstanceProvider *sharedAdProvider = nil; + ++ (instancetype)sharedProvider +{ + static dispatch_once_t once; + dispatch_once(&once, ^{ + sharedAdProvider = [[self alloc] init]; + }); + + return sharedAdProvider; +} + +- (id)init +{ + self = [super init]; + if (self) { + self.singletons = [NSMutableDictionary dictionary]; + } + return self; +} + +- (id)singletonForClass:(Class)klass provider:(MPSingletonProviderBlock)provider +{ + return [self singletonForClass:klass provider:provider context:@"default"]; +} + +- (id)singletonForClass:(Class)klass provider:(MPSingletonProviderBlock)provider context:(id)context +{ + id singleton = [[self.singletons objectForKey:klass] objectForKey:context]; + if (!singleton) { + singleton = provider(); + NSMutableDictionary *singletonsForClass = [self.singletons objectForKey:klass]; + if (!singletonsForClass) { + NSMutableDictionary *singletonsForContext = [NSMutableDictionary dictionaryWithObjectsAndKeys:singleton, context, nil]; + [self.singletons setObject:singletonsForContext forKey:(id)klass]; + } else { + [singletonsForClass setObject:singleton forKey:context]; + } + } + return singleton; +} + +#pragma mark - Banners + +- (MPBannerAdManager *)buildMPBannerAdManagerWithDelegate:(id)delegate +{ + return [(MPBannerAdManager *)[MPBannerAdManager alloc] initWithDelegate:delegate]; +} + +- (MPBaseBannerAdapter *)buildBannerAdapterForConfiguration:(MPAdConfiguration *)configuration + delegate:(id)delegate +{ + if (configuration.customEventClass) { + return [(MPBannerCustomEventAdapter *)[MPBannerCustomEventAdapter alloc] initWithDelegate:delegate]; + } + + return nil; +} + +- (MPBannerCustomEvent *)buildBannerCustomEventFromCustomClass:(Class)customClass + delegate:(id)delegate +{ + MPBannerCustomEvent *customEvent = [[customClass alloc] init]; + if (![customEvent isKindOfClass:[MPBannerCustomEvent class]]) { + MPLogError(@"**** Custom Event Class: %@ does not extend MPBannerCustomEvent ****", NSStringFromClass(customClass)); + return nil; + } + customEvent.delegate = delegate; + return customEvent; +} + +#pragma mark - Interstitials + +- (MPInterstitialAdManager *)buildMPInterstitialAdManagerWithDelegate:(id)delegate +{ + return [(MPInterstitialAdManager *)[MPInterstitialAdManager alloc] initWithDelegate:delegate]; +} + + +- (MPBaseInterstitialAdapter *)buildInterstitialAdapterForConfiguration:(MPAdConfiguration *)configuration + delegate:(id)delegate +{ + if (configuration.customEventClass) { + return [(MPInterstitialCustomEventAdapter *)[MPInterstitialCustomEventAdapter alloc] initWithDelegate:delegate]; + } + + return nil; +} + +- (MPInterstitialCustomEvent *)buildInterstitialCustomEventFromCustomClass:(Class)customClass + delegate:(id)delegate +{ + MPInterstitialCustomEvent *customEvent = [[customClass alloc] init]; + if (![customEvent isKindOfClass:[MPInterstitialCustomEvent class]]) { + MPLogError(@"**** Custom Event Class: %@ does not extend MPInterstitialCustomEvent ****", NSStringFromClass(customClass)); + return nil; + } + if ([customEvent respondsToSelector:@selector(customEventDidUnload)]) { + MPLogWarn(@"**** Custom Event Class: %@ implements the deprecated -customEventDidUnload method. This is no longer called. Use -dealloc for cleanup instead ****", NSStringFromClass(customClass)); + } + customEvent.delegate = delegate; + return customEvent; +} + +- (MPHTMLInterstitialViewController *)buildMPHTMLInterstitialViewControllerWithDelegate:(id)delegate + orientationType:(MPInterstitialOrientationType)type +{ + MPHTMLInterstitialViewController *controller = [[MPHTMLInterstitialViewController alloc] init]; + controller.delegate = delegate; + controller.orientationType = type; + return controller; +} + +- (MPMRAIDInterstitialViewController *)buildMPMRAIDInterstitialViewControllerWithDelegate:(id)delegate + configuration:(MPAdConfiguration *)configuration +{ + MPMRAIDInterstitialViewController *controller = [[MPMRAIDInterstitialViewController alloc] initWithAdConfiguration:configuration]; + controller.delegate = delegate; + return controller; +} + +#pragma mark - Rewarded Video + +- (MPRewardedVideoAdManager *)buildRewardedVideoAdManagerWithAdUnitID:(NSString *)adUnitID delegate:(id)delegate +{ + return [[MPRewardedVideoAdManager alloc] initWithAdUnitID:adUnitID delegate:delegate]; +} + +- (MPRewardedVideoAdapter *)buildRewardedVideoAdapterWithDelegate:(id)delegate +{ + return [[MPRewardedVideoAdapter alloc] initWithDelegate:delegate]; +} + +- (MPRewardedVideoCustomEvent *)buildRewardedVideoCustomEventFromCustomClass:(Class)customClass delegate:(id)delegate +{ + MPRewardedVideoCustomEvent *customEvent = [[customClass alloc] init]; + + if (![customEvent isKindOfClass:[MPRewardedVideoCustomEvent class]]) { + MPLogError(@"**** Custom Event Class: %@ does not extend MPRewardedVideoCustomEvent ****", NSStringFromClass(customClass)); + return nil; + } + + customEvent.delegate = delegate; + return customEvent; +} + +#pragma mark - HTML Ads + +- (MPAdWebViewAgent *)buildMPAdWebViewAgentWithAdWebViewFrame:(CGRect)frame delegate:(id)delegate +{ + return [[MPAdWebViewAgent alloc] initWithAdWebViewFrame:frame delegate:delegate]; +} + +#pragma mark - MRAID + +- (MPClosableView *)buildMRAIDMPClosableViewWithFrame:(CGRect)frame webView:(MPWebView *)webView delegate:(id)delegate +{ + MPClosableView *adView = [[MPClosableView alloc] initWithFrame:frame closeButtonType:MPClosableViewCloseButtonTypeTappableWithImage]; + adView.delegate = delegate; + adView.clipsToBounds = YES; + webView.frame = adView.bounds; + [adView addSubview:webView]; + return adView; +} + +- (MRBundleManager *)buildMRBundleManager +{ + return [MRBundleManager sharedManager]; +} + +- (MRController *)buildBannerMRControllerWithFrame:(CGRect)frame delegate:(id)delegate +{ + return [self buildMRControllerWithFrame:frame placementType:MRAdViewPlacementTypeInline delegate:delegate]; +} + +- (MRController *)buildInterstitialMRControllerWithFrame:(CGRect)frame delegate:(id)delegate +{ + return [self buildMRControllerWithFrame:frame placementType:MRAdViewPlacementTypeInterstitial delegate:delegate]; +} + +- (MRController *)buildMRControllerWithFrame:(CGRect)frame placementType:(MRAdViewPlacementType)placementType delegate:(id)delegate +{ + MRController *controller = [[MRController alloc] initWithAdViewFrame:frame adPlacementType:placementType]; + controller.delegate = delegate; + return controller; +} + +- (MRBridge *)buildMRBridgeWithWebView:(MPWebView *)webView delegate:(id)delegate +{ + MRBridge *bridge = [[MRBridge alloc] initWithWebView:webView]; + bridge.delegate = delegate; + bridge.shouldHandleRequests = YES; + return bridge; +} + +- (MRVideoPlayerManager *)buildMRVideoPlayerManagerWithDelegate:(id)delegate +{ + return [[MRVideoPlayerManager alloc] initWithDelegate:delegate]; +} + +- (MPMoviePlayerViewController *)buildMPMoviePlayerViewControllerWithURL:(NSURL *)URL +{ + // ImageContext used to avoid CGErrors + // http://stackoverflow.com/questions/13203336/iphone-mpmovieplayerviewcontroller-cgcontext-errors/14669166#14669166 + UIGraphicsBeginImageContext(CGSizeMake(1,1)); + MPMoviePlayerViewController *playerViewController = [[MPMoviePlayerViewController alloc] initWithContentURL:URL]; + UIGraphicsEndImageContext(); + + return playerViewController; +} + +- (MRNativeCommandHandler *)buildMRNativeCommandHandlerWithDelegate:(id)delegate +{ + return [[MRNativeCommandHandler alloc] initWithDelegate:delegate]; +} + +#pragma mark - Native + +#if MP_HAS_NATIVE_PACKAGE + +- (MPNativeCustomEvent *)buildNativeCustomEventFromCustomClass:(Class)customClass + delegate:(id)delegate +{ + MPNativeCustomEvent *customEvent = [[customClass alloc] init]; + if (![customEvent isKindOfClass:[MPNativeCustomEvent class]]) { + MPLogError(@"**** Custom Event Class: %@ does not extend MPNativeCustomEvent ****", NSStringFromClass(customClass)); + return nil; + } + customEvent.delegate = delegate; + return customEvent; +} + +- (MPNativeAdSource *)buildNativeAdSourceWithDelegate:(id)delegate +{ + MPNativeAdSource *source = [MPNativeAdSource source]; + source.delegate = delegate; + return source; +} + +- (MPNativePositionSource *)buildNativePositioningSource +{ + return [[MPNativePositionSource alloc] init]; +} + +- (MPStreamAdPlacementData *)buildStreamAdPlacementDataWithPositioning:(MPAdPositioning *)positioning +{ + MPStreamAdPlacementData *placementData = [[MPStreamAdPlacementData alloc] initWithPositioning:positioning]; + return placementData; +} + +- (MPStreamAdPlacer *)buildStreamAdPlacerWithViewController:(UIViewController *)controller adPositioning:(MPAdPositioning *)positioning rendererConfigurations:(NSArray *)rendererConfigurations +{ + return [MPStreamAdPlacer placerWithViewController:controller adPositioning:positioning rendererConfigurations:rendererConfigurations]; +} + +#endif + +@end + diff --git a/iphone/Maps/3party/MoPubSDK/Internal/MPVASTTracking.h b/iphone/Maps/3party/MoPubSDK/Internal/MPVASTTracking.h new file mode 100644 index 00000000000..da50565489e --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/MPVASTTracking.h @@ -0,0 +1,37 @@ +// +// MPVASTTracking.h +// MoPubSDK +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import + +@class MPVideoConfig; + +typedef NS_ENUM(NSUInteger, MPVideoEventType) { + MPVideoEventTypeTimeUpdate = 0, + MPVideoEventTypeMuted, + MPVideoEventTypeUnmuted, + MPVideoEventTypePause, + MPVideoEventTypeResume, + MPVideoEventTypeFullScreen, + MPVideoEventTypeExitFullScreen, + MPVideoEventTypeExpand, + MPVideoEventTypeCollapse, + MPVideoEventTypeCompleted, + MPVideoEventTypeImpression, + MPVideoEventTypeClick, + MPVideoEventTypeError +}; + +@interface MPVASTTracking : NSObject + +@property (nonatomic, readonly) MPVideoConfig *videoConfig; +@property (nonatomic) NSTimeInterval videoDuration; + +- (instancetype)initWithMPVideoConfig:(MPVideoConfig *)videoConfig videoView:(UIView *)videoView; +- (void)handleVideoEvent:(MPVideoEventType)videoEventType videoTimeOffset:(NSTimeInterval)timeOffset; +- (void)handleNewVideoView:(UIView *)videoView; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/MPVASTTracking.m b/iphone/Maps/3party/MoPubSDK/Internal/MPVASTTracking.m new file mode 100644 index 00000000000..e3fa043040a --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/MPVASTTracking.m @@ -0,0 +1,253 @@ +// +// MPVASTTracking.m +// MoPubSDK +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import "MOPUBNativeVideoImpressionAgent.h" +#import "MPAnalyticsTracker.h" +#import "MPCoreInstanceProvider.h" +#import "MPLogging.h" +#import "MPVASTMacroProcessor.h" +#import "MPVASTTracking.h" +#import "MPVideoConfig.h" + +// Do not fire the start tracker until this time has been reached in the video +static const NSInteger kStartTrackerTime = 0; + +@interface VASTTrackingURL : NSObject + +@property (nonatomic, copy) NSURL *url; +@property (nonatomic) MPVASTDurationOffset *progressOffset; + +@end + +@implementation VASTTrackingURL + +@end + +@interface VASTEventTracker : NSObject + +@property (nonatomic, assign) BOOL trackersFired; +@property (nonatomic) NSArray *trackingEvents; // NSArray + +@end + +@implementation VASTEventTracker + ++ (VASTEventTracker *)eventTrackerWithMPVastTrackingEvents:(NSArray *)events +{ + VASTEventTracker *result = [[VASTEventTracker alloc] init]; + NSMutableArray *trackingEvents = [NSMutableArray array]; + for (MPVASTTrackingEvent *event in events) { + VASTTrackingURL *tracker = [[VASTTrackingURL alloc] init]; + tracker.url = event.URL; + tracker.progressOffset = event.progressOffset; + [trackingEvents addObject:tracker]; + } + + result.trackingEvents = trackingEvents; + + return result; +} + ++ (VASTEventTracker *)eventTrackerWithURLs:(NSArray *)urls +{ + VASTEventTracker *result = [[VASTEventTracker alloc] init]; + NSMutableArray *trackingEvents = [NSMutableArray array]; + for (NSURL *url in urls) { + VASTTrackingURL *tracker = [[VASTTrackingURL alloc] init]; + tracker.url = url; + tracker.progressOffset = nil; + [trackingEvents addObject:tracker]; + } + + result.trackingEvents = trackingEvents; + + return result; +} + +@end + +@interface MPVASTTracking() + +@property (nonatomic) VASTEventTracker *errorTracker; +@property (nonatomic) VASTEventTracker *impressionTracker; +@property (nonatomic) VASTEventTracker *clickTracker; +@property (nonatomic) VASTEventTracker *customViewabilityTracker; + +@property (nonatomic) VASTEventTracker *startTracker; +@property (nonatomic) VASTEventTracker *firstQuartileTracker; +@property (nonatomic) VASTEventTracker *midPointTracker; +@property (nonatomic) VASTEventTracker *thirdQuartileTracker; +@property (nonatomic) VASTEventTracker *completionTracker; + +@property (nonatomic) NSMutableArray *variableProgressTrackers; //NSMutableArray + +@property (nonatomic) VASTEventTracker *muteTracker; +@property (nonatomic) VASTEventTracker *unmuteTracker; +@property (nonatomic) VASTEventTracker *pauseTracker; +@property (nonatomic) VASTEventTracker *rewindTracker; +@property (nonatomic) VASTEventTracker *resumeTracker; +@property (nonatomic) VASTEventTracker *fullscreenTracker; +@property (nonatomic) VASTEventTracker *exitFullscreenTracker; +@property (nonatomic) VASTEventTracker *expandTracker; +@property (nonatomic) VASTEventTracker *collapseTracker; + +@property (nonatomic) MOPUBNativeVideoImpressionAgent *customViewabilityTrackingAgent; + +@end + +@implementation MPVASTTracking + +- (instancetype)initWithMPVideoConfig:(MPVideoConfig *)videoConfig videoView:(UIView *)videoView; +{ + self = [super init]; + if (self) { + _videoConfig = videoConfig; + _videoDuration = -1; + + _errorTracker = [VASTEventTracker eventTrackerWithURLs:_videoConfig.errorURLs]; + _impressionTracker = [VASTEventTracker eventTrackerWithURLs:_videoConfig.impressionURLs]; + _clickTracker = [VASTEventTracker eventTrackerWithURLs:_videoConfig.clickTrackingURLs]; + + _startTracker = [VASTEventTracker eventTrackerWithMPVastTrackingEvents:_videoConfig.startTrackers]; + _firstQuartileTracker = [VASTEventTracker eventTrackerWithMPVastTrackingEvents:_videoConfig.firstQuartileTrackers]; + _midPointTracker = [VASTEventTracker eventTrackerWithMPVastTrackingEvents:_videoConfig.midpointTrackers]; + _thirdQuartileTracker = [VASTEventTracker eventTrackerWithMPVastTrackingEvents:_videoConfig.thirdQuartileTrackers]; + _completionTracker = [VASTEventTracker eventTrackerWithMPVastTrackingEvents:_videoConfig.completionTrackers]; + + _variableProgressTrackers = [NSMutableArray array]; + for (MPVASTTrackingEvent *event in _videoConfig.otherProgressTrackers) { + [_variableProgressTrackers addObject:[VASTEventTracker eventTrackerWithMPVastTrackingEvents:[NSArray arrayWithObject:event]]]; + } + + _muteTracker = [VASTEventTracker eventTrackerWithMPVastTrackingEvents:_videoConfig.muteTrackers]; + _unmuteTracker = [VASTEventTracker eventTrackerWithMPVastTrackingEvents:_videoConfig.unmuteTrackers]; + _pauseTracker = [VASTEventTracker eventTrackerWithMPVastTrackingEvents:_videoConfig.pauseTrackers]; + _rewindTracker = [VASTEventTracker eventTrackerWithMPVastTrackingEvents:_videoConfig.rewindTrackers]; + _resumeTracker = [VASTEventTracker eventTrackerWithMPVastTrackingEvents:_videoConfig.resumeTrackers]; + _fullscreenTracker = [VASTEventTracker eventTrackerWithMPVastTrackingEvents:_videoConfig.fullscreenTrackers]; + _exitFullscreenTracker = [VASTEventTracker eventTrackerWithMPVastTrackingEvents:_videoConfig.exitFullscreenTrackers]; + _expandTracker = [VASTEventTracker eventTrackerWithMPVastTrackingEvents:_videoConfig.expandTrackers]; + _collapseTracker = [VASTEventTracker eventTrackerWithMPVastTrackingEvents:_videoConfig.collapseTrackers]; + + if (_videoConfig.viewabilityTrackingURL) { + _customViewabilityTracker = [VASTEventTracker eventTrackerWithURLs:[NSArray arrayWithObject:_videoConfig.viewabilityTrackingURL]]; + _customViewabilityTrackingAgent = [[MOPUBNativeVideoImpressionAgent alloc] initWithVideoView:videoView requiredVisibilityPercentage:videoConfig.minimumFractionOfVideoVisible requiredPlaybackDuration:videoConfig.minimumViewabilityTimeInterval]; + } + } + return self; +} + +- (void)handleVideoEvent:(MPVideoEventType)videoEventType videoTimeOffset:(NSTimeInterval)timeOffset +{ + if (self.videoConfig && (self.videoDuration > 0 || videoEventType == MPVideoEventTypeError)) { + if (videoEventType == MPVideoEventTypeTimeUpdate) { + [self handleProgressTrackers:timeOffset]; + } else { + VASTEventTracker *eventTrackerToFire; + switch (videoEventType) { + case MPVideoEventTypeMuted: + eventTrackerToFire = self.muteTracker; + break; + case MPVideoEventTypeUnmuted: + eventTrackerToFire = self.unmuteTracker; + break; + case MPVideoEventTypePause: + eventTrackerToFire = self.pauseTracker; + break; + case MPVideoEventTypeResume: + eventTrackerToFire = self.resumeTracker; + break; + case MPVideoEventTypeFullScreen: + eventTrackerToFire = self.fullscreenTracker; + break; + case MPVideoEventTypeExitFullScreen: + eventTrackerToFire = self.exitFullscreenTracker; + break; + case MPVideoEventTypeExpand: + eventTrackerToFire = self.expandTracker; + break; + case MPVideoEventTypeCollapse: + eventTrackerToFire = self.collapseTracker; + break; + case MPVideoEventTypeError: + eventTrackerToFire = self.errorTracker; + break; + case MPVideoEventTypeImpression: + if (!self.impressionTracker.trackersFired) { + eventTrackerToFire = self.impressionTracker; + } + break; + case MPVideoEventTypeClick: + if (!self.clickTracker.trackersFired) { + eventTrackerToFire = self.clickTracker; + } + break; + case MPVideoEventTypeCompleted: + if (!self.completionTracker.trackersFired) { + eventTrackerToFire = self.completionTracker; + } + break; + default: + eventTrackerToFire = nil; + } + // Only fire event trackers after the video has started playing + if (eventTrackerToFire && (self.startTracker.trackersFired || videoEventType == MPVideoEventTypeError)) { + [self cleanAndSendTrackingEvents:eventTrackerToFire timeOffset:timeOffset]; + } + } + } +} + +- (void)handleProgressTrackers:(NSTimeInterval)timeOffset +{ + if (timeOffset >= kStartTrackerTime && !self.startTracker.trackersFired) { + [self cleanAndSendTrackingEvents:self.startTracker timeOffset:timeOffset]; + } + + if ((0.75 * self.videoDuration) <= timeOffset && !self.thirdQuartileTracker.trackersFired) { + [self cleanAndSendTrackingEvents:self.thirdQuartileTracker timeOffset:timeOffset]; + } + + if ((0.50 * self.videoDuration) <= timeOffset && !self.midPointTracker.trackersFired) { + [self cleanAndSendTrackingEvents:self.midPointTracker timeOffset:timeOffset]; + } + + if ((0.25 * self.videoDuration) <= timeOffset && !self.firstQuartileTracker.trackersFired) { + [self cleanAndSendTrackingEvents:self.firstQuartileTracker timeOffset:timeOffset]; + } + + for (VASTEventTracker *progressTracker in self.variableProgressTrackers) { + VASTTrackingURL *progressTrackingURL = progressTracker.trackingEvents[0]; // there's only one + if (!progressTracker.trackersFired && [progressTrackingURL.progressOffset timeIntervalForVideoWithDuration:self.videoDuration] <= timeOffset) { + [self cleanAndSendTrackingEvents:progressTracker timeOffset:timeOffset]; + } + } + + if (self.customViewabilityTracker && !self.customViewabilityTracker.trackersFired && + [self.customViewabilityTrackingAgent shouldTrackImpressionWithCurrentPlaybackTime:timeOffset]) { + [self cleanAndSendTrackingEvents:self.customViewabilityTracker timeOffset:timeOffset]; + } +} + +- (void)cleanAndSendTrackingEvents:(VASTEventTracker *)vastEventTracker timeOffset:(NSTimeInterval)timeOffset +{ + if (vastEventTracker && [vastEventTracker.trackingEvents count]) { + NSMutableArray *cleanedTrackingURLs = [NSMutableArray array]; + for (VASTTrackingURL *vastTrackingURL in vastEventTracker.trackingEvents) { + [cleanedTrackingURLs addObject:[MPVASTMacroProcessor macroExpandedURLForURL:vastTrackingURL.url errorCode:nil videoTimeOffset:timeOffset videoAssetURL:self.videoConfig.mediaURL]]; + } + [[[MPCoreInstanceProvider sharedProvider] sharedMPAnalyticsTracker] sendTrackingRequestForURLs:cleanedTrackingURLs]; + } + vastEventTracker.trackersFired = YES; +} + +- (void)handleNewVideoView:(UIView *)videoView +{ + self.customViewabilityTrackingAgent = [[MOPUBNativeVideoImpressionAgent alloc] initWithVideoView:videoView requiredVisibilityPercentage:self.videoConfig.minimumFractionOfVideoVisible requiredPlaybackDuration:self.videoConfig.minimumViewabilityTimeInterval]; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/MRAID/LICENSE b/iphone/Maps/3party/MoPubSDK/Internal/MRAID/LICENSE new file mode 100644 index 00000000000..b62d673bf84 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/MRAID/LICENSE @@ -0,0 +1,12 @@ +Copyright (c) 2011, The ORMMA.org project authors. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +- Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +- Neither the name of the ORMMA.org nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS MR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER MR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, MR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS MR SERVICES; LOSS OF USE, DATA, MR PROFITS; MR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, MR TORT (INCLUDING NEGLIGENCE MR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MPForceableOrientationProtocol.h b/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MPForceableOrientationProtocol.h new file mode 100644 index 00000000000..7f56818a048 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MPForceableOrientationProtocol.h @@ -0,0 +1,18 @@ +// +// MPForceableOrientationProtocol.h +// MoPub +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import + +@protocol MPForceableOrientationProtocol + +/** + * An orientation mask that defines the orientations the view controller supports. + * This cannot force a change in orientation though. + */ +- (void)setSupportedOrientationMask:(UIInterfaceOrientationMask)supportedOrientationMask; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MPMRAIDBannerCustomEvent.h b/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MPMRAIDBannerCustomEvent.h new file mode 100644 index 00000000000..94a031ba0fb --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MPMRAIDBannerCustomEvent.h @@ -0,0 +1,18 @@ +// +// MPMRAIDBannerCustomEvent.h +// MoPub +// +// Copyright (c) 2013 MoPub. All rights reserved. +// + +#import "MPBannerCustomEvent.h" +#import "MPPrivateBannerCustomEventDelegate.h" + +@interface MPMRAIDBannerCustomEvent : MPBannerCustomEvent + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wobjc-property-synthesis" +@property (nonatomic, weak) id delegate; +#pragma clang diagnostic pop + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MPMRAIDBannerCustomEvent.m b/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MPMRAIDBannerCustomEvent.m new file mode 100644 index 00000000000..da41a97bcfa --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MPMRAIDBannerCustomEvent.m @@ -0,0 +1,88 @@ +// +// MPMRAIDBannerCustomEvent.m +// MoPub +// +// Copyright (c) 2013 MoPub. All rights reserved. +// + +#import "MPMRAIDBannerCustomEvent.h" +#import "MPLogging.h" +#import "MPAdConfiguration.h" +#import "MPInstanceProvider.h" +#import "MRController.h" + +@interface MPMRAIDBannerCustomEvent () + +@property (nonatomic, strong) MRController *mraidController; + +@end + +@implementation MPMRAIDBannerCustomEvent + +- (void)requestAdWithSize:(CGSize)size customEventInfo:(NSDictionary *)info +{ + MPLogInfo(@"Loading MoPub MRAID banner"); + MPAdConfiguration *configuration = [self.delegate configuration]; + + CGRect adViewFrame = CGRectZero; + if ([configuration hasPreferredSize]) { + adViewFrame = CGRectMake(0, 0, configuration.preferredSize.width, + configuration.preferredSize.height); + } + + self.mraidController = [[MPInstanceProvider sharedProvider] buildBannerMRControllerWithFrame:adViewFrame delegate:self]; + [self.mraidController loadAdWithConfiguration:configuration]; +} + +#pragma mark - MRControllerDelegate + +- (CLLocation *)location +{ + return [self.delegate location]; +} + +- (NSString *)adUnitId +{ + return [self.delegate adUnitId]; +} + +- (MPAdConfiguration *)adConfiguration +{ + return [self.delegate configuration]; +} + +- (UIViewController *)viewControllerForPresentingModalView +{ + return [self.delegate viewControllerForPresentingModalView]; +} + +- (void)adDidLoad:(UIView *)adView +{ + MPLogInfo(@"MoPub MRAID banner did load"); + [self.delegate bannerCustomEvent:self didLoadAd:adView]; +} + +- (void)adDidFailToLoad:(UIView *)adView +{ + MPLogInfo(@"MoPub MRAID banner did fail"); + [self.delegate bannerCustomEvent:self didFailToLoadAdWithError:nil]; +} + +- (void)closeButtonPressed +{ + //don't care +} + +- (void)appShouldSuspendForAd:(UIView *)adView +{ + MPLogInfo(@"MoPub MRAID banner will begin action"); + [self.delegate bannerCustomEventWillBeginAction:self]; +} + +- (void)appShouldResumeFromAd:(UIView *)adView +{ + MPLogInfo(@"MoPub MRAID banner did end action"); + [self.delegate bannerCustomEventDidFinishAction:self]; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MPMRAIDInterstitialCustomEvent.h b/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MPMRAIDInterstitialCustomEvent.h new file mode 100644 index 00000000000..2c27929b963 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MPMRAIDInterstitialCustomEvent.h @@ -0,0 +1,19 @@ +// +// MPMRAIDInterstitialCustomEvent.h +// MoPub +// +// Copyright (c) 2013 MoPub. All rights reserved. +// + +#import "MPInterstitialCustomEvent.h" +#import "MPMRAIDInterstitialViewController.h" +#import "MPPrivateInterstitialCustomEventDelegate.h" + +@interface MPMRAIDInterstitialCustomEvent : MPInterstitialCustomEvent + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wobjc-property-synthesis" +@property (nonatomic, weak) id delegate; +#pragma clang diagnostic pop + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MPMRAIDInterstitialCustomEvent.m b/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MPMRAIDInterstitialCustomEvent.m new file mode 100644 index 00000000000..d4153156031 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MPMRAIDInterstitialCustomEvent.m @@ -0,0 +1,103 @@ +// +// MPMRAIDInterstitialCustomEvent.m +// MoPub +// +// Copyright (c) 2013 MoPub. All rights reserved. +// + +#import "MPMRAIDInterstitialCustomEvent.h" +#import "MPInstanceProvider.h" +#import "MPLogging.h" + +@interface MPMRAIDInterstitialCustomEvent () + +@property (nonatomic, strong) MPMRAIDInterstitialViewController *interstitial; + +@end + +@implementation MPMRAIDInterstitialCustomEvent + +@synthesize interstitial = _interstitial; + +- (void)requestInterstitialWithCustomEventInfo:(NSDictionary *)info +{ + MPLogInfo(@"Loading MoPub MRAID interstitial"); + self.interstitial = [[MPInstanceProvider sharedProvider] buildMPMRAIDInterstitialViewControllerWithDelegate:self + configuration:[self.delegate configuration]]; + + // The MRAID ad view will handle the close button so we don't need the MPInterstitialViewController's close button. + [self.interstitial setCloseButtonStyle:MPInterstitialCloseButtonStyleAlwaysHidden]; + [self.interstitial startLoading]; +} + +- (void)showInterstitialFromRootViewController:(UIViewController *)controller +{ + [self.interstitial presentInterstitialFromViewController:controller]; +} + +#pragma mark - MPMRAIDInterstitialViewControllerDelegate + +- (CLLocation *)location +{ + return [self.delegate location]; +} + +- (NSString *)adUnitId +{ + return [self.delegate adUnitId]; +} + +- (void)interstitialDidLoadAd:(MPInterstitialViewController *)interstitial +{ + MPLogInfo(@"MoPub MRAID interstitial did load"); + [self.delegate interstitialCustomEvent:self didLoadAd:self.interstitial]; +} + +- (void)interstitialDidFailToLoadAd:(MPInterstitialViewController *)interstitial +{ + MPLogInfo(@"MoPub MRAID interstitial did fail"); + [self.delegate interstitialCustomEvent:self didFailToLoadAdWithError:nil]; +} + +- (void)interstitialWillAppear:(MPInterstitialViewController *)interstitial +{ + MPLogInfo(@"MoPub MRAID interstitial will appear"); + [self.delegate interstitialCustomEventWillAppear:self]; +} + +- (void)interstitialDidAppear:(MPInterstitialViewController *)interstitial +{ + MPLogInfo(@"MoPub MRAID interstitial did appear"); + [self.delegate interstitialCustomEventDidAppear:self]; +} + +- (void)interstitialWillDisappear:(MPInterstitialViewController *)interstitial +{ + MPLogInfo(@"MoPub MRAID interstitial will disappear"); + [self.delegate interstitialCustomEventWillDisappear:self]; +} + +- (void)interstitialDidDisappear:(MPInterstitialViewController *)interstitial +{ + MPLogInfo(@"MoPub MRAID interstitial did disappear"); + [self.delegate interstitialCustomEventDidDisappear:self]; + + // Deallocate the interstitial as we don't need it anymore. If we don't deallocate the interstitial after dismissal, + // then the html in the webview will continue to run which could lead to bugs such as continuing to play the sound of an inline + // video since the app may hold onto the interstitial ad controller. Moreover, we keep an array of controllers around as well. + self.interstitial = nil; +} + +- (void)interstitialDidReceiveTapEvent:(MPInterstitialViewController *)interstitial +{ + MPLogInfo(@"MoPub MRAID interstitial did receive tap event"); + [self.delegate interstitialCustomEventDidReceiveTapEvent:self]; +} + +- (void)interstitialWillLeaveApplication:(MPInterstitialViewController *)interstitial +{ + MPLogInfo(@"MoPub MRAID interstitial will leave application"); + [self.delegate interstitialCustomEventWillLeaveApplication:self]; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MPMRAIDInterstitialViewController.h b/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MPMRAIDInterstitialViewController.h new file mode 100644 index 00000000000..3cf03e377ab --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MPMRAIDInterstitialViewController.h @@ -0,0 +1,24 @@ +// +// MPMRAIDInterstitialViewController.h +// MoPub +// +// Copyright (c) 2012 MoPub, Inc. All rights reserved. +// + +#import "MPInterstitialViewController.h" +#import "MPForceableOrientationProtocol.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@protocol MPMRAIDInterstitialViewControllerDelegate; +@class MPAdConfiguration; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface MPMRAIDInterstitialViewController : MPInterstitialViewController + +- (id)initWithAdConfiguration:(MPAdConfiguration *)configuration; +- (void)startLoading; + +@end + diff --git a/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MPMRAIDInterstitialViewController.m b/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MPMRAIDInterstitialViewController.m new file mode 100644 index 00000000000..be5dee1d14f --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MPMRAIDInterstitialViewController.m @@ -0,0 +1,190 @@ +// +// MPMRAIDInterstitialViewController.m +// MoPub +// +// Copyright (c) 2012 MoPub, Inc. All rights reserved. +// + +#import "MPMRAIDInterstitialViewController.h" +#import "MPInstanceProvider.h" +#import "MPAdConfiguration.h" +#import "MRController.h" + +@interface MPMRAIDInterstitialViewController () + +@property (nonatomic, strong) MPAdConfiguration *configuration; +@property (nonatomic, strong) MRController *mraidController; +@property (nonatomic, strong) UIView *interstitialView; +@property (nonatomic, assign) UIInterfaceOrientationMask supportedOrientationMask; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation MPMRAIDInterstitialViewController + + +- (id)initWithAdConfiguration:(MPAdConfiguration *)configuration +{ + self = [super init]; + if (self) { + CGFloat width = MAX(configuration.preferredSize.width, 1); + CGFloat height = MAX(configuration.preferredSize.height, 1); + CGRect frame = CGRectMake(0, 0, width, height); + self.mraidController = [[MPInstanceProvider sharedProvider] buildInterstitialMRControllerWithFrame:frame delegate:self]; + + self.configuration = configuration; + self.orientationType = [self.configuration orientationType]; + } + return self; +} + +#pragma mark - Public + +- (void)startLoading +{ + [self.mraidController loadAdWithConfiguration:self.configuration]; +} + +- (void)willPresentInterstitial +{ + [self.mraidController disableRequestHandling]; + if ([self.delegate respondsToSelector:@selector(interstitialWillAppear:)]) { + [self.delegate interstitialWillAppear:self]; + } +} + +- (void)didPresentInterstitial +{ + // This ensures that we handle didPresentInterstitial at the end of the run loop, and prevents a bug + // where code is run before UIKit thinks the presentViewController animation is complete, even though + // this is is called from the completion block for said animation. + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0), dispatch_get_main_queue(), ^{ + [self.mraidController handleMRAIDInterstitialDidPresentWithViewController:self]; + if ([self.delegate respondsToSelector:@selector(interstitialDidAppear:)]) { + [self.delegate interstitialDidAppear:self]; + } + }); +} + +- (void)willDismissInterstitial +{ + [self.mraidController disableRequestHandling]; + if ([self.delegate respondsToSelector:@selector(interstitialWillDisappear:)]) { + [self.delegate interstitialWillDisappear:self]; + } +} + +- (void)didDismissInterstitial +{ + if ([self.delegate respondsToSelector:@selector(interstitialDidDisappear:)]) { + [self.delegate interstitialDidDisappear:self]; + } +} + +#pragma mark - MRControllerDelegate + +- (CLLocation *)location +{ + if ([self.delegate respondsToSelector:@selector(location)]) { + return [self.delegate location]; + } + return nil; +} + +- (NSString *)adUnitId +{ + return [self.delegate adUnitId]; +} + +- (MPAdConfiguration *)adConfiguration +{ + return self.configuration; +} + +- (UIViewController *)viewControllerForPresentingModalView +{ + return self; +} + +- (void)adDidLoad:(UIView *)adView +{ + [self.interstitialView removeFromSuperview]; + + self.interstitialView = adView; + self.interstitialView.frame = self.view.bounds; + self.interstitialView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + [self.view addSubview:self.interstitialView]; + + if ([self.delegate respondsToSelector:@selector(interstitialDidLoadAd:)]) { + [self.delegate interstitialDidLoadAd:self]; + } +} + +- (void)adDidFailToLoad:(UIView *)adView +{ + if ([self.delegate respondsToSelector:@selector(interstitialDidFailToLoadAd:)]) { + [self.delegate interstitialDidFailToLoadAd:self]; + } +} + +- (void)adWillClose:(UIView *)adView +{ + [self dismissInterstitialAnimated:YES]; +} + +- (void)adDidClose:(UIView *)adView +{ + // TODO: +} + +- (void)appShouldSuspendForAd:(UIView *)adView +{ + [self.delegate interstitialDidReceiveTapEvent:self]; +} + +- (void)appShouldResumeFromAd:(UIView *)adView +{ + +} + +- (void)setSupportedOrientationMask:(UIInterfaceOrientationMask)supportedOrientationMask +{ + _supportedOrientationMask = supportedOrientationMask; + + // This should be called whenever the return value of -shouldAutorotateToInterfaceOrientation changes. Since the return + // value is based on _supportedOrientationMask, we do that here. Prevents possible rotation bugs. + [UIViewController attemptRotationToDeviceOrientation]; +} + +- (void)rewardedVideoEnded +{ + if ([self.delegate respondsToSelector:@selector(interstitialRewardedVideoEnded)]) { + [self.delegate interstitialRewardedVideoEnded]; + } +} + +#pragma mark - Orientation Handling + +// supportedInterfaceOrientations and shouldAutorotate are for ios 6, 7, and 8. +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= MP_IOS_9_0 +- (UIInterfaceOrientationMask)supportedInterfaceOrientations +#else +- (NSUInteger)supportedInterfaceOrientations +#endif +{ + return ([[UIApplication sharedApplication] mp_supportsOrientationMask:self.supportedOrientationMask]) ? self.supportedOrientationMask : UIInterfaceOrientationMaskAll; +} + +- (BOOL)shouldAutorotate +{ + return YES; +} + +// shouldAutorotateToInterfaceOrientation is for ios 5. +- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation +{ + return [[UIApplication sharedApplication] mp_doesOrientation:interfaceOrientation matchOrientationMask:self.supportedOrientationMask]; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MRBridge.h b/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MRBridge.h new file mode 100644 index 00000000000..ac0531bc996 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MRBridge.h @@ -0,0 +1,78 @@ +// +// MRBridge.h +// MoPubSDK +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import +#import "MRConstants.h" +#import "MPWebView.h" + +@class MRProperty; +@protocol MRBridgeDelegate; + +/** + * The `MRBridge` class is an intermediate object between native code and JavaScript for + * MRAID ads. The MRAID web view communicates events to `MRBridge` which translates them + * down to native code. Likewise, native code will communicate with `MRBridge` to execute + * commands inside the JavaScript. `MRBridge` also inserts mraid.js into the web view when + * loading an ad's HTML. + */ +@interface MRBridge : NSObject + +@property (nonatomic, assign) BOOL shouldHandleRequests; +@property (nonatomic, weak) id delegate; + +- (instancetype)initWithWebView:(MPWebView *)webView; + +- (void)loadHTMLString:(NSString *)HTML baseURL:(NSURL *)baseURL; + +- (void)fireReadyEvent; +- (void)fireChangeEventForProperty:(MRProperty *)property; +- (void)fireChangeEventsForProperties:(NSArray *)properties; +- (void)fireErrorEventForAction:(NSString *)action withMessage:(NSString *)message; + +/* + * fireSizeChangeEvent: will always execute the javascript to notify mraid bridge that the size of the ad may have + * changed. mraid.js will only fire the change event if the size has actually changed. + */ +- (void)fireSizeChangeEvent:(CGSize)size; + +- (void)fireSetScreenSize:(CGSize)size; +- (void)fireSetPlacementType:(NSString *)placementType; +- (void)fireSetCurrentPositionWithPositionRect:(CGRect)positionRect; +- (void)fireSetDefaultPositionWithPositionRect:(CGRect)positionRect; +- (void)fireSetMaxSize:(CGSize)maxSize; + +@end + +/** + * The delegate of an `MRBridge` object that implements `MRBridgeDelegate` must provide information + * about the state of an MRAID ad through `isLoadingAd` and `hasUserInteractedWithWebView` so `MRBridge` + * can correctly process web view events. The delegate will be notified of specific events that need + * to be handled natively for an MRAID ad. The delegate is also notified of most web view events so it + * can perform necessary actions such as changing the ad's state. + */ +@protocol MRBridgeDelegate + +- (BOOL)isLoadingAd; +- (MRAdViewPlacementType)placementType; +- (BOOL)hasUserInteractedWithWebViewForBridge:(MRBridge *)bridge; +- (UIViewController *)viewControllerForPresentingModalView; + +- (void)nativeCommandWillPresentModalView; +- (void)nativeCommandDidDismissModalView; + +- (void)bridge:(MRBridge *)bridge didFinishLoadingWebView:(MPWebView *)webView; +- (void)bridge:(MRBridge *)bridge didFailLoadingWebView:(MPWebView *)webView error:(NSError *)error; + +- (void)handleNativeCommandCloseWithBridge:(MRBridge *)bridge; +- (void)bridge:(MRBridge *)bridge performActionForMoPubSpecificURL:(NSURL *)url; +- (void)bridge:(MRBridge *)bridge handleDisplayForDestinationURL:(NSURL *)URL; +- (void)bridge:(MRBridge *)bridge handleNativeCommandUseCustomClose:(BOOL)useCustomClose; +- (void)bridge:(MRBridge *)bridge handleNativeCommandSetOrientationPropertiesWithForceOrientationMask:(UIInterfaceOrientationMask)forceOrientationMask; +- (void)bridge:(MRBridge *)bridge handleNativeCommandExpandWithURL:(NSURL *)url useCustomClose:(BOOL)useCustomClose; +- (void)bridge:(MRBridge *)bridge handleNativeCommandResizeWithParameters:(NSDictionary *)parameters; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MRBridge.m b/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MRBridge.m new file mode 100644 index 00000000000..af3153dc538 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MRBridge.m @@ -0,0 +1,306 @@ +// +// MRBridge.m +// MoPubSDK +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import "MRBridge.h" +#import "MPConstants.h" +#import "MPLogging.h" +#import "NSURL+MPAdditions.h" +#import "MPGlobal.h" +#import "MRBundleManager.h" +#import "MPInstanceProvider.h" +#import "UIWebView+MPAdditions.h" +#import "MRError.h" +#import "MRProperty.h" +#import "MRNativeCommandHandler.h" + +static NSString * const kMraidURLScheme = @"mraid"; + +@interface MRBridge () + +@property (nonatomic, strong) MPWebView *webView; +@property (nonatomic, strong) MRNativeCommandHandler *nativeCommandHandler; + +@end + +@implementation MRBridge + +- (instancetype)initWithWebView:(MPWebView *)webView +{ + if (self = [super init]) { + _webView = webView; + _webView.delegate = self; + _nativeCommandHandler = [[MPInstanceProvider sharedProvider] buildMRNativeCommandHandlerWithDelegate:self]; + } + + return self; +} + +- (void)dealloc +{ + _webView.delegate = nil; +} + +- (void)loadHTMLString:(NSString *)HTML baseURL:(NSURL *)baseURL +{ + // Bail out if we can't locate mraid.js. + if (![self MRAIDScriptPath]) { + NSError *error = [NSError errorWithDomain:MoPubMRAIDAdsSDKDomain code:MRErrorMRAIDJSNotFound userInfo:nil]; + [self.delegate bridge:self didFailLoadingWebView:self.webView error:error]; + return; + } + + if (HTML) { + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + // Execute the javascript in the web view directly. + NSString *mraidString = [NSString stringWithContentsOfFile:[self MRAIDScriptPath] encoding:NSUTF8StringEncoding error:nil]; + + // Once done loading from the file, execute the javascript and load the html into the web view. + dispatch_async(dispatch_get_main_queue(), ^{ + [self.webView evaluateJavaScript:mraidString completionHandler:^(id result, NSError *error){ + [self.webView disableJavaScriptDialogs]; + [self.webView loadHTMLString:HTML baseURL:baseURL]; + }]; + }); + }); + } +} + +- (void)fireReadyEvent +{ + [self executeJavascript:@"window.mraidbridge.fireReadyEvent();"]; +} + +- (void)fireChangeEventForProperty:(MRProperty *)property +{ + NSString *JSON = [NSString stringWithFormat:@"{%@}", property]; + [self executeJavascript:@"window.mraidbridge.fireChangeEvent(%@);", JSON]; + MPLogTrace(@"JSON: %@", JSON); +} + +- (void)fireChangeEventsForProperties:(NSArray *)properties +{ + NSString *JSON = [NSString stringWithFormat:@"{%@}", [properties componentsJoinedByString:@", "]]; + [self executeJavascript:@"window.mraidbridge.fireChangeEvent(%@);", JSON]; + MPLogTrace(@"JSON: %@", JSON); +} + +- (void)fireErrorEventForAction:(NSString *)action withMessage:(NSString *)message +{ + [self executeJavascript:@"window.mraidbridge.fireErrorEvent('%@', '%@');", message, action]; +} + +- (void)fireSizeChangeEvent:(CGSize)size +{ + [self executeJavascript:@"window.mraidbridge.notifySizeChangeEvent(%.1f, %.1f);", size.width, size.height]; +} + +- (void)fireSetScreenSize:(CGSize)size +{ + [self executeJavascript:@"window.mraidbridge.setScreenSize(%.1f, %.1f);", size.width, size.height]; +} + +- (void)fireSetPlacementType:(NSString *)placementType +{ + [self executeJavascript:@"window.mraidbridge.setPlacementType('%@');", placementType]; +} + +- (void)fireSetCurrentPositionWithPositionRect:(CGRect)positionRect +{ + [self executeJavascript:@"window.mraidbridge.setCurrentPosition(%.1f, %.1f, %.1f, %.1f);", positionRect.origin.x, positionRect.origin.y, + positionRect.size.width, positionRect.size.height]; +} + +- (void)fireSetDefaultPositionWithPositionRect:(CGRect)positionRect +{ + [self executeJavascript:@"window.mraidbridge.setDefaultPosition(%.1f, %.1f, %.1f, %.1f);", positionRect.origin.x, positionRect.origin.y, + positionRect.size.width, positionRect.size.height]; +} + +- (void)fireSetMaxSize:(CGSize)maxSize +{ + [self executeJavascript:@"window.mraidbridge.setMaxSize(%.1f, %.1f);", maxSize.width, maxSize.height]; +} + +#pragma mark - + +- (BOOL)webView:(MPWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType +{ + NSURL *url = [request URL]; + NSMutableString *urlString = [NSMutableString stringWithString:[url absoluteString]]; + NSString *scheme = url.scheme; + + if ([scheme isEqualToString:kMraidURLScheme]) { + // Some native commands such as useCustomClose should be allowed to run even if we're not handling requests. + // The command handler will make sure we don't execute native commands that aren't allowed to execute while we're not handling requests. + MPLogDebug(@"Trying to process command: %@", urlString); + NSString *command = url.host; + NSDictionary *properties = MPDictionaryFromQueryString(url.query); + [self.nativeCommandHandler handleNativeCommand:command withProperties:properties]; + return NO; + } else if ([url mp_isMoPubScheme]) { + [self.delegate bridge:self performActionForMoPubSpecificURL:url]; + return NO; + } else if ([scheme isEqualToString:@"ios-log"]) { + [urlString replaceOccurrencesOfString:@"%20" + withString:@" " + options:NSLiteralSearch + range:NSMakeRange(0, [urlString length])]; + MPLogDebug(@"Web console: %@", urlString); + return NO; + } + + if (!self.shouldHandleRequests) { + return NO; + } + + if ([url mp_hasTelephoneScheme] || [url mp_hasTelephonePromptScheme]) { + [self.delegate bridge:self handleDisplayForDestinationURL:url]; + return NO; + } + + BOOL isLoading = [self.delegate isLoadingAd]; + BOOL userInteractedWithWebView = [self.delegate hasUserInteractedWithWebViewForBridge:self]; + BOOL safeToAutoloadLink = navigationType == UIWebViewNavigationTypeLinkClicked || userInteractedWithWebView || [url mp_isSafeForLoadingWithoutUserAction]; + + if (!isLoading && (navigationType == UIWebViewNavigationTypeOther || navigationType == UIWebViewNavigationTypeLinkClicked)) { + BOOL iframe = ![request.URL isEqual:request.mainDocumentURL]; + + // If we load a URL from an iFrame that did not originate from a click or + // is a deep link, handle normally and return safeToAutoloadLink. + if (iframe && !((navigationType == UIWebViewNavigationTypeLinkClicked) && ([scheme isEqualToString:@"https"] || [scheme isEqualToString:@"http"]))) { + return safeToAutoloadLink; + } + + // Otherwise, open the URL in a new web view. + [self.delegate bridge:self handleDisplayForDestinationURL:url]; + return NO; + } + + return safeToAutoloadLink; +} + +- (void)webViewDidStartLoad:(MPWebView *)webView +{ + [webView disableJavaScriptDialogs]; +} + +- (void)webViewDidFinishLoad:(MPWebView *)webView +{ + [self.delegate bridge:self didFinishLoadingWebView:webView]; +} + +- (void)webView:(MPWebView *)webView didFailLoadWithError:(NSError *)error +{ + if (error.code == NSURLErrorCancelled) { + return; + } + + [self.delegate bridge:self didFailLoadingWebView:webView error:error]; +} + +#pragma mark - Private + +- (NSString *)MRAIDScriptPath +{ + MRBundleManager *bundleManager = [[MPInstanceProvider sharedProvider] buildMRBundleManager]; + return [bundleManager mraidPath]; +} + +- (void)executeJavascript:(NSString *)javascript, ... +{ + va_list args; + va_start(args, javascript); + [self executeJavascript:javascript withVarArgs:args]; + va_end(args); +} + +- (void)fireNativeCommandCompleteEvent:(NSString *)command +{ + [self executeJavascript:@"window.mraidbridge.nativeCallComplete('%@');", command]; +} + +- (void)executeJavascript:(NSString *)javascript withVarArgs:(va_list)args +{ + NSString *js = [[NSString alloc] initWithFormat:javascript arguments:args]; + [self.webView stringByEvaluatingJavaScriptFromString:js]; +} + +#pragma mark - MRNativeCommandHandlerDelegate + +- (void)handleMRAIDUseCustomClose:(BOOL)useCustomClose +{ + [self.delegate bridge:self handleNativeCommandUseCustomClose:useCustomClose]; +} + +- (void)handleMRAIDSetOrientationPropertiesWithForceOrientationMask:(UIInterfaceOrientationMask)forceOrientationMask +{ + [self.delegate bridge:self handleNativeCommandSetOrientationPropertiesWithForceOrientationMask:forceOrientationMask]; +} + +- (void)handleMRAIDOpenCallForURL:(NSURL *)URL +{ + [self.delegate bridge:self handleDisplayForDestinationURL:URL]; +} + +- (void)handleMRAIDExpandWithParameters:(NSDictionary *)params +{ + id urlValue = [params objectForKey:@"url"]; + [self.delegate bridge:self handleNativeCommandExpandWithURL:(urlValue == [NSNull null]) ? nil : urlValue + useCustomClose:[[params objectForKey:@"useCustomClose"] boolValue]]; +} + +- (void)handleMRAIDResizeWithParameters:(NSDictionary *)params +{ + [self.delegate bridge:self handleNativeCommandResizeWithParameters:params]; +} + +- (void)handleMRAIDClose +{ + [self.delegate handleNativeCommandCloseWithBridge:self]; +} + +- (void)nativeCommandWillPresentModalView +{ + [self.delegate nativeCommandWillPresentModalView]; +} + +- (void)nativeCommandDidDismissModalView +{ + [self.delegate nativeCommandDidDismissModalView]; +} + +- (void)nativeCommandCompleted:(NSString *)command +{ + [self fireNativeCommandCompleteEvent:command]; +} + +- (void)nativeCommandFailed:(NSString *)command withMessage:(NSString *)message +{ + [self fireErrorEventForAction:command withMessage:message]; +} + +- (UIViewController *)viewControllerForPresentingModalView +{ + return [self.delegate viewControllerForPresentingModalView]; +} + +- (MRAdViewPlacementType)adViewPlacementType +{ + return [self.delegate placementType]; +} + +- (BOOL)userInteractedWithWebView +{ + return [self.delegate hasUserInteractedWithWebViewForBridge:self]; +} + +- (BOOL)handlingWebviewRequests +{ + return self.shouldHandleRequests; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MRBundleManager.h b/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MRBundleManager.h new file mode 100644 index 00000000000..e701d8be5b1 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MRBundleManager.h @@ -0,0 +1,15 @@ +// +// MRBundleManager.h +// MoPub +// +// Copyright (c) 2013 MoPub. All rights reserved. +// + +#import + +@interface MRBundleManager : NSObject + ++ (MRBundleManager *)sharedManager; +- (NSString *)mraidPath; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MRBundleManager.m b/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MRBundleManager.m new file mode 100644 index 00000000000..aa3cc249f09 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MRBundleManager.m @@ -0,0 +1,32 @@ +// +// MRBundleManager.m +// MoPub +// +// Copyright (c) 2013 MoPub. All rights reserved. +// + +#import "MRBundleManager.h" +#import "NSBundle+MPAdditions.h" + +@implementation MRBundleManager + +static MRBundleManager *sharedManager = nil; + ++ (MRBundleManager *)sharedManager +{ + if (!sharedManager) { + sharedManager = [[MRBundleManager alloc] init]; + } + return sharedManager; +} + +- (NSString *)mraidPath +{ + NSBundle *parentBundle = [NSBundle resourceBundleForClass:self.class]; + + NSString *mraidBundlePath = [parentBundle pathForResource:@"MRAID" ofType:@"bundle"]; + NSBundle *mraidBundle = [NSBundle bundleWithPath:mraidBundlePath]; + return [mraidBundle pathForResource:@"mraid" ofType:@"js"]; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MRCommand.h b/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MRCommand.h new file mode 100644 index 00000000000..f6c7ff4679c --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MRCommand.h @@ -0,0 +1,98 @@ +// +// MRCommand.h +// MoPub +// +// Copyright (c) 2011 MoPub, Inc. All rights reserved. +// + +#import +#import + +@class MRCommand; + +@protocol MRCommandDelegate + +- (void)mrCommand:(MRCommand *)command createCalendarEventWithParams:(NSDictionary *)params __deprecated; +- (void)mrCommand:(MRCommand *)command playVideoWithURL:(NSURL *)url; +- (void)mrCommand:(MRCommand *)command storePictureWithURL:(NSURL *)url __deprecated; +- (void)mrCommand:(MRCommand *)command shouldUseCustomClose:(BOOL)useCustomClose; +- (void)mrCommand:(MRCommand *)command setOrientationPropertiesWithForceOrientation:(UIInterfaceOrientationMask)forceOrientation; +- (void)mrCommand:(MRCommand *)command openURL:(NSURL *)url; +- (void)mrCommand:(MRCommand *)command expandWithParams:(NSDictionary *)params; +- (void)mrCommand:(MRCommand *)command resizeWithParams:(NSDictionary *)params; +- (void)mrCommandClose:(MRCommand *)command; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface MRCommand : NSObject + +@property (nonatomic, weak) id delegate; + ++ (id)commandForString:(NSString *)string; + +// returns YES by default for user safety +- (BOOL)requiresUserInteractionForPlacementType:(NSUInteger)placementType; +// This allows commands to run even if the delegate is not handling webview requests. Returns NO by default to avoid race conditions. This is +// primarily used to stop commands that can cause bad side effects while the mraid ad is presenting, dismissing, resizing, expanding and pretty much +// just animating at all. If you decide to return YES for this method, you must make sure that the command can operate safely at any point in time +// during an MRAID ad's lifetime from starting presentation to complete dismissal. +- (BOOL)executableWhileBlockingRequests; +- (BOOL)executeWithParams:(NSDictionary *)params; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface MRCloseCommand : MRCommand + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface MRExpandCommand : MRCommand + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface MRResizeCommand : MRCommand + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface MRUseCustomCloseCommand : MRCommand + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface MRSetOrientationPropertiesCommand : MRCommand + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface MROpenCommand : MRCommand + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface MRCreateCalendarEventCommand : MRCommand + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface MRPlayVideoCommand : MRCommand + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface MRStorePictureCommand : MRCommand + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MRCommand.m b/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MRCommand.m new file mode 100644 index 00000000000..3dd0f496498 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MRCommand.m @@ -0,0 +1,405 @@ +// +// MRCommand.m +// MoPub +// +// Copyright (c) 2011 MoPub, Inc. All rights reserved. +// + +#import "MRCommand.h" +#import "MPGlobal.h" +#import "MPLogging.h" +#import "MRConstants.h" + +@implementation MRCommand + +@synthesize delegate = _delegate; + ++ (NSMutableDictionary *)sharedCommandClassMap +{ + static NSMutableDictionary *sharedMap = nil; + + static dispatch_once_t once; + dispatch_once(&once, ^{ + sharedMap = [[NSMutableDictionary alloc] init]; + }); + + return sharedMap; +} + ++ (void)registerCommand:(Class)commandClass +{ + NSMutableDictionary *map = [self sharedCommandClassMap]; + @synchronized(self) { + [map setValue:commandClass forKey:[commandClass commandType]]; + } +} + ++ (NSString *)commandType +{ + return @"BASE_CMD_TYPE"; +} + ++ (Class)commandClassForString:(NSString *)string +{ + NSMutableDictionary *map = [self sharedCommandClassMap]; + @synchronized(self) { + return [map objectForKey:string]; + } +} + ++ (id)commandForString:(NSString *)string +{ + Class commandClass = [self commandClassForString:string]; + return [[commandClass alloc] init]; +} + +// return YES by default for user safety +- (BOOL)requiresUserInteractionForPlacementType:(NSUInteger)placementType +{ + return YES; +} + +// Default to NO to avoid race conditions. +- (BOOL)executableWhileBlockingRequests +{ + return NO; +} + +- (BOOL)executeWithParams:(NSDictionary *)params +{ + return YES; +} + +- (CGFloat)floatFromParameters:(NSDictionary *)parameters forKey:(NSString *)key +{ + return [self floatFromParameters:parameters forKey:key withDefault:0.0]; +} + +- (CGFloat)floatFromParameters:(NSDictionary *)parameters forKey:(NSString *)key withDefault:(CGFloat)defaultValue +{ + NSString *stringValue = [parameters valueForKey:key]; + return stringValue ? [stringValue floatValue] : defaultValue; +} + +- (BOOL)boolFromParameters:(NSDictionary *)parameters forKey:(NSString *)key +{ + NSString *stringValue = [parameters valueForKey:key]; + return [stringValue isEqualToString:@"true"] || [stringValue isEqualToString:@"1"]; +} + +- (int)intFromParameters:(NSDictionary *)parameters forKey:(NSString *)key +{ + NSString *stringValue = [parameters valueForKey:key]; + return stringValue ? [stringValue intValue] : -1; +} + +- (NSString *)stringFromParameters:(NSDictionary *)parameters forKey:(NSString *)key +{ + NSString *value = [parameters objectForKey:key]; + if (!value || [value isEqual:[NSNull null]]) return nil; + + value = [value stringByTrimmingCharactersInSet: + [NSCharacterSet whitespaceAndNewlineCharacterSet]]; + if (!value || [value isEqual:[NSNull null]] || value.length == 0) return nil; + + return value; +} + +- (NSURL *)urlFromParameters:(NSDictionary *)parameters forKey:(NSString *)key +{ + NSString *value = [self stringFromParameters:parameters forKey:key]; + return [NSURL URLWithString:value]; +} + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation MRCloseCommand + ++ (void)load +{ + [MRCommand registerCommand:self]; +} + ++ (NSString *)commandType +{ + return @"close"; +} + +- (BOOL)executeWithParams:(NSDictionary *)params +{ + [self.delegate mrCommandClose:self]; + return YES; +} + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation MRExpandCommand + ++ (void)load +{ + [MRCommand registerCommand:self]; +} + ++ (NSString *)commandType +{ + return @"expand"; +} + +- (BOOL)executeWithParams:(NSDictionary *)params +{ + NSURL *url = [self urlFromParameters:params forKey:@"url"]; + + NSDictionary *expandParams = [NSDictionary dictionaryWithObjectsAndKeys: + (url == nil) ? [NSNull null] : url , @"url", + [NSNumber numberWithBool:[self boolFromParameters:params forKey:@"shouldUseCustomClose"]], @"useCustomClose", + nil]; + + [self.delegate mrCommand:self expandWithParams:expandParams]; + + return YES; +} +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation MRResizeCommand + ++ (void)load +{ + [MRCommand registerCommand:self]; +} + ++ (NSString *)commandType +{ + return @"resize"; +} + +- (BOOL)executeWithParams:(NSDictionary *)params +{ + [self.delegate mrCommand:self resizeWithParams:params]; + + return YES; +} + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation MRUseCustomCloseCommand + ++ (void)load +{ + [MRCommand registerCommand:self]; +} + +// We allow useCustomClose to run while we're blocking requests because it only controls how we present a UIButton. +// It can't present/dismiss any view or view controllers. It also doesn't affect any mraid ad/screen metrics. +- (BOOL)executableWhileBlockingRequests +{ + return YES; +} + +- (BOOL)requiresUserInteractionForPlacementType:(NSUInteger)placementType +{ + return NO; +} + ++ (NSString *)commandType +{ + return @"usecustomclose"; +} + +- (BOOL)executeWithParams:(NSDictionary *)params +{ + [self.delegate mrCommand:self shouldUseCustomClose:[self boolFromParameters:params forKey:@"shouldUseCustomClose"]]; + + return YES; +} + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation MRSetOrientationPropertiesCommand + ++ (void)load +{ + [MRCommand registerCommand:self]; +} + ++ (NSString *)commandType +{ + return @"setOrientationProperties"; +} + +- (BOOL)requiresUserInteractionForPlacementType:(NSUInteger)placementType +{ + return NO; +} + +- (BOOL)executeWithParams:(NSDictionary *)params +{ + // We can take the forceOrientation and allowOrientationChange values and boil them down to an orientation mask + // that will represent the intention of the ad. + UIInterfaceOrientationMask forceOrientationMaskValue; + + NSString *forceOrientationString = params[@"forceOrientation"]; + + // Give a default value of "none" for forceOrientationString if it didn't come in through the params. + if (!forceOrientationString) { + forceOrientationString = kOrientationPropertyForceOrientationNoneKey; + } + + // Do not allow orientation changing if we're given a force orientation other than none. Based on the spec, + // we believe that forceOrientation takes precedence over allowOrientationChange and should not allow + // orientation changes when a forceOrientation other than 'none' is given. + if ([forceOrientationString isEqualToString:kOrientationPropertyForceOrientationPortraitKey]) { + forceOrientationMaskValue = UIInterfaceOrientationMaskPortrait; + } else if ([forceOrientationString isEqualToString:kOrientationPropertyForceOrientationLandscapeKey]) { + forceOrientationMaskValue = UIInterfaceOrientationMaskLandscape; + } else { + // Default allowing orientation change to YES. We will change this only if we received a value for this in our params. + BOOL allowOrientationChangeValue = YES; + + // If we end up allowing orientation change, then we're going to allow any orientation. + forceOrientationMaskValue = UIInterfaceOrientationMaskAll; + + NSObject *allowOrientationChangeObj = params[@"allowOrientationChange"]; + + if (allowOrientationChangeObj) { + allowOrientationChangeValue = [self boolFromParameters:params forKey:@"allowOrientationChange"]; + } + + // If we don't allow orientation change, we're locking the user into the current orientation. + if (!allowOrientationChangeValue) { + UIInterfaceOrientation currentOrientation = MPInterfaceOrientation(); + + if (UIInterfaceOrientationIsLandscape(currentOrientation)) { + forceOrientationMaskValue = UIInterfaceOrientationMaskLandscape; + } else if (currentOrientation == UIInterfaceOrientationPortrait) { + forceOrientationMaskValue = UIInterfaceOrientationMaskPortrait; + } else if (currentOrientation == UIInterfaceOrientationPortraitUpsideDown) { + forceOrientationMaskValue = UIInterfaceOrientationMaskPortraitUpsideDown; + } + } + } + + [self.delegate mrCommand:self setOrientationPropertiesWithForceOrientation:forceOrientationMaskValue]; + + return YES; +} +/* + * We allow setOrientationProperties to run while we're blocking requests because this command can occur during the presentation + * animation of an interstitial, and has a strong effect on how an ad is presented so we want to make sure it's executed. + * + * Even though we return YES here, updating orientation while blocking requests is not safe. MRController receives the appropriate + * delegate call, and caches the intended call in a block, which it executes when request-blocking is disabled. + */ +- (BOOL)executableWhileBlockingRequests +{ + return YES; +} + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation MROpenCommand + ++ (void)load +{ + [MRCommand registerCommand:self]; +} + ++ (NSString *)commandType +{ + return @"open"; +} + +- (BOOL)executeWithParams:(NSDictionary *)params +{ + [self.delegate mrCommand:self openURL:[self urlFromParameters:params forKey:@"url"]]; + + return YES; +} + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation MRCreateCalendarEventCommand + ++ (void)load +{ + [MRCommand registerCommand:self]; +} + ++ (NSString *)commandType +{ + return @"createCalendarEvent"; +} + +- (BOOL)executeWithParams:(NSDictionary *)params +{ + [self.delegate mrCommand:self createCalendarEventWithParams:params]; + + return YES; +} + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation MRPlayVideoCommand + ++ (void)load +{ + [MRCommand registerCommand:self]; +} + ++ (NSString *)commandType +{ + return @"playVideo"; +} + +- (BOOL)requiresUserInteractionForPlacementType:(NSUInteger)placementType +{ + // allow interstitials to auto-play video + return placementType != MRAdViewPlacementTypeInterstitial; +} + +- (BOOL)executeWithParams:(NSDictionary *)params +{ + [self.delegate mrCommand:self playVideoWithURL:[self urlFromParameters:params forKey:@"uri"]]; + + return YES; +} + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation MRStorePictureCommand + ++ (void)load +{ + [MRCommand registerCommand:self]; +} + ++ (NSString *)commandType +{ + return @"storePicture"; +} + +- (BOOL)executeWithParams:(NSDictionary *)params +{ + [self.delegate mrCommand:self storePictureWithURL:[self urlFromParameters:params forKey:@"uri"]]; + + return YES; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MRConstants.h b/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MRConstants.h new file mode 100644 index 00000000000..f994cd3469a --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MRConstants.h @@ -0,0 +1,24 @@ +// +// MRConstants.h +// MoPubSDK +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +enum { + MRAdViewStateHidden, + MRAdViewStateDefault, + MRAdViewStateExpanded, + MRAdViewStateResized +}; +typedef NSUInteger MRAdViewState; + +enum { + MRAdViewPlacementTypeInline, + MRAdViewPlacementTypeInterstitial +}; +typedef NSUInteger MRAdViewPlacementType; + +extern NSString *const kOrientationPropertyForceOrientationPortraitKey; +extern NSString *const kOrientationPropertyForceOrientationLandscapeKey; +extern NSString *const kOrientationPropertyForceOrientationNoneKey; diff --git a/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MRConstants.m b/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MRConstants.m new file mode 100644 index 00000000000..75fb85a9500 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MRConstants.m @@ -0,0 +1,12 @@ +// +// MRConstants.m +// MoPubSDK +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import + +NSString *const kOrientationPropertyForceOrientationPortraitKey = @"portrait"; +NSString *const kOrientationPropertyForceOrientationLandscapeKey = @"landscape"; +NSString *const kOrientationPropertyForceOrientationNoneKey = @"none"; diff --git a/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MRController.h b/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MRController.h new file mode 100644 index 00000000000..65c7e23221e --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MRController.h @@ -0,0 +1,76 @@ +// +// MRController.h +// MoPubSDK +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import +#import +#import "MRConstants.h" +#import "MPMRAIDInterstitialViewController.h" + +@protocol MRControllerDelegate; +@class MPAdConfiguration; +@class CLLocation; + +/** + * The `MRController` class is used to load and interact with MRAID ads. + * It contains two MRAID ad views and uses a separate `MRBridge` to + * communicate to each view. `MRController` handles view-related MRAID + * native calls such as expand(), resize(), close(), and open(). + */ +@interface MRController : NSObject + +@property (nonatomic, weak) id delegate; + +- (instancetype)initWithAdViewFrame:(CGRect)adViewFrame adPlacementType:(MRAdViewPlacementType)placementType; + +- (void)loadAdWithConfiguration:(MPAdConfiguration *)configuration; +- (void)handleMRAIDInterstitialDidPresentWithViewController:(MPMRAIDInterstitialViewController *)viewController; +- (void)enableRequestHandling; +- (void)disableRequestHandling; + +@end + +/** + * The `MRControllerDelegate` will relay specific events such as ad loading to + * the object that implements the delegate. It also requires information + * such as adUnitId, adConfiguation, and location in order to use its + * ad alert manager. + **/ +@protocol MRControllerDelegate + +@required + +- (NSString *)adUnitId; +- (MPAdConfiguration *)adConfiguration; +- (CLLocation *)location; + +// Retrieves the view controller from which modal views should be presented. +- (UIViewController *)viewControllerForPresentingModalView; + +// Called when the ad is about to display modal content (thus taking over the screen). +- (void)appShouldSuspendForAd:(UIView *)adView; + +// Called when the ad has dismissed any modal content (removing any on-screen takeovers). +- (void)appShouldResumeFromAd:(UIView *)adView; + +@optional + +// Called when the ad loads successfully. +- (void)adDidLoad:(UIView *)adView; + +// Called when the ad fails to load. +- (void)adDidFailToLoad:(UIView *)adView; + +// Called just before the ad closes. +- (void)adWillClose:(UIView *)adView; + +// Called just after the ad has closed. +- (void)adDidClose:(UIView *)adView; + +// Called after the rewarded video finishes playing +- (void)rewardedVideoEnded; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MRController.m b/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MRController.m new file mode 100644 index 00000000000..24b43d98a7c --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MRController.m @@ -0,0 +1,1176 @@ +// +// MRController.m +// MoPubSDK +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import "MRController.h" +#import "MRBridge.h" +#import "MRCommand.h" +#import "MRProperty.h" +#import "MPAdAlertManager.h" +#import "MPAdConfiguration.h" +#import "MPAdDestinationDisplayAgent.h" +#import "MRExpandModalViewController.h" +#import "MPCoreInstanceProvider.h" +#import "MPClosableView.h" +#import "MPGlobal.h" +#import "MPInstanceProvider.h" +#import "MPLogging.h" +#import "MPTimer.h" +#import "NSHTTPURLResponse+MPAdditions.h" +#import "NSURL+MPAdditions.h" +#import "UIWebView+MPAdditions.h" +#import "MPForceableOrientationProtocol.h" +#import "MPAPIEndPoints.h" + +static const NSTimeInterval kAdPropertyUpdateTimerInterval = 1.0; +static const NSTimeInterval kMRAIDResizeAnimationTimeInterval = 0.3; + +static NSString *const kMRAIDCommandExpand = @"expand"; +static NSString *const kMRAIDCommandResize = @"resize"; + +@interface MRController () + +@property (nonatomic) MPAdConfiguration *adConfiguration; +@property (nonatomic, strong) MRBridge *mraidBridge; +@property (nonatomic, strong) MRBridge *mraidBridgeTwoPart; +@property (nonatomic, strong) MPClosableView *mraidAdView; +@property (nonatomic, strong) MPClosableView *mraidAdViewTwoPart; +@property (nonatomic, strong) UIView *resizeBackgroundView; +@property (nonatomic, strong) MPTimer *adPropertyUpdateTimer; +@property (nonatomic, assign) MRAdViewPlacementType placementType; +@property (nonatomic, strong) MRExpandModalViewController *expandModalViewController; +@property (nonatomic, weak) MPMRAIDInterstitialViewController *interstitialViewController; +@property (nonatomic, strong) NSMutableData *twoPartExpandData; +@property (nonatomic, assign) NSStringEncoding responseEncoding; +@property (nonatomic, assign) CGRect mraidDefaultAdFrame; +@property (nonatomic, assign) CGRect mraidDefaultAdFrameInKeyWindow; +@property (nonatomic, assign) CGSize currentAdSize; +@property (nonatomic, assign) NSUInteger modalViewCount; +@property (nonatomic, assign) MRAdViewState currentState; +@property (nonatomic, assign) BOOL shouldUseUIWebView; +// Track the original super view for when we move the ad view to the key window for a 1-part expand. +@property (nonatomic, weak) UIView *originalSuperview; +@property (nonatomic, assign) BOOL isViewable; +@property (nonatomic, assign) BOOL isAnimatingAdSize; +@property (nonatomic, assign) BOOL isAdLoading; +// Whether or not an interstitial requires precaching. Does not affect banners. +@property (nonatomic, assign) BOOL adRequiresPrecaching; +@property (nonatomic, assign) BOOL isAdVastVideoPlayer; +@property (nonatomic, assign) BOOL firedReadyEventForDefaultAd; + +// Points to mraidAdView (one-part expand) or mraidAdViewTwoPart (two-part expand) while expanded. +@property (nonatomic, strong) MPClosableView *expansionContentView; + +@property (nonatomic, strong) MPAdDestinationDisplayAgent *destinationDisplayAgent; +@property (nonatomic, strong) id adAlertManager; +@property (nonatomic, strong) id adAlertManagerTwoPart; + +// Use UIInterfaceOrientationMaskALL to specify no forcing. +@property (nonatomic, assign) UIInterfaceOrientationMask forceOrientationMask; + +@property (nonatomic, assign) UIInterfaceOrientation currentInterfaceOrientation; + +@property (nonatomic, copy) void (^forceOrientationAfterAnimationBlock)(); + +@end + +@implementation MRController + +- (instancetype)initWithAdViewFrame:(CGRect)adViewFrame adPlacementType:(MRAdViewPlacementType)placementType +{ + if (self = [super init]) { + _placementType = placementType; + _currentState = MRAdViewStateDefault; + _forceOrientationMask = UIInterfaceOrientationMaskAll; + _isAnimatingAdSize = NO; + _firedReadyEventForDefaultAd = NO; + _currentAdSize = CGSizeZero; + + _mraidDefaultAdFrame = adViewFrame; + + _adPropertyUpdateTimer = [[MPCoreInstanceProvider sharedProvider] buildMPTimerWithTimeInterval:kAdPropertyUpdateTimerInterval + target:self + selector:@selector(updateMRAIDProperties) + repeats:YES]; + _adPropertyUpdateTimer.runLoopMode = NSRunLoopCommonModes; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(viewEnteredBackground) + name:UIApplicationDidEnterBackgroundNotification + object:nil]; + + //Setting the frame here is irrelevant - we update it whenever an ad resizes to match the + //application frame. + _resizeBackgroundView = [[UIView alloc] initWithFrame:adViewFrame]; + _resizeBackgroundView.backgroundColor = [UIColor clearColor]; + + + _destinationDisplayAgent = [[MPCoreInstanceProvider sharedProvider] buildMPAdDestinationDisplayAgentWithDelegate:self]; + + _adAlertManager = [[MPCoreInstanceProvider sharedProvider] buildMPAdAlertManagerWithDelegate:self]; + _adAlertManagerTwoPart = [[MPCoreInstanceProvider sharedProvider] buildMPAdAlertManagerWithDelegate:self]; + } + + return self; +} + +- (void)dealloc +{ + // Transfer delegation to the expand modal view controller in the event the modal is still being presented so it can dismiss itself. + _expansionContentView.delegate = _expandModalViewController; + + [_adPropertyUpdateTimer invalidate]; + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +#pragma mark - Public + +- (void)loadAdWithConfiguration:(MPAdConfiguration *)configuration +{ + self.adConfiguration = configuration; + self.isAdLoading = YES; + self.adRequiresPrecaching = configuration.precacheRequired; + self.isAdVastVideoPlayer = configuration.isVastVideoPlayer; + self.shouldUseUIWebView = self.isAdVastVideoPlayer || self.adConfiguration.forceUIWebView; + + MPWebView *webView = [self buildMRAIDWebViewWithFrame:self.mraidDefaultAdFrame + forceUIWebView:self.shouldUseUIWebView]; + + self.mraidBridge = [[MPInstanceProvider sharedProvider] buildMRBridgeWithWebView:webView delegate:self]; + self.mraidAdView = [[MPInstanceProvider sharedProvider] buildMRAIDMPClosableViewWithFrame:self.mraidDefaultAdFrame + webView:webView + delegate:self]; + if (self.placementType == MRAdViewPlacementTypeInterstitial) { + self.mraidAdView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + } + + [self initAdAlertManager:self.adAlertManager forAdView:self.mraidAdView]; + + // Initially turn off the close button for default banner MRAID ads while defaulting to turning it on for interstitials. + if (self.placementType == MRAdViewPlacementTypeInline) { + self.mraidAdView.closeButtonType = MPClosableViewCloseButtonTypeNone; + } else if (self.placementType == MRAdViewPlacementTypeInterstitial) { + self.mraidAdView.closeButtonType = MPClosableViewCloseButtonTypeTappableWithImage; + } + + // This load is guaranteed to never be called for a two-part expand so we know we need to load the HTML into the default web view. + NSString *HTML = [configuration adResponseHTMLString]; + [self.mraidBridge loadHTMLString:HTML + baseURL:[NSURL URLWithString:[MPAPIEndpoints baseURL]] + ]; +} + +- (void)handleMRAIDInterstitialDidPresentWithViewController:(MPMRAIDInterstitialViewController *)viewController +{ + self.interstitialViewController = viewController; + [self enableRequestHandling]; + [self checkViewability]; +} + +- (void)enableRequestHandling +{ + self.mraidBridge.shouldHandleRequests = YES; + self.mraidBridgeTwoPart.shouldHandleRequests = YES; + // If orientation has been forced while requests are disabled (during animation), we need to execute that command through the block forceOrientationAfterAnimationBlock() after the presentation completes. + if (self.forceOrientationAfterAnimationBlock) { + self.forceOrientationAfterAnimationBlock(); + self.forceOrientationAfterAnimationBlock = nil; + } +} + +- (void)disableRequestHandling +{ + self.mraidBridge.shouldHandleRequests = NO; + self.mraidBridgeTwoPart.shouldHandleRequests = NO; + [self.destinationDisplayAgent cancel]; +} + +#pragma mark - Loading Two Part Expand (NSURLConnectionDelegate) + +- (void)loadTwoPartCreativeFromURL:(NSURL *)url +{ + self.isAdLoading = YES; + + NSURLConnection *connection = [NSURLConnection connectionWithRequest:[NSURLRequest requestWithURL:url] delegate:self]; + if (connection) { + self.twoPartExpandData = [NSMutableData data]; + } +} + +- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response +{ + [self.twoPartExpandData setLength:0]; + + NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; + + NSDictionary *headers = [httpResponse allHeaderFields]; + NSString *contentType = [headers objectForKey:kMoPubHTTPHeaderContentType]; + self.responseEncoding = [httpResponse stringEncodingFromContentType:contentType]; +} + +- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data +{ + [self.twoPartExpandData appendData:data]; +} + +- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error +{ + self.isAdLoading = NO; + // No matter what, show the close button on the expanded view. + self.expansionContentView.closeButtonType = MPClosableViewCloseButtonTypeTappableWithImage; + [self.mraidBridge fireErrorEventForAction:kMRAIDCommandExpand withMessage:@"Could not load URL."]; +} + +- (void)connectionDidFinishLoading:(NSURLConnection *)connection +{ + NSString *str = [[NSString alloc] initWithData:self.twoPartExpandData encoding:self.responseEncoding]; + [self.mraidBridgeTwoPart loadHTMLString:str baseURL:connection.currentRequest.URL]; +} + +#pragma mark - Private + +- (void)initAdAlertManager:(id)adAlertManager forAdView:(MPClosableView *)adView +{ + adAlertManager.adConfiguration = [self.delegate adConfiguration]; + adAlertManager.adUnitId = [self.delegate adUnitId]; + adAlertManager.targetAdView = adView; + adAlertManager.location = [self.delegate location]; + [adAlertManager beginMonitoringAlerts]; +} + +- (MPClosableView *)adViewForBridge:(MRBridge *)bridge +{ + if (bridge == self.mraidBridgeTwoPart) { + return self.mraidAdViewTwoPart; + } + + return self.mraidAdView; +} + +- (MRBridge *)bridgeForAdView:(MPClosableView *)view +{ + if (view == self.mraidAdViewTwoPart) { + return self.mraidBridgeTwoPart; + } + + return self.mraidBridge; +} + +- (MPClosableView *)activeView +{ + if (self.currentState == MRAdViewStateExpanded) { + return self.expansionContentView; + } + + return self.mraidAdView; +} + +- (MRBridge *)bridgeForActiveAdView +{ + MRBridge *bridge = [self bridgeForAdView:[self activeView]]; + return bridge; +} + +- (MPWebView *)buildMRAIDWebViewWithFrame:(CGRect)frame forceUIWebView:(BOOL)forceUIWebView +{ + MPWebView *webView = [[MPWebView alloc] initWithFrame:frame forceUIWebView:forceUIWebView]; + webView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + webView.backgroundColor = [UIColor clearColor]; + webView.clipsToBounds = YES; + webView.opaque = NO; + [webView mp_setScrollable:NO]; + + return webView; +} + +#pragma mark - Orientation Notifications + +- (void)orientationDidChange:(NSNotification *)notification +{ + // We listen for device notification changes because at that point our ad's frame is in + // the correct state; however, MRAID updates should only happen when the interface changes, so the update logic is only executed if the interface orientation has changed. + + //MPInterfaceOrientation is guaranteed to be the new orientation at this point. + UIInterfaceOrientation newInterfaceOrientation = MPInterfaceOrientation(); + if (newInterfaceOrientation != self.currentInterfaceOrientation) { + // Update all properties and fire a size change event. + [self updateMRAIDProperties]; + + //According to MRAID Specs, a resized ad should close when there's an orientation change + //due to unpredictability of the new layout. + if (self.currentState == MRAdViewStateResized) { + [self close]; + } + + self.currentInterfaceOrientation = newInterfaceOrientation; + } +} + +#pragma mark - Executing Javascript + +- (void)initializeLoadedAdForBridge:(MRBridge *)bridge +{ + // Set up some initial properties so mraid can operate. + MPLogDebug(@"Injecting initial JavaScript state."); + NSArray *startingMraidProperties = @[[MRHostSDKVersionProperty defaultProperty], + [MRPlacementTypeProperty propertyWithType:self.placementType], + [MRSupportsProperty defaultProperty], + [MRStateProperty propertyWithState:self.currentState] + ]; + + [bridge fireChangeEventsForProperties:startingMraidProperties]; + + [self updateMRAIDProperties]; + + [bridge fireReadyEvent]; +} + +- (void)fireChangeEventToBothBridgesForProperty:(MRProperty *)property +{ + [self.mraidBridge fireChangeEventForProperty:property]; + [self.mraidBridgeTwoPart fireChangeEventForProperty:property]; +} + +#pragma mark - Resize Helpers + +- (CGRect)adjustedFrameForFrame:(CGRect)frame allowOffscreen:(BOOL)allowOffscreen +{ + if (allowOffscreen) { + return frame; + } + + CGRect applicationFrame = MPApplicationFrame(); + CGFloat applicationWidth = CGRectGetWidth(applicationFrame); + CGFloat applicationHeight = CGRectGetHeight(applicationFrame); + CGFloat adFrameWidth = CGRectGetWidth(frame); + CGFloat adFrameHeight = CGRectGetHeight(frame); + + //Checking that the ad's frame falls offscreen, and then it is smaller than the screen's bounds (so when + //moved onscreen, it will fit). If not, we bail out, and validation is done separately. + if (!CGRectContainsRect(applicationFrame, frame) && adFrameWidth <= applicationWidth && adFrameHeight <= applicationHeight) { + + CGFloat applicationMinX = CGRectGetMinX(applicationFrame); + CGFloat applicationMaxX = CGRectGetMaxX(applicationFrame); + CGFloat adFrameMinX = CGRectGetMinX(frame); + CGFloat adFrameMaxX = CGRectGetMaxX(frame); + + if (adFrameMinX < applicationMinX) { + frame.origin.x += applicationMinX - adFrameMinX; + } else if (adFrameMaxX > applicationMaxX) { + frame.origin.x -= adFrameMaxX - applicationMaxX; + } + + CGFloat applicationMinY = CGRectGetMinY(applicationFrame); + CGFloat applicationMaxY = CGRectGetMaxY(applicationFrame); + CGFloat adFrameMinY = CGRectGetMinY(frame); + CGFloat adFrameMaxY = CGRectGetMaxY(frame); + + if (adFrameMinY < applicationMinY) { + frame.origin.y += applicationMinY - adFrameMinY; + } else if (adFrameMaxY > applicationMaxY) { + frame.origin.y -= adFrameMaxY - applicationMaxY; + } + } + + return frame; +} + +- (BOOL)isValidResizeFrame:(CGRect)frame allowOffscreen:(BOOL)allowOffscreen +{ + BOOL valid = YES; + if (!allowOffscreen && !CGRectContainsRect(MPApplicationFrame(), frame)) { + valid = NO; + } else if (CGRectGetWidth(frame) < 50.0f || CGRectGetHeight(frame) < 50.0f) { + valid = NO; + } + + return valid; +} + +- (BOOL)isValidResizeCloseButtonPlacementInFrame:(CGRect)newFrame +{ + CGRect closeButtonFrameForResize = MPClosableViewCustomCloseButtonFrame(newFrame.size, self.mraidAdView.closeButtonLocation); + //Manually calculating Button's Frame in the window (newFrame's soon-to-be superview) because newFrame is not + //part of the view hierarchy yet. + CGRect closeButtonFrameInWindow = CGRectOffset(closeButtonFrameForResize, CGRectGetMinX(newFrame), CGRectGetMinY(newFrame)); + + return CGRectContainsRect(MPApplicationFrame(), closeButtonFrameInWindow); +} + +- (MPClosableViewCloseButtonLocation)adCloseButtonLocationFromString:(NSString *)closeButtonLocationString +{ + if ([closeButtonLocationString isEqualToString:@"top-left"]) { + return MPClosableViewCloseButtonLocationTopLeft; + } else if ([closeButtonLocationString isEqualToString:@"top-center"]) { + return MPClosableViewCloseButtonLocationTopCenter; + } else if ([closeButtonLocationString isEqualToString:@"bottom-left"]) { + return MPClosableViewCloseButtonLocationBottomLeft; + } else if ([closeButtonLocationString isEqualToString:@"bottom-center"]) { + return MPClosableViewCloseButtonLocationBottomCenter; + } else if ([closeButtonLocationString isEqualToString:@"bottom-right"]) { + return MPClosableViewCloseButtonLocationBottomRight; + } else if ([closeButtonLocationString isEqualToString:@"center"]) { + return MPClosableViewCloseButtonLocationCenter; + } else { + return MPClosableViewCloseButtonLocationTopRight; + } +} + +- (void)animateViewFromDefaultStateToResizedState:(MPClosableView *)view withFrame:(CGRect)newFrame +{ + [self willBeginAnimatingAdSize]; + + [UIView animateWithDuration:kMRAIDResizeAnimationTimeInterval animations:^{ + self.mraidAdView.frame = newFrame; + } completion:^(BOOL finished) { + [self changeStateTo:MRAdViewStateResized]; + [self didEndAnimatingAdSize]; + }]; +} + +#pragma mark - Expand Helpers + +- (void)presentExpandModalViewControllerWithView:(MPClosableView *)view animated:(BOOL)animated +{ + [self presentExpandModalViewControllerWithView:view animated:animated completion:nil]; +} + +- (void)presentExpandModalViewControllerWithView:(MPClosableView *)view animated:(BOOL)animated completion:(void (^)())completionBlock +{ + [self willBeginAnimatingAdSize]; + + self.expandModalViewController = [[MRExpandModalViewController alloc] initWithOrientationMask:self.forceOrientationMask]; + [self.expandModalViewController.view addSubview:view]; + view.frame = self.expandModalViewController.view.bounds; + view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + self.expandModalViewController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve; + [self.expandModalViewController hideStatusBar]; + + [[self.delegate viewControllerForPresentingModalView] presentViewController:self.expandModalViewController + animated:animated + completion:^{ + self.currentInterfaceOrientation = MPInterfaceOrientation(); + [self didEndAnimatingAdSize]; + + if (completionBlock) { + completionBlock(); + } + }]; +} + +- (void)willBeginAnimatingAdSize +{ + self.isAnimatingAdSize = YES; + [self disableRequestHandling]; +} + +- (void)didEndAnimatingAdSize +{ + self.isAnimatingAdSize = NO; + [self enableRequestHandling]; +} + +#pragma mark - Close Helpers + +- (void)close +{ + switch (self.currentState) { + case MRAdViewStateDefault: + [self closeFromDefaultState]; + break; + case MRAdViewStateExpanded: + [self closeFromExpandedState]; + break; + case MRAdViewStateResized: + [self closeFromResizedState]; + break; + case MRAdViewStateHidden: + break; + default: + break; + } +} + +- (void)closeFromDefaultState +{ + [self adWillClose]; + + self.mraidAdView.hidden = YES; + [self changeStateTo:MRAdViewStateHidden]; + + [self adDidClose]; +} + +- (void)closeFromExpandedState +{ + self.mraidAdView.closeButtonType = MPClosableViewCloseButtonTypeNone; + + // Immediately re-parent the ad so it will show up as the expand modal goes away rather than after. + [self.originalSuperview addSubview:self.mraidAdView]; + self.mraidAdView.frame = self.mraidDefaultAdFrame; + if (self.placementType != MRAdViewPlacementTypeInterstitial) { + self.mraidAdView.autoresizingMask = UIViewAutoresizingNone; + } + + // Track isAnimatingAdSize because we have a timer that will update the mraid ad properties. We don't want to examine our views when + // they're in a transitional state. + [self willBeginAnimatingAdSize]; + + // Tell the modal view controller to restore the state of the status bar back to what the application had it set to. + [self.expandModalViewController restoreStatusBarVisibility]; + __weak __typeof__(self) weakSelf = self; + [self.expandModalViewController dismissViewControllerAnimated:YES completion:^{ + __typeof__(self) strongSelf = weakSelf; + + + [strongSelf didEndAnimatingAdSize]; + [strongSelf adDidDismissModalView]; + + // Get rid of the bridge and view if we are closing from two-part expand. + if (strongSelf.mraidAdViewTwoPart) { + [strongSelf.adAlertManagerTwoPart endMonitoringAlerts]; + strongSelf.mraidAdViewTwoPart = nil; + strongSelf.mraidBridgeTwoPart = nil; + } + + strongSelf.expansionContentView = nil; + strongSelf.expandModalViewController = nil; + + // Waiting this long to change the state results in some awkward animation. The full screen ad will briefly appear in the banner's + // frame after the modal dismisses. However, this is a much safer time to change the state and results in less side effects. + [strongSelf changeStateTo:MRAdViewStateDefault]; + }]; +} + +- (void)closeFromResizedState +{ + self.mraidAdView.closeButtonType = MPClosableViewCloseButtonTypeNone; + + [self willBeginAnimatingAdSize]; + + [UIView animateWithDuration:kMRAIDResizeAnimationTimeInterval animations:^{ + self.mraidAdView.frame = self.mraidDefaultAdFrameInKeyWindow; + } completion:^(BOOL finished) { + [self.resizeBackgroundView removeFromSuperview]; + [self.originalSuperview addSubview:self.mraidAdView]; + self.mraidAdView.frame = self.mraidDefaultAdFrame; + [self changeStateTo:MRAdViewStateDefault]; + [self didEndAnimatingAdSize]; + [self adDidDismissModalView]; + }]; +} + +#pragma mark - + +- (BOOL)isLoadingAd +{ + return self.isAdLoading; +} + +- (BOOL)hasUserInteractedWithWebViewForBridge:(MRBridge *)bridge +{ + // inline videos seem to delay tap gesture recognition so that we get the click through + // request in the webview delegate BEFORE we get the gesture recognizer triggered callback. For now + // excuse all MRAID interstitials from the user interaction requirement. We do the same for expanded ads. + // We can go ahead and return true for either bridge (default, expanded) if the current state is expanded since + // the fact we're in expanded means that the first webview has been tapped. Then we just don't require the check + // for the expanded bridge. + if (self.placementType == MRAdViewPlacementTypeInterstitial || self.currentState == MRAdViewStateExpanded) { + return YES; + } + + MPClosableView *adView = [self adViewForBridge:bridge]; + return adView.wasTapped; +} + +- (UIViewController *)viewControllerForPresentingModalView +{ + UIViewController *delegateVC = [self.delegate viewControllerForPresentingModalView]; + + // Use the expand modal view controller as the presenting modal if it's being presented. + if (self.expandModalViewController.presentingViewController != nil) { + return self.expandModalViewController; + } + + return delegateVC; +} + +- (void)nativeCommandWillPresentModalView +{ + [self adWillPresentModalView]; +} + +- (void)nativeCommandDidDismissModalView +{ + [self adDidDismissModalView]; +} + +- (void)bridge:(MRBridge *)bridge didFinishLoadingWebView:(MPWebView *)webView +{ + // Loading an iframe can cause this method to execute and could potentially cause us to initialize the javascript for a two-part expand + // and fire the ready event more than once. The isAdLoading flags helps us prevent that from happening. + if (self.isAdLoading) { + + self.isAdLoading = NO; + + if (!self.adRequiresPrecaching) { + // Only tell the delegate that the ad loaded when the view is the default ad view and not a two-part ad view. + if (bridge == self.mraidBridge) { + // We do not intialize the javascript/fire ready event, or start our timer for a banner load yet. We wait until + // the ad is in the view hierarchy. We are notified by the view when it is potentially added to the hierarchy in + // -closableView:didMoveToWindow:. + [self adDidLoad]; + } else if (bridge == self.mraidBridgeTwoPart) { + // If the default ad was already viewable, we need to simply tell the two part it is viewable. Otherwise, if the default + // ad wasn't viewable, we need to update the state across both webviews and the controller. + if (self.isViewable) { + [self.mraidBridgeTwoPart fireChangeEventForProperty:[MRViewableProperty propertyWithViewable:YES]]; + } else { + [self updateViewabilityWithBool:YES]; + } + + // We initialize javascript and fire the ready event for the two part ad view once it loads + // since it'll already be in the view hierarchy. + [self initializeLoadedAdForBridge:bridge]; + } + } + } +} + +- (void)bridge:(MRBridge *)bridge didFailLoadingWebView:(MPWebView *)webView error:(NSError *)error +{ + self.isAdLoading = NO; + + if (bridge == self.mraidBridge) { + // We need to report that the ad failed to load when the default ad fails to load. + [self adDidFailToLoad]; + } else if (bridge == self.mraidBridgeTwoPart) { + // Always show the close button when the two-part expand fails. + self.expansionContentView.closeButtonType = MPClosableViewCloseButtonTypeTappableWithImage; + + // For two-part expands, we don't want to tell the delegate anything went wrong since the ad did successfully load. + // We will fire an error to the javascript though. + [self.mraidBridge fireErrorEventForAction:kMRAIDCommandExpand withMessage:@"Could not load URL."]; + } +} + +- (void)bridge:(MRBridge *)bridge performActionForMoPubSpecificURL:(NSURL *)url +{ + MPLogDebug(@"MRController - loading MoPub URL: %@", url); + MPMoPubHostCommand command = [url mp_mopubHostCommand]; + if (command == MPMoPubHostCommandPrecacheComplete && self.adRequiresPrecaching) { + [self adDidLoad]; + } else if (command == MPMoPubHostCommandFailLoad) { + [self adDidFailToLoad]; + } else if (command == MPMoPubHostCommandRewardedVideoEnded) { + [self.delegate rewardedVideoEnded]; + } else { + MPLogWarn(@"MRController - unsupported MoPub URL: %@", [url absoluteString]); + } +} + +- (void)handleNativeCommandCloseWithBridge:(MRBridge *)bridge +{ + [self close]; +} + +- (void)bridge:(MRBridge *)bridge handleDisplayForDestinationURL:(NSURL *)URL +{ + if ([self hasUserInteractedWithWebViewForBridge:bridge]) { + [self.destinationDisplayAgent displayDestinationForURL:URL]; + } +} + +- (void)bridge:(MRBridge *)bridge handleNativeCommandUseCustomClose:(BOOL)useCustomClose +{ + // Calling useCustomClose() for banners won't take effect until expand() is called so we don't need to take + // any action here as useCustomClose will be given to us when expand is called. Interstitials can have their + // close buttons changed at any time though. + if (self.placementType != MRAdViewPlacementTypeInterstitial) { + return; + } + + [self configureCloseButtonForView:self.mraidAdView forUseCustomClose:useCustomClose]; +} + +- (void)configureCloseButtonForView:(MPClosableView *)view forUseCustomClose:(BOOL)useCustomClose +{ + if (useCustomClose) { + // When using custom close, we must leave a tappable region on the screen and just hide the image + // unless the ad is a vast video ad. For vast video, we expect that the creative will have a tappable + // close region. + if (self.isAdVastVideoPlayer) { + view.closeButtonType = MPClosableViewCloseButtonTypeNone; + } else { + view.closeButtonType = MPClosableViewCloseButtonTypeTappableWithoutImage; + } + } else { + // When not using custom close, show our own image with a tappable region. + view.closeButtonType = MPClosableViewCloseButtonTypeTappableWithImage; + } +} + +- (void)bridge:(MRBridge *)bridge handleNativeCommandSetOrientationPropertiesWithForceOrientationMask:(UIInterfaceOrientationMask)forceOrientationMask +{ + // If the ad is trying to force an orientation that the app doesn't support, we shouldn't try to force the orientation. + if (![[UIApplication sharedApplication] mp_supportsOrientationMask:forceOrientationMask]) { + return; + } + + BOOL inExpandedState = self.currentState == MRAdViewStateExpanded; + + // If we aren't expanded or showing an interstitial ad, we don't have to force orientation on our ad. + if (!inExpandedState && self.placementType != MRAdViewPlacementTypeInterstitial) { + return; + } + + // If request handling is paused, we want to queue up this method to be called again when they are re-enabled. + if (!bridge.shouldHandleRequests) { + __weak __typeof__(self) weakSelf = self; + self.forceOrientationAfterAnimationBlock = ^void() { + __typeof__(self) strongSelf = weakSelf; + [strongSelf bridge:bridge handleNativeCommandSetOrientationPropertiesWithForceOrientationMask:forceOrientationMask]; + }; + return; + } + + // By this point, we've committed to forcing the orientation so we don't need a forceOrientationAfterAnimationBlock. + self.forceOrientationAfterAnimationBlock = nil; + self.forceOrientationMask = forceOrientationMask; + + BOOL inSameOrientation = [[UIApplication sharedApplication] mp_doesOrientation:MPInterfaceOrientation() matchOrientationMask:forceOrientationMask]; + UIViewController *fullScreenAdViewController = inExpandedState ? self.expandModalViewController : self.interstitialViewController; + + // If we're currently in the force orientation, we don't need to do any rotation. However, we still need to make sure + // that the view controller knows to use the forced orientation when the user rotates the device. + if (inSameOrientation) { + fullScreenAdViewController.supportedOrientationMask = forceOrientationMask; + } else { + // It doesn't seem possible to force orientation in iOS 7+. So we dismiss the current view controller and re-present it with the forced orientation. + // If it's an expanded ad, we need to restore the status bar visibility before we dismiss the current VC since we don't show the status bar in expanded state. + if (inExpandedState) { + [self.expandModalViewController restoreStatusBarVisibility]; + } + + // Block our timer from updating properties while we force orientation on the view controller. + [self willBeginAnimatingAdSize]; + + UIViewController *presentingViewController = fullScreenAdViewController.presentingViewController; + __weak __typeof__(self) weakSelf = self; + [fullScreenAdViewController dismissViewControllerAnimated:NO completion:^{ + __typeof__(self) strongSelf = weakSelf; + + if (inExpandedState) { + [strongSelf didEndAnimatingAdSize]; + + // If expanded, we don't need to change the state of the ad once the modal is present here as the ad is technically + // always in the expanded state throughout the process of dismissing and presenting. + [strongSelf presentExpandModalViewControllerWithView:strongSelf.expansionContentView animated:NO completion:^{ + [strongSelf updateMRAIDProperties]; + }]; + } else { + fullScreenAdViewController.supportedOrientationMask = forceOrientationMask; + [presentingViewController presentViewController:fullScreenAdViewController animated:NO completion:^{ + [strongSelf didEndAnimatingAdSize]; + strongSelf.currentInterfaceOrientation = MPInterfaceOrientation(); + [strongSelf updateMRAIDProperties]; + }]; + } + }]; + } +} + +- (void)bridge:(MRBridge *)bridge handleNativeCommandExpandWithURL:(NSURL *)url useCustomClose:(BOOL)useCustomClose +{ + if (self.placementType != MRAdViewPlacementTypeInline) { + [bridge fireErrorEventForAction:kMRAIDCommandExpand withMessage:@"Cannot expand from interstitial ads."]; + return; + } + + // Save the state of the default ad view if it's in default state. If it's resized, the controller has already + // been informed of a modal being presented on resize, and the expand basically takes its place. Additionally, + // self.mraidDefaultAdFrame has already been set from resize, and the mraidAdView's frame is not the correct default. + if (self.currentState != MRAdViewStateResized) { + self.mraidDefaultAdFrame = self.mraidAdView.frame; + [self adWillPresentModalView]; + } else { + [self.resizeBackgroundView removeFromSuperview]; + } + + // We change the state after the modal is fully presented which results in an undesirable animation where the banner will briefly appear in the modal which then + // will instantly change to the full screen ad. However, it is far safer to update the state like this and has less side effects. + if (url) { + // It doesn't matter what frame we use for the two-part expand. We'll overwrite it with a new frame when presenting the modal. + CGRect twoPartFrame = self.mraidAdView.frame; + + MPWebView *twoPartWebView = [self buildMRAIDWebViewWithFrame:twoPartFrame forceUIWebView:self.shouldUseUIWebView]; + self.mraidBridgeTwoPart = [[MPInstanceProvider sharedProvider] buildMRBridgeWithWebView:twoPartWebView delegate:self]; + self.mraidAdViewTwoPart = [[MPInstanceProvider sharedProvider] buildMRAIDMPClosableViewWithFrame:twoPartFrame webView:twoPartWebView delegate:self]; + self.isAdLoading = YES; + + self.expansionContentView = self.mraidAdViewTwoPart; + + // To avoid race conditions, we start loading the two part creative after the ad has fully expanded. + [self presentExpandModalViewControllerWithView:self.expansionContentView animated:YES completion:^{ + [self initAdAlertManager:self.adAlertManagerTwoPart forAdView:self.mraidAdViewTwoPart]; + [self loadTwoPartCreativeFromURL:url]; + [self changeStateTo:MRAdViewStateExpanded]; + }]; + } else { + self.expansionContentView = self.mraidAdView; + //If the ad is resized, the original superview has already been set. + if (self.currentState != MRAdViewStateResized) { + self.originalSuperview = self.mraidAdView.superview; + } + [self presentExpandModalViewControllerWithView:self.expansionContentView animated:YES completion:^{ + [self changeStateTo:MRAdViewStateExpanded]; + }]; + } + + [self configureCloseButtonForView:self.expansionContentView forUseCustomClose:useCustomClose]; +} + +- (void)bridge:(MRBridge *)bridge handleNativeCommandResizeWithParameters:(NSDictionary *)parameters +{ + NSArray *parameterKeys = [parameters allKeys]; + if (self.currentState == MRAdViewStateExpanded) { + [bridge fireErrorEventForAction:kMRAIDCommandResize withMessage:@"Cannot resize from and expanded state."]; + return; + } else if (self.placementType != MRAdViewPlacementTypeInline) { + [bridge fireErrorEventForAction:kMRAIDCommandResize withMessage:@"Cannot resize from interstitial ads."]; + return; + } else if (![parameterKeys containsObject:@"width"] || ![parameterKeys containsObject:@"height"] || ![parameterKeys containsObject:@"offsetX"] || ![parameterKeys containsObject:@"offsetY"]) { + [bridge fireErrorEventForAction:kMRAIDCommandResize withMessage:@"Cannot resize when missing required parameter(s)."]; + return; + } + + CGFloat width = [[parameters objectForKey:@"width"] floatValue]; + CGFloat height = [[parameters objectForKey:@"height"] floatValue]; + CGFloat offsetX = [[parameters objectForKey:@"offsetX"] floatValue]; + CGFloat offsetY = [[parameters objectForKey:@"offsetY"] floatValue]; + BOOL allowOffscreen = [parameters objectForKey:@"allowOffscreen"] ? [[parameters objectForKey:@"allowOffscreen"] boolValue] : YES; + NSString *customClosePositionString = [[parameters objectForKey:@"customClosePosition"] length] ? [parameters objectForKey:@"customClosePosition"] : @"top-right"; + + //save default frame of the ad view + if (self.currentState == MRAdViewStateDefault) { + self.mraidDefaultAdFrameInKeyWindow = [self.mraidAdView.superview convertRect:self.mraidAdView.frame toView:MPKeyWindow().rootViewController.view]; + } + + CGRect newFrame = CGRectMake(CGRectGetMinX(self.mraidDefaultAdFrameInKeyWindow) + offsetX, CGRectGetMinY(self.mraidDefaultAdFrameInKeyWindow) + offsetY, width, height); + newFrame = [self adjustedFrameForFrame:newFrame allowOffscreen:allowOffscreen]; + + self.mraidAdView.closeButtonType = MPClosableViewCloseButtonTypeTappableWithoutImage; + self.mraidAdView.closeButtonLocation = [self adCloseButtonLocationFromString:customClosePositionString]; + + if (![self isValidResizeFrame:newFrame allowOffscreen:allowOffscreen]) { + [self.mraidBridge fireErrorEventForAction:kMRAIDCommandResize withMessage:@"Could not display desired frame in compliance with MRAID 2.0 specifications."]; + } else if (![self isValidResizeCloseButtonPlacementInFrame:newFrame]) { + [self.mraidBridge fireErrorEventForAction:kMRAIDCommandResize withMessage:@"Custom close event region is offscreen."]; + } else { + // If current state is default, save our current frame as the default frame, set originalSuperview, setup resizeBackgroundView, + // move mraidAdView to rootViewController's view, and call adWillPresentModalView + if (self.currentState == MRAdViewStateDefault) { + self.mraidDefaultAdFrame = self.mraidAdView.frame; + self.originalSuperview = self.mraidAdView.superview; + + self.mraidAdView.frame = self.mraidDefaultAdFrameInKeyWindow; + self.resizeBackgroundView.frame = MPApplicationFrame(); + + [MPKeyWindow().rootViewController.view addSubview:self.resizeBackgroundView]; + [MPKeyWindow().rootViewController.view addSubview:self.mraidAdView]; + + [self adWillPresentModalView]; + } + + [self animateViewFromDefaultStateToResizedState:self.mraidAdView withFrame:newFrame]; + } +} + +#pragma mark - + +- (void)closeButtonPressed:(MPClosableView *)view +{ + [self close]; +} + +- (void)closableView:(MPClosableView *)closableView didMoveToWindow:(UIWindow *)window +{ + // Fire the ready event and initialize properties if the view has a window. + MRBridge *bridge = [self bridgeForAdView:closableView]; + + if (!self.firedReadyEventForDefaultAd && bridge == self.mraidBridge) { + // The window may be nil if it was removed from a window or added to a view that isn't attached to a window so make sure it actually has a window. + if (window != nil) { + // Just in case this code is executed twice, ensures that self is only added as + // an observer once. + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIDeviceOrientationDidChangeNotification object:nil]; + + //Keep track of the orientation before we start observing changes. + self.currentInterfaceOrientation = MPInterfaceOrientation(); + + // Placing orientation notification observing here ensures that the controller only + // observes changes after it's been added to the view hierarchy. Subscribing to + // orientation changes so we can notify the javascript about the new screen size. + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(orientationDidChange:) + name:UIDeviceOrientationDidChangeNotification + object:nil]; + + [self.adPropertyUpdateTimer scheduleNow]; + [self initializeLoadedAdForBridge:bridge]; + self.firedReadyEventForDefaultAd = YES; + } + } +} + +#pragma mark - + +- (void)displayAgentWillPresentModal +{ + [self adWillPresentModalView]; +} + +- (void)displayAgentDidDismissModal +{ + [self adDidDismissModalView]; +} + +- (void)displayAgentWillLeaveApplication +{ + // Do nothing. +} + +// - (MPAdConfiguration *)adConfiguration delegate method is automatically implemented via the adConfiguration property declaration. + +#pragma mark - Property Updating + +- (void)updateMRAIDProperties +{ + // Don't need to update mraid properties while animating as they'll be set correctly when the animations start/finish and it + // requires a bit of extra state logic to handle. We also don't want to check if the ad is visible during animation because + // the view is transitioning to a parent view that may or may not be on screen at any given time. + if (!self.isAnimatingAdSize) { + [self checkViewability]; + [self updateCurrentPosition]; + [self updateDefaultPosition]; + [self updateScreenSize]; + [self updateMaxSize]; + [self updateEventSizeChange]; + } +} + +- (CGRect)activeAdFrameInScreenSpace +{ + CGRect visibleFrame = CGRectZero; + + if (self.placementType == MRAdViewPlacementTypeInline) { + if (self.currentState == MRAdViewStateExpanded) { + // We're in a modal so we can just return the expanded view's frame. + visibleFrame = self.expansionContentView.frame; + } else { + UIWindow *keyWindow = MPKeyWindow(); + visibleFrame = [self.mraidAdView.superview convertRect:self.mraidAdView.frame toView:keyWindow.rootViewController.view]; + } + } else if (self.placementType == MRAdViewPlacementTypeInterstitial) { + visibleFrame = self.mraidAdView.frame; + } + + return visibleFrame; +} + +- (CGRect)defaultAdFrameInScreenSpace +{ + CGRect defaultFrame = CGRectZero; + + if (self.placementType == MRAdViewPlacementTypeInline) { + UIWindow *keyWindow = MPKeyWindow(); + if (self.expansionContentView == self.mraidAdViewTwoPart) { + defaultFrame = [self.mraidAdView.superview convertRect:self.mraidAdView.frame toView:keyWindow.rootViewController.view]; + } else { + defaultFrame = [self.originalSuperview convertRect:self.mraidDefaultAdFrame toView:keyWindow.rootViewController.view]; + } + } else if (self.placementType == MRAdViewPlacementTypeInterstitial) { + defaultFrame = self.mraidAdView.frame; + } + + return defaultFrame; +} + +- (void)updateCurrentPosition +{ + CGRect frame = [self activeAdFrameInScreenSpace]; + + // Only fire to the active ad view. + MRBridge *activeBridge = [self bridgeForActiveAdView]; + [activeBridge fireSetCurrentPositionWithPositionRect:frame]; + + MPLogTrace(@"Current Position: %@", NSStringFromCGRect(frame)); +} + +- (void)updateDefaultPosition +{ + CGRect defaultFrame = [self defaultAdFrameInScreenSpace]; + + // Not necessary to fire to both ad views, but it's better that the two-part expand knows the default position than not. + [self.mraidBridge fireSetDefaultPositionWithPositionRect:defaultFrame]; + [self.mraidBridgeTwoPart fireSetDefaultPositionWithPositionRect:defaultFrame]; + + MPLogTrace(@"Default Position: %@", NSStringFromCGRect(defaultFrame)); +} + +- (void)updateScreenSize +{ + // Fire an event for screen size changing. This includes the area of the status bar in its calculation. + CGSize screenSize = MPScreenBounds().size; + + // Fire to both ad views as it pertains to both views. + [self.mraidBridge fireSetScreenSize:screenSize]; + [self.mraidBridgeTwoPart fireSetScreenSize:screenSize]; + + MPLogTrace(@"Screen Size: %@", NSStringFromCGSize(screenSize)); +} + +- (void)updateMaxSize +{ + // Similar to updateScreenSize except this doesn't include the area of the status bar in its calculation. + CGSize maxSize = MPApplicationFrame().size; + + // Fire to both ad views as it pertains to both views. + [self.mraidBridge fireSetMaxSize:maxSize]; + [self.mraidBridgeTwoPart fireSetMaxSize:maxSize]; + + MPLogTrace(@"Max Size: %@", NSStringFromCGSize(maxSize)); +} + +#pragma mark - MRAID events + +- (void)updateEventSizeChange +{ + CGSize adSize = [self activeAdFrameInScreenSpace].size; + + // Firing the size change event will broadcast the event to the ad. The ad may subscribe to this event and + // perform some action when it receives the event. As a result, we don't want to have the ad do any work + // when the size hasn't changed. So we make sure we don't fire the size change event unless the size has + // actually changed. We don't place similar guards around updating properties that don't broadcast events + // since the ad won't be notified when we update the properties. Thus, the ad can't do any unnecessary work + // when we update other properties. + if (!CGSizeEqualToSize(adSize, self.currentAdSize)) { + MRBridge *activeBridge = [self bridgeForActiveAdView]; + self.currentAdSize = adSize; + + MPLogDebug(@"Ad Size (Size Event): %@", NSStringFromCGSize(self.currentAdSize)); + [activeBridge fireSizeChangeEvent:adSize]; + } +} + +- (void)changeStateTo:(MRAdViewState)state +{ + self.currentState = state; + + // Update the mraid properties so they're ready before the state change happens. + [self updateMRAIDProperties]; + [self fireChangeEventToBothBridgesForProperty:[MRStateProperty propertyWithState:self.currentState]]; +} + +#pragma mark - Viewability Helpers + +- (void)checkViewability +{ + BOOL viewable = MPViewIsVisible([self activeView]) && + ([[UIApplication sharedApplication] applicationState] == UIApplicationStateActive); + [self updateViewabilityWithBool:viewable]; +} + +- (void)viewEnteredBackground +{ + [self updateViewabilityWithBool:NO]; +} + +- (void)updateViewabilityWithBool:(BOOL)currentViewability +{ + if (self.isViewable != currentViewability) + { + MPLogDebug(@"Viewable changed to: %@", currentViewability ? @"YES" : @"NO"); + self.isViewable = currentViewability; + + // Both views in two-part expand need to report if they're viewable or not depending on the active one. + [self fireChangeEventToBothBridgesForProperty:[MRViewableProperty propertyWithViewable:self.isViewable]]; + } +} + +#pragma mark - + +- (UIViewController *)viewControllerForPresentingMailVC +{ + return [self viewControllerForPresentingModalView]; +} + +- (void)adAlertManagerDidTriggerAlert:(MPAdAlertManager *)manager +{ + [manager processAdAlertOnce]; +} + +#pragma mark - Delegation Wrappers + +- (void)adDidLoad +{ + if ([self.delegate respondsToSelector:@selector(adDidLoad:)]) { + [self.delegate adDidLoad:self.mraidAdView]; + } +} + +- (void)adDidFailToLoad +{ + if ([self.delegate respondsToSelector:@selector(adDidFailToLoad:)]) { + [self.delegate adDidFailToLoad:self.mraidAdView]; + } +} + +- (void)adWillClose +{ + if ([self.delegate respondsToSelector:@selector(adWillClose:)]) { + [self.delegate adWillClose:self.mraidAdView]; + } +} + +- (void)adDidClose +{ + if ([self.delegate respondsToSelector:@selector(adDidClose:)]) { + [self.delegate adDidClose:self.mraidAdView]; + } +} + +- (void)adWillPresentModalView +{ + self.modalViewCount++; + if (self.modalViewCount == 1) { + [self appShouldSuspend]; + } +} + +- (void)adDidDismissModalView +{ + self.modalViewCount--; + if (self.modalViewCount == 0) { + [self appShouldResume]; + } +} + +- (void)appShouldSuspend +{ + if ([self.delegate respondsToSelector:@selector(appShouldSuspendForAd:)]) { + [self.delegate appShouldSuspendForAd:self.mraidAdView]; + } +} + +- (void)appShouldResume +{ + if ([self.delegate respondsToSelector:@selector(appShouldResumeFromAd:)]) { + [self.delegate appShouldResumeFromAd:self.mraidAdView]; + } +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MRError.h b/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MRError.h new file mode 100644 index 00000000000..eeb10539540 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MRError.h @@ -0,0 +1,15 @@ +// +// MRError.h +// MoPubSDK +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import + +extern NSString * const MoPubMRAIDAdsSDKDomain; + +enum { + MRErrorMRAIDJSNotFound +}; +typedef NSInteger MRErrorCode; diff --git a/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MRError.m b/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MRError.m new file mode 100644 index 00000000000..6df5d883bdb --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MRError.m @@ -0,0 +1,10 @@ +// +// MRError.m +// MoPubSDK +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import "MRError.h" + +NSString * const MoPubMRAIDAdsSDKDomain = @"com.mopub.iossdk.mraid"; diff --git a/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MRExpandModalViewController.h b/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MRExpandModalViewController.h new file mode 100644 index 00000000000..63cc829a32d --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MRExpandModalViewController.h @@ -0,0 +1,41 @@ +// +// MRExpandModalViewController.h +// MoPubSDK +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import +#import "MPClosableView.h" +#import "MPForceableOrientationProtocol.h" +@protocol MRExpandModalViewControllerDelegate; + +/** + * `MRExpandModalViewController` is specifically for presenting expanded MRAID ads. Its orientation can be + * forced via the orientationMask property. + */ +@interface MRExpandModalViewController : UIViewController + +/** + * Initializes the view controller with an orientation mask that defines what orientation + * the view controller supports. When using an orientation mask on initialization, the view controlller + * will force the orientation of the device to match the orientation mask if the app supports it. + */ +- (instancetype)initWithOrientationMask:(UIInterfaceOrientationMask)orientationMask; + +/** + * Hides the status bar when called. Every call to hideStatusBar should be matched with a call to + * restoreStatusBarVisibility. That is, each time hideStatusBar is called, restoreStatusBarVisibility + * must be called before calling hideStatusBar again. If the methods aren't called in the correct order, + * consecutive calls to this method become no ops. + */ +- (void)hideStatusBar; + +/** + * This will set the visibility of the status bar based on whether or not the status bar was hidden when hideStatusBar was called. + * A call to this method should be matched with a call to hideStatusBar. That is, each call to restoreStatusBarVisibility should + * be preceded by a call to hideStatusBar. Calling this method consecutively will not affect the status bar beyond the first call. + */ +- (void)restoreStatusBarVisibility; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MRExpandModalViewController.m b/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MRExpandModalViewController.m new file mode 100644 index 00000000000..1ec84cafc86 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MRExpandModalViewController.m @@ -0,0 +1,110 @@ +// +// MRExpandModalViewController.m +// MoPubSDK +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import "MRExpandModalViewController.h" +#import "MPGlobal.h" + +@interface MRExpandModalViewController () + +@property (nonatomic, assign) BOOL statusBarHidden; +@property (nonatomic, assign) BOOL applicationHidesStatusBar; +@property (nonatomic, assign) UIInterfaceOrientationMask supportedOrientationMask; + +@end + +@implementation MRExpandModalViewController + +- (instancetype)initWithOrientationMask:(UIInterfaceOrientationMask)orientationMask +{ + if (self = [super init]) { + _supportedOrientationMask = orientationMask; + } + + return self; +} + +- (void)viewDidLoad +{ + [super viewDidLoad]; + + self.view.backgroundColor = [UIColor blackColor]; +} + +- (void)hideStatusBar +{ + if (!self.statusBarHidden) { + self.statusBarHidden = YES; + self.applicationHidesStatusBar = [UIApplication sharedApplication].statusBarHidden; + + // pre-ios 7 hiding status bar + [[UIApplication sharedApplication] mp_preIOS7setApplicationStatusBarHidden:YES]; + + // In the event we come back to this view controller from another modal, we need to update the status bar's + // visibility again in ios 7/8. + if ([self respondsToSelector:@selector(setNeedsStatusBarAppearanceUpdate)]) { + [self setNeedsStatusBarAppearanceUpdate]; + } + } +} + +- (void)restoreStatusBarVisibility +{ + self.statusBarHidden = self.applicationHidesStatusBar; + + // pre-ios 7 restoring the status bar + [[UIApplication sharedApplication] mp_preIOS7setApplicationStatusBarHidden:self.applicationHidesStatusBar]; + + // ios 7/8 restoring status bar + if ([self respondsToSelector:@selector(setNeedsStatusBarAppearanceUpdate)]) { + [self setNeedsStatusBarAppearanceUpdate]; + } +} + +- (BOOL)prefersStatusBarHidden +{ + // ios 7 hiding status bar + return self.statusBarHidden; +} + +- (void)setSupportedOrientationMask:(UIInterfaceOrientationMask)supportedOrientationMask +{ + _supportedOrientationMask = supportedOrientationMask; + + [UIViewController attemptRotationToDeviceOrientation]; +} + +// supportedInterfaceOrientations and shouldAutorotate are for ios 6, 7, and 8. +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= MP_IOS_9_0 +- (UIInterfaceOrientationMask)supportedInterfaceOrientations +#else +- (NSUInteger)supportedInterfaceOrientations +#endif +{ + return ([[UIApplication sharedApplication] mp_supportsOrientationMask:self.supportedOrientationMask]) ? self.supportedOrientationMask : UIInterfaceOrientationMaskAll; +} + +- (BOOL)shouldAutorotate +{ + return YES; +} + +// shouldAutorotateToInterfaceOrientation is for ios 5. +- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation +{ + return [[UIApplication sharedApplication] mp_doesOrientation:interfaceOrientation matchOrientationMask:self.supportedOrientationMask]; +} + +#pragma mark - + +// We transfer closable view delegation to the expand view controller in the event MRController is deallocated and the expand modal is presented. +- (void)closeButtonPressed:(MPClosableView *)closableView +{ + // All we need to do is dismiss ourself. + [self dismissViewControllerAnimated:YES completion:nil]; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MRNativeCommandHandler.h b/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MRNativeCommandHandler.h new file mode 100644 index 00000000000..656bf57a093 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MRNativeCommandHandler.h @@ -0,0 +1,49 @@ +// +// MRNativeCommandHandler.h +// MoPubSDK +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import +#import +#import "MRConstants.h" + +@class MRCommand; +@protocol MRNativeCommandHandlerDelegate; + +/** + * The `MRNativeCommandHandler` class is an object that encapsulates functionality that processes, + * and where possible, executes MRAID commands. + */ +@interface MRNativeCommandHandler : NSObject + +- (instancetype)initWithDelegate:(id)delegate; +- (void)handleNativeCommand:(NSString *)command withProperties:(NSDictionary *)properties; + +@end + +/** + * The delegate of an `MRNativeCommandHandler` object that implements `MRNativeCommandHandlerDelegate` + * must provide information and a view controller that allow the `MRNativeCommandHandler` to execute + * MRAID commands. The `MRNativeCommandHandlerDelegate` is also notified of certain events and + * expected to respond appropriately to them. + */ +@protocol MRNativeCommandHandlerDelegate + +- (void)handleMRAIDUseCustomClose:(BOOL)useCustomClose; +- (void)handleMRAIDSetOrientationPropertiesWithForceOrientationMask:(UIInterfaceOrientationMask)forceOrientationMask; +- (void)handleMRAIDExpandWithParameters:(NSDictionary *)params; +- (void)handleMRAIDResizeWithParameters:(NSDictionary *)params; +- (void)handleMRAIDClose; +- (void)handleMRAIDOpenCallForURL:(NSURL *)URL; +- (void)nativeCommandWillPresentModalView; +- (void)nativeCommandDidDismissModalView; +- (void)nativeCommandCompleted:(NSString *)command; +- (void)nativeCommandFailed:(NSString *)command withMessage:(NSString *)message; +- (UIViewController *)viewControllerForPresentingModalView; +- (MRAdViewPlacementType)adViewPlacementType; +- (BOOL)userInteractedWithWebView; +- (BOOL)handlingWebviewRequests; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MRNativeCommandHandler.m b/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MRNativeCommandHandler.m new file mode 100644 index 00000000000..40dbb969500 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MRNativeCommandHandler.m @@ -0,0 +1,136 @@ +// +// MRNativeCommandHandler.m +// MoPubSDK +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import "MRNativeCommandHandler.h" +#import "MRCommand.h" +#import "MPGlobal.h" +#import "MPInstanceProvider.h" +#import "MPLogging.h" +#import "MRVideoPlayerManager.h" + +@interface MRNativeCommandHandler () + +@property (nonatomic, weak) id delegate; +@property (nonatomic, strong) MRVideoPlayerManager *videoPlayerManager; + +@end + +@implementation MRNativeCommandHandler + +- (instancetype)initWithDelegate:(id)delegate +{ + self = [super init]; + if (self) { + _delegate = delegate; + + _videoPlayerManager = [[MPInstanceProvider sharedProvider] buildMRVideoPlayerManagerWithDelegate:self]; + } + + return self; +} + +- (void)handleNativeCommand:(NSString *)command withProperties:(NSDictionary *)properties +{ + BOOL success = YES; + + MRCommand *cmd = [MRCommand commandForString:command]; + if (cmd == nil) { + success = NO; + } else if ([self shouldExecuteMRCommand:cmd]) { + cmd.delegate = self; + success = [cmd executeWithParams:properties]; + } + + [self.delegate nativeCommandCompleted:command]; + + if (!success) { + MPLogDebug(@"Unknown command: %@", command); + [self.delegate nativeCommandFailed:command withMessage:@"Specified command is not implemented."]; + } +} + +- (BOOL)shouldExecuteMRCommand:(MRCommand *)cmd +{ + // The command may not be whitelisted to run while the delegate is not handling webview requests. + if (![self.delegate handlingWebviewRequests] && ![cmd executableWhileBlockingRequests]) { + return NO; + } + + // some MRAID commands may not require user interaction + return ![cmd requiresUserInteractionForPlacementType:[self.delegate adViewPlacementType]] || [self.delegate userInteractedWithWebView]; +} + +#pragma mark - MRCommandDelegate + +- (void)mrCommand:(MRCommand *)command createCalendarEventWithParams:(NSDictionary *)params +{ + // DEPRECATED +} + +- (void)mrCommand:(MRCommand *)command playVideoWithURL:(NSURL *)url +{ + [self.videoPlayerManager playVideo:url]; +} + +- (void)mrCommand:(MRCommand *)command storePictureWithURL:(NSURL *)url +{ + // DEPRECATED +} + +- (void)mrCommand:(MRCommand *)command shouldUseCustomClose:(BOOL)useCustomClose +{ + [self.delegate handleMRAIDUseCustomClose:useCustomClose]; +} + +- (void)mrCommand:(MRCommand *)command setOrientationPropertiesWithForceOrientation:(UIInterfaceOrientationMask)forceOrientation +{ + [self.delegate handleMRAIDSetOrientationPropertiesWithForceOrientationMask:forceOrientation]; +} + +- (void)mrCommand:(MRCommand *)command openURL:(NSURL *)url +{ + [self.delegate handleMRAIDOpenCallForURL:url]; +} + +- (void)mrCommand:(MRCommand *)command expandWithParams:(NSDictionary *)params +{ + [self.delegate handleMRAIDExpandWithParameters:params]; +} + +- (void)mrCommand:(MRCommand *)command resizeWithParams:(NSDictionary *)params +{ + [self.delegate handleMRAIDResizeWithParameters:params]; +} + +- (void)mrCommandClose:(MRCommand *)command +{ + [self.delegate handleMRAIDClose]; +} + +#pragma mark - + +- (void)videoPlayerManager:(MRVideoPlayerManager *)manager didFailToPlayVideoWithErrorMessage:(NSString *)message +{ + [self.delegate nativeCommandFailed:@"playVideo" withMessage:message]; +} + +- (void)videoPlayerManagerWillPresentVideo:(MRVideoPlayerManager *)manager +{ + [self.delegate nativeCommandWillPresentModalView]; +} + +- (void)videoPlayerManagerDidDismissVideo:(MRVideoPlayerManager *)manager +{ + [self.delegate nativeCommandDidDismissModalView]; +} + +- (UIViewController *)viewControllerForPresentingVideoPlayer +{ + return [self.delegate viewControllerForPresentingModalView]; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MRProperty.h b/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MRProperty.h new file mode 100644 index 00000000000..6264849f9ca --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MRProperty.h @@ -0,0 +1,91 @@ +// +// MRProperty.h +// MoPub +// +// Copyright (c) 2011 MoPub, Inc. All rights reserved. +// + +#import +#import +#import "MRConstants.h" + +@interface MRProperty : NSObject + +- (NSString *)description; +- (NSString *)jsonString; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface MRHostSDKVersionProperty : MRProperty + +@property (nonatomic, copy) NSString *version; + ++ (MRHostSDKVersionProperty *)defaultProperty; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface MRPlacementTypeProperty : MRProperty { + MRAdViewPlacementType _placementType; +} + +@property (nonatomic, assign) MRAdViewPlacementType placementType; + ++ (MRPlacementTypeProperty *)propertyWithType:(MRAdViewPlacementType)type; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface MRStateProperty : MRProperty { + MRAdViewState _state; +} + +@property (nonatomic, assign) MRAdViewState state; + ++ (MRStateProperty *)propertyWithState:(MRAdViewState)state; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface MRScreenSizeProperty : MRProperty { + CGSize _screenSize; +} + +@property (nonatomic, assign) CGSize screenSize; + ++ (MRScreenSizeProperty *)propertyWithSize:(CGSize)size; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface MRSupportsProperty : MRProperty + +@property (nonatomic, assign) BOOL supportsSms; +@property (nonatomic, assign) BOOL supportsTel; +@property (nonatomic, assign) BOOL supportsCalendar; +@property (nonatomic, assign) BOOL supportsStorePicture; +@property (nonatomic, assign) BOOL supportsInlineVideo; + ++ (NSDictionary *)supportedFeatures; ++ (MRSupportsProperty *)defaultProperty; ++ (MRSupportsProperty *)propertyWithSupportedFeaturesDictionary:(NSDictionary *)dictionary; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface MRViewableProperty : MRProperty { + BOOL _isViewable; +} + +@property (nonatomic, assign) BOOL isViewable; + ++ (MRViewableProperty *)propertyWithViewable:(BOOL)viewable; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MRProperty.m b/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MRProperty.m new file mode 100644 index 00000000000..e47b151a2c6 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MRProperty.m @@ -0,0 +1,193 @@ +// +// MRProperty.m +// MoPub +// +// Created by Andrew He on 12/13/11. +// Copyright (c) 2011 MoPub, Inc. All rights reserved. +// + +#import "MRProperty.h" +#import "MPConstants.h" +#import "MPCoreInstanceProvider.h" + +@implementation MRProperty + +- (NSString *)description { + return @""; +} + +- (NSString *)jsonString { + return @"{}"; +} + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation MRHostSDKVersionProperty : MRProperty + +@synthesize version = _version; + ++ (instancetype)defaultProperty +{ + MRHostSDKVersionProperty *property = [[self alloc] init]; + property.version = MP_SDK_VERSION; + return property; +} + +- (NSString *)description +{ + return [NSString stringWithFormat:@"hostSDKVersion: '%@'", self.version]; +} + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation MRPlacementTypeProperty : MRProperty + +@synthesize placementType = _placementType; + ++ (MRPlacementTypeProperty *)propertyWithType:(MRAdViewPlacementType)type { + MRPlacementTypeProperty *property = [[self alloc] init]; + property.placementType = type; + return property; +} + +- (NSString *)description { + NSString *placementTypeString = @"unknown"; + switch (_placementType) { + case MRAdViewPlacementTypeInline: placementTypeString = @"inline"; break; + case MRAdViewPlacementTypeInterstitial: placementTypeString = @"interstitial"; break; + default: break; + } + + return [NSString stringWithFormat:@"placementType: '%@'", placementTypeString]; +} + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation MRStateProperty + +@synthesize state = _state; + ++ (MRStateProperty *)propertyWithState:(MRAdViewState)state { + MRStateProperty *property = [[self alloc] init]; + property.state = state; + return property; +} + +- (NSString *)description { + NSString *stateString; + switch (_state) { + case MRAdViewStateHidden: + stateString = @"hidden"; + break; + case MRAdViewStateDefault: + stateString = @"default"; + break; + case MRAdViewStateExpanded: + stateString = @"expanded"; + break; + case MRAdViewStateResized: + stateString = @"resized"; + break; + default: + stateString = @"loading"; + break; + } + return [NSString stringWithFormat:@"state: '%@'", stateString]; +} + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation MRScreenSizeProperty : MRProperty + +@synthesize screenSize = _screenSize; + ++ (MRScreenSizeProperty *)propertyWithSize:(CGSize)size { + MRScreenSizeProperty *property = [[self alloc] init]; + property.screenSize = size; + return property; +} + +- (NSString *)description { + return [NSString stringWithFormat:@"screenSize: {width: %f, height: %f}", + _screenSize.width, + _screenSize.height]; +} + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation MRSupportsProperty : MRProperty + ++ (NSDictionary *)supportedFeatures +{ + BOOL supportsSms, supportsTel; + supportsSms = supportsTel = [MPCoreInstanceProvider sharedProvider].sharedCarrierInfo[@"carrierName"] != nil; + + return [NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithBool:supportsSms], @"sms", + [NSNumber numberWithBool:supportsTel], @"tel", + [NSNumber numberWithBool:NO], @"calendar", + [NSNumber numberWithBool:NO], @"storePicture", + [NSNumber numberWithBool:YES], @"inlineVideo", + nil]; +} + ++ (MRSupportsProperty *)defaultProperty +{ + return [self propertyWithSupportedFeaturesDictionary:[self supportedFeatures]]; +} + ++ (MRSupportsProperty *)propertyWithSupportedFeaturesDictionary:(NSDictionary *)dictionary +{ + MRSupportsProperty *property = [[self alloc] init]; + property.supportsSms = [[dictionary objectForKey:@"sms"] boolValue]; + property.supportsTel = [[dictionary objectForKey:@"tel"] boolValue]; + property.supportsCalendar = NO; + property.supportsStorePicture = NO; + property.supportsInlineVideo = [[dictionary objectForKey:@"inlineVideo"] boolValue]; + return property; +} + +- (NSString *)description +{ + return [NSString stringWithFormat:@"supports: {sms: %@, tel: %@, calendar: %@, storePicture: %@, inlineVideo: %@}", + [self javascriptBooleanStringFromBoolValue:self.supportsSms], + [self javascriptBooleanStringFromBoolValue:self.supportsTel], + [self javascriptBooleanStringFromBoolValue:self.supportsCalendar], + [self javascriptBooleanStringFromBoolValue:self.supportsStorePicture], + [self javascriptBooleanStringFromBoolValue:self.supportsInlineVideo]]; +} + +- (NSString *)javascriptBooleanStringFromBoolValue:(BOOL)value +{ + return value ? @"true" : @"false"; +} + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation MRViewableProperty : MRProperty + +@synthesize isViewable = _isViewable; + ++ (MRViewableProperty *)propertyWithViewable:(BOOL)viewable { + MRViewableProperty *property = [[self alloc] init]; + property.isViewable = viewable; + return property; +} + +- (NSString *)description { + return [NSString stringWithFormat:@"viewable: %@", _isViewable ? @"true" : @"false"]; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MRVideoPlayerManager.h b/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MRVideoPlayerManager.h new file mode 100644 index 00000000000..373f327df5a --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MRVideoPlayerManager.h @@ -0,0 +1,26 @@ +// +// Copyright (c) 2013 MoPub. All rights reserved. +// + +#import + +@protocol MRVideoPlayerManagerDelegate; + +@interface MRVideoPlayerManager : NSObject + +@property (nonatomic, weak) id delegate; + +- (id)initWithDelegate:(id)delegate; +- (void)playVideo:(NSURL *)url; + +@end + +@protocol MRVideoPlayerManagerDelegate + +- (UIViewController *)viewControllerForPresentingVideoPlayer; +- (void)videoPlayerManagerWillPresentVideo:(MRVideoPlayerManager *)manager; +- (void)videoPlayerManagerDidDismissVideo:(MRVideoPlayerManager *)manager; +- (void)videoPlayerManager:(MRVideoPlayerManager *)manager + didFailToPlayVideoWithErrorMessage:(NSString *)message; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MRVideoPlayerManager.m b/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MRVideoPlayerManager.m new file mode 100644 index 00000000000..7f144ccd966 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/MRAID/MRVideoPlayerManager.m @@ -0,0 +1,57 @@ +// +// Copyright (c) 2013 MoPub. All rights reserved. +// + +#import "MRVideoPlayerManager.h" +#import +#import "MPInstanceProvider.h" + +@implementation MRVideoPlayerManager + +@synthesize delegate = _delegate; + +- (id)initWithDelegate:(id)delegate +{ + self = [super init]; + if (self) { + _delegate = delegate; + } + return self; +} + +- (void)dealloc +{ + [[NSNotificationCenter defaultCenter] removeObserver:self + name:MPMoviePlayerPlaybackDidFinishNotification + object:nil]; +} + +- (void)playVideo:(NSURL *)url +{ + if (!url) { + [self.delegate videoPlayerManager:self didFailToPlayVideoWithErrorMessage:@"URI was not valid."]; + return; + } + + MPMoviePlayerViewController *controller = [[MPInstanceProvider sharedProvider] buildMPMoviePlayerViewControllerWithURL:url]; + + [self.delegate videoPlayerManagerWillPresentVideo:self]; + [[self.delegate viewControllerForPresentingVideoPlayer] presentViewController:controller animated:MP_ANIMATED completion:nil]; + + // Avoid subscribing to the notification multiple times in the event the user plays the video more than once. + [[NSNotificationCenter defaultCenter] removeObserver:self + name:MPMoviePlayerPlaybackDidFinishNotification + object:nil]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(moviePlayerPlaybackDidFinish:) + name:MPMoviePlayerPlaybackDidFinishNotification + object:nil]; +} + +- (void)moviePlayerPlaybackDidFinish:(NSNotification *)notification +{ + [self.delegate videoPlayerManagerDidDismissVideo:self]; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Utility/Categories/NSBundle+MPAdditions.h b/iphone/Maps/3party/MoPubSDK/Internal/Utility/Categories/NSBundle+MPAdditions.h new file mode 100644 index 00000000000..780314a3e9f --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Utility/Categories/NSBundle+MPAdditions.h @@ -0,0 +1,19 @@ +// +// NSBundle+MPAdditions.h +// MoPubSDK +// +// Copyright © 2016 MoPub. All rights reserved. +// + +#import + +@interface NSBundle (MPAdditions) + +/** + * Retrieves the bundle that contains the MoPubSDK resources. + * @param aClass MoPub class. Typically will be self.class. + * @returns The bundle containing the MoPubSDK resources. + */ ++ (NSBundle *)resourceBundleForClass:(Class)aClass; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Utility/Categories/NSBundle+MPAdditions.m b/iphone/Maps/3party/MoPubSDK/Internal/Utility/Categories/NSBundle+MPAdditions.m new file mode 100644 index 00000000000..fcb3ff55a67 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Utility/Categories/NSBundle+MPAdditions.m @@ -0,0 +1,17 @@ +// +// NSBundle+MPAdditions.m +// MoPubSDK +// +// Copyright © 2016 MoPub. All rights reserved. +// + +#import "NSBundle+MPAdditions.h" + +@implementation NSBundle (MPAdditions) + ++ (NSBundle *)resourceBundleForClass:(Class)aClass { + NSString * resourceBundlePath = [[NSBundle mainBundle] pathForResource:@"MoPub" ofType:@"bundle"]; + return (resourceBundlePath != nil ? [NSBundle bundleWithPath:resourceBundlePath] : [NSBundle bundleForClass:aClass]); +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Utility/Categories/NSHTTPURLResponse+MPAdditions.h b/iphone/Maps/3party/MoPubSDK/Internal/Utility/Categories/NSHTTPURLResponse+MPAdditions.h new file mode 100644 index 00000000000..76f041c49bb --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Utility/Categories/NSHTTPURLResponse+MPAdditions.h @@ -0,0 +1,16 @@ +// +// NSHTTPURLResponse+MPAdditions.h +// MoPubSDK +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import + +extern NSString * const kMoPubHTTPHeaderContentType; + +@interface NSHTTPURLResponse (MPAdditions) + +- (NSStringEncoding)stringEncodingFromContentType:(NSString *)contentType; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Utility/Categories/NSHTTPURLResponse+MPAdditions.m b/iphone/Maps/3party/MoPubSDK/Internal/Utility/Categories/NSHTTPURLResponse+MPAdditions.m new file mode 100644 index 00000000000..f9b40770b28 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Utility/Categories/NSHTTPURLResponse+MPAdditions.m @@ -0,0 +1,44 @@ +// +// NSHTTPURLResponse+MPAdditions.m +// MoPubSDK +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import "NSHTTPURLResponse+MPAdditions.h" +#import "MPLogging.h" + +NSString * const kMoPubHTTPHeaderContentType = @"Content-Type"; + +@implementation NSHTTPURLResponse (MPAdditions) + +- (NSStringEncoding)stringEncodingFromContentType:(NSString *)contentType +{ + NSStringEncoding encoding = NSUTF8StringEncoding; + + if (![contentType length]) { + MPLogWarn(@"Attempting to set string encoding from nil %@", kMoPubHTTPHeaderContentType); + return encoding; + } + + NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"(?<=charset=)[^;]*" options:kNilOptions error:nil]; + + NSTextCheckingResult *charsetResult = [regex firstMatchInString:contentType options:kNilOptions range:NSMakeRange(0, [contentType length])]; + if (charsetResult && charsetResult.range.location != NSNotFound) { + NSString *charset = [contentType substringWithRange:[charsetResult range]]; + + // ensure that charset is not deallocated early + CFStringRef cfCharset = (CFStringRef)CFBridgingRetain(charset); + CFStringEncoding cfEncoding = CFStringConvertIANACharSetNameToEncoding(cfCharset); + CFBridgingRelease(cfCharset); + + if (cfEncoding == kCFStringEncodingInvalidId) { + return encoding; + } + encoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding); + } + + return encoding; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Utility/Categories/NSJSONSerialization+MPAdditions.h b/iphone/Maps/3party/MoPubSDK/Internal/Utility/Categories/NSJSONSerialization+MPAdditions.h new file mode 100644 index 00000000000..106388a1025 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Utility/Categories/NSJSONSerialization+MPAdditions.h @@ -0,0 +1,14 @@ +// +// NSJSONSerialization+MPAdditions.h +// MoPub +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import + +@interface NSJSONSerialization (MPAdditions) + ++ (id)mp_JSONObjectWithData:(NSData *)data options:(NSJSONReadingOptions)opt clearNullObjects:(BOOL)clearNulls error:(NSError **)error; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Utility/Categories/NSJSONSerialization+MPAdditions.m b/iphone/Maps/3party/MoPubSDK/Internal/Utility/Categories/NSJSONSerialization+MPAdditions.m new file mode 100644 index 00000000000..1dd19342924 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Utility/Categories/NSJSONSerialization+MPAdditions.m @@ -0,0 +1,93 @@ +// +// NSJSONSerialization+MPAdditions.m +// MoPub +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import "NSJSONSerialization+MPAdditions.h" + +@interface NSMutableDictionary (RemoveNullObjects) + +- (void)mp_removeNullsRecursively; + +@end + +@interface NSMutableArray (RemoveNullObjects) + +- (void)mp_removeNullsRecursively; + +@end + +@implementation NSJSONSerialization (MPAdditions) + ++ (id)mp_JSONObjectWithData:(NSData *)data options:(NSJSONReadingOptions)opt clearNullObjects:(BOOL)clearNulls error:(NSError **)error +{ + if (clearNulls) { + opt |= NSJSONReadingMutableContainers; + } + + id JSONObject = [NSJSONSerialization JSONObjectWithData:data options:opt error:error]; + + if (error || !clearNulls) { + return JSONObject; + } + + [JSONObject mp_removeNullsRecursively]; + + return JSONObject; +} + +@end + +@implementation NSMutableDictionary (RemovingNulls) + +-(void)mp_removeNullsRecursively +{ + // First, filter out directly stored nulls + NSMutableArray *nullKeys = [NSMutableArray array]; + NSMutableArray *arrayKeys = [NSMutableArray array]; + NSMutableArray *dictionaryKeys = [NSMutableArray array]; + + [self enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { + if ([obj isEqual:[NSNull null]]) { + [nullKeys addObject:key]; + } else if ([obj isKindOfClass:[NSDictionary class]]) { + [dictionaryKeys addObject:key]; + } else if ([obj isKindOfClass:[NSArray class]]) { + [arrayKeys addObject:key]; + } + }]; + + // Remove all the nulls + [self removeObjectsForKeys:nullKeys]; + + // Cascade down the dictionaries + for (id dictionaryKey in dictionaryKeys) { + NSMutableDictionary *dictionary = [self objectForKey:dictionaryKey]; + [dictionary mp_removeNullsRecursively]; + } + + // Recursively remove nulls from arrays + for (id arrayKey in arrayKeys) { + NSMutableArray *array = [self objectForKey:arrayKey]; + [array mp_removeNullsRecursively]; + } +} + +@end + +@implementation NSMutableArray (RemovingNulls) + +-(void)mp_removeNullsRecursively +{ + [self removeObjectIdenticalTo:[NSNull null]]; + + for (id object in self) { + if ([object respondsToSelector:@selector(mp_removeNullsRecursively)]) { + [(NSMutableDictionary *)object mp_removeNullsRecursively]; + } + } +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Utility/Categories/NSURL+MPAdditions.h b/iphone/Maps/3party/MoPubSDK/Internal/Utility/Categories/NSURL+MPAdditions.h new file mode 100644 index 00000000000..f66bb525ea6 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Utility/Categories/NSURL+MPAdditions.h @@ -0,0 +1,37 @@ +// +// NSURL+MPAdditions.h +// MoPub +// +// Copyright (c) 2013 MoPub. All rights reserved. +// + +#import + +typedef enum { + MPMoPubHostCommandUnrecognized, + MPMoPubHostCommandClose, + MPMoPubHostCommandFinishLoad, + MPMoPubHostCommandFailLoad, + MPMoPubHostCommandPrecacheComplete, + MPMoPubHostCommandRewardedVideoEnded +} MPMoPubHostCommand; + +typedef enum { + MPMoPubShareHostCommandTweet, + MPMoPubShareHostCommandUnrecognized +} MPMoPubShareHostCommand; + +@interface NSURL (MPAdditions) + +- (NSString *)mp_queryParameterForKey:(NSString *)key; +- (NSArray *)mp_queryParametersForKey:(NSString *)key; +- (NSDictionary *)mp_queryAsDictionary; +- (BOOL)mp_hasTelephoneScheme; +- (BOOL)mp_hasTelephonePromptScheme; +- (BOOL)mp_isSafeForLoadingWithoutUserAction; +- (BOOL)mp_isMoPubScheme; +- (MPMoPubHostCommand)mp_mopubHostCommand; +- (BOOL)mp_isMoPubShareScheme; +- (MPMoPubShareHostCommand)mp_MoPubShareHostCommand; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Utility/Categories/NSURL+MPAdditions.m b/iphone/Maps/3party/MoPubSDK/Internal/Utility/Categories/NSURL+MPAdditions.m new file mode 100644 index 00000000000..86a8abca474 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Utility/Categories/NSURL+MPAdditions.m @@ -0,0 +1,131 @@ +// +// NSURL+MPAdditions.m +// MoPub +// +// Copyright (c) 2013 MoPub. All rights reserved. +// + +#import "NSURL+MPAdditions.h" + +static NSString * const kTelephoneScheme = @"tel"; +static NSString * const kTelephonePromptScheme = @"telprompt"; + +// Share Constants +static NSString * const kMoPubShareScheme = @"mopubshare"; +static NSString * const kMoPubShareTweetHost = @"tweet"; + +// Commands Constants +static NSString * const kMoPubURLScheme = @"mopub"; +static NSString * const kMoPubCloseHost = @"close"; +static NSString * const kMoPubFinishLoadHost = @"finishLoad"; +static NSString * const kMoPubFailLoadHost = @"failLoad"; +static NSString * const kMoPubPrecacheCompleteHost = @"precacheComplete"; +static NSString * const kMoPubRewardedVideoEndedHost = @"rewardedVideoEnded"; + +@implementation NSURL (MPAdditions) + +- (NSString *)mp_queryParameterForKey:(NSString *)key +{ + NSArray *queryElements = [self.query componentsSeparatedByString:@"&"]; + for (NSString *element in queryElements) { + NSArray *keyAndValue = [element componentsSeparatedByString:@"="]; + if (keyAndValue.count >= 2 && + [[keyAndValue objectAtIndex:0] isEqualToString:key] && + [[keyAndValue objectAtIndex:1] length] > 0) { + return [[keyAndValue objectAtIndex:1] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; + } + } + return nil; +} + +- (NSArray *)mp_queryParametersForKey:(NSString *)key +{ + NSMutableArray *matchingParameters = [NSMutableArray array]; + NSArray *queryElements = [self.query componentsSeparatedByString:@"&"]; + for (NSString *element in queryElements) { + NSArray *keyAndValue = [element componentsSeparatedByString:@"="]; + if (keyAndValue.count >= 2 && + [[keyAndValue objectAtIndex:0] isEqualToString:key] && + [[keyAndValue objectAtIndex:1] length] > 0) { + [matchingParameters addObject:[[keyAndValue objectAtIndex:1] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]; + } + } + return [NSArray arrayWithArray:matchingParameters]; +} + +- (NSDictionary *)mp_queryAsDictionary +{ + NSMutableDictionary *queryDict = [NSMutableDictionary dictionary]; + NSArray *queryElements = [self.query componentsSeparatedByString:@"&"]; + for (NSString *element in queryElements) { + NSArray *keyVal = [element componentsSeparatedByString:@"="]; + if (keyVal.count >= 2) { + NSString *key = [keyVal objectAtIndex:0]; + NSString *value = [keyVal objectAtIndex:1]; + [queryDict setObject:[value stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding] + forKey:key]; + } + } + return queryDict; +} + +- (BOOL)mp_hasTelephoneScheme +{ + return [[[self scheme] lowercaseString] isEqualToString:kTelephoneScheme]; +} + +- (BOOL)mp_hasTelephonePromptScheme +{ + return [[[self scheme] lowercaseString] isEqualToString:kTelephonePromptScheme]; +} + +- (BOOL)mp_isSafeForLoadingWithoutUserAction +{ + return [[self scheme].lowercaseString isEqualToString:@"http"] || + [[self scheme].lowercaseString isEqualToString:@"https"] || + [[self scheme].lowercaseString isEqualToString:@"about"]; +} + +- (BOOL)mp_isMoPubScheme +{ + return [[self scheme] isEqualToString:kMoPubURLScheme]; +} + +- (MPMoPubShareHostCommand)mp_MoPubShareHostCommand +{ + NSString *host = [self host]; + if (![self mp_isMoPubShareScheme]) { + return MPMoPubShareHostCommandUnrecognized; + } else if ([host isEqualToString:kMoPubShareTweetHost]) { + return MPMoPubShareHostCommandTweet; + } else { + return MPMoPubShareHostCommandUnrecognized; + } +} + +- (MPMoPubHostCommand)mp_mopubHostCommand +{ + NSString *host = [self host]; + if (![self mp_isMoPubScheme]) { + return MPMoPubHostCommandUnrecognized; + } else if ([host isEqualToString:kMoPubCloseHost]) { + return MPMoPubHostCommandClose; + } else if ([host isEqualToString:kMoPubFinishLoadHost]) { + return MPMoPubHostCommandFinishLoad; + } else if ([host isEqualToString:kMoPubFailLoadHost]) { + return MPMoPubHostCommandFailLoad; + } else if ([host isEqualToString:kMoPubPrecacheCompleteHost]) { + return MPMoPubHostCommandPrecacheComplete; + } else if ([host isEqualToString:kMoPubRewardedVideoEndedHost]) { + return MPMoPubHostCommandRewardedVideoEnded; + } else { + return MPMoPubHostCommandUnrecognized; + } +} + +- (BOOL)mp_isMoPubShareScheme +{ + return [[self scheme] isEqualToString:kMoPubShareScheme]; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Utility/Categories/UIButton+MPAdditions.h b/iphone/Maps/3party/MoPubSDK/Internal/Utility/Categories/UIButton+MPAdditions.h new file mode 100644 index 00000000000..6dcf8411e9a --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Utility/Categories/UIButton+MPAdditions.h @@ -0,0 +1,12 @@ +// +// UIButton+MPAdditions.h +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import + +@interface UIButton (MPAdditions) + +@property (nonatomic) UIEdgeInsets mp_TouchAreaInsets; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Utility/Categories/UIButton+MPAdditions.m b/iphone/Maps/3party/MoPubSDK/Internal/Utility/Categories/UIButton+MPAdditions.m new file mode 100644 index 00000000000..9a49ecb3dee --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Utility/Categories/UIButton+MPAdditions.m @@ -0,0 +1,33 @@ +// +// UIButton+MPAdditions.m +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import "UIButton+MPAdditions.h" +#import + +@implementation UIButton (MPAdditions) + +- (UIEdgeInsets)mp_TouchAreaInsets +{ + return [objc_getAssociatedObject(self, @selector(mp_TouchAreaInsets)) UIEdgeInsetsValue]; +} + +- (void)setMp_TouchAreaInsets:(UIEdgeInsets)touchAreaInsets +{ + NSValue *value = [NSValue valueWithUIEdgeInsets:touchAreaInsets]; + objc_setAssociatedObject(self, @selector(mp_TouchAreaInsets), value, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + +- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event +{ + UIEdgeInsets touchAreaInsets = self.mp_TouchAreaInsets; + CGRect bounds = self.bounds; + bounds = CGRectMake(bounds.origin.x - touchAreaInsets.left, + bounds.origin.y - touchAreaInsets.top, + bounds.size.width + touchAreaInsets.left + touchAreaInsets.right, + bounds.size.height + touchAreaInsets.top + touchAreaInsets.bottom); + return CGRectContainsPoint(bounds, point); +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Utility/Categories/UIColor+MPAdditions.h b/iphone/Maps/3party/MoPubSDK/Internal/Utility/Categories/UIColor+MPAdditions.h new file mode 100644 index 00000000000..02ff51c88ad --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Utility/Categories/UIColor+MPAdditions.h @@ -0,0 +1,12 @@ +// +// UIColor+MPAdditions.h +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import + +@interface UIColor (MPAdditions) + ++ (UIColor *)mp_colorFromHexString:(NSString *)hexString alpha:(CGFloat)alpha; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Utility/Categories/UIColor+MPAdditions.m b/iphone/Maps/3party/MoPubSDK/Internal/Utility/Categories/UIColor+MPAdditions.m new file mode 100644 index 00000000000..bc5e5d7fa60 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Utility/Categories/UIColor+MPAdditions.m @@ -0,0 +1,19 @@ +// +// UIColor+MPAdditions.m +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import "UIColor+MPAdditions.h" + +@implementation UIColor (MPAdditions) + ++ (UIColor *)mp_colorFromHexString:(NSString *)hexString alpha:(CGFloat)alpha +{ + unsigned rgbValue = 0; + NSScanner *scanner = [NSScanner scannerWithString:hexString]; + [scanner setScanLocation:1]; // bypass '#' character + [scanner scanHexInt:&rgbValue]; + return [UIColor colorWithRed:((rgbValue & 0xFF0000) >> 16)/255.0 green:((rgbValue & 0xFF00) >> 8)/255.0 blue:(rgbValue & 0xFF)/255.0 alpha:alpha]; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Utility/Categories/UIView+MPAdditions.h b/iphone/Maps/3party/MoPubSDK/Internal/Utility/Categories/UIView+MPAdditions.h new file mode 100644 index 00000000000..6b4ea348b17 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Utility/Categories/UIView+MPAdditions.h @@ -0,0 +1,25 @@ +// +// UIView+MPAdditions.h +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import + +@interface UIView (MPAdditions) + +@property (nonatomic) CGFloat mp_x; +@property (nonatomic) CGFloat mp_y; +@property (nonatomic) CGFloat mp_height; +@property (nonatomic) CGFloat mp_width; + +- (void)setMp_x:(CGFloat)mp_x; +- (void)setMp_y:(CGFloat)mp_y; +- (void)setMp_width:(CGFloat)mp_width; +- (void)setMp_height:(CGFloat)mp_height; + +- (UIView *)mp_snapshotView; + +// convert any UIView to UIImage view. We can apply blur effect on UIImage. +- (UIImage *)mp_snapshot:(BOOL)usePresentationLayer; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Utility/Categories/UIView+MPAdditions.m b/iphone/Maps/3party/MoPubSDK/Internal/Utility/Categories/UIView+MPAdditions.m new file mode 100644 index 00000000000..3420d8b25cb --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Utility/Categories/UIView+MPAdditions.m @@ -0,0 +1,97 @@ +// +// UIView+MPAdditions.m +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import "UIView+MPAdditions.h" + +@implementation UIView (Helper) + +- (CGFloat)mp_x +{ + return self.frame.origin.x; +} + +- (CGFloat)mp_y +{ + return self.frame.origin.y; +} + +- (CGFloat)mp_width +{ + return self.frame.size.width; +} + +- (CGFloat)mp_height +{ + return self.frame.size.height; +} + +- (void)setMp_x:(CGFloat)mp_x +{ + [self setX:mp_x andY:self.frame.origin.y]; +} + +- (void)setMp_y:(CGFloat)mp_y +{ + [self setX:self.frame.origin.x andY:mp_y]; +} + +- (void)setX:(CGFloat)x andY:(CGFloat)y +{ + CGRect f = self.frame; + self.frame = CGRectMake(x, y, f.size.width, f.size.height); +} + + +- (void)setMp_width:(CGFloat)mp_width +{ + CGRect frame = self.frame; + frame.size.width = mp_width; + self.frame = frame; +} + +- (void)setMp_height:(CGFloat)mp_height +{ + CGRect frame = self.frame; + frame.size.height = mp_height; + self.frame = frame; +} + +- (UIView *)mp_snapshotView +{ + CGRect rect = self.bounds; + UIGraphicsBeginImageContextWithOptions(rect.size, NO, self.window.screen.scale); + UIView *snapshotView; + if ([self respondsToSelector:@selector(snapshotViewAfterScreenUpdates:)]) { + snapshotView = [self snapshotViewAfterScreenUpdates:NO]; + } else { + CGContextRef ctx = UIGraphicsGetCurrentContext(); + [self.layer renderInContext:ctx]; + UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); + snapshotView = [[UIImageView alloc] initWithImage:image]; + } + UIGraphicsEndImageContext(); + return snapshotView; +} + +- (UIImage *)mp_snapshot:(BOOL)usePresentationLayer +{ + CGRect rect = self.bounds; + UIGraphicsBeginImageContextWithOptions(rect.size, NO, self.window.screen.scale); + if (!usePresentationLayer && [self respondsToSelector:@selector(drawViewHierarchyInRect:afterScreenUpdates:)]) { + [self drawViewHierarchyInRect:rect afterScreenUpdates:NO]; + } else { + CGContextRef ctx = UIGraphicsGetCurrentContext(); + if (usePresentationLayer) { + [self.layer.presentationLayer renderInContext:ctx]; + } else { + [self.layer renderInContext:ctx]; + } + } + UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + return image; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Utility/Categories/UIWebView+MPAdditions.h b/iphone/Maps/3party/MoPubSDK/Internal/Utility/Categories/UIWebView+MPAdditions.h new file mode 100644 index 00000000000..3ffde613456 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Utility/Categories/UIWebView+MPAdditions.h @@ -0,0 +1,19 @@ +// +// UIWebView+MPAdditions.h +// MoPub +// +// Created by Andrew He on 11/6/11. +// Copyright (c) 2011 MoPub, Inc. All rights reserved. +// + +#import +#import + +extern NSString *const kJavaScriptDisableDialogSnippet; + +@interface UIWebView (MPAdditions) + +- (void)mp_setScrollable:(BOOL)scrollable; +- (void)disableJavaScriptDialogs; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Utility/Categories/UIWebView+MPAdditions.m b/iphone/Maps/3party/MoPubSDK/Internal/Utility/Categories/UIWebView+MPAdditions.m new file mode 100644 index 00000000000..0a1157327a0 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Utility/Categories/UIWebView+MPAdditions.m @@ -0,0 +1,51 @@ +// +// UIWebView+MPAdditions.m +// MoPub +// +// Created by Andrew He on 11/6/11. +// Copyright (c) 2011 MoPub, Inc. All rights reserved. +// + +#import "UIWebView+MPAdditions.h" + +NSString *const kJavaScriptDisableDialogSnippet = @"window.alert = function() { }; window.prompt = function() { }; window.confirm = function() { };"; + +@implementation UIWebView (MPAdditions) + +/* + * Find all subviews that are UIScrollViews or subclasses and set their scrolling and bounce. + */ +- (void)mp_setScrollable:(BOOL)scrollable { + #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 50000 // iOS 5.0+ + if ([self respondsToSelector:@selector(scrollView)]) + { + UIScrollView *scrollView = self.scrollView; + scrollView.scrollEnabled = scrollable; + scrollView.bounces = scrollable; + } + else + #endif + { + UIScrollView *scrollView = nil; + for (UIView *v in self.subviews) + { + if ([v isKindOfClass:[UIScrollView class]]) + { + scrollView = (UIScrollView *)v; + break; + } + } + scrollView.scrollEnabled = scrollable; + scrollView.bounces = scrollable; + } +} + +/* + * Redefine alert, prompt, and confirm to do nothing + */ +- (void)disableJavaScriptDialogs +{ + [self stringByEvaluatingJavaScriptFromString:kJavaScriptDisableDialogSnippet]; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPAnalyticsTracker.h b/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPAnalyticsTracker.h new file mode 100644 index 00000000000..422c405acf3 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPAnalyticsTracker.h @@ -0,0 +1,20 @@ +// +// MPAnalyticsTracker.h +// MoPub +// +// Copyright (c) 2013 MoPub. All rights reserved. +// + +#import + +@class MPAdConfiguration; + +@interface MPAnalyticsTracker : NSObject + ++ (MPAnalyticsTracker *)tracker; + +- (void)trackImpressionForConfiguration:(MPAdConfiguration *)configuration; +- (void)trackClickForConfiguration:(MPAdConfiguration *)configuration; +- (void)sendTrackingRequestForURLs:(NSArray *)URLs; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPAnalyticsTracker.m b/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPAnalyticsTracker.m new file mode 100644 index 00000000000..adf0b695372 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPAnalyticsTracker.m @@ -0,0 +1,55 @@ +// +// MPAnalyticsTracker.m +// MoPub +// +// Copyright (c) 2013 MoPub. All rights reserved. +// + +#import "MPAnalyticsTracker.h" +#import "MPAdConfiguration.h" +#import "MPCoreInstanceProvider.h" +#import "MPLogging.h" + +@interface MPAnalyticsTracker () + +- (NSURLRequest *)requestForURL:(NSURL *)URL; + +@end + +@implementation MPAnalyticsTracker + ++ (MPAnalyticsTracker *)tracker +{ + return [[MPAnalyticsTracker alloc] init]; +} + +- (void)trackImpressionForConfiguration:(MPAdConfiguration *)configuration +{ + MPLogDebug(@"Tracking impression: %@", configuration.impressionTrackingURL); + [NSURLConnection connectionWithRequest:[self requestForURL:configuration.impressionTrackingURL] + delegate:nil]; +} + +- (void)trackClickForConfiguration:(MPAdConfiguration *)configuration +{ + MPLogDebug(@"Tracking click: %@", configuration.clickTrackingURL); + [NSURLConnection connectionWithRequest:[self requestForURL:configuration.clickTrackingURL] + delegate:nil]; +} + +- (void)sendTrackingRequestForURLs:(NSArray *)URLs +{ + for (NSURL *URL in URLs) { + NSURLRequest *trackingRequest = [self requestForURL:URL]; + [NSURLConnection connectionWithRequest:trackingRequest delegate:nil]; + } +} + +- (NSURLRequest *)requestForURL:(NSURL *)URL +{ + NSMutableURLRequest *request = [[MPCoreInstanceProvider sharedProvider] buildConfiguredURLRequestWithURL:URL]; + request.cachePolicy = NSURLRequestReloadIgnoringCacheData; + return request; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPError.h b/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPError.h new file mode 100644 index 00000000000..1c72ddb814e --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPError.h @@ -0,0 +1,27 @@ +// +// MPError.h +// MoPub +// +// Copyright (c) 2012 MoPub. All rights reserved. +// + +#import + +extern NSString * const kMOPUBErrorDomain; + +typedef enum { + MOPUBErrorUnknown = -1, + MOPUBErrorNoInventory = 0, + MOPUBErrorAdUnitWarmingUp = 1, + MOPUBErrorNetworkTimedOut = 4, + MOPUBErrorServerError = 8, + MOPUBErrorAdapterNotFound = 16, + MOPUBErrorAdapterInvalid = 17, + MOPUBErrorAdapterHasNoInventory = 18 +} MOPUBErrorCode; + +@interface MOPUBError : NSError + ++ (MOPUBError *)errorWithCode:(MOPUBErrorCode)code; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPError.m b/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPError.m new file mode 100644 index 00000000000..a41c284695e --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPError.m @@ -0,0 +1,19 @@ +// +// MPError.m +// MoPub +// +// Copyright (c) 2012 MoPub. All rights reserved. +// + +#import "MPError.h" + +NSString * const kMOPUBErrorDomain = @"com.mopub.iossdk"; + +@implementation MOPUBError + ++ (MOPUBError *)errorWithCode:(MOPUBErrorCode)code +{ + return [self errorWithDomain:kMOPUBErrorDomain code:code userInfo:nil]; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPGeolocationProvider.h b/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPGeolocationProvider.h new file mode 100644 index 00000000000..5fae5347f82 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPGeolocationProvider.h @@ -0,0 +1,31 @@ +// +// MPGeolocationProvider.h +// MoPub +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import +#import + +@interface MPGeolocationProvider : NSObject + +/** + * Returns the shared instance of the `MPGeolocationProvider` class. + * + * @return The shared instance of the `MPGeolocationProvider` class. + */ ++ (instancetype)sharedProvider; + +/** + * The most recent location determined by the location provider. + */ +@property (nonatomic, readonly) CLLocation *lastKnownLocation; + +/** + * Determines whether the location provider should attempt to listen for location updates. The + * default value is YES. + */ +@property (nonatomic, assign) BOOL locationUpdatesEnabled; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPGeolocationProvider.m b/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPGeolocationProvider.m new file mode 100644 index 00000000000..753735f8290 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPGeolocationProvider.m @@ -0,0 +1,297 @@ +// +// MPGeolocationProvider.m +// MoPub +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import "MPGeolocationProvider.h" + +#import "MPCoreInstanceProvider.h" +#import "MPIdentityProvider.h" +#import "MPLogging.h" +#import "MPTimer.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +// The minimum distance (meters) a device must move horizontally before CLLocationManager generates +// an update event. Used to limit the amount of events generated. +const CLLocationDistance kMPCityBlockDistanceFilter = 100.0; + +// The duration (seconds) for which we want to listen for location updates (i.e. how long we wait to +// call -stopUpdatingLocation after calling -startUpdatingLocation). +const NSTimeInterval kMPLocationUpdateDuration = 15.0; + +// The duration (seconds) between calls to -startUpdatingLocation. +const NSTimeInterval kMPLocationUpdateInterval = 10.0 * 60.0; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface MPGeolocationProvider () + +@property (nonatomic, readwrite) CLLocation *lastKnownLocation; +@property (nonatomic) CLLocationManager *locationManager; +@property (nonatomic) BOOL authorizedForLocationServices; +@property (nonatomic) NSDate *timeOfLastLocationUpdate; +@property (nonatomic) MPTimer *nextLocationUpdateTimer; +@property (nonatomic) MPTimer *locationUpdateDurationTimer; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation MPGeolocationProvider + ++ (instancetype)sharedProvider +{ + static MPGeolocationProvider *sharedProvider = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sharedProvider = [[[self class] alloc] init]; + }); + return sharedProvider; +} + +- (instancetype)init +{ + self = [super init]; + if (self) { + _locationUpdatesEnabled = YES; + + _locationManager = [[MPCoreInstanceProvider sharedProvider] buildCLLocationManager]; + _locationManager.delegate = self; + _locationManager.distanceFilter = kMPCityBlockDistanceFilter; + + // CLLocationManager's `location` property may already contain location data upon + // initialization (for example, if the application uses significant location updates). + CLLocation *existingLocation = _locationManager.location; + if ([self locationHasValidCoordinates:existingLocation]) { + _lastKnownLocation = existingLocation; + MPLogDebug(@"Found previous location information."); + } + + // Avoid processing location updates when the application enters the background. + [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidEnterBackgroundNotification object:[UIApplication sharedApplication] queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { + [self stopAllCurrentOrScheduledLocationUpdates]; + }]; + + // Re-activate location updates when the application comes back to the foreground. + [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillEnterForegroundNotification object:[UIApplication sharedApplication] queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { + if (_locationUpdatesEnabled) { + [self resumeLocationUpdatesAfterBackgrounding]; + } + }]; + + [self startRecurringLocationUpdates]; + } + return self; +} + +- (void)dealloc +{ + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:[UIApplication sharedApplication]]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillEnterForegroundNotification object:[UIApplication sharedApplication]]; +} + +#pragma mark - Public + +- (CLLocation *)lastKnownLocation +{ + if (!self.locationUpdatesEnabled) { + return nil; + } + + return _lastKnownLocation; +} + +- (void)setLocationUpdatesEnabled:(BOOL)enabled +{ + _locationUpdatesEnabled = enabled; + + if (!_locationUpdatesEnabled) { + [self stopAllCurrentOrScheduledLocationUpdates]; + self.lastKnownLocation = nil; + } else if (![self.locationUpdateDurationTimer isValid] && ![self.nextLocationUpdateTimer isValid]) { + [self startRecurringLocationUpdates]; + } +} + +#pragma mark - Internal + +- (void)setAuthorizedForLocationServices:(BOOL)authorizedForLocationServices +{ + _authorizedForLocationServices = authorizedForLocationServices; + + if (_authorizedForLocationServices && [CLLocationManager locationServicesEnabled]) { + [self startRecurringLocationUpdates]; + } else { + [self stopAllCurrentOrScheduledLocationUpdates]; + self.lastKnownLocation = nil; + } +} + +- (BOOL)isAuthorizedStatus:(CLAuthorizationStatus)status +{ +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 + return (status == kCLAuthorizationStatusAuthorizedAlways) || (status == kCLAuthorizationStatusAuthorizedWhenInUse); +#else + return status == kCLAuthorizationStatusAuthorized; +#endif +} + +/** + * Tells the location provider to start periodically retrieving new location data. + * + * The location provider will activate its underlying location manager for a specified amount of + * time, during which the provider may receive delegate callbacks about location updates. After this + * duration, the provider will schedule a future update. These updates can be stopped via + * -stopAllCurrentOrScheduledLocationUpdates. + */ +- (void)startRecurringLocationUpdates +{ + self.timeOfLastLocationUpdate = [NSDate date]; + + if (![CLLocationManager locationServicesEnabled] || ![self isAuthorizedStatus:[CLLocationManager authorizationStatus]]) { + MPLogDebug(@"Will not start location updates: the application is not authorized " + @"for location services."); + return; + } + + if (!_locationUpdatesEnabled) { + MPLogDebug(@"Will not start location updates because they have been disabled."); + return; + } + + [self.locationManager startUpdatingLocation]; + + [self.locationUpdateDurationTimer invalidate]; + self.locationUpdateDurationTimer = [[MPCoreInstanceProvider sharedProvider] buildMPTimerWithTimeInterval:kMPLocationUpdateDuration target:self selector:@selector(currentLocationUpdateDidFinish) repeats:NO]; + [self.locationUpdateDurationTimer scheduleNow]; +} + +- (void)currentLocationUpdateDidFinish +{ + MPLogDebug(@"Stopping the current location update session and scheduling the next session."); + [self.locationUpdateDurationTimer invalidate]; + [self.locationManager stopUpdatingLocation]; + + [self scheduleNextLocationUpdateAfterDelay:kMPLocationUpdateInterval]; +} + +- (void)scheduleNextLocationUpdateAfterDelay:(NSTimeInterval)delay +{ + MPLogDebug(@"Next user location update due in %.1f seconds.", delay); + [self.nextLocationUpdateTimer invalidate]; + self.nextLocationUpdateTimer = [[MPCoreInstanceProvider sharedProvider] buildMPTimerWithTimeInterval:delay target:self selector:@selector(startRecurringLocationUpdates) repeats:NO]; + [self.nextLocationUpdateTimer scheduleNow]; +} + +- (void)stopAllCurrentOrScheduledLocationUpdates +{ + MPLogDebug(@"Stopping any scheduled location updates."); + [self.locationUpdateDurationTimer invalidate]; + [self.locationManager stopUpdatingLocation]; + + [self.nextLocationUpdateTimer invalidate]; +} + +- (void)resumeLocationUpdatesAfterBackgrounding +{ + NSTimeInterval timeSinceLastUpdate = [[NSDate date] timeIntervalSinceDate:self.timeOfLastLocationUpdate]; + + if (timeSinceLastUpdate >= kMPLocationUpdateInterval) { + MPLogDebug(@"Last known user location is stale. Updating location."); + [self startRecurringLocationUpdates]; + } else if (timeSinceLastUpdate >= 0) { + NSTimeInterval timeToNextUpdate = kMPLocationUpdateInterval - timeSinceLastUpdate; + [self scheduleNextLocationUpdateAfterDelay:timeToNextUpdate]; + } else { + [self scheduleNextLocationUpdateAfterDelay:kMPLocationUpdateInterval]; + } +} + +#pragma mark - CLLocation Helpers + +- (BOOL)isLocation:(CLLocation *)location betterThanLocation:(CLLocation *)otherLocation +{ + if (!otherLocation) { + return YES; + } + + // Nil locations and locations with invalid horizontal accuracy are worse than any location. + if (![self locationHasValidCoordinates:location]) { + return NO; + } + + if ([self isLocation:location olderThanLocation:otherLocation]) { + return NO; + } + + return YES; +} + +- (BOOL)locationHasValidCoordinates:(CLLocation *)location +{ + return location && location.horizontalAccuracy > 0; +} + +- (BOOL)isLocation:(CLLocation *)location olderThanLocation:(CLLocation *)otherLocation +{ + return [location.timestamp timeIntervalSinceDate:otherLocation.timestamp] < 0; +} + +#pragma mark - (iOS 6.0+) + +- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status +{ + MPLogDebug(@"Location authorization status changed to: %ld", (long)status); + + switch (status) { + case kCLAuthorizationStatusNotDetermined: + case kCLAuthorizationStatusDenied: + case kCLAuthorizationStatusRestricted: + self.authorizedForLocationServices = NO; + break; + case kCLAuthorizationStatusAuthorized: // same as kCLAuthorizationStatusAuthorizedAlways +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 + case kCLAuthorizationStatusAuthorizedWhenInUse: +#endif + self.authorizedForLocationServices = YES; + break; + default: + self.authorizedForLocationServices = NO; + break; + } +} + +- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations +{ + for (CLLocation *location in locations) { + if ([self isLocation:location betterThanLocation:self.lastKnownLocation]) { + self.lastKnownLocation = location; + MPLogDebug(@"Updated last known user location: %@", location); + } + } +} + +- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error +{ + if (error.code == kCLErrorDenied) { + MPLogDebug(@"Location manager failed: the user has denied access to location services."); + [self stopAllCurrentOrScheduledLocationUpdates]; + } else if (error.code == kCLErrorLocationUnknown) { + MPLogDebug(@"Location manager could not obtain a location right now."); + } +} + +#pragma mark - (iOS < 6.0) + +- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation +{ + if ([self isLocation:newLocation betterThanLocation:self.lastKnownLocation]) { + self.lastKnownLocation = newLocation; + MPLogDebug(@"Updated last known user location."); + } +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPGlobal.h b/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPGlobal.h new file mode 100644 index 00000000000..168d389ceb2 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPGlobal.h @@ -0,0 +1,131 @@ +// +// MPGlobal.h +// MoPub +// +// Copyright 2011 MoPub, Inc. All rights reserved. +// + +#import +#import + +#ifndef MP_ANIMATED +#define MP_ANIMATED YES +#endif + +UIInterfaceOrientation MPInterfaceOrientation(void); +UIWindow *MPKeyWindow(void); +CGFloat MPStatusBarHeight(void); +CGRect MPApplicationFrame(void); +CGRect MPScreenBounds(void); +CGSize MPScreenResolution(void); +CGFloat MPDeviceScaleFactor(void); +NSDictionary *MPDictionaryFromQueryString(NSString *query); +NSString *MPSHA1Digest(NSString *string); +BOOL MPViewIsVisible(UIView *view); +BOOL MPViewIntersectsParentWindowWithPercent(UIView *view, CGFloat percentVisible); +NSString *MPResourcePathForResource(NSString *resourceName); +NSArray *MPConvertStringArrayToURLArray(NSArray *strArray); +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/* + * Availability constants. + */ + +#define MP_IOS_2_0 20000 +#define MP_IOS_2_1 20100 +#define MP_IOS_2_2 20200 +#define MP_IOS_3_0 30000 +#define MP_IOS_3_1 30100 +#define MP_IOS_3_2 30200 +#define MP_IOS_4_0 40000 +#define MP_IOS_4_1 40100 +#define MP_IOS_4_2 40200 +#define MP_IOS_4_3 40300 +#define MP_IOS_5_0 50000 +#define MP_IOS_5_1 50100 +#define MP_IOS_6_0 60000 +#define MP_IOS_7_0 70000 +#define MP_IOS_8_0 80000 +#define MP_IOS_9_0 90000 + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +enum { + MPInterstitialCloseButtonStyleAlwaysVisible, + MPInterstitialCloseButtonStyleAlwaysHidden, + MPInterstitialCloseButtonStyleAdControlled +}; +typedef NSUInteger MPInterstitialCloseButtonStyle; + +enum { + MPInterstitialOrientationTypePortrait, + MPInterstitialOrientationTypeLandscape, + MPInterstitialOrientationTypeAll +}; +typedef NSUInteger MPInterstitialOrientationType; + + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface NSString (MPAdditions) + +/* + * Returns string with reserved/unsafe characters encoded. + */ +- (NSString *)mp_URLEncodedString; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface UIDevice (MPAdditions) + +- (NSString *)mp_hardwareDeviceName; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface UIApplication (MPAdditions) + +// Correct way to hide/show the status bar on pre-ios 7. +- (void)mp_preIOS7setApplicationStatusBarHidden:(BOOL)hidden; +- (BOOL)mp_supportsOrientationMask:(UIInterfaceOrientationMask)orientationMask; +- (BOOL)mp_doesOrientation:(UIInterfaceOrientation)orientation matchOrientationMask:(UIInterfaceOrientationMask)orientationMask; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Optional Class Forward Def Protocols +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@class MPAdConfiguration, CLLocation; + +@protocol MPAdAlertManagerProtocol + +@property (nonatomic, strong) MPAdConfiguration *adConfiguration; +@property (nonatomic, copy) NSString *adUnitId; +@property (nonatomic, copy) CLLocation *location; +@property (nonatomic, weak) UIView *targetAdView; +@property (nonatomic, weak) id delegate; + +- (void)beginMonitoringAlerts; +- (void)endMonitoringAlerts; +- (void)processAdAlertOnce; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Small alert wrapper class to handle telephone protocol prompting +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@class MPTelephoneConfirmationController; + +typedef void (^MPTelephoneConfirmationControllerClickHandler)(NSURL *targetTelephoneURL, BOOL confirmed); + +@interface MPTelephoneConfirmationController : NSObject + +- (id)initWithURL:(NSURL *)url clickHandler:(MPTelephoneConfirmationControllerClickHandler)clickHandler; +- (void)show; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPGlobal.m b/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPGlobal.m new file mode 100644 index 00000000000..1c8632746c5 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPGlobal.m @@ -0,0 +1,401 @@ +// +// MPGlobal.m +// MoPub +// +// Copyright 2011 MoPub, Inc. All rights reserved. +// + +#import "MPGlobal.h" +#import "MPConstants.h" +#import "MPLogging.h" +#import "NSURL+MPAdditions.h" +#import "MoPub.h" +#import + +#import +#import + +BOOL MPViewHasHiddenAncestor(UIView *view); +UIWindow *MPViewGetParentWindow(UIView *view); +BOOL MPViewIntersectsParentWindow(UIView *view); +NSString *MPSHA1Digest(NSString *string); + +UIInterfaceOrientation MPInterfaceOrientation() +{ + return [UIApplication sharedApplication].statusBarOrientation; +} + +UIWindow *MPKeyWindow() +{ + return [UIApplication sharedApplication].keyWindow; +} + +CGFloat MPStatusBarHeight() { + if ([UIApplication sharedApplication].statusBarHidden) return 0.0f; + + CGFloat width = CGRectGetWidth([UIApplication sharedApplication].statusBarFrame); + CGFloat height = CGRectGetHeight([UIApplication sharedApplication].statusBarFrame); + + return (width < height) ? width : height; +} + +CGRect MPApplicationFrame() +{ + CGRect frame = MPScreenBounds(); + + frame.origin.y += MPStatusBarHeight(); + frame.size.height -= MPStatusBarHeight(); + + return frame; +} + +CGRect MPScreenBounds() +{ + // Prior to iOS 8, window and screen coordinates were fixed and always specified relative to the + // device’s screen in a portrait orientation. Starting with iOS8, the `fixedCoordinateSpace` + // property was introduced which specifies bounds that always reflect the screen dimensions of + // the device in a portrait-up orientation. + CGRect bounds = [UIScreen mainScreen].bounds; + if ([[UIScreen mainScreen] respondsToSelector:@selector(fixedCoordinateSpace)]) { + bounds = [UIScreen mainScreen].fixedCoordinateSpace.bounds; + } + + // Rotate the portrait-up bounds if the orientation of the device is in landscape. + if (UIInterfaceOrientationIsLandscape(MPInterfaceOrientation())) { + CGFloat width = bounds.size.width; + bounds.size.width = bounds.size.height; + bounds.size.height = width; + } + + return bounds; +} + +CGSize MPScreenResolution() +{ + CGRect bounds = MPScreenBounds(); + CGFloat scale = MPDeviceScaleFactor(); + + return CGSizeMake(bounds.size.width*scale, bounds.size.height*scale); +} + +CGFloat MPDeviceScaleFactor() +{ + if ([[UIScreen mainScreen] respondsToSelector:@selector(displayLinkWithTarget:selector:)] && + [[UIScreen mainScreen] respondsToSelector:@selector(scale)]) { + return [[UIScreen mainScreen] scale]; + } else { + return 1.0; + } +} + +NSDictionary *MPDictionaryFromQueryString(NSString *query) { + NSMutableDictionary *queryDict = [NSMutableDictionary dictionary]; + NSArray *queryElements = [query componentsSeparatedByString:@"&"]; + for (NSString *element in queryElements) { + NSArray *keyVal = [element componentsSeparatedByString:@"="]; + NSString *key = [keyVal objectAtIndex:0]; + NSString *value = [keyVal lastObject]; + [queryDict setObject:[value stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding] + forKey:key]; + } + return queryDict; +} + +NSString *MPSHA1Digest(NSString *string) +{ + unsigned char digest[CC_SHA1_DIGEST_LENGTH]; + NSData *data = [string dataUsingEncoding:NSASCIIStringEncoding]; + CC_SHA1([data bytes], (CC_LONG)[data length], digest); + + NSMutableString *output = [NSMutableString stringWithCapacity:CC_SHA1_DIGEST_LENGTH * 2]; + for (int i = 0; i < CC_SHA1_DIGEST_LENGTH; i++) { + [output appendFormat:@"%02x", digest[i]]; + } + + return output; +} + +BOOL MPViewIsVisible(UIView *view) +{ + // In order for a view to be visible, it: + // 1) must not be hidden, + // 2) must not have an ancestor that is hidden, + // 3) must be within the frame of its parent window. + // + // Note: this function does not check whether any part of the view is obscured by another view. + + return (!view.hidden && + !MPViewHasHiddenAncestor(view) && + MPViewIntersectsParentWindow(view)); +} + +BOOL MPViewHasHiddenAncestor(UIView *view) +{ + UIView *ancestor = view.superview; + while (ancestor) { + if (ancestor.hidden) return YES; + ancestor = ancestor.superview; + } + return NO; +} + +UIWindow *MPViewGetParentWindow(UIView *view) +{ + UIView *ancestor = view.superview; + while (ancestor) { + if ([ancestor isKindOfClass:[UIWindow class]]) { + return (UIWindow *)ancestor; + } + ancestor = ancestor.superview; + } + return nil; +} + +BOOL MPViewIntersectsParentWindow(UIView *view) +{ + UIWindow *parentWindow = MPViewGetParentWindow(view); + + if (parentWindow == nil) { + return NO; + } + + // We need to call convertRect:toView: on this view's superview rather than on this view itself. + CGRect viewFrameInWindowCoordinates = [view.superview convertRect:view.frame toView:parentWindow]; + + return CGRectIntersectsRect(viewFrameInWindowCoordinates, parentWindow.frame); +} + +BOOL MPViewIntersectsParentWindowWithPercent(UIView *view, CGFloat percentVisible) +{ + UIWindow *parentWindow = MPViewGetParentWindow(view); + + if (parentWindow == nil) { + return NO; + } + + // We need to call convertRect:toView: on this view's superview rather than on this view itself. + CGRect viewFrameInWindowCoordinates = [view.superview convertRect:view.frame toView:parentWindow]; + CGRect intersection = CGRectIntersection(viewFrameInWindowCoordinates, parentWindow.frame); + + CGFloat intersectionArea = CGRectGetWidth(intersection) * CGRectGetHeight(intersection); + CGFloat originalArea = CGRectGetWidth(view.bounds) * CGRectGetHeight(view.bounds); + + return intersectionArea >= (originalArea * percentVisible); +} + +NSString *MPResourcePathForResource(NSString *resourceName) +{ + if ([[NSBundle mainBundle] pathForResource:@"MoPub" ofType:@"bundle"] != nil) { + return [@"MoPub.bundle" stringByAppendingPathComponent:resourceName]; + } + else if ([[UIDevice currentDevice].systemVersion compare:@"8.0" options:NSNumericSearch] != NSOrderedAscending) { + // When using open source or cocoapods (on ios 8 and above), we can rely on the MoPub class + // living in the same bundle/framework as the assets. + // We can use pathForResource on ios 8 and above to succesfully load resources. + NSBundle *resourceBundle = [NSBundle bundleForClass:[MoPub class]]; + NSString *resourcePath = [resourceBundle pathForResource:resourceName ofType:nil]; + return resourcePath; + } + else { + // We can just return the resource name because: + // 1. This is being used as an open source release so the resource will be + // in the main bundle. + // 2. This is cocoapods but CAN'T be using frameworks since that is only allowed + // on ios 8 and above. + return resourceName; + } +} + +NSArray *MPConvertStringArrayToURLArray(NSArray *strArray) +{ + NSMutableArray *urls = [NSMutableArray array]; + + for (NSObject *str in strArray) { + if ([str isKindOfClass:[NSString class]]) { + NSURL *url = [NSURL URLWithString:(NSString *)str]; + if (url) { + [urls addObject:url]; + } + } + } + + return urls; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation NSString (MPAdditions) + +- (NSString *)mp_URLEncodedString +{ + NSString *result = (NSString *)CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(NULL, + (CFStringRef)self, + NULL, + (CFStringRef)@"!*'();:@&=+$,/?%#[]<>", + kCFStringEncodingUTF8)); + return result; +} + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation UIDevice (MPAdditions) + +- (NSString *)mp_hardwareDeviceName +{ + size_t size; + sysctlbyname("hw.machine", NULL, &size, NULL, 0); + char *machine = malloc(size); + sysctlbyname("hw.machine", machine, &size, NULL, 0); + NSString *platform = [NSString stringWithCString:machine encoding:NSUTF8StringEncoding]; + free(machine); + return platform; +} + +@end + +@implementation UIApplication (MPAdditions) + +- (void)mp_preIOS7setApplicationStatusBarHidden:(BOOL)hidden +{ + // Hiding the status bar should use a fade effect. + // Displaying the status bar should use no animation. + UIStatusBarAnimation animation = hidden ? + UIStatusBarAnimationFade : UIStatusBarAnimationNone; + [[UIApplication sharedApplication] setStatusBarHidden:hidden withAnimation:animation]; +} + +- (BOOL)mp_supportsOrientationMask:(UIInterfaceOrientationMask)orientationMask +{ + NSArray *supportedOrientations = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"UISupportedInterfaceOrientations"]; + + if (orientationMask & UIInterfaceOrientationMaskLandscapeLeft) { + if ([supportedOrientations containsObject:@"UIInterfaceOrientationLandscapeLeft"]) { + return YES; + } + } + + if (orientationMask & UIInterfaceOrientationMaskLandscapeRight) { + if ([supportedOrientations containsObject:@"UIInterfaceOrientationLandscapeRight"]) { + return YES; + } + } + + if (orientationMask & UIInterfaceOrientationMaskPortrait) { + if ([supportedOrientations containsObject:@"UIInterfaceOrientationPortrait"]) { + return YES; + } + } + + if (orientationMask & UIInterfaceOrientationMaskPortraitUpsideDown) { + if ([supportedOrientations containsObject:@"UIInterfaceOrientationPortraitUpsideDown"]) { + return YES; + } + } + + return NO; +} + +- (BOOL)mp_doesOrientation:(UIInterfaceOrientation)orientation matchOrientationMask:(UIInterfaceOrientationMask)orientationMask +{ + BOOL supportsLandscapeLeft = (orientationMask & UIInterfaceOrientationMaskLandscapeLeft) > 0; + BOOL supportsLandscapeRight = (orientationMask & UIInterfaceOrientationMaskLandscapeRight) > 0; + BOOL supportsPortrait = (orientationMask & UIInterfaceOrientationMaskPortrait) > 0; + BOOL supportsPortraitUpsideDown = (orientationMask & UIInterfaceOrientationMaskPortraitUpsideDown) > 0; + + if (supportsLandscapeLeft && orientation == UIInterfaceOrientationLandscapeLeft) { + return YES; + } + + if (supportsLandscapeRight && orientation == UIInterfaceOrientationLandscapeRight) { + return YES; + } + + if (supportsPortrait && orientation == UIInterfaceOrientationPortrait) { + return YES; + } + + if (supportsPortraitUpsideDown && orientation == UIInterfaceOrientationPortraitUpsideDown) { + return YES; + } + + return NO; +} + +@end +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface MPTelephoneConfirmationController () + +@property (nonatomic, strong) UIAlertView *alertView; +@property (nonatomic, strong) NSURL *telephoneURL; +@property (nonatomic, copy) MPTelephoneConfirmationControllerClickHandler clickHandler; + +@end + +@implementation MPTelephoneConfirmationController + +- (id)initWithURL:(NSURL *)url clickHandler:(MPTelephoneConfirmationControllerClickHandler)clickHandler +{ + if (![url mp_hasTelephoneScheme] && ![url mp_hasTelephonePromptScheme]) { + // Shouldn't be here as the url must have a tel or telPrompt scheme. + MPLogError(@"Processing URL as a telephone URL when %@ doesn't follow the tel:// or telprompt:// schemes", url.absoluteString); + return nil; + } + + if (self = [super init]) { + // If using tel://xxxxxxx, the host will be the number. If using tel:xxxxxxx, we will try the resourceIdentifier. + NSString *phoneNumber = [url host]; + + if (!phoneNumber) { + phoneNumber = [url resourceSpecifier]; + if ([phoneNumber length] == 0) { + MPLogError(@"Invalid telelphone URL: %@.", url.absoluteString); + return nil; + } + } + + _alertView = [[UIAlertView alloc] initWithTitle: @"Are you sure you want to call?" + message:phoneNumber + delegate:self + cancelButtonTitle:@"Cancel" + otherButtonTitles:@"Call", nil]; + self.clickHandler = clickHandler; + + // We want to manually handle telPrompt scheme alerts. So we'll convert telPrompt schemes to tel schemes. + if ([url mp_hasTelephonePromptScheme]) { + self.telephoneURL = [NSURL URLWithString:[NSString stringWithFormat:@"tel://%@", phoneNumber]]; + } else { + self.telephoneURL = url; + } + } + + return self; +} + +- (void)dealloc +{ + self.alertView.delegate = nil; + [self.alertView dismissWithClickedButtonIndex:0 animated:YES]; +} + +- (void)show +{ + [self.alertView show]; +} + +#pragma mark - UIAlertViewDelegate + +- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex +{ + BOOL confirmed = (buttonIndex == 1); + + if (self.clickHandler) { + self.clickHandler(self.telephoneURL, confirmed); + } + +} + +@end + diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPIdentityProvider.h b/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPIdentityProvider.h new file mode 100644 index 00000000000..835ee0cfb47 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPIdentityProvider.h @@ -0,0 +1,28 @@ +// +// MPIdentityProvider.h +// MoPub +// +// Copyright (c) 2013 MoPub. All rights reserved. +// + +#import + +@interface MPIdentityProvider : NSObject + ++ (NSString *)identifier; ++ (NSString *)obfuscatedIdentifier; ++ (BOOL)advertisingTrackingEnabled; + +/** + * A Boolean value indicating whether the MoPub SDK should create a MoPub ID that can be used + * for frequency capping when Limit ad tracking is on & the IDFA we get is + * 00000000-0000-0000-0000-000000000000. + * + * When set to NO, the SDK will not create a MoPub ID in the above case. When set to YES, the + * SDK will generate a MoPub ID. The default value is YES. + * + */ ++ (void)setFrequencyCappingIdUsageEnabled:(BOOL)frequencyCappingIdUsageEnabled; ++ (BOOL)frequencyCappingIdUsageEnabled; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPIdentityProvider.m b/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPIdentityProvider.m new file mode 100644 index 00000000000..15877d2b3b3 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPIdentityProvider.m @@ -0,0 +1,125 @@ +// +// MPIdentityProvider.m +// MoPub +// +// Copyright (c) 2013 MoPub. All rights reserved. +// + +#import "MPIdentityProvider.h" +#import "MPGlobal.h" +#import + +#define MOPUB_IDENTIFIER_DEFAULTS_KEY @"com.mopub.identifier" +#define MOPUB_IDENTIFIER_LAST_SET_TIME_KEY @"com.mopub.identifiertime" +#define MOPUB_DAY_IN_SECONDS 24 * 60 * 60 +#define MOPUB_ALL_ZERO_UUID @"00000000-0000-0000-0000-000000000000" + +static BOOL gFrequencyCappingIdUsageEnabled = YES; + +@interface MPIdentityProvider () + ++ (NSString *)identifierFromASIdentifierManager:(BOOL)obfuscate; ++ (NSString *)mopubIdentifier:(BOOL)obfuscate; + +@end + +@implementation MPIdentityProvider + ++ (NSString *)identifier +{ + return [self _identifier:NO]; +} + ++ (NSString *)obfuscatedIdentifier +{ + return [self _identifier:YES]; +} + ++ (NSString *)_identifier:(BOOL)obfuscate +{ + if (![self isAdvertisingIdAllZero]) { + return [self identifierFromASIdentifierManager:obfuscate]; + } else { + return [self mopubIdentifier:obfuscate]; + } +} + ++ (BOOL)advertisingTrackingEnabled +{ + return [[ASIdentifierManager sharedManager] isAdvertisingTrackingEnabled]; +} + ++ (NSString *)identifierFromASIdentifierManager:(BOOL)obfuscate +{ + if (obfuscate) { + return @"ifa:XXXX"; + } + + NSString *identifier = [[ASIdentifierManager sharedManager].advertisingIdentifier UUIDString]; + + return [NSString stringWithFormat:@"ifa:%@", [identifier uppercaseString]]; +} + ++ (NSString *)mopubIdentifier:(BOOL)obfuscate +{ + if (![self frequencyCappingIdUsageEnabled]) { + return [NSString stringWithFormat:@"ifa:%@", MOPUB_ALL_ZERO_UUID]; + } + + if (obfuscate) { + return @"mopub:XXXX"; + } + + // reset identifier every 24 hours + NSDate *lastSetDate = [[NSUserDefaults standardUserDefaults] objectForKey:MOPUB_IDENTIFIER_LAST_SET_TIME_KEY]; + if (!lastSetDate) { + [[NSUserDefaults standardUserDefaults] setObject:[NSDate date] forKey:MOPUB_IDENTIFIER_LAST_SET_TIME_KEY]; + [[NSUserDefaults standardUserDefaults] synchronize]; + } else { + NSTimeInterval diff = [[NSDate date] timeIntervalSinceDate:lastSetDate]; + if (diff > MOPUB_DAY_IN_SECONDS) { + [[NSUserDefaults standardUserDefaults] setObject:[NSDate date] forKey:MOPUB_IDENTIFIER_LAST_SET_TIME_KEY]; + [[NSUserDefaults standardUserDefaults] removeObjectForKey:MOPUB_IDENTIFIER_DEFAULTS_KEY]; + } + } + + NSString *identifier = [[NSUserDefaults standardUserDefaults] objectForKey:MOPUB_IDENTIFIER_DEFAULTS_KEY]; + if (!identifier) { + CFUUIDRef uuidObject = CFUUIDCreate(kCFAllocatorDefault); + NSString *uuidStr = (NSString *)CFBridgingRelease(CFUUIDCreateString(kCFAllocatorDefault, uuidObject)); + CFRelease(uuidObject); + + identifier = [NSString stringWithFormat:@"mopub:%@", [uuidStr uppercaseString]]; + [[NSUserDefaults standardUserDefaults] setObject:identifier forKey:MOPUB_IDENTIFIER_DEFAULTS_KEY]; + [[NSUserDefaults standardUserDefaults] synchronize]; + } + + return identifier; +} + ++ (void)setFrequencyCappingIdUsageEnabled:(BOOL)frequencyCappingIdUsageEnabled +{ + gFrequencyCappingIdUsageEnabled = frequencyCappingIdUsageEnabled; +} + ++ (BOOL)frequencyCappingIdUsageEnabled +{ + return gFrequencyCappingIdUsageEnabled; +} + + + +// Beginning in iOS 10, when a user enables "Limit Ad Tracking", the OS will send advertising identifier with value of +// 00000000-0000-0000-0000-000000000000 + ++ (BOOL)isAdvertisingIdAllZero { + NSString *identifier = [[ASIdentifierManager sharedManager].advertisingIdentifier UUIDString]; + if (!identifier) { + // when identifier is nil, ifa:(null) is sent. + return false; + } else { + return [identifier isEqualToString:MOPUB_ALL_ZERO_UUID]; + } +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPInternalUtils.h b/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPInternalUtils.h new file mode 100644 index 00000000000..3fd069cf8dd --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPInternalUtils.h @@ -0,0 +1,25 @@ +// +// MPInternalUtils.h +// MoPubSDK +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import + +#define SUPPRESS_PERFORM_SELECTOR_LEAK_WARNING(code) \ + _Pragma("clang diagnostic push") \ + _Pragma("clang diagnostic ignored \"-Warc-performSelector-leaks\"") \ + code; \ + _Pragma("clang diagnostic pop") \ + +@interface MPInternalUtils : NSObject + +@end + +@interface NSMutableDictionary (MPInternalUtils) + +- (void)mp_safeSetObject:(id)obj forKey:(id)key; +- (void)mp_safeSetObject:(id)obj forKey:(id)key withDefault:(id)defaultObj; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPInternalUtils.m b/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPInternalUtils.m new file mode 100644 index 00000000000..5b7cc07cccd --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPInternalUtils.m @@ -0,0 +1,32 @@ +// +// MPInternalUtils.m +// MoPubSDK +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import "MPInternalUtils.h" + +@implementation MPInternalUtils + +@end + +@implementation NSMutableDictionary (MPInternalUtils) + +- (void)mp_safeSetObject:(id)obj forKey:(id)key +{ + if (obj != nil) { + [self setObject:obj forKey:key]; + } +} + +- (void)mp_safeSetObject:(id)obj forKey:(id)key withDefault:(id)defaultObj +{ + if (obj != nil) { + [self setObject:obj forKey:key]; + } else { + [self setObject:defaultObj forKey:key]; + } +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPLogProvider.h b/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPLogProvider.h new file mode 100644 index 00000000000..96f9a6739fb --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPLogProvider.h @@ -0,0 +1,27 @@ +// +// MPLogProvider.h +// MoPub +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import +#import "MPLogging.h" + +@protocol MPLogger; + +@interface MPLogProvider : NSObject + ++ (MPLogProvider *)sharedLogProvider; +- (void)addLogger:(id)logger; +- (void)removeLogger:(id)logger; +- (void)logMessage:(NSString *)message atLogLevel:(MPLogLevel)logLevel; + +@end + +@protocol MPLogger + +- (MPLogLevel)logLevel; +- (void)logMessage:(NSString *)message; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPLogProvider.m b/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPLogProvider.m new file mode 100644 index 00000000000..b9aa1d0a90e --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPLogProvider.m @@ -0,0 +1,83 @@ +// +// MPLogProvider.m +// MoPub +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import "MPLogProvider.h" + +@interface MPLogProvider () + +@property (nonatomic, strong) NSMutableArray *loggers; + +@end + +@interface MPSystemLogger : NSObject +@end + +@implementation MPLogProvider + +#pragma mark - Singleton instance + ++ (MPLogProvider *)sharedLogProvider +{ + static dispatch_once_t once; + static MPLogProvider *sharedLogProvider; + dispatch_once(&once, ^{ + sharedLogProvider = [[self alloc] init]; + }); + + return sharedLogProvider; +} + +#pragma mark - Object Lifecycle + +- (id)init +{ + self = [super init]; + if (self) { + _loggers = [NSMutableArray array]; + [self addLogger:[[MPSystemLogger alloc] init]]; + } + return self; +} + +#pragma mark - Loggers + +- (void)addLogger:(id)logger +{ + [self.loggers addObject:logger]; +} + +- (void)removeLogger:(id)logger +{ + [self.loggers removeObject:logger]; +} + +#pragma mark - Logging + +- (void)logMessage:(NSString *)message atLogLevel:(MPLogLevel)logLevel +{ + [self.loggers enumerateObjectsUsingBlock:^(id logger, NSUInteger idx, BOOL *stop) { + if ([logger logLevel] <= logLevel) { + [logger logMessage:message]; + } + }]; +} + +@end + +@implementation MPSystemLogger + +- (void)logMessage:(NSString *)message +{ + NSLog(@"%@", message); +} + +- (MPLogLevel)logLevel +{ + return MPLogGetLevel(); +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPLogging.h b/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPLogging.h new file mode 100644 index 00000000000..02b3069c463 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPLogging.h @@ -0,0 +1,54 @@ +// +// MPLogging.h +// MoPub +// +// Copyright 2011 MoPub, Inc. All rights reserved. +// + +#import +#import "MPConstants.h" + +extern NSString * const kMPClearErrorLogFormatWithAdUnitID; +extern NSString * const kMPWarmingUpErrorLogFormatWithAdUnitID; + +// Lower = finer-grained logs. +typedef enum +{ + MPLogLevelAll = 0, + MPLogLevelTrace = 10, + MPLogLevelDebug = 20, + MPLogLevelInfo = 30, + MPLogLevelWarn = 40, + MPLogLevelError = 50, + MPLogLevelFatal = 60, + MPLogLevelOff = 70 +} MPLogLevel; + +MPLogLevel MPLogGetLevel(void); +void MPLogSetLevel(MPLogLevel level); +void _MPLogTrace(NSString *format, ...); +void _MPLogDebug(NSString *format, ...); +void _MPLogInfo(NSString *format, ...); +void _MPLogWarn(NSString *format, ...); +void _MPLogError(NSString *format, ...); +void _MPLogFatal(NSString *format, ...); + +#if MP_DEBUG_MODE && !SPECS + +#define MPLogTrace(...) _MPLogTrace(__VA_ARGS__) +#define MPLogDebug(...) _MPLogDebug(__VA_ARGS__) +#define MPLogInfo(...) _MPLogInfo(__VA_ARGS__) +#define MPLogWarn(...) _MPLogWarn(__VA_ARGS__) +#define MPLogError(...) _MPLogError(__VA_ARGS__) +#define MPLogFatal(...) _MPLogFatal(__VA_ARGS__) + +#else + +#define MPLogTrace(...) {} +#define MPLogDebug(...) {} +#define MPLogInfo(...) {} +#define MPLogWarn(...) {} +#define MPLogError(...) {} +#define MPLogFatal(...) {} + +#endif diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPLogging.m b/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPLogging.m new file mode 100644 index 00000000000..5dee8463caf --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPLogging.m @@ -0,0 +1,101 @@ +// +// MPLogging.m +// MoPub +// +// Copyright 2011 MoPub, Inc. All rights reserved. +// + +#import "MPLogging.h" +#import "MPIdentityProvider.h" +#import "MPLogProvider.h" + +NSString * const kMPClearErrorLogFormatWithAdUnitID = @"No ads found for ad unit: %@"; +NSString * const kMPWarmingUpErrorLogFormatWithAdUnitID = @"Ad unit %@ is currently warming up. Please try again in a few minutes."; +NSString * const kMPSystemLogPrefix = @"MOPUB: %@"; + +static MPLogLevel systemLogLevel = MPLogLevelInfo; + +MPLogLevel MPLogGetLevel() +{ + return systemLogLevel; +} + +void MPLogSetLevel(MPLogLevel level) +{ + systemLogLevel = level; +} + +void _MPLog(MPLogLevel level, NSString *format, va_list args) +{ + static NSString *sIdentifier; + static NSString *sObfuscatedIdentifier; + + if (!sIdentifier) { + sIdentifier = [[MPIdentityProvider identifier] copy]; + } + + if (!sObfuscatedIdentifier) { + sObfuscatedIdentifier = [[MPIdentityProvider obfuscatedIdentifier] copy]; + } + + NSString *logString = [[NSString alloc] initWithFormat:format arguments:args]; + + // Replace identifier with a obfuscated version when logging. + logString = [logString stringByReplacingOccurrencesOfString:sIdentifier withString:sObfuscatedIdentifier]; + + [[MPLogProvider sharedLogProvider] logMessage:logString atLogLevel:level]; +} + +void _MPLogTrace(NSString *format, ...) +{ + format = [NSString stringWithFormat:kMPSystemLogPrefix, format]; + va_list args; + va_start(args, format); + _MPLog(MPLogLevelTrace, format, args); + va_end(args); +} + +void _MPLogDebug(NSString *format, ...) +{ + format = [NSString stringWithFormat:kMPSystemLogPrefix, format]; + va_list args; + va_start(args, format); + _MPLog(MPLogLevelDebug, format, args); + va_end(args); +} + +void _MPLogWarn(NSString *format, ...) +{ + format = [NSString stringWithFormat:kMPSystemLogPrefix, format]; + va_list args; + va_start(args, format); + _MPLog(MPLogLevelWarn, format, args); + va_end(args); +} + +void _MPLogInfo(NSString *format, ...) +{ + format = [NSString stringWithFormat:kMPSystemLogPrefix, format]; + va_list args; + va_start(args, format); + _MPLog(MPLogLevelInfo, format, args); + va_end(args); +} + +void _MPLogError(NSString *format, ...) +{ + format = [NSString stringWithFormat:kMPSystemLogPrefix, format]; + va_list args; + va_start(args, format); + _MPLog(MPLogLevelError, format, args); + va_end(args); +} + +void _MPLogFatal(NSString *format, ...) +{ + format = [NSString stringWithFormat:kMPSystemLogPrefix, format]; + va_list args; + va_start(args, format); + _MPLog(MPLogLevelFatal, format, args); + va_end(args); +} diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPReachability.h b/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPReachability.h new file mode 100644 index 00000000000..af934489113 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPReachability.h @@ -0,0 +1,100 @@ +/* + File: Reachability.h + Abstract: Basic demonstration of how to use the SystemConfiguration Reachablity APIs. + Version: 3.5 + + Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple + Inc. ("Apple") in consideration of your agreement to the following + terms, and your use, installation, modification or redistribution of + this Apple software constitutes acceptance of these terms. If you do + not agree with these terms, please do not use, install, modify or + redistribute this Apple software. + + In consideration of your agreement to abide by the following terms, and + subject to these terms, Apple grants you a personal, non-exclusive + license, under Apple's copyrights in this original Apple software (the + "Apple Software"), to use, reproduce, modify and redistribute the Apple + Software, with or without modifications, in source and/or binary forms; + provided that if you redistribute the Apple Software in its entirety and + without modifications, you must retain this notice and the following + text and disclaimers in all such redistributions of the Apple Software. + Neither the name, trademarks, service marks or logos of Apple Inc. may + be used to endorse or promote products derived from the Apple Software + without specific prior written permission from Apple. Except as + expressly stated in this notice, no other rights or licenses, express or + implied, are granted by Apple herein, including but not limited to any + patent rights that may be infringed by your derivative works or by other + works in which the Apple Software may be incorporated. + + The Apple Software is provided by Apple on an "AS IS" basis. APPLE + MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION + THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS + FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND + OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. + + IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, + MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED + AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), + STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + + Copyright (C) 2014 Apple Inc. All Rights Reserved. + + */ + +#import +#import +#import + + +typedef enum : NSInteger { + MPNotReachable = 0, + MPReachableViaWiFi, + MPReachableViaWWAN +} MPNetworkStatus; + + +extern NSString *kMPReachabilityChangedNotification; + + +@interface MPReachability : NSObject + +/*! + * Use to check the reachability of a given host name. + */ ++ (instancetype)reachabilityWithHostName:(NSString *)hostName; + +/*! + * Use to check the reachability of a given IP address. + */ ++ (instancetype)reachabilityWithAddress:(const struct sockaddr_in *)hostAddress; + +/*! + * Checks whether the default route is available. Should be used by applications that do not connect to a particular host. + */ ++ (instancetype)reachabilityForInternetConnection; + +/*! + * Checks whether a local WiFi connection is available. + */ ++ (instancetype)reachabilityForLocalWiFi; + +/*! + * Start listening for reachability notifications on the current run loop. + */ +- (BOOL)startNotifier; +- (void)stopNotifier; + +- (MPNetworkStatus)currentReachabilityStatus; + +/*! + * WWAN may be available, but not active until a connection has been established. WiFi may require a connection for VPN on Demand. + */ +- (BOOL)connectionRequired; +- (BOOL)hasWifi; +- (BOOL)hasCellular; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPReachability.m b/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPReachability.m new file mode 100644 index 00000000000..e17fa4b20ae --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPReachability.m @@ -0,0 +1,331 @@ +/* + File: Reachability.m + Abstract: Basic demonstration of how to use the SystemConfiguration Reachablity APIs. + Version: 3.5 + + Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple + Inc. ("Apple") in consideration of your agreement to the following + terms, and your use, installation, modification or redistribution of + this Apple software constitutes acceptance of these terms. If you do + not agree with these terms, please do not use, install, modify or + redistribute this Apple software. + + In consideration of your agreement to abide by the following terms, and + subject to these terms, Apple grants you a personal, non-exclusive + license, under Apple's copyrights in this original Apple software (the + "Apple Software"), to use, reproduce, modify and redistribute the Apple + Software, with or without modifications, in source and/or binary forms; + provided that if you redistribute the Apple Software in its entirety and + without modifications, you must retain this notice and the following + text and disclaimers in all such redistributions of the Apple Software. + Neither the name, trademarks, service marks or logos of Apple Inc. may + be used to endorse or promote products derived from the Apple Software + without specific prior written permission from Apple. Except as + expressly stated in this notice, no other rights or licenses, express or + implied, are granted by Apple herein, including but not limited to any + patent rights that may be infringed by your derivative works or by other + works in which the Apple Software may be incorporated. + + The Apple Software is provided by Apple on an "AS IS" basis. APPLE + MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION + THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS + FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND + OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. + + IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, + MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED + AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), + STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + + Copyright (C) 2014 Apple Inc. All Rights Reserved. + + */ + +#import +#import +#import +#import + +#import + +#import "MPReachability.h" +#import "MPLogging.h" + + +NSString *kMPReachabilityChangedNotification = @"kMPReachabilityChangedNotification"; + + +#pragma mark - Supporting functions + +#define kShouldPrintReachabilityFlags 0 + +static void PrintReachabilityFlags(SCNetworkReachabilityFlags flags, const char* comment) +{ +#if kShouldPrintReachabilityFlags + + NSLog(@"Reachability Flag Status: %c%c %c%c%c%c%c%c%c %s\n", + (flags & kSCNetworkReachabilityFlagsIsWWAN) ? 'W' : '-', + (flags & kSCNetworkReachabilityFlagsReachable) ? 'R' : '-', + + (flags & kSCNetworkReachabilityFlagsTransientConnection) ? 't' : '-', + (flags & kSCNetworkReachabilityFlagsConnectionRequired) ? 'c' : '-', + (flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) ? 'C' : '-', + (flags & kSCNetworkReachabilityFlagsInterventionRequired) ? 'i' : '-', + (flags & kSCNetworkReachabilityFlagsConnectionOnDemand) ? 'D' : '-', + (flags & kSCNetworkReachabilityFlagsIsLocalAddress) ? 'l' : '-', + (flags & kSCNetworkReachabilityFlagsIsDirect) ? 'd' : '-', + comment + ); +#endif +} + + +static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void* info) +{ +#pragma unused (target, flags) + if (info == NULL) { + MPLogWarn(@"info was NULL in ReachabilityCallback"); + } + if (![(__bridge NSObject*) info isKindOfClass: [MPReachability class]]) { + MPLogWarn(@"info was wrong class in ReachabilityCallback"); + } + + MPReachability* reachabilityObject = (__bridge MPReachability *)info; + // Post a notification to notify the client that the network reachability changed. + [[NSNotificationCenter defaultCenter] postNotificationName: kMPReachabilityChangedNotification object: reachabilityObject]; +} + + +#pragma mark - Reachability implementation + +@implementation MPReachability +{ + BOOL _alwaysReturnLocalWiFiStatus; //default is NO + SCNetworkReachabilityRef _reachabilityRef; +} + ++ (instancetype)reachabilityWithHostName:(NSString *)hostName +{ + MPReachability* returnValue = NULL; + SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithName(NULL, [hostName UTF8String]); + if (reachability != NULL) + { + returnValue= [[self alloc] init]; + if (returnValue != NULL) + { + returnValue->_reachabilityRef = reachability; + returnValue->_alwaysReturnLocalWiFiStatus = NO; + } else { + CFRelease(reachability); + } + } + return returnValue; +} + + ++ (instancetype)reachabilityWithAddress:(const struct sockaddr_in *)hostAddress +{ + SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr *)hostAddress); + + MPReachability* returnValue = NULL; + + if (reachability != NULL) + { + returnValue = [[self alloc] init]; + if (returnValue != NULL) + { + returnValue->_reachabilityRef = reachability; + returnValue->_alwaysReturnLocalWiFiStatus = NO; + } else { + CFRelease(reachability); + } + } + return returnValue; +} + + + ++ (instancetype)reachabilityForInternetConnection +{ + struct sockaddr_in zeroAddress; + bzero(&zeroAddress, sizeof(zeroAddress)); + zeroAddress.sin_len = sizeof(zeroAddress); + zeroAddress.sin_family = AF_INET; + + return [self reachabilityWithAddress:&zeroAddress]; +} + + ++ (instancetype)reachabilityForLocalWiFi +{ + struct sockaddr_in localWifiAddress; + bzero(&localWifiAddress, sizeof(localWifiAddress)); + localWifiAddress.sin_len = sizeof(localWifiAddress); + localWifiAddress.sin_family = AF_INET; + + // IN_LINKLOCALNETNUM is defined in as 169.254.0.0. + localWifiAddress.sin_addr.s_addr = htonl(IN_LINKLOCALNETNUM); + + MPReachability* returnValue = [self reachabilityWithAddress: &localWifiAddress]; + if (returnValue != NULL) + { + returnValue->_alwaysReturnLocalWiFiStatus = YES; + } + + return returnValue; +} + + +#pragma mark - Start and stop notifier + +- (BOOL)startNotifier +{ + BOOL returnValue = NO; + SCNetworkReachabilityContext context = {0, (__bridge void *)(self), NULL, NULL, NULL}; + + if (SCNetworkReachabilitySetCallback(_reachabilityRef, ReachabilityCallback, &context)) + { + if (SCNetworkReachabilityScheduleWithRunLoop(_reachabilityRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode)) + { + returnValue = YES; + } + } + + return returnValue; +} + + +- (void)stopNotifier +{ + if (_reachabilityRef != NULL) + { + SCNetworkReachabilityUnscheduleFromRunLoop(_reachabilityRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); + } +} + + +- (void)dealloc +{ + [self stopNotifier]; + if (_reachabilityRef != NULL) + { + CFRelease(_reachabilityRef); + } +} + + +#pragma mark - Network Flag Handling + +- (MPNetworkStatus)localWiFiStatusForFlags:(SCNetworkReachabilityFlags)flags +{ + PrintReachabilityFlags(flags, "localWiFiStatusForFlags"); + MPNetworkStatus returnValue = MPNotReachable; + + if ((flags & kSCNetworkReachabilityFlagsReachable) && (flags & kSCNetworkReachabilityFlagsIsDirect)) + { + returnValue = MPReachableViaWiFi; + } + + return returnValue; +} + + +- (MPNetworkStatus)networkStatusForFlags:(SCNetworkReachabilityFlags)flags +{ + PrintReachabilityFlags(flags, "networkStatusForFlags"); + if ((flags & kSCNetworkReachabilityFlagsReachable) == 0) + { + // The target host is not reachable. + return MPNotReachable; + } + + MPNetworkStatus returnValue = MPNotReachable; + + if ((flags & kSCNetworkReachabilityFlagsConnectionRequired) == 0) + { + /* + If the target host is reachable and no connection is required then we'll assume (for now) that you're on Wi-Fi... + */ + returnValue = MPReachableViaWiFi; + } + + if ((((flags & kSCNetworkReachabilityFlagsConnectionOnDemand ) != 0) || + (flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) != 0)) + { + /* + ... and the connection is on-demand (or on-traffic) if the calling application is using the CFSocketStream or higher APIs... + */ + + if ((flags & kSCNetworkReachabilityFlagsInterventionRequired) == 0) + { + /* + ... and no [user] intervention is needed... + */ + returnValue = MPReachableViaWiFi; + } + } + + if ((flags & kSCNetworkReachabilityFlagsIsWWAN) == kSCNetworkReachabilityFlagsIsWWAN) + { + /* + ... but WWAN connections are OK if the calling application is using the CFNetwork APIs. + */ + returnValue = MPReachableViaWWAN; + } + + return returnValue; +} + + +- (BOOL)connectionRequired +{ + NSAssert(_reachabilityRef != NULL, @"connectionRequired called with NULL reachabilityRef"); + SCNetworkReachabilityFlags flags; + + if (SCNetworkReachabilityGetFlags(_reachabilityRef, &flags)) + { + return (flags & kSCNetworkReachabilityFlagsConnectionRequired); + } + + return NO; +} + + +- (MPNetworkStatus)currentReachabilityStatus +{ + NSAssert(_reachabilityRef != NULL, @"currentNetworkStatus called with NULL SCNetworkReachabilityRef"); + MPNetworkStatus returnValue = MPNotReachable; + SCNetworkReachabilityFlags flags; + + if (SCNetworkReachabilityGetFlags(_reachabilityRef, &flags)) + { + if (_alwaysReturnLocalWiFiStatus) + { + returnValue = [self localWiFiStatusForFlags:flags]; + } + else + { + returnValue = [self networkStatusForFlags:flags]; + } + } + + return returnValue; +} + + +- (BOOL)hasWifi +{ + return [self currentReachabilityStatus] == MPReachableViaWiFi; +} + +- (BOOL)hasCellular +{ + return [self currentReachabilityStatus] == MPReachableViaWWAN; +} + + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPSessionTracker.h b/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPSessionTracker.h new file mode 100644 index 00000000000..630cb06b1d0 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPSessionTracker.h @@ -0,0 +1,12 @@ +// +// MPSessionTracker.h +// MoPub +// +// Copyright (c) 2013 MoPub. All rights reserved. +// + +#import + +@interface MPSessionTracker : NSObject + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPSessionTracker.m b/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPSessionTracker.m new file mode 100644 index 00000000000..d9ed3e707ff --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPSessionTracker.m @@ -0,0 +1,51 @@ +// +// MPSessionTracker.m +// MoPub +// +// Copyright (c) 2013 MoPub. All rights reserved. +// + +#import "MPSessionTracker.h" +#import "MPConstants.h" +#import "MPIdentityProvider.h" +#import "MPGlobal.h" +#import "MPCoreInstanceProvider.h" +#import "MPAPIEndpoints.h" + +@implementation MPSessionTracker + ++ (void)load +{ + if (SESSION_TRACKING_ENABLED) { + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(trackEvent) + name:UIApplicationWillEnterForegroundNotification + object:nil]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(trackEvent) + name:UIApplicationDidFinishLaunchingNotification + object:nil]; + } +} + ++ (void)trackEvent +{ + [NSURLConnection connectionWithRequest:[[MPCoreInstanceProvider sharedProvider] buildConfiguredURLRequestWithURL:[self URL]] + delegate:nil]; +} + ++ (NSURL *)URL +{ + NSString *path = [NSString stringWithFormat:@"%@?v=%@&udid=%@&id=%@&av=%@&st=1", + [MPAPIEndpoints baseURLStringWithPath:MOPUB_API_PATH_SESSION testing:NO], + MP_SERVER_VERSION, + [MPIdentityProvider identifier], + [[[NSBundle mainBundle] bundleIdentifier] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding], + [[[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding] + ]; + + return [NSURL URLWithString:path]; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPStoreKitProvider.h b/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPStoreKitProvider.h new file mode 100644 index 00000000000..716120d388b --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPStoreKitProvider.h @@ -0,0 +1,30 @@ +// +// MPFeatureDetector.h +// MoPub +// +// Copyright (c) 2013 MoPub. All rights reserved. +// + +#import +#import "MPGlobal.h" +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= MP_IOS_6_0 +#import +#endif + +@class SKStoreProductViewController; + +@interface MPStoreKitProvider : NSObject + ++ (BOOL)deviceHasStoreKit; +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= MP_IOS_6_0 ++ (SKStoreProductViewController *)buildController; +#endif + +@end + +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= MP_IOS_6_0 +@protocol MPSKStoreProductViewControllerDelegate +#else +@protocol MPSKStoreProductViewControllerDelegate +#endif +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPStoreKitProvider.m b/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPStoreKitProvider.m new file mode 100644 index 00000000000..e196fc760a9 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPStoreKitProvider.m @@ -0,0 +1,50 @@ +// +// MPFeatureDetector.m +// MoPub +// +// Copyright (c) 2013 MoPub. All rights reserved. +// + +#import "MPStoreKitProvider.h" +#import "MPGlobal.h" + +#import + +/* + * On iOS 7 and above, SKStoreProductViewController can cause a crash if the application does not list Portrait as a supported + * interface orientation. Specifically, SKStoreProductViewController's shouldAutorotate returns YES, even though + * the SKStoreProductViewController's supported interface orientations does not intersect with the application's list. + * + * To fix, we disallow autorotation so the SKStoreProductViewController will use its supported orientation on iOS 7 devices. + */ +@interface MPiOS7SafeStoreProductViewController : SKStoreProductViewController + +@end + +@implementation MPiOS7SafeStoreProductViewController + +- (BOOL)shouldAutorotate +{ + return NO; +} + +@end + +@implementation MPStoreKitProvider + ++ (BOOL)deviceHasStoreKit +{ + return !!NSClassFromString(@"SKStoreProductViewController"); +} + ++ (SKStoreProductViewController *)buildController +{ + // use our safe subclass on iOS 7 and above + if ([[UIDevice currentDevice].systemVersion compare:@"7.0" options:NSNumericSearch] != NSOrderedAscending) { + return [[MPiOS7SafeStoreProductViewController alloc] init]; + } else { + return [[SKStoreProductViewController alloc] init]; + } +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPTimer.h b/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPTimer.h new file mode 100644 index 00000000000..40954143241 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPTimer.h @@ -0,0 +1,31 @@ +// +// MPTimer.h +// MoPub +// +// Created by Andrew He on 3/8/11. +// Copyright 2011 MoPub, Inc. All rights reserved. +// + +#import + +/* + * MPTimer wraps an NSTimer and adds pause/resume functionality. + */ +@interface MPTimer : NSObject + +@property (nonatomic, copy) NSString *runLoopMode; + ++ (MPTimer *)timerWithTimeInterval:(NSTimeInterval)seconds + target:(id)target + selector:(SEL)aSelector + repeats:(BOOL)repeats; + +- (BOOL)isValid; +- (void)invalidate; +- (BOOL)isScheduled; +- (BOOL)scheduleNow; +- (BOOL)pause; +- (BOOL)resume; +- (NSTimeInterval)initialTimeInterval; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPTimer.m b/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPTimer.m new file mode 100644 index 00000000000..544501ec3ef --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPTimer.m @@ -0,0 +1,177 @@ +// +// MPTimer.m +// MoPub +// +// Created by Andrew He on 3/8/11. +// Copyright 2011 MoPub, Inc. All rights reserved. +// + +#import "MPTimer.h" +#import "MPLogging.h" +#import "MPInternalUtils.h" + +@interface MPTimer () +@property (nonatomic, assign) NSTimeInterval timeInterval; +@property (nonatomic, strong) NSTimer *timer; +@property (nonatomic, copy) NSDate *pauseDate; +@property (nonatomic, assign) BOOL isPaused; +@end + +@interface MPTimer () + +@property (nonatomic, weak) id target; +@property (nonatomic, assign) SEL selector; + +@end + +@implementation MPTimer + +@synthesize timeInterval = _timeInterval; +@synthesize timer = _timer; +@synthesize pauseDate = _pauseDate; +@synthesize target = _target; +@synthesize selector = _selector; +@synthesize isPaused = _isPaused; + ++ (MPTimer *)timerWithTimeInterval:(NSTimeInterval)seconds + target:(id)target + selector:(SEL)aSelector + repeats:(BOOL)repeats +{ + MPTimer *timer = [[MPTimer alloc] init]; + timer.target = target; + timer.selector = aSelector; + timer.timer = [NSTimer timerWithTimeInterval:seconds + target:timer + selector:@selector(timerDidFire) + userInfo:nil + repeats:repeats]; + timer.timeInterval = seconds; + timer.runLoopMode = NSDefaultRunLoopMode; + return timer; +} + +- (void)dealloc +{ + [self.timer invalidate]; +} + +- (void)timerDidFire +{ + SUPPRESS_PERFORM_SELECTOR_LEAK_WARNING( + [self.target performSelector:self.selector withObject:nil] + ); +} + +- (BOOL)isValid +{ + return [self.timer isValid]; +} + +- (void)invalidate +{ + self.target = nil; + self.selector = nil; + [self.timer invalidate]; + self.timer = nil; +} + +- (BOOL)isScheduled +{ + if (!self.timer) { + return NO; + } + CFRunLoopRef runLoopRef = [[NSRunLoop currentRunLoop] getCFRunLoop]; + CFArrayRef arrayRef = CFRunLoopCopyAllModes(runLoopRef); + CFIndex count = CFArrayGetCount(arrayRef); + + for (CFIndex i = 0; i < count; ++i) { + CFStringRef runLoopMode = CFArrayGetValueAtIndex(arrayRef, i); + if (CFRunLoopContainsTimer(runLoopRef, (__bridge CFRunLoopTimerRef)self.timer, runLoopMode)) { + CFRelease(arrayRef); + return YES; + } + } + + CFRelease(arrayRef); + return NO; +} + +- (BOOL)scheduleNow +{ + if (![self.timer isValid]) { + MPLogDebug(@"Could not schedule invalidated MPTimer (%p).", self); + return NO; + } + + [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:self.runLoopMode]; + return YES; +} + +- (BOOL)pause +{ + NSTimeInterval secondsLeft; + if (self.isPaused) { + MPLogDebug(@"No-op: tried to pause an MPTimer (%p) that was already paused.", self); + return NO; + } + + if (![self.timer isValid]) { + MPLogDebug(@"Cannot pause invalidated MPTimer (%p).", self); + return NO; + } + + if (![self isScheduled]) { + MPLogDebug(@"No-op: tried to pause an MPTimer (%p) that was never scheduled.", self); + return NO; + } + + NSDate *fireDate = [self.timer fireDate]; + self.pauseDate = [NSDate date]; + secondsLeft = [fireDate timeIntervalSinceDate:self.pauseDate]; + if (secondsLeft <= 0) { + MPLogWarn(@"An MPTimer was somehow paused after it was supposed to fire."); + } else { + MPLogDebug(@"Paused MPTimer (%p) %.1f seconds left before firing.", self, secondsLeft); + } + + // Pause the timer by setting its fire date far into the future. + [self.timer setFireDate:[NSDate distantFuture]]; + self.isPaused = YES; + + return YES; +} + +- (BOOL)resume +{ + if (![self.timer isValid]) { + MPLogDebug(@"Cannot resume invalidated MPTimer (%p).", self); + return NO; + } + + if (!self.isPaused) { + MPLogDebug(@"No-op: tried to resume an MPTimer (%p) that was never paused.", self); + return NO; + } + + MPLogDebug(@"Resumed MPTimer (%p), should fire in %.1f seconds.", self.timeInterval); + + // Resume the timer. + NSDate *newFireDate = [NSDate dateWithTimeInterval:self.timeInterval sinceDate:[NSDate date]]; + [self.timer setFireDate:newFireDate]; + + if (![self isScheduled]) { + [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:self.runLoopMode]; + } + + self.isPaused = NO; + return YES; +} + +- (NSTimeInterval)initialTimeInterval +{ + return self.timeInterval; +} + +@end + diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPUserInteractionGestureRecognizer.h b/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPUserInteractionGestureRecognizer.h new file mode 100644 index 00000000000..0f77145c1a9 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPUserInteractionGestureRecognizer.h @@ -0,0 +1,12 @@ +// +// MPUserInteractionGestureRecognizer.h +// MoPub +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import + +@interface MPUserInteractionGestureRecognizer : UIGestureRecognizer + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPUserInteractionGestureRecognizer.m b/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPUserInteractionGestureRecognizer.m new file mode 100644 index 00000000000..23dc47c7dba --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/Utility/MPUserInteractionGestureRecognizer.m @@ -0,0 +1,45 @@ +// +// MPUserInteractionGestureRecognizer.m +// MoPub +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import "MPUserInteractionGestureRecognizer.h" + +#import + +@implementation MPUserInteractionGestureRecognizer + +// Currently, we treat any touch as evidence of user interaction +- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event +{ + [super touchesBegan:touches withEvent:event]; + + if (self.state == UIGestureRecognizerStatePossible) { + self.state = UIGestureRecognizerStateRecognized; + } +} + +- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event +{ + [super touchesMoved:touches withEvent:event]; + + self.state = UIGestureRecognizerStateFailed; +} + +- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event +{ + [super touchesEnded:touches withEvent:event]; + + self.state = UIGestureRecognizerStateFailed; +} + +- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event +{ + [super touchesCancelled:touches withEvent:event]; + + self.state = UIGestureRecognizerStateFailed; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTAd.h b/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTAd.h new file mode 100644 index 00000000000..5ad3e7d9a36 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTAd.h @@ -0,0 +1,21 @@ +// +// MPVASTAd.h +// MoPub +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import +#import "MPVASTModel.h" + +@class MPVASTInline; +@class MPVASTWrapper; + +@interface MPVASTAd : MPVASTModel + +@property (nonatomic, copy, readonly) NSString *identifier; +@property (nonatomic, copy, readonly) NSString *sequence; +@property (nonatomic, readonly) MPVASTInline *inlineAd; +@property (nonatomic, readonly) MPVASTWrapper *wrapper; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTAd.m b/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTAd.m new file mode 100644 index 00000000000..ceb1f334b18 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTAd.m @@ -0,0 +1,38 @@ +// +// MPVASTAd.m +// MoPub +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import "MPVASTAd.h" +#import "MPVASTInline.h" +#import "MPVASTWrapper.h" +#import "MPLogging.h" + +@implementation MPVASTAd + +- (instancetype)initWithDictionary:(NSDictionary *)dictionary +{ + self = [super initWithDictionary:dictionary]; + if (self) { + // The VAST spec (2.2.2.2) prohibits an element from having both an and a + // element. If both are present, we'll only allow the element. + if (_inlineAd && _wrapper) { + MPLogWarn(@"VAST element is not allowed to contain both an and a " + @". The will be ignored."); + _wrapper = nil; + } + } + return self; +} + ++ (NSDictionary *)modelMap +{ + return @{@"identifier": @"id", + @"sequence": @"sequence", + @"inlineAd": @[@"InLine", MPParseClass([MPVASTInline class])], + @"wrapper": @[@"Wrapper", MPParseClass([MPVASTWrapper class])]}; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTCompanionAd.h b/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTCompanionAd.h new file mode 100644 index 00000000000..07416a90d83 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTCompanionAd.h @@ -0,0 +1,26 @@ +// +// MPVASTCompanionAd.h +// MoPub +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import +#import +#import "MPVASTModel.h" + +@interface MPVASTCompanionAd : MPVASTModel + +@property (nonatomic, readonly) CGFloat assetHeight; +@property (nonatomic, readonly) CGFloat assetWidth; +@property (nonatomic, copy, readonly) NSURL *clickThroughURL; +@property (nonatomic, readonly) NSArray *clickTrackingURLs; +@property (nonatomic, readonly) CGFloat height; +@property (nonatomic, readonly) NSArray *HTMLResources; +@property (nonatomic, copy, readonly) NSString *identifier; +@property (nonatomic, readonly) NSArray *iframeResources; +@property (nonatomic, readonly) NSArray *staticResources; +@property (nonatomic, readonly) NSDictionary *trackingEvents; +@property (nonatomic, readonly) CGFloat width; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTCompanionAd.m b/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTCompanionAd.m new file mode 100644 index 00000000000..f97f26d184c --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTCompanionAd.m @@ -0,0 +1,52 @@ +// +// MPVASTCompanionAd.m +// MoPub +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import "MPVASTCompanionAd.h" +#import "MPVASTResource.h" +#import "MPVASTStringUtilities.h" +#import "MPVASTTrackingEvent.h" + +@implementation MPVASTCompanionAd + +- (instancetype)initWithDictionary:(NSDictionary *)dictionary +{ + self = [super initWithDictionary:dictionary]; + if (self) { + NSArray *trackingEvents = [self generateModelsFromDictionaryValue:dictionary[@"TrackingEvents"][@"Tracking"] + modelProvider:^id(NSDictionary *dictionary) { + return [[MPVASTTrackingEvent alloc] initWithDictionary:dictionary]; + }]; + NSMutableDictionary *eventsDictionary = [NSMutableDictionary dictionary]; + for (MPVASTTrackingEvent *event in trackingEvents) { + NSMutableArray *events = [eventsDictionary objectForKey:event.eventType]; + if (!events) { + [eventsDictionary setObject:[NSMutableArray array] forKey:event.eventType]; + events = [eventsDictionary objectForKey:event.eventType]; + } + [events addObject:event]; + } + _trackingEvents = eventsDictionary; + } + return self; +} + ++ (NSDictionary *)modelMap +{ + return @{@"assetHeight": @[@"assetHeight", MPParseNumberFromString(NSNumberFormatterDecimalStyle)], + @"assetWidth": @[@"assetWidth", MPParseNumberFromString(NSNumberFormatterDecimalStyle)], + @"height": @[@"height", MPParseNumberFromString(NSNumberFormatterDecimalStyle)], + @"width": @[@"width", MPParseNumberFromString(NSNumberFormatterDecimalStyle)], + @"clickThroughURL": @[@"CompanionClickThrough.text", MPParseURLFromString()], + @"clickTrackingURLs": @[@"CompanionClickTracking.text", MPParseArrayOf(MPParseURLFromString())], + @"viewTrackingURLs": @[@"IconViewTracking.text", MPParseArrayOf(MPParseURLFromString())], + @"identifier": @"id", + @"HTMLResources": @[@"HTMLResource", MPParseArrayOf(MPParseClass([MPVASTResource class]))], + @"iframeResources": @[@"IFrameResource", MPParseArrayOf(MPParseClass([MPVASTResource class]))], + @"staticResources": @[@"StaticResource", MPParseArrayOf(MPParseClass([MPVASTResource class]))]}; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTCreative.h b/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTCreative.h new file mode 100644 index 00000000000..125f86c7b88 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTCreative.h @@ -0,0 +1,21 @@ +// +// MPVASTCreative.h +// MoPub +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import +#import "MPVASTModel.h" + +@class MPVASTLinearAd; + +@interface MPVASTCreative : MPVASTModel + +@property (nonatomic, copy, readonly) NSString *identifier; +@property (nonatomic, copy, readonly) NSString *sequence; +@property (nonatomic, copy, readonly) NSString *adID; +@property (nonatomic, readonly) MPVASTLinearAd *linearAd; +@property (nonatomic, readonly) NSArray *companionAds; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTCreative.m b/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTCreative.m new file mode 100644 index 00000000000..ea34ab89d75 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTCreative.m @@ -0,0 +1,23 @@ +// +// MPVASTCreative.m +// MoPub +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import "MPVASTCreative.h" +#import "MPVASTLinearAd.h" +#import "MPVASTCompanionAd.h" + +@implementation MPVASTCreative + ++ (NSDictionary *)modelMap +{ + return @{@"identifier": @"id", + @"sequence": @"sequence", + @"adID": @"adID", + @"linearAd": @[@"Linear", MPParseClass([MPVASTLinearAd class])], + @"companionAds": @[@"CompanionAds.Companion", MPParseArrayOf(MPParseClass([MPVASTCompanionAd class]))]}; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTDurationOffset.h b/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTDurationOffset.h new file mode 100644 index 00000000000..4594c4e427f --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTDurationOffset.h @@ -0,0 +1,23 @@ +// +// MPVASTDurationOffset.h +// MoPub +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import +#import "MPVASTModel.h" + +typedef NS_ENUM(NSUInteger, MPVASTDurationOffsetType) { + MPVASTDurationOffsetTypeAbsolute, + MPVASTDurationOffsetTypePercentage, +}; + +@interface MPVASTDurationOffset : MPVASTModel + +@property (nonatomic, readonly) MPVASTDurationOffsetType type; +@property (nonatomic, copy, readonly) NSString *offset; + +- (NSTimeInterval)timeIntervalForVideoWithDuration:(NSTimeInterval)duration; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTDurationOffset.m b/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTDurationOffset.m new file mode 100644 index 00000000000..1b86d359b24 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTDurationOffset.m @@ -0,0 +1,49 @@ +// +// MPVASTDurationOffset.m +// MoPub +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import "MPVASTDurationOffset.h" +#import "MPVASTStringUtilities.h" + +@implementation MPVASTDurationOffset + +- (instancetype)initWithDictionary:(NSDictionary *)dictionary +{ + self = [super initWithDictionary:dictionary]; + if (self) { + _offset = dictionary[@"offset"] ?: dictionary[@"skipoffset"]; + if (!_offset) { + return nil; + } + + BOOL isPercentage = [MPVASTStringUtilities stringRepresentsNonNegativePercentage:_offset]; + BOOL isDuration = [MPVASTStringUtilities stringRepresentsNonNegativeDuration:_offset]; + if (!isPercentage && !isDuration) { + return nil; + } + + _type = isDuration ? MPVASTDurationOffsetTypeAbsolute : MPVASTDurationOffsetTypePercentage; + } + return self; +} + +- (NSTimeInterval)timeIntervalForVideoWithDuration:(NSTimeInterval)duration +{ + if (duration < 0) { + return 0; + } + + if (self.type == MPVASTDurationOffsetTypeAbsolute) { + return [MPVASTStringUtilities timeIntervalFromString:self.offset]; + } else if (self.type == MPVASTDurationOffsetTypePercentage) { + NSInteger percentage = [MPVASTStringUtilities percentageFromString:self.offset]; + return duration * percentage / 100.0f; + } else { + return 0; + } +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTIndustryIcon.h b/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTIndustryIcon.h new file mode 100644 index 00000000000..38460d72e71 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTIndustryIcon.h @@ -0,0 +1,35 @@ +// +// MPVASTIndustryIcon.h +// MoPub +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import +#import +#import "MPVASTModel.h" + +@class MPVASTDurationOffset; +@class MPVASTResource; + +@interface MPVASTIndustryIcon : MPVASTModel + +@property (nonatomic, copy, readonly) NSString *program; +@property (nonatomic, readonly) CGFloat height; +@property (nonatomic, readonly) CGFloat width; +@property (nonatomic, copy, readonly) NSString *xPosition; +@property (nonatomic, copy, readonly) NSString *yPosition; + +@property (nonatomic, copy, readonly) NSString *apiFramework; +@property (nonatomic, readonly) NSTimeInterval duration; +@property (nonatomic, readonly) MPVASTDurationOffset *offset; + +@property (nonatomic, copy, readonly) NSURL *clickThroughURL; +@property (nonatomic, readonly) NSArray *clickTrackingURLs; +@property (nonatomic, readonly) NSArray *viewTrackingURLs; + +@property (nonatomic, readonly) MPVASTResource *HTMLResource; +@property (nonatomic, readonly) MPVASTResource *iframeResource; +@property (nonatomic, readonly) MPVASTResource *staticResource; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTIndustryIcon.m b/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTIndustryIcon.m new file mode 100644 index 00000000000..27b838829de --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTIndustryIcon.m @@ -0,0 +1,34 @@ +// +// MPVASTIndustryIcon.m +// MoPub +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import "MPVASTIndustryIcon.h" +#import "MPVASTDurationOffset.h" +#import "MPVASTResource.h" +#import "MPVASTStringUtilities.h" + +@implementation MPVASTIndustryIcon + ++ (NSDictionary *)modelMap +{ + return @{@"program": @"program", + @"height": @[@"height", MPParseNumberFromString(NSNumberFormatterDecimalStyle)], + @"width": @[@"width", MPParseNumberFromString(NSNumberFormatterDecimalStyle)], + @"xPosition": @"xPosition", + @"yPosition": @"yPosition", + @"clickThroughURL": @[@"IconClicks.IconClickThrough.text", MPParseURLFromString()], + @"clickTrackingURLs": @[@"IconClicks.IconClickTracking.text", MPParseArrayOf(MPParseURLFromString())], + @"viewTrackingURLs": @[@"IconViewTracking.text", MPParseArrayOf(MPParseURLFromString())], + @"apiFramework": @"apiFramework", + @"duration": @[@"duration", MPParseTimeIntervalFromDurationString()], + @"offset": @[@"@self", MPParseClass([MPVASTDurationOffset class])], + @"HTMLResource": @[@"HTMLResource", MPParseArrayOf(MPParseClass([MPVASTResource class]))], + @"iframeResource": @[@"IFrameResource", MPParseArrayOf(MPParseClass([MPVASTResource class]))], + @"staticResource": @[@"StaticResource", MPParseArrayOf(MPParseClass([MPVASTResource class]))]}; + +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTInline.h b/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTInline.h new file mode 100644 index 00000000000..b64333849e9 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTInline.h @@ -0,0 +1,18 @@ +// +// MPVASTInline.h +// MoPub +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import +#import "MPVASTModel.h" + +@interface MPVASTInline : MPVASTModel + +@property (nonatomic, readonly) NSArray *creatives; +@property (nonatomic, readonly) NSArray *errorURLs; +@property (nonatomic, readonly) NSDictionary *extensions; +@property (nonatomic, readonly) NSArray *impressionURLs; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTInline.m b/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTInline.m new file mode 100644 index 00000000000..3b73b505a6f --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTInline.m @@ -0,0 +1,32 @@ +// +// MPVASTInline.m +// MoPub +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import "MPVASTInline.h" +#import "MPVASTCreative.h" + +@implementation MPVASTInline + +- (instancetype)initWithDictionary:(NSDictionary *)dictionary +{ + self = [super initWithDictionary:dictionary]; + if (self) { + _extensions = [self generateModelFromDictionaryValue:dictionary[@"Extensions"] + modelProvider:^id(NSDictionary *dictionary) { + return dictionary; + }]; + } + return self; +} + ++ (NSDictionary *)modelMap +{ + return @{@"creatives": @[@"Creatives.Creative", MPParseArrayOf(MPParseClass([MPVASTCreative class]))], + @"errorURLs": @[@"Error.text", MPParseArrayOf(MPParseURLFromString())], + @"impressionURLs": @[@"Impression.text", MPParseArrayOf(MPParseURLFromString())]}; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTLinearAd.h b/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTLinearAd.h new file mode 100644 index 00000000000..905c0e4db45 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTLinearAd.h @@ -0,0 +1,30 @@ +// +// MPVASTLinearAd.h +// MoPub +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import +#import "MPVASTModel.h" + +@class MPVASTDurationOffset; +@class MPVASTMediaFile; + +@interface MPVASTLinearAd : MPVASTModel + +@property (nonatomic, copy, readonly) NSURL *clickThroughURL; +@property (nonatomic, readonly) NSArray *clickTrackingURLs; +@property (nonatomic, readonly) NSArray *customClickURLs; +@property (nonatomic, readonly) NSTimeInterval duration; +@property (nonatomic, readonly) NSArray *industryIcons; +@property (nonatomic, readonly) NSArray *mediaFiles; +@property (nonatomic, readonly) MPVASTDurationOffset *skipOffset; +@property (nonatomic, readonly) NSDictionary *trackingEvents; + +@end + +@interface MPVASTLinearAd (Media) +@property (nonatomic, readonly) MPVASTMediaFile *highestBitrateMediaFile; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTLinearAd.m b/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTLinearAd.m new file mode 100644 index 00000000000..c5b3d980052 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTLinearAd.m @@ -0,0 +1,87 @@ +// +// MPVASTLinearAd.m +// MoPub +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import "MPVASTLinearAd.h" +#import "MPVASTDurationOffset.h" +#import "MPVASTIndustryIcon.h" +#import "MPVASTMediaFile.h" +#import "MPVASTStringUtilities.h" +#import "MPVASTTrackingEvent.h" + +@interface MPVASTLinearAd () + +@property (nonatomic, readwrite) NSArray *clickTrackingURLs; +@property (nonatomic, readwrite) NSArray *customClickURLs; +@property (nonatomic, readwrite) NSArray *industryIcons; +@property (nonatomic, readwrite) NSDictionary *trackingEvents; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation MPVASTLinearAd + +- (instancetype)initWithDictionary:(NSDictionary *)dictionary +{ + self = [super initWithDictionary:dictionary]; + if (self) { + NSArray *trackingEvents = [self generateModelsFromDictionaryValue:dictionary[@"TrackingEvents"][@"Tracking"] + modelProvider:^id(NSDictionary *dictionary) { + return [[MPVASTTrackingEvent alloc] initWithDictionary:dictionary]; + }]; + NSMutableDictionary *eventsDictionary = [NSMutableDictionary dictionary]; + for (MPVASTTrackingEvent *event in trackingEvents) { + NSMutableArray *events = [eventsDictionary objectForKey:event.eventType]; + if (!events) { + [eventsDictionary setObject:[NSMutableArray array] forKey:event.eventType]; + events = [eventsDictionary objectForKey:event.eventType]; + } + [events addObject:event]; + } + _trackingEvents = eventsDictionary; + } + return self; +} + ++ (NSDictionary *)modelMap +{ + return @{@"clickThroughURL": @[@"VideoClicks.ClickThrough.text", MPParseURLFromString()], + @"clickTrackingURLs": @[@"VideoClicks.ClickTracking.text", MPParseArrayOf(MPParseURLFromString())], + @"customClickURLs": @[@"VideoClicks.CustomClick.text", MPParseArrayOf(MPParseURLFromString())], + @"duration": @[@"Duration.text", MPParseTimeIntervalFromDurationString()], + @"industryIcons": @[@"Icons.Icon", MPParseArrayOf(MPParseClass([MPVASTIndustryIcon class]))], + @"mediaFiles": @[@"MediaFiles.MediaFile", MPParseArrayOf(MPParseClass([MPVASTMediaFile class]))], + @"skipOffset": @[@"@self", MPParseClass([MPVASTDurationOffset class])]}; +} + +@end + +@implementation MPVASTLinearAd (Media) + +// Static set of supported MIME types for native video. +- (NSSet *)validVideoMimeTypes { + static dispatch_once_t onceToken; + static NSSet * validVideoMimeTypes = nil; + dispatch_once(&onceToken, ^{ + validVideoMimeTypes = [NSSet setWithObjects:@"video/quicktime", @"video/mp4", @"video/3gpp", @"video/3gpp2", @"video/x-m4v", nil]; + }); + + return validVideoMimeTypes; +} + +// Filters out unsupported media files and selects the highest bitrate video +- (MPVASTMediaFile *)highestBitrateMediaFile { + NSPredicate * predicate = [NSPredicate predicateWithFormat:@"mimeType IN %@", self.validVideoMimeTypes]; + NSArray * filteredMediaFiles = [self.mediaFiles filteredArrayUsingPredicate:predicate]; + NSArray * sortedMediaFiles = [filteredMediaFiles sortedArrayUsingComparator:^NSComparisonResult(MPVASTMediaFile * a, MPVASTMediaFile * b) { + return a.bitrate < b.bitrate; + }]; + + return (sortedMediaFiles.count > 0 ? sortedMediaFiles[0] : nil); +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTMacroProcessor.h b/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTMacroProcessor.h new file mode 100644 index 00000000000..b105fa554e6 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTMacroProcessor.h @@ -0,0 +1,15 @@ +// +// MPVASTMacroProcessor.h +// MoPub +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import + +@interface MPVASTMacroProcessor : NSObject + ++ (NSURL *)macroExpandedURLForURL:(NSURL *)URL errorCode:(NSString *)errorCode; ++ (NSURL *)macroExpandedURLForURL:(NSURL *)URL errorCode:(NSString *)errorCode videoTimeOffset:(NSTimeInterval)timeOffset videoAssetURL:(NSURL *)videoAssetURL; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTMacroProcessor.m b/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTMacroProcessor.m new file mode 100644 index 00000000000..40e7589759c --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTMacroProcessor.m @@ -0,0 +1,48 @@ +// +// MPVASTMacroProcessor.m +// MoPub +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import "MPVASTMacroProcessor.h" +#import "MPGlobal.h" +#import "MPVASTStringUtilities.h" + +@implementation MPVASTMacroProcessor + ++ (NSURL *)macroExpandedURLForURL:(NSURL *)URL errorCode:(NSString *)errorCode +{ + return [self macroExpandedURLForURL:URL errorCode:errorCode videoTimeOffset:-1 videoAssetURL:nil]; +} + ++ (NSURL *)macroExpandedURLForURL:(NSURL *)URL errorCode:(NSString *)errorCode videoTimeOffset:(NSTimeInterval)timeOffset videoAssetURL:(NSURL *)assetURL +{ + NSMutableString *URLString = [[URL absoluteString] mutableCopy]; + + NSString *trimmedErrorCode = [errorCode stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; + if ([trimmedErrorCode length]) { + [URLString replaceOccurrencesOfString:@"[ERRORCODE]" withString:errorCode options:0 range:NSMakeRange(0, [URLString length])]; + [URLString replaceOccurrencesOfString:@"%5BERRORCODE%5D" withString:errorCode options:0 range:NSMakeRange(0, [URLString length])]; + } + + if (timeOffset >= 0) { + NSString *timeOffsetString = [MPVASTStringUtilities stringFromTimeInterval:timeOffset]; + [URLString replaceOccurrencesOfString:@"[CONTENTPLAYHEAD]" withString:timeOffsetString options:0 range:NSMakeRange(0, [URLString length])]; + [URLString replaceOccurrencesOfString:@"%5BCONTENTPLAYHEAD%5D" withString:timeOffsetString options:0 range:NSMakeRange(0, [URLString length])]; + } + + if (assetURL) { + NSString *encodedAssetURLString = [[assetURL absoluteString] mp_URLEncodedString]; + [URLString replaceOccurrencesOfString:@"[ASSETURI]" withString:encodedAssetURLString options:0 range:NSMakeRange(0, [URLString length])]; + [URLString replaceOccurrencesOfString:@"%5BASSETURI%5D" withString:encodedAssetURLString options:0 range:NSMakeRange(0, [URLString length])]; + } + + NSString *cachebuster = [NSString stringWithFormat:@"%u", arc4random() % 90000000 + 10000000]; + [URLString replaceOccurrencesOfString:@"[CACHEBUSTING]" withString:cachebuster options:0 range:NSMakeRange(0, [URLString length])]; + [URLString replaceOccurrencesOfString:@"%5BCACHEBUSTING%5D" withString:cachebuster options:0 range:NSMakeRange(0, [URLString length])]; + + return [NSURL URLWithString:URLString]; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTManager.h b/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTManager.h new file mode 100644 index 00000000000..4a42b87d532 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTManager.h @@ -0,0 +1,22 @@ +// +// MPVASTManager.h +// MoPub +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import +#import "MPVASTResponse.h" + +typedef enum { + MPVASTErrorXMLParseFailure, + MPVASTErrorExceededMaximumWrapperDepth, + MPVASTErrorNoAdsFound +} MPVASTError; + +@interface MPVASTManager : NSObject + ++ (void)fetchVASTWithURL:(NSURL *)URL completion:(void (^)(MPVASTResponse *, NSError *))completion; ++ (void)fetchVASTWithData:(NSData *)data completion:(void (^)(MPVASTResponse *, NSError *))completion; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTManager.m b/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTManager.m new file mode 100644 index 00000000000..765f4dadb8e --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTManager.m @@ -0,0 +1,136 @@ +// +// MPVASTManager.m +// MoPub +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import "MPVASTManager.h" +#import "MPVASTAd.h" +#import "MPVASTWrapper.h" +#import "MPXMLParser.h" + +@interface MPVASTWrapper (MPVASTManager) + +@property (nonatomic, readwrite) MPVASTResponse *wrappedVASTResponse; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +static const NSInteger kMaximumWrapperDepth = 10; +static NSString * const kMPVASTManagerErrorDomain = @"com.mopub.MPVASTManager"; + +@implementation MPVASTManager + ++ (void)fetchVASTWithURL:(NSURL *)URL completion:(void (^)(MPVASTResponse *, NSError *))completion +{ + [NSURLConnection sendAsynchronousRequest:[NSURLRequest requestWithURL:URL] + queue:[NSOperationQueue mainQueue] + completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) { + [self fetchVASTWithData:data completion:completion]; + }]; +} + ++ (void)fetchVASTWithData:(NSData *)data completion:(void (^)(MPVASTResponse *, NSError *))completion +{ + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + [self parseVASTResponseFromData:data depth:0 completion:^(MPVASTResponse *response, NSError *error) { + dispatch_async(dispatch_get_main_queue(), ^{ + if (error) { + completion(nil, error); + } else { + completion(response, nil); + } + }); + }]; + }); +} + ++ (void)parseVASTResponseFromData:(NSData *)data depth:(NSInteger)depth completion:(void (^)(MPVASTResponse *response, NSError *error))completion +{ + if (depth >= kMaximumWrapperDepth) { + completion(nil, [NSError errorWithDomain:kMPVASTManagerErrorDomain code:MPVASTErrorExceededMaximumWrapperDepth userInfo:nil]); + return; + } + + NSError *XMLParserError = nil; + MPXMLParser *parser = [[MPXMLParser alloc] init]; + NSDictionary *dictionary = [parser dictionaryWithData:data error:&XMLParserError]; + if (XMLParserError) { + completion(nil, [NSError errorWithDomain:kMPVASTManagerErrorDomain code:MPVASTErrorXMLParseFailure userInfo:nil]); + return; + } + + MPVASTResponse *VASTResponse = [[MPVASTResponse alloc] initWithDictionary:dictionary]; + NSArray *wrappers = [self wrappersForVASTResponse:VASTResponse]; + if ([wrappers count] == 0) { + if ([self VASTResponseContainsAtLeastOneAd:VASTResponse]) { + completion(VASTResponse, nil); + return; + } else { + completion(nil, [NSError errorWithDomain:kMPVASTManagerErrorDomain code:MPVASTErrorNoAdsFound userInfo:nil]); + return; + } + } + + __block NSInteger wrappersFetched = 0; + for (MPVASTWrapper *wrapper in wrappers) { + [NSURLConnection sendAsynchronousRequest:[NSURLRequest requestWithURL:wrapper.VASTAdTagURI] + queue:[NSOperationQueue mainQueue] + completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) { + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + if (connectionError) { + wrapper.wrappedVASTResponse = nil; + } else if (data) { + [self parseVASTResponseFromData:data depth:depth + 1 completion:^(MPVASTResponse *response, NSError *error) { + if (error) { + completion(nil, error); + return; + } + + wrapper.wrappedVASTResponse = response; + wrappersFetched++; + + // Once we've fetched all wrappers within the VAST + // response, we can call the top-level completion + // handler. + if (wrappersFetched == [wrappers count]) { + if ([self VASTResponseContainsAtLeastOneAd:VASTResponse]) { + completion(VASTResponse, nil); + return; + } else { + completion(nil, [NSError errorWithDomain:kMPVASTManagerErrorDomain code:MPVASTErrorNoAdsFound userInfo:nil]); + return; + } + } + }]; + } + }); + }]; + } +} + ++ (NSArray *)wrappersForVASTResponse:(MPVASTResponse *)response +{ + NSMutableArray *wrappers = [NSMutableArray array]; + for (MPVASTAd *ad in response.ads) { + if (ad.wrapper) { + [wrappers addObject:ad.wrapper]; + } + } + return wrappers; +} + ++ (BOOL)VASTResponseContainsAtLeastOneAd:(MPVASTResponse *)response +{ + for (MPVASTAd *ad in response.ads) { + if (ad.inlineAd || ad.wrapper.wrappedVASTResponse) { + return YES; + } + } + return NO; +} + +@end + diff --git a/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTMediaFile.h b/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTMediaFile.h new file mode 100644 index 00000000000..70bf6b9bdfa --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTMediaFile.h @@ -0,0 +1,22 @@ +// +// MPVASTMediaFile.h +// MoPub +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import +#import +#import "MPVASTModel.h" + +@interface MPVASTMediaFile : MPVASTModel + +@property (nonatomic, copy, readonly) NSString *identifier; +@property (nonatomic, copy, readonly) NSString *delivery; +@property (nonatomic, copy, readonly) NSString *mimeType; +@property (nonatomic, readonly) double bitrate; +@property (nonatomic, readonly) CGFloat width; +@property (nonatomic, readonly) CGFloat height; +@property (nonatomic, copy, readonly) NSURL *URL; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTMediaFile.m b/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTMediaFile.m new file mode 100644 index 00000000000..7e0377e6f5b --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTMediaFile.m @@ -0,0 +1,24 @@ +// +// MPVASTMediaFile.m +// MoPub +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import "MPVASTMediaFile.h" +#import "MPVASTStringUtilities.h" + +@implementation MPVASTMediaFile + ++ (NSDictionary *)modelMap +{ + return @{@"bitrate": @[@"bitrate", MPParseNumberFromString(NSNumberFormatterDecimalStyle)], + @"height": @[@"height", MPParseNumberFromString(NSNumberFormatterDecimalStyle)], + @"width": @[@"width", MPParseNumberFromString(NSNumberFormatterDecimalStyle)], + @"identifier": @"id", + @"delivery": @"delivery", + @"mimeType": @"type", + @"URL": @[@"text", MPParseURLFromString()]}; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTModel.h b/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTModel.h new file mode 100644 index 00000000000..bbee686b228 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTModel.h @@ -0,0 +1,68 @@ +// +// MPVASTModel.h +// MoPub +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import + +@protocol MPObjectMapper + +- (id)mappedObjectFromSourceObject:(id)object; +- (Class)requiredSourceObjectClass; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +id MPParseArrayOf(id internalMapper); +id MPParseURLFromString(); +id MPParseNumberFromString(NSNumberFormatterStyle numberStyle); +id MPParseTimeIntervalFromDurationString(); +id MPParseClass(Class destinationClass); + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface MPNSStringToNSURLMapper : NSObject +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface MPDurationStringToTimeIntervalMapper : NSObject +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface MPStringToNumberMapper : NSObject + +- (id)initWithNumberStyle:(NSNumberFormatterStyle)numberStyle; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface MPClassMapper : NSObject + +- (id)initWithDestinationClass:(Class)destinationClass; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface MPNSArrayMapper : NSObject + +- (id)initWithInternalMapper:(id)mapper; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface MPVASTModel : NSObject + ++ (NSDictionary *)modelMap; +- (instancetype)initWithDictionary:(NSDictionary *)dictionary; +- (id)generateModelFromDictionaryValue:(id)value modelProvider:(id(^)(id))provider; +- (NSArray *)generateModelsFromDictionaryValue:(id)value modelProvider:(id(^)(id))provider; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTModel.m b/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTModel.m new file mode 100644 index 00000000000..700ade06308 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTModel.m @@ -0,0 +1,335 @@ +// +// MPVASTModel.m +// MoPub +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import "MPVASTModel.h" +#import "MPVASTStringUtilities.h" +#import "MPLogging.h" +#import + +id MPParseArrayOf(id internalMapper) +{ + return [[MPNSArrayMapper alloc] initWithInternalMapper:internalMapper]; +} + +id MPParseURLFromString() +{ + return [[MPNSStringToNSURLMapper alloc] init]; +} + +id MPParseNumberFromString(NSNumberFormatterStyle numberStyle) +{ + return [[MPStringToNumberMapper alloc] initWithNumberStyle:numberStyle]; +} + +id MPParseTimeIntervalFromDurationString() +{ + return [[MPDurationStringToTimeIntervalMapper alloc] init]; +} + +id MPParseClass(Class destinationClass) +{ + return [[MPClassMapper alloc] initWithDestinationClass:destinationClass]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation MPNSStringToNSURLMapper + +- (id)mappedObjectFromSourceObject:(id)object +{ + NSString *URLString = [object stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; + return [NSURL URLWithString:URLString]; +} + +- (Class)requiredSourceObjectClass +{ + return [NSString class]; +} + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation MPDurationStringToTimeIntervalMapper + +- (id)mappedObjectFromSourceObject:(id)object +{ + return @([MPVASTStringUtilities timeIntervalFromString:object]); +} + +- (Class)requiredSourceObjectClass +{ + return [NSString class]; +} + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface MPStringToNumberMapper () + +@property (nonatomic) NSNumberFormatter *numberFormatter; + +@end + +@implementation MPStringToNumberMapper + +- (id)init +{ + [self doesNotRecognizeSelector:_cmd]; + return nil; +} + +- (id)initWithNumberStyle:(NSNumberFormatterStyle)numberStyle +{ + self = [super init]; + if (self) { + _numberFormatter = [[NSNumberFormatter alloc] init]; + _numberFormatter.numberStyle = numberStyle; + } + return self; +} + +- (id)mappedObjectFromSourceObject:(id)object +{ + return [self.numberFormatter numberFromString:object]; +} + +- (Class)requiredSourceObjectClass +{ + return [NSString class]; +} + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface MPClassMapper () + +@property (nonatomic) Class destinationClass; + +@end + +@implementation MPClassMapper + +- (id)init +{ + [self doesNotRecognizeSelector:_cmd]; + return nil; +} + +- (id)initWithDestinationClass:(Class)destinationClass +{ + self = [super init]; + if (self) { + _destinationClass = destinationClass; + } + return self; +} + +- (id)mappedObjectFromSourceObject:(id)object +{ + if (![self.destinationClass isSubclassOfClass:[MPVASTModel class]] || + ![self.destinationClass instancesRespondToSelector:@selector(initWithDictionary:)]) { + return nil; + } + + return [[self.destinationClass alloc] initWithDictionary:object]; +} + +- (Class)requiredSourceObjectClass +{ + return [NSDictionary class]; +} + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface MPNSArrayMapper () + +@property (nonatomic) id mapper; + +@end + +@implementation MPNSArrayMapper + +- (id)init +{ + [self doesNotRecognizeSelector:_cmd]; + return nil; +} + +- (id)initWithInternalMapper:(id)mapper +{ + self = [super init]; + if (self) { + _mapper = mapper; + } + return self; +} + +- (id)mappedObjectFromSourceObject:(id)object +{ + NSMutableArray *result = [NSMutableArray array]; + + if ([object isKindOfClass:[NSArray class]]) { + for (id obj in object) { + id model = [self.mapper mappedObjectFromSourceObject:obj]; + if (model) { + [result addObject:model]; + } + } + } else if ([object isKindOfClass:[self.mapper requiredSourceObjectClass]]) { + id model = [self.mapper mappedObjectFromSourceObject:object]; + if (model) { + [result addObject:model]; + } + } + + return result; +} + +- (Class)requiredSourceObjectClass +{ + return [NSArray class]; +} + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation MPVASTModel + +- (instancetype)initWithDictionary:(NSDictionary *)dictionary +{ + self = [super init]; + if (self) { + if (!dictionary) { + return nil; + } + + NSDictionary *modelMap = [[self class] modelMap]; + for (NSString *key in modelMap) { + if ([self hasPropertyNamed:key]) { + id modelMapValue = modelMap[key]; + + if ([modelMapValue isKindOfClass:[NSString class]]) { + // The simple case: grab the value corresponding to the given key path and + // assign it to the property. + id value = [dictionary valueForKeyPath:modelMapValue]; + if (value) { + [self setValue:value forKey:key]; + } + } else if ([modelMapValue isKindOfClass:[NSArray class]] && [modelMapValue count] == 2) { + NSString *dictionaryKeyPath = modelMapValue[0]; + id mapper = modelMapValue[1]; + + if ([mapper conformsToProtocol:@protocol(MPObjectMapper)]) { + id sourceObject = [dictionary valueForKeyPath:dictionaryKeyPath]; + if (sourceObject) { + id model = [mapper mappedObjectFromSourceObject:sourceObject]; + if (model) { + [self setValue:model forKey:key]; + } + } + } + } else { + MPLogError(@"Could not populate %@ of class %@ because its mapper is invalid.", + key, NSStringFromClass([self class])); + } + } + } + } + return self; +} + ++ (NSDictionary *)modelMap +{ + // Implemented by subclasses. + return nil; +} + +- (id)generateModelFromDictionaryValue:(id)value modelProvider:(id(^)(id))provider { + if (value && [value isKindOfClass:[NSArray class]] && [value count] > 0) { + return provider(value[0]); + } else if (value && [value isKindOfClass:[NSDictionary class]]) { + return provider(value); + } else { + return nil; + } +} + +- (NSArray *)generateModelsFromDictionaryValue:(id)value modelProvider:(id(^)(id))provider { + NSMutableArray *models = [NSMutableArray array]; + + if (value && [value isKindOfClass:[NSArray class]]) { + for (NSDictionary *dictionary in value) { + id model = provider(dictionary); + if (model) { + [models addObject:model]; + } + } + } else if (value && [value isKindOfClass:[NSDictionary class]]) { + id model = provider(value); + if (model) { + [models addObject:model]; + } + } + + return [models copy]; +} + +#pragma mark - Internal + +- (BOOL)hasPropertyNamed:(NSString *)name +{ + // This method uses the objc runtime API to check whether the current model class has a given + // property. After we grab the set of properties for a given class, we cache it for efficiency. + + static NSMutableDictionary *propertyNamesForClass; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + propertyNamesForClass = [NSMutableDictionary dictionary]; + }); + + NSString *className = NSStringFromClass([self class]); + NSMutableSet *propertyNames = propertyNamesForClass[className]; + + if (!propertyNames) { + unsigned int propertyCount; + objc_property_t *properties = class_copyPropertyList([self class], &propertyCount); + propertyNames = [NSMutableSet setWithCapacity:propertyCount]; + for (unsigned int i = 0; i < propertyCount; i++) { + objc_property_t property = properties[i]; + [propertyNames addObject:[NSString stringWithUTF8String:property_getName(property)]]; + } + propertyNamesForClass[className] = propertyNames; + free(properties); + } + + return [propertyNames containsObject:name]; +} + +- (NSString *)description +{ + NSMutableString *descriptionString = [NSMutableString stringWithFormat:@"%@:", NSStringFromClass([self class])]; + + unsigned int propertyCount; + objc_property_t *properties = class_copyPropertyList([self class], &propertyCount); + + for (unsigned int i = 0; i < propertyCount; i++) { + objc_property_t property = properties[i]; + NSString *propertyName = [[NSString alloc] initWithUTF8String:property_getName(property)]; + [descriptionString appendFormat:@"\n\t%s = %s", propertyName.UTF8String, [[self valueForKey:propertyName] description].UTF8String]; + } + + free(properties); + return descriptionString; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTResource.h b/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTResource.h new file mode 100644 index 00000000000..82b642b28c8 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTResource.h @@ -0,0 +1,16 @@ +// +// MPVASTResource.h +// MoPub +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import +#import "MPVASTModel.h" + +@interface MPVASTResource : MPVASTModel + +@property (nonatomic, readonly) NSString *content; +@property (nonatomic, readonly) NSString *staticCreativeType; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTResource.m b/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTResource.m new file mode 100644 index 00000000000..7c0ce3779f0 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTResource.m @@ -0,0 +1,18 @@ +// +// MPVASTResource.m +// MoPub +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import "MPVASTResource.h" + +@implementation MPVASTResource + ++ (NSDictionary *)modelMap +{ + return @{@"content": @"text", + @"staticCreativeType": @"creativeType"}; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTResponse.h b/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTResponse.h new file mode 100644 index 00000000000..3d6f70279d0 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTResponse.h @@ -0,0 +1,29 @@ +// +// MPVASTResponse.h +// MoPub +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import +#import "MPVASTModel.h" + +#import "MPVASTAd.h" +#import "MPVASTCompanionAd.h" +#import "MPVASTCreative.h" +#import "MPVASTDurationOffset.h" +#import "MPVASTIndustryIcon.h" +#import "MPVASTInline.h" +#import "MPVASTLinearAd.h" +#import "MPVASTMediaFile.h" +#import "MPVASTResource.h" +#import "MPVASTTrackingEvent.h" +#import "MPVASTWrapper.h" + +@interface MPVASTResponse : MPVASTModel + +@property (nonatomic, readonly) NSArray *ads; +@property (nonatomic, readonly) NSArray *errorURLs; +@property (nonatomic, copy, readonly) NSString *version; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTResponse.m b/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTResponse.m new file mode 100644 index 00000000000..fa93b49b178 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTResponse.m @@ -0,0 +1,28 @@ +// +// MPVASTResponse.m +// MoPub +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import "MPVASTResponse.h" + +@interface MPVASTResponse () + +@property (nonatomic) NSArray *ads; +@property (nonatomic) NSArray *errorURLs; +@property (nonatomic, copy) NSString *version; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation MPVASTResponse + ++ (NSDictionary *)modelMap +{ + return @{@"ads": @[@"VAST.Ad", MPParseArrayOf(MPParseClass([MPVASTAd class]))], + @"version": @"VAST.version"}; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTStringUtilities.h b/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTStringUtilities.h new file mode 100644 index 00000000000..13a89dca79e --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTStringUtilities.h @@ -0,0 +1,19 @@ +// +// MPVASTStringUtilities.h +// MoPub +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import + +@interface MPVASTStringUtilities : NSObject + ++ (double)doubleFromString:(NSString *)string; ++ (BOOL)stringRepresentsNonNegativePercentage:(NSString *)string; ++ (BOOL)stringRepresentsNonNegativeDuration:(NSString *)string; ++ (NSInteger)percentageFromString:(NSString *)string; ++ (NSTimeInterval)timeIntervalFromString:(NSString *)string; ++ (NSString *)stringFromTimeInterval:(NSTimeInterval)timeInterval; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTStringUtilities.m b/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTStringUtilities.m new file mode 100644 index 00000000000..14d6e167ee0 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTStringUtilities.m @@ -0,0 +1,146 @@ +// +// MPVASTStringUtilities.m +// MoPub +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import "MPVASTStringUtilities.h" + +// Expected format is a decimal number from 0-100 followed by the % sign. +static NSString * const kPercentageRegexString = @"^(\\d?\\d(\\.\\d*)?|100(?:\\.0*)?)%$"; +static dispatch_once_t percentageRegexOnceToken; +static NSRegularExpression *percentageRegex; + +// Expected format is either HH:mm:ss.mmm or simply a floating-point number. +static NSString * const kDurationRegexString = @"^(\\d{2}):([0-5]\\d):([0-5]\\d(?:\\.\\d{1,3})?)|(^[0-9]*\\.?[0-9]+$)"; +static dispatch_once_t durationRegexOnceToken; +static NSRegularExpression *durationRegex; + +@implementation MPVASTStringUtilities + ++ (double)doubleFromString:(NSString *)string +{ + static NSNumberFormatter *formatter = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + formatter = [[NSNumberFormatter alloc] init]; + formatter.numberStyle = NSNumberFormatterDecimalStyle; + }); + + return [[formatter numberFromString:string] doubleValue]; +} + ++ (BOOL)stringRepresentsNonNegativePercentage:(NSString *)string +{ + dispatch_once(&percentageRegexOnceToken, ^{ + percentageRegex = [NSRegularExpression regularExpressionWithPattern:kPercentageRegexString options:0 error:nil]; + }); + + NSArray *matches = [percentageRegex matchesInString:string options:0 range:NSMakeRange(0, [string length])]; + + if (![matches count]) { + return NO; + } + + NSTextCheckingResult *match = matches[0]; + return (match.range.location != NSNotFound); +} + ++ (BOOL)stringRepresentsNonNegativeDuration:(NSString *)string +{ + dispatch_once(&durationRegexOnceToken, ^{ + durationRegex = [NSRegularExpression regularExpressionWithPattern:kDurationRegexString options:0 error:nil]; + }); + + NSArray *matches = [durationRegex matchesInString:string options:0 range:NSMakeRange(0, [string length])]; + + if (![matches count]) { + return NO; + } + + NSTextCheckingResult *match = matches[0]; + return (match.range.location != NSNotFound); +} + ++ (NSInteger)percentageFromString:(NSString *)string +{ + dispatch_once(&percentageRegexOnceToken, ^{ + percentageRegex = [NSRegularExpression regularExpressionWithPattern:kPercentageRegexString options:0 error:nil]; + }); + + if (![string length]) { + return 0; + } + + NSArray *matches = [percentageRegex matchesInString:string options:0 range:NSMakeRange(0, [string length])]; + if ([matches count]) { + NSTextCheckingResult *match = matches[0]; + if (match.range.location == NSNotFound) { + return 0; + } + + return [[string substringWithRange:[match rangeAtIndex:1]] integerValue]; + } else { + return 0; + } +} + ++ (NSTimeInterval)timeIntervalFromString:(NSString *)string +{ + dispatch_once(&durationRegexOnceToken, ^{ + durationRegex = [NSRegularExpression regularExpressionWithPattern:kDurationRegexString options:0 error:nil]; + }); + + if (![string length]) { + return 0; + } + + NSArray *matches = [durationRegex matchesInString:string options:0 range:NSMakeRange(0, [string length])]; + + if (![matches count]) { + return 0; + } + + NSTextCheckingResult *match = matches[0]; + if (match.range.location == NSNotFound) { + return 0; + } + + // This is the case where the string is simply a floating-point number. + if ([match rangeAtIndex:4].location != NSNotFound) { + return [[string substringWithRange:[match rangeAtIndex:4]] doubleValue]; + } + + // Fail if hours, minutes, or seconds are missing. + if ([match rangeAtIndex:1].location == NSNotFound || + [match rangeAtIndex:2].location == NSNotFound || + [match rangeAtIndex:3].location == NSNotFound) { + return 0; + } + + NSInteger hours = 0; + NSInteger minutes = 0; + double seconds = 0; + + hours = [[string substringWithRange:[match rangeAtIndex:1]] integerValue]; + minutes = [[string substringWithRange:[match rangeAtIndex:2]] integerValue]; + seconds = [[string substringWithRange:[match rangeAtIndex:3]] doubleValue]; + + return hours * 60 * 60 + minutes * 60 + seconds; +} + ++ (NSString *)stringFromTimeInterval:(NSTimeInterval)timeInterval +{ + if (timeInterval < 0) { + return @"00:00:00.000"; + } + + NSInteger flooredTimeInterval = (NSInteger)timeInterval; + NSInteger hours = flooredTimeInterval / 3600; + NSInteger minutes = (flooredTimeInterval / 60) % 60; + NSTimeInterval seconds = fmod(timeInterval, 60); + return [NSString stringWithFormat:@"%02ld:%02ld:%06.3f", (long)hours, (long)minutes, seconds]; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTTrackingEvent.h b/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTTrackingEvent.h new file mode 100644 index 00000000000..a9c41044b3e --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTTrackingEvent.h @@ -0,0 +1,39 @@ +// +// MPVASTTrackingEvent.h +// MoPub +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import +#import "MPVASTModel.h" + +@class MPVASTDurationOffset; + +extern NSString * const MPVASTTrackingEventTypeCreativeView; +extern NSString * const MPVASTTrackingEventTypeStart; +extern NSString * const MPVASTTrackingEventTypeFirstQuartile; +extern NSString * const MPVASTTrackingEventTypeMidpoint; +extern NSString * const MPVASTTrackingEventTypeThirdQuartile; +extern NSString * const MPVASTTrackingEventTypeComplete; +extern NSString * const MPVASTTrackingEventTypeMute; +extern NSString * const MPVASTTrackingEventTypeUnmute; +extern NSString * const MPVASTTrackingEventTypePause; +extern NSString * const MPVASTTrackingEventTypeRewind; +extern NSString * const MPVASTTrackingEventTypeResume; +extern NSString * const MPVASTTrackingEventTypeFullscreen; +extern NSString * const MPVASTTrackingEventTypeExitFullscreen; +extern NSString * const MPVASTTrackingEventTypeExpand; +extern NSString * const MPVASTTrackingEventTypeCollapse; +extern NSString * const MPVASTTrackingEventTypeAcceptInvitationLinear; +extern NSString * const MPVASTTrackingEventTypeCloseLinear; +extern NSString * const MPVASTTrackingEventTypeSkip; +extern NSString * const MPVASTTrackingEventTypeProgress; + +@interface MPVASTTrackingEvent : MPVASTModel + +@property (nonatomic, copy, readonly) NSString *eventType; +@property (nonatomic, copy, readonly) NSURL *URL; +@property (nonatomic, readonly) MPVASTDurationOffset *progressOffset; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTTrackingEvent.m b/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTTrackingEvent.m new file mode 100644 index 00000000000..a89a1222916 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTTrackingEvent.m @@ -0,0 +1,50 @@ +// +// MPVASTTrackingEvent.m +// MoPub +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import "MPVASTTrackingEvent.h" +#import "MPVASTDurationOffset.h" + +NSString * const MPVASTTrackingEventTypeCreativeView = @"creativeView"; +NSString * const MPVASTTrackingEventTypeStart = @"start"; +NSString * const MPVASTTrackingEventTypeFirstQuartile = @"firstQuartile"; +NSString * const MPVASTTrackingEventTypeMidpoint = @"midpoint"; +NSString * const MPVASTTrackingEventTypeThirdQuartile = @"thirdQuartile"; +NSString * const MPVASTTrackingEventTypeComplete = @"complete"; +NSString * const MPVASTTrackingEventTypeMute = @"mute"; +NSString * const MPVASTTrackingEventTypeUnmute = @"unmute"; +NSString * const MPVASTTrackingEventTypePause = @"pause"; +NSString * const MPVASTTrackingEventTypeRewind = @"rewind"; +NSString * const MPVASTTrackingEventTypeResume = @"resume"; +NSString * const MPVASTTrackingEventTypeFullscreen = @"fullscreen"; +NSString * const MPVASTTrackingEventTypeExitFullscreen = @"exitFullscreen"; +NSString * const MPVASTTrackingEventTypeExpand = @"expand"; +NSString * const MPVASTTrackingEventTypeCollapse = @"collapse"; +NSString * const MPVASTTrackingEventTypeAcceptInvitationLinear = @"acceptInvitationLinear"; +NSString * const MPVASTTrackingEventTypeCloseLinear = @"closeLinear"; +NSString * const MPVASTTrackingEventTypeSkip = @"skip"; +NSString * const MPVASTTrackingEventTypeProgress = @"progress"; + +@implementation MPVASTTrackingEvent + +- (instancetype)initWithDictionary:(NSDictionary *)dictionary +{ + self = [super initWithDictionary:dictionary]; + if (self) { + _eventType = dictionary[@"event"]; + _URL = [self generateModelFromDictionaryValue:dictionary + modelProvider:^id(NSDictionary *dictionary) { + return [NSURL URLWithString:[dictionary[@"text"] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]]; + }]; + _progressOffset = [self generateModelFromDictionaryValue:dictionary + modelProvider:^id(NSDictionary *dictionary) { + return [[MPVASTDurationOffset alloc] initWithDictionary:dictionary]; + }]; + } + return self; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTWrapper.h b/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTWrapper.h new file mode 100644 index 00000000000..27f47441776 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTWrapper.h @@ -0,0 +1,22 @@ +// +// MPVASTWrapper.h +// MoPub +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import +#import "MPVASTModel.h" + +@class MPVASTResponse; + +@interface MPVASTWrapper : MPVASTModel + +@property (nonatomic, readonly) NSArray *creatives; +@property (nonatomic, readonly) NSArray *errorURLs; +@property (nonatomic, readonly) NSArray *extensions; +@property (nonatomic, readonly) NSArray *impressionURLs; +@property (nonatomic, copy, readonly) NSURL *VASTAdTagURI; +@property (nonatomic, readonly) MPVASTResponse *wrappedVASTResponse; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTWrapper.m b/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTWrapper.m new file mode 100644 index 00000000000..ebd4fba002b --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Internal/VAST/MPVASTWrapper.m @@ -0,0 +1,42 @@ +// +// MPVASTWrapper.m +// MoPub +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import "MPVASTWrapper.h" +#import "MPVASTCreative.h" + +@interface MPVASTWrapper () + +@property (nonatomic, readwrite) MPVASTResponse *wrappedVASTResponse; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation MPVASTWrapper + +- (instancetype)initWithDictionary:(NSDictionary *)dictionary +{ + self = [super initWithDictionary:dictionary]; + if (self) { + _extensions = [self generateModelsFromDictionaryValue:dictionary[@"Extensions"][@"Extension"] + modelProvider:^id(NSDictionary *dictionary) { + return dictionary; + }]; + } + return self; +} + ++ (NSDictionary *)modelMap +{ + return @{@"creatives": @[@"Creatives.Creative", MPParseArrayOf(MPParseClass([MPVASTCreative class]))], + @"errorURLs": @[@"Error.text", MPParseArrayOf(MPParseURLFromString())], + @"impressionURLs": @[@"Impression.text", MPParseArrayOf(MPParseURLFromString())], + // @"extensions": @[@"Extensions.Extension"], + @"VASTAdTagURI": @[@"VASTAdTagURI.text", MPParseURLFromString()]}; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/MPAdConversionTracker.h b/iphone/Maps/3party/MoPubSDK/MPAdConversionTracker.h new file mode 100644 index 00000000000..3672d70a49d --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/MPAdConversionTracker.h @@ -0,0 +1,55 @@ +// +// MPAdConversionTracker.h +// MoPub +// +// Created by Andrew He on 2/4/11. +// Copyright 2011 MoPub, Inc. All rights reserved. +// + +#import + +/** + * The `MPAdConversionTracker` class provides a mechanism for reporting application download + * (conversion) events to MoPub. This type of tracking is important for measuring the effectiveness + * of cross-promotional and direct-sold advertising. + * + * To track application downloads, get a reference to the shared instance of this class using the + * `sharedConversionTracker` method. Then, in your application delegate's + * `application:didFinishLaunchingWithOptions:` method, call the + * `reportApplicationOpenForApplicationID:` method on the shared instance. With this call in place, + * the conversion tracker will report an event to MoPub whenever the application is launched on a + * given device for the first time. Any subsequent launches will not be recorded as conversion + * events. + */ + +@interface MPAdConversionTracker : NSObject + +/** @name Recording Conversions */ + +/** + * Returns the shared instance of the `MPAdConversionTracker` class. + * + * @return A singleton `MPAdConversionTracker` object. + */ ++ (MPAdConversionTracker *)sharedConversionTracker; + +/** + * Notifies MoPub that a conversion event should be recorded for the application corresponding to + * the specified `appID`. + * + * A conversion event will only be reported once per application download, even if this method + * is called multiple times. + * + * @param appID An iTunes application ID. + * + * The easiest way to find the correct ID for your application is to generate an iTunes URL using + * the [iTunes Link Maker](https://itunes.apple.com/linkmaker), and then extract the number + * immediately following the "id" string. + * + * For example, the iTunes URL for the "Find My Friends" application is + * https://itunes.apple.com/us/app/find-my-friends/id466122094, so its application ID is + * 466122094. + */ +- (void)reportApplicationOpenForApplicationID:(NSString *)appID; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/MPAdConversionTracker.m b/iphone/Maps/3party/MoPubSDK/MPAdConversionTracker.m new file mode 100644 index 00000000000..2f4b85b5013 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/MPAdConversionTracker.m @@ -0,0 +1,96 @@ +// +// MPAdConversionTracker.m +// MoPub +// +// Created by Andrew He on 2/4/11. +// Copyright 2011 MoPub, Inc. All rights reserved. +// + +#import "MPAdConversionTracker.h" +#import "MPConstants.h" +#import "MPGlobal.h" +#import "MPLogging.h" +#import "MPIdentityProvider.h" +#import "MPCoreInstanceProvider.h" +#import "MPAPIEndpoints.h" + +#define MOPUB_CONVERSION_DEFAULTS_KEY @"com.mopub.conversion" + +@interface MPAdConversionTracker () + +@property (nonatomic, strong) NSMutableData *responseData; +@property (nonatomic, assign) NSInteger statusCode; + +- (NSURL *)URLForAppID:(NSString *)appID; + +@end + +@implementation MPAdConversionTracker + +@synthesize responseData = _responseData; +@synthesize statusCode = _statusCode; + ++ (MPAdConversionTracker *)sharedConversionTracker +{ + static MPAdConversionTracker *sharedConversionTracker; + + @synchronized(self) + { + if (!sharedConversionTracker) + sharedConversionTracker = [[MPAdConversionTracker alloc] init]; + return sharedConversionTracker; + } +} + + +- (void)reportApplicationOpenForApplicationID:(NSString *)appID +{ + if (![[NSUserDefaults standardUserDefaults] boolForKey:MOPUB_CONVERSION_DEFAULTS_KEY]) { + MPLogInfo(@"Tracking conversion"); + NSMutableURLRequest *request = [[MPCoreInstanceProvider sharedProvider] buildConfiguredURLRequestWithURL:[self URLForAppID:appID]]; + self.responseData = [NSMutableData data]; + [NSURLConnection connectionWithRequest:request delegate:self]; + } +} + +#pragma mark - + +- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response +{ + self.statusCode = [(NSHTTPURLResponse *)response statusCode]; +} + +- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data +{ + [self.responseData appendData:data]; +} + +- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error +{ + //NOOP +} + +- (void)connectionDidFinishLoading:(NSURLConnection *)connection +{ + if (self.statusCode == 200 && [self.responseData length] > 0) { + [[NSUserDefaults standardUserDefaults] setBool:YES forKey:MOPUB_CONVERSION_DEFAULTS_KEY]; + [[NSUserDefaults standardUserDefaults] synchronize]; + } +} + +#pragma mark - +#pragma mark Internal + +- (NSURL *)URLForAppID:(NSString *)appID +{ + NSString *path = [NSString stringWithFormat:@"%@?v=%@&udid=%@&id=%@&av=%@", + [MPAPIEndpoints baseURLStringWithPath:MOPUB_API_PATH_CONVERSION testing:NO], + MP_SERVER_VERSION, + [MPIdentityProvider identifier], + appID, + [[[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding] + ]; + + return [NSURL URLWithString:path]; +} +@end diff --git a/iphone/Maps/3party/MoPubSDK/MPAdView.h b/iphone/Maps/3party/MoPubSDK/MPAdView.h new file mode 100644 index 00000000000..686a9f93a1f --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/MPAdView.h @@ -0,0 +1,296 @@ +// +// MPAdView.h +// MoPub +// +// Created by Nafis Jamal on 1/19/11. +// Copyright 2011 MoPub, Inc. All rights reserved. +// + +#import +#import +#import "MPConstants.h" + +typedef enum +{ + MPNativeAdOrientationAny, + MPNativeAdOrientationPortrait, + MPNativeAdOrientationLandscape +} MPNativeAdOrientation; + +@protocol MPAdViewDelegate; + +/** + * The MPAdView class provides a view that can display banner advertisements. + */ + +@interface MPAdView : UIView + +/** @name Initializing a Banner Ad */ + +/** + * Initializes an MPAdView with the given ad unit ID and banner size. + * + * @param adUnitId A string representing a MoPub ad unit ID. + * @param size The desired ad size. A list of standard ad sizes is available in MPConstants.h. + * @return A newly initialized ad view corresponding to the given ad unit ID and size. + */ +- (id)initWithAdUnitId:(NSString *)adUnitId size:(CGSize)size; + +/** @name Setting and Getting the Delegate */ + +/** + * The delegate (`MPAdViewDelegate`) of the ad view. + * + * @warning **Important**: Before releasing an instance of `MPAdView`, you must set its delegate + * property to `nil`. + */ +@property (nonatomic, weak) id delegate; + +/** @name Setting Request Parameters */ + +/** + * The MoPub ad unit ID for this ad view. + * + * Ad unit IDs are created on the MoPub website. An ad unit is a defined placement in your + * application set aside for advertising. If no ad unit ID is set, the ad view will use a default + * ID that only receives test ads. + */ +@property (nonatomic, copy) NSString *adUnitId; + +/** + * A string representing a set of keywords that should be passed to the MoPub ad server to receive + * more relevant advertising. + * + * Keywords are typically used to target ad campaigns at specific user segments. They should be + * formatted as comma-separated key-value pairs (e.g. "marital:single,age:24"). + * + * On the MoPub website, keyword targeting options can be found under the "Advanced Targeting" + * section when managing campaigns. + */ +@property (nonatomic, copy) NSString *keywords; + +/** + * A `CLLocation` object representing a user's location that should be passed to the MoPub ad server + * to receive more relevant advertising. + */ +@property (nonatomic, copy) CLLocation *location; + +/** @name Enabling Test Mode */ + +/** + * A Boolean value that determines whether the ad view should request ads in test mode. + * + * The default value is NO. + * @warning **Important**: If you set this value to YES, make sure to reset it to NO before + * submitting your application to the App Store. + */ +@property (nonatomic, assign, getter = isTesting) BOOL testing; + +/** @name Loading a Banner Ad */ + +/** + * Requests a new ad from the MoPub ad server. + * + * If the ad view is already loading an ad, this call will be ignored. You may use `forceRefreshAd` + * if you would like cancel any existing ad requests and force a new ad to load. + */ +- (void)loadAd; + +/** + * Cancels any existing ad requests and requests a new ad from the MoPub ad server. + */ +- (void)forceRefreshAd; + +/** @name Handling Orientation Changes */ + +/** + * Informs the ad view that the device orientation has changed. + * + * Banners from some third-party ad networks have orientation-specific behavior. You should call + * this method when your application's orientation changes if you want mediated ads to acknowledge + * their new orientation. + * + * If your application layout needs to change based on the size of the mediated ad, you may want to + * check the value of `adContentViewSize` after calling this method, in case the orientation change + * causes the mediated ad to resize. + * + * @param newOrientation The new interface orientation (after orientation changes have occurred). + */ +- (void)rotateToOrientation:(UIInterfaceOrientation)newOrientation; + +/** + * Forces third-party native ad networks to only use ads sized for the specified orientation. + * + * Banners from some third-party ad networks have orientation-specific behaviors and/or sizes. + * You may use this method to lock ads to a certain orientation. For instance, + * if you call this with MPInterfaceOrientationPortrait, native networks will never + * return ads sized for the landscape orientation. + * + * @param orientation An MPNativeAdOrientation enum value. + * + *
typedef enum {
+ *          MPNativeAdOrientationAny,
+ *          MPNativeAdOrientationPortrait,
+ *          MPNativeAdOrientationLandscape
+ *      } MPNativeAdOrientation;
+ * 
+ * + * @see unlockNativeAdsOrientation + * @see allowedNativeAdsOrientation + */ + +- (void)lockNativeAdsToOrientation:(MPNativeAdOrientation)orientation; + +/** + * Allows third-party native ad networks to use ads sized for any orientation. + * + * You do not need to call this method unless you have previously called + * `lockNativeAdsToOrientation:`. + * + * @see lockNativeAdsToOrientation: + * @see allowedNativeAdsOrientation + */ +- (void)unlockNativeAdsOrientation; + +/** + * Returns the banner orientations that third-party ad networks are allowed to use. + * + * @return An enum value representing an allowed set of orientations. + * + * @see lockNativeAdsToOrientation: + * @see unlockNativeAdsOrientation + */ +- (MPNativeAdOrientation)allowedNativeAdsOrientation; + +/** @name Obtaining the Size of the Current Ad */ + +/** + * Returns the size of the current ad being displayed in the ad view. + * + * Ad sizes may vary between different ad networks. This method returns the actual size of the + * underlying mediated ad. This size may be different from the original, initialized size of the + * ad view. You may use this size to determine to adjust the size or positioning of the ad view + * to avoid clipping or border issues. + * + * @returns The size of the underlying mediated ad. + */ +- (CGSize)adContentViewSize; + +/** @name Managing the Automatic Refreshing of Ads */ + +/** + * Stops the ad view from periodically loading new advertisements. + * + * By default, an ad view is allowed to automatically load new advertisements if a refresh interval + * has been configured on the MoPub website. This method prevents new ads from automatically loading, + * even if a refresh interval has been specified. + * + * As a best practice, you should call this method whenever the ad view will be hidden from the user + * for any period of time, in order to avoid unnecessary ad requests. You can then call + * `startAutomaticallyRefreshingContents` to re-enable the refresh behavior when the ad view becomes + * visible. + * + * @see startAutomaticallyRefreshingContents + */ +- (void)stopAutomaticallyRefreshingContents; + +/** + * Causes the ad view to periodically load new advertisements in accordance with user-defined + * refresh settings on the MoPub website. + * + * Calling this method is only necessary if you have previously stopped the ad view's refresh + * behavior using `stopAutomaticallyRefreshingContents`. By default, an ad view is allowed to + * automatically load new advertisements if a refresh interval has been configured on the MoPub + * website. This method has no effect if a refresh interval has not been set. + * + * @see stopAutomaticallyRefreshingContents + */ +- (void)startAutomaticallyRefreshingContents; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma mark - + +/** + * The delegate of an `MPAdView` object must adopt the `MPAdViewDelegate` protocol. It must + * implement `viewControllerForPresentingModalView` to provide a root view controller from which + * the ad view should present modal content. + * + * Optional methods of this protocol allow the delegate to be notified of banner success or + * failure, as well as other lifecycle events. + */ + +@protocol MPAdViewDelegate + +@required + +/** @name Managing Modal Content Presentation */ + +/** + * Asks the delegate for a view controller to use for presenting modal content, such as the in-app + * browser that can appear when an ad is tapped. + * + * @return A view controller that should be used for presenting modal content. + */ +- (UIViewController *)viewControllerForPresentingModalView; + +@optional + +/** @name Detecting When a Banner Ad is Loaded */ + +/** + * Sent when an ad view successfully loads an ad. + * + * Your implementation of this method should insert the ad view into the view hierarchy, if you + * have not already done so. + * + * @param view The ad view sending the message. + */ +- (void)adViewDidLoadAd:(MPAdView *)view; + +/** + * Sent when an ad view fails to load an ad. + * + * To avoid displaying blank ads, you should hide the ad view in response to this message. + * + * @param view The ad view sending the message. + */ +- (void)adViewDidFailToLoadAd:(MPAdView *)view; + +/** @name Detecting When a User Interacts With the Ad View */ + +/** + * Sent when an ad view is about to present modal content. + * + * This method is called when the user taps on the ad view. Your implementation of this method + * should pause any application activity that requires user interaction. + * + * @param view The ad view sending the message. + * @see `didDismissModalViewForAd:` + */ +- (void)willPresentModalViewForAd:(MPAdView *)view; + +/** + * Sent when an ad view has dismissed its modal content, returning control to your application. + * + * Your implementation of this method should resume any application activity that was paused + * in response to `willPresentModalViewForAd:`. + * + * @param view The ad view sending the message. + * @see `willPresentModalViewForAd:` + */ +- (void)didDismissModalViewForAd:(MPAdView *)view; + +/** + * Sent when a user is about to leave your application as a result of tapping + * on an ad. + * + * Your application will be moved to the background shortly after this method is called. + * + * @param view The ad view sending the message. + */ +- (void)willLeaveApplicationFromAd:(MPAdView *)view; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/MPAdView.m b/iphone/Maps/3party/MoPubSDK/MPAdView.m new file mode 100644 index 00000000000..c46ebdee1a4 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/MPAdView.m @@ -0,0 +1,190 @@ +// +// MPAdView.m +// MoPub +// +// Created by Nafis Jamal on 1/19/11. +// Copyright 2011 MoPub, Inc. All rights reserved. +// + +#import "MPAdView.h" +#import "MPClosableView.h" +#import "MPBannerAdManager.h" +#import "MPInstanceProvider.h" +#import "MPBannerAdManagerDelegate.h" +#import "MPLogging.h" + +@interface MPAdView () + +@property (nonatomic, strong) MPBannerAdManager *adManager; +@property (nonatomic, weak) UIView *adContentView; +@property (nonatomic, assign) CGSize originalSize; +@property (nonatomic, assign) MPNativeAdOrientation allowedNativeAdOrientation; + +@end + +@implementation MPAdView +@synthesize location = _location; +@synthesize adManager = _adManager; +@synthesize adUnitId = _adUnitId; +@synthesize keywords = _keywords; +@synthesize delegate = _delegate; +@synthesize originalSize = _originalSize; +@synthesize testing = _testing; +@synthesize adContentView = _adContentView; +@synthesize allowedNativeAdOrientation = _allowedNativeAdOrientation; + +#pragma mark - +#pragma mark Lifecycle + +- (id)initWithAdUnitId:(NSString *)adUnitId size:(CGSize)size +{ + CGRect f = (CGRect){{0, 0}, size}; + if (self = [super initWithFrame:f]) + { + self.backgroundColor = [UIColor clearColor]; + self.clipsToBounds = YES; + self.originalSize = size; + self.allowedNativeAdOrientation = MPNativeAdOrientationAny; + self.adUnitId = (adUnitId) ? adUnitId : DEFAULT_PUB_ID; + self.adManager = [[MPInstanceProvider sharedProvider] buildMPBannerAdManagerWithDelegate:self]; + } + return self; +} + +- (void)dealloc +{ + self.adManager.delegate = nil; +} + +#pragma mark - + +- (void)setAdContentView:(UIView *)view +{ + [self.adContentView removeFromSuperview]; + _adContentView = view; + [self addSubview:view]; +} + +- (CGSize)adContentViewSize +{ + // MPClosableView represents an MRAID ad. + if (!self.adContentView || [self.adContentView isKindOfClass:[MPClosableView class]]) { + return self.originalSize; + } else { + return self.adContentView.bounds.size; + } +} + +- (void)rotateToOrientation:(UIInterfaceOrientation)newOrientation +{ + [self.adManager rotateToOrientation:newOrientation]; +} + +- (void)loadAd +{ + [self.adManager loadAd]; +} + +- (void)refreshAd +{ + [self loadAd]; +} + +- (void)forceRefreshAd +{ + [self.adManager forceRefreshAd]; +} + +- (void)stopAutomaticallyRefreshingContents +{ + [self.adManager stopAutomaticallyRefreshingContents]; +} + +- (void)startAutomaticallyRefreshingContents +{ + [self.adManager startAutomaticallyRefreshingContents]; +} + +- (void)lockNativeAdsToOrientation:(MPNativeAdOrientation)orientation +{ + self.allowedNativeAdOrientation = orientation; +} + +- (void)unlockNativeAdsOrientation +{ + self.allowedNativeAdOrientation = MPNativeAdOrientationAny; +} + +- (MPNativeAdOrientation)allowedNativeAdsOrientation +{ + return self.allowedNativeAdOrientation; +} + +#pragma mark - + +- (MPAdView *)banner +{ + return self; +} + +- (id)bannerDelegate +{ + return self.delegate; +} + +- (CGSize)containerSize +{ + return self.originalSize; +} + +- (UIViewController *)viewControllerForPresentingModalView +{ + return [self.delegate viewControllerForPresentingModalView]; +} + +- (void)invalidateContentView +{ + [self setAdContentView:nil]; +} + +- (void)managerDidFailToLoadAd +{ + if ([self.delegate respondsToSelector:@selector(adViewDidFailToLoadAd:)]) { + // make sure we are not released synchronously as objects owned by us + // may do additional work after this callback + [[MPCoreInstanceProvider sharedProvider] keepObjectAliveForCurrentRunLoopIteration:self]; + + [self.delegate adViewDidFailToLoadAd:self]; + } +} + +- (void)managerDidLoadAd:(UIView *)ad +{ + [self setAdContentView:ad]; + if ([self.delegate respondsToSelector:@selector(adViewDidLoadAd:)]) { + [self.delegate adViewDidLoadAd:self]; + } +} + +- (void)userActionWillBegin +{ + if ([self.delegate respondsToSelector:@selector(willPresentModalViewForAd:)]) { + [self.delegate willPresentModalViewForAd:self]; + } +} + +- (void)userActionDidFinish +{ + if ([self.delegate respondsToSelector:@selector(didDismissModalViewForAd:)]) { + [self.delegate didDismissModalViewForAd:self]; + } +} + +- (void)userWillLeaveApplication +{ + if ([self.delegate respondsToSelector:@selector(willLeaveApplicationFromAd:)]) { + [self.delegate willLeaveApplicationFromAd:self]; + } +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/MPBannerCustomEvent.h b/iphone/Maps/3party/MoPubSDK/MPBannerCustomEvent.h new file mode 100644 index 00000000000..bc6d3483dd3 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/MPBannerCustomEvent.h @@ -0,0 +1,93 @@ +// +// MPBannerCustomEvent.h +// MoPub +// +// Copyright (c) 2012 MoPub, Inc. All rights reserved. +// + +#import +#import "MPBannerCustomEventDelegate.h" + +/** + * The MoPub iOS SDK mediates third party Ad Networks using custom events. The custom events are + * responsible for instantiating and manipulating objects in the third party SDK and translating + * and communicating events from those objects back to the MoPub SDK by notifying a delegate. + * + * `MPBannerCustomEvent` is a base class for custom events that support banners. By implementing + * subclasses of `MPBannerCustomEvent` you can enable the MoPub SDK to natively support a wide + * variety of third-party ad networks. + * + * At runtime, the MoPub SDK will find and instantiate an `MPBannerCustomEvent` subclass as needed and + * invoke its `-requestAdWithSize:customEventInfo:` method. + */ + +@interface MPBannerCustomEvent : NSObject + +/** @name Requesting a Banner Ad */ + +/** + * Called when the MoPub SDK requires a new banner ad. + * + * When the MoPub SDK receives a response indicating it should load a custom event, it will send + * this message to your custom event class. Your implementation of this method can either load a + * banner ad from a third-party ad network, or execute any application code. It must also notify the + * `MPBannerCustomEventDelegate` of certain lifecycle events. + * + * @param size The current size of the parent `MPAdView`. You should use this information to create + * and request a banner of the appropriate size. + * + * @param info A dictionary containing additional custom data associated with a given custom event + * request. This data is configurable on the MoPub website, and may be used to pass dynamic information, such as publisher IDs. + */ +- (void)requestAdWithSize:(CGSize)size customEventInfo:(NSDictionary *)info; + +/** @name Callbacks */ + +/** + * Called when a banner rotation should occur. + * + * If you call `-rotateToOrientation` on an `MPAdView`, it will forward the message to its custom event. + * You can implement this method for third-party ad networks that have special behavior when + * orientation changes happen. + * + * @param newOrientation The `UIInterfaceOrientation` passed to the `MPAdView`'s `rotateToOrientation` method. + * + */ +- (void)rotateToOrientation:(UIInterfaceOrientation)newOrientation; + +/** + * Calld when the banner is presented on screen. + * + * If you decide to [opt out of automatic impression tracking](enableAutomaticImpressionAndClickTracking), you should place your + * manual calls to [-trackImpression]([MPBannerCustomEventDelegate trackImpression]) in this method to ensure correct metrics. + */ +- (void)didDisplayAd; + +/** @name Impression and Click Tracking */ + +/** + * Override to opt out of automatic impression and click tracking. + * + * By default, the MPBannerCustomEventDelegate will automatically record impressions and clicks in + * response to the appropriate callbacks. You may override this behavior by implementing this method + * to return `NO`. + * + * @warning **Important**: If you do this, you are responsible for calling the `[-trackImpression]([MPBannerCustomEventDelegate trackImpression])` and + * `[-trackClick]([MPBannerCustomEventDelegate trackClick])` methods on the custom event delegate. Additionally, you should make sure that these + * methods are only called **once** per ad. + * + */ +- (BOOL)enableAutomaticImpressionAndClickTracking; + +/** @name Communicating with the MoPub SDK */ + +/** + * The `MPBannerCustomEventDelegate` to send messages to as events occur. + * + * The `delegate` object defines several methods that you should call in order to inform both MoPub + * and your `MPAdView`'s delegate of the progress of your custom event. + * + */ +@property (nonatomic, weak) id delegate; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/MPBannerCustomEvent.m b/iphone/Maps/3party/MoPubSDK/MPBannerCustomEvent.m new file mode 100644 index 00000000000..c1a89857079 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/MPBannerCustomEvent.m @@ -0,0 +1,39 @@ +// +// MPBannerCustomEvent.m +// MoPub +// +// Copyright (c) 2012 MoPub, Inc. All rights reserved. +// + +#import "MPBannerCustomEvent.h" + +@implementation MPBannerCustomEvent + +@synthesize delegate; + +- (void)requestAdWithSize:(CGSize)size customEventInfo:(NSDictionary *)info +{ + // The default implementation of this method does nothing. Subclasses must override this method + // and implement code to load a banner here. +} + +- (void)didDisplayAd +{ + // The default implementation of this method does nothing. Subclasses may override this method + // to be notified when the ad is actually displayed on screen. +} + +- (BOOL)enableAutomaticImpressionAndClickTracking +{ + // Subclasses may override this method to return NO to perform impression and click tracking + // manually. + return YES; +} + +- (void)rotateToOrientation:(UIInterfaceOrientation)newOrientation +{ + // The default implementation of this method does nothing. Subclasses may override this method + // to be notified when the parent MPAdView receives -rotateToOrientation: calls. +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/MPBannerCustomEventDelegate.h b/iphone/Maps/3party/MoPubSDK/MPBannerCustomEventDelegate.h new file mode 100644 index 00000000000..f7b7be5f339 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/MPBannerCustomEventDelegate.h @@ -0,0 +1,135 @@ +// +// MPBannerCustomEventDelegate.h +// MoPub +// +// Copyright (c) 2012 MoPub, Inc. All rights reserved. +// + +#import +#import + +@class MPBannerCustomEvent; + +/** + * Instances of your custom subclass of `MPBannerCustomEvent` will have an `MPBannerCustomEventDelegate` delegate. + * You use this delegate to communicate events ad events back to the MoPub SDK. + * + * When mediating a third party ad network it is important to call as many of these methods + * as accurately as possible. Not all ad networks support all these events, and some support + * different events. It is your responsibility to find an appropriate mapping betwen the ad + * network's events and the callbacks defined on `MPBannerCustomEventDelegate`. + */ + +@protocol MPBannerCustomEventDelegate + +/** + * The view controller instance to use when presenting modals. + * + * @return `viewControllerForPresentingModalView` returns the same view controller that you + * specify when implementing the `MPAdViewDelegate` protocol. + */ +- (UIViewController *)viewControllerForPresentingModalView; + +/** + * The user's current location. + * + * @return This method provides the location that was passed into the parent `MPAdView`. The MoPub + * SDK does **not** automatically request the user's location. It is your responsibility to pass the location + * into `MPAdView`. + * + * You may use this to inform third-party ad networks of the user's location. + */ +- (CLLocation *)location; + +/** @name Banner Ad Event Callbacks - Fetching Ads */ + +/** + * Call this method immediately after an ad loads succesfully. + * + * @param event You should pass `self` to allow the MoPub SDK to associate this event with the correct + * instance of your custom event. + * + * @param ad The `UIView` representing the banner ad. This view will be inserted into the `MPAdView` + * and presented to the user by the MoPub SDK. + * + * @warning **Important**: Your custom event subclass **must** call this method when it successfully loads an ad. + * Failure to do so will disrupt the mediation waterfall and cause future ad requests to stall. + */ +- (void)bannerCustomEvent:(MPBannerCustomEvent *)event didLoadAd:(UIView *)ad; + +/** + * Call this method immediately after an ad fails to load. + * + * @param event You should pass `self` to allow the MoPub SDK to associate this event with the correct + * instance of your custom event. + * + * @param error (*optional*) You may pass an error describing the failure. + * + * @warning **Important**: Your custom event subclass **must** call this method when it fails to load an ad. + * Failure to do so will disrupt the mediation waterfall and cause future ad requests to stall. + */ +- (void)bannerCustomEvent:(MPBannerCustomEvent *)event didFailToLoadAdWithError:(NSError *)error; + +/** @name Banner Ad Event Callbacks - User Interaction */ + +/** + * Call this method when the user taps on the banner ad. + * + * This method is optional. When automatic click and impression tracking is enabled (the default) + * this method will track a click (the click is guaranteed to only be tracked once per ad). + * + * @param event You should pass `self` to allow the MoPub SDK to associate this event with the correct + * instance of your custom event. + * + * @warning **Important**: If you call `-bannerCustomEventWillBeginAction:`, you _**must**_ also call + * `-bannerCustomEventDidFinishAction:` at a later point. + */ +- (void)bannerCustomEventWillBeginAction:(MPBannerCustomEvent *)event; + +/** + * Call this method when the user finishes interacting with the banner ad. + * + * For example, the user may have dismissed any modal content. This method is optional. + * + * @param event You should pass `self` to allow the MoPub SDK to associate this event with the correct + * instance of your custom event. + * + * @warning **Important**: If you call `-bannerCustomEventWillBeginAction:`, you _**must**_ also call + * `-bannerCustomEventDidFinishAction:` at a later point. + */ +- (void)bannerCustomEventDidFinishAction:(MPBannerCustomEvent *)event; + +/** + * Call this method when the banner ad will cause the user to leave the application. + * + * For example, the user may have tapped on a link to visit the App Store or Safari. + * + * @param event You should pass `self` to allow the MoPub SDK to associate this event with the correct + * instance of your custom event. + * + */ +- (void)bannerCustomEventWillLeaveApplication:(MPBannerCustomEvent *)event; + +/** @name Impression and Click Tracking */ + +/** + * Call this method to track an impression. + * + * @warning **Important**: You should **only** call this method if you have [opted out of automatic click and impression tracking]([MPBannerCustomEvent enableAutomaticImpressionAndClickTracking]). + * By default the MoPub SDK automatically tracks impressions. + * + * **Important**: In order to obtain accurate metrics, it is your responsibility to call `trackImpression` only **once** per ad. + */ +- (void)trackImpression; + +/** + * Call this method to track a click. + * + * @warning **Important**: You should **only** call this method if you have [opted out of automatic click and impression tracking]([MPBannerCustomEvent enableAutomaticImpressionAndClickTracking]). + * By default the MoPub SDK automatically tracks clicks. + * + * **Important**: In order to obtain accurate metrics, it is your responsibility to call `trackClick` only **once** per ad. + */ +- (void)trackClick; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/MPConstants.h b/iphone/Maps/3party/MoPubSDK/MPConstants.h new file mode 100644 index 00000000000..fa3a54dccec --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/MPConstants.h @@ -0,0 +1,33 @@ +// +// MPConstants.h +// MoPub +// +// Copyright 2011 MoPub, Inc. All rights reserved. +// + +#import + +#define MP_DEBUG_MODE 1 + +#define MP_HAS_NATIVE_PACKAGE 1 + +#define DEFAULT_PUB_ID @"agltb3B1Yi1pbmNyDAsSBFNpdGUYkaoMDA" +#define MP_SERVER_VERSION @"8" +#define MP_BUNDLE_IDENTIFIER @"com.mopub.mopub" +#define MP_SDK_VERSION @"4.13.0" + +// Sizing constants. +extern CGSize const MOPUB_BANNER_SIZE; +extern CGSize const MOPUB_MEDIUM_RECT_SIZE; +extern CGSize const MOPUB_LEADERBOARD_SIZE; +extern CGSize const MOPUB_WIDE_SKYSCRAPER_SIZE; + +// Miscellaneous constants. +#define MINIMUM_REFRESH_INTERVAL 10.0 +#define DEFAULT_BANNER_REFRESH_INTERVAL 60 +#define BANNER_TIMEOUT_INTERVAL 10 +#define INTERSTITIAL_TIMEOUT_INTERVAL 30 +#define REWARDED_VIDEO_TIMEOUT_INTERVAL 30 + +// Feature Flags +#define SESSION_TRACKING_ENABLED 1 diff --git a/iphone/Maps/3party/MoPubSDK/MPConstants.m b/iphone/Maps/3party/MoPubSDK/MPConstants.m new file mode 100644 index 00000000000..b6d281a81fb --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/MPConstants.m @@ -0,0 +1,13 @@ +// +// MPConstants.m +// MoPubSDK +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import "MPConstants.h" + +CGSize const MOPUB_BANNER_SIZE = { .width = 320.0f, .height = 50.0f }; +CGSize const MOPUB_MEDIUM_RECT_SIZE = { .width = 300.0f, .height = 250.0f }; +CGSize const MOPUB_LEADERBOARD_SIZE = { .width = 728.0f, .height = 90.0f }; +CGSize const MOPUB_WIDE_SKYSCRAPER_SIZE = { .width = 160.0f, .height = 600.0f }; diff --git a/iphone/Maps/3party/MoPubSDK/MPInterstitialAdController.h b/iphone/Maps/3party/MoPubSDK/MPInterstitialAdController.h new file mode 100644 index 00000000000..bc0f37f038b --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/MPInterstitialAdController.h @@ -0,0 +1,249 @@ +// +// MPInterstitialAdController.h +// MoPub +// +// Copyright (c) 2012 MoPub, Inc. All rights reserved. +// + +#import +#import + +@protocol MPInterstitialAdControllerDelegate; + +/** + * The `MPInterstitialAdController` class provides a full-screen advertisement that can be + * displayed during natural transition points in your application. + */ + +@interface MPInterstitialAdController : UIViewController + +/** @name Obtaining an Interstitial Ad */ + +/** + * Returns an interstitial ad object matching the given ad unit ID. + * + * The first time this method is called for a given ad unit ID, a new interstitial ad object is + * created, stored in a shared pool, and returned. Subsequent calls for the same ad unit ID will + * return that object, unless you have disposed of the object using + * `removeSharedInterstitialAdController:`. + * + * There can only be one interstitial object for an ad unit ID at a given time. + * + * @param adUnitId A string representing a MoPub ad unit ID. + */ ++ (MPInterstitialAdController *)interstitialAdControllerForAdUnitId:(NSString *)adUnitId; + +/** @name Setting and Getting the Delegate */ + +/** + * The delegate (`MPInterstitialAdControllerDelegate`) of the interstitial ad object. + */ +@property (nonatomic, weak) id delegate; + +/** @name Setting Request Parameters */ + +/** + * The MoPub ad unit ID for this interstitial ad. + * + * Ad unit IDs are created on the MoPub website. An ad unit is a defined placement in your + * application set aside for advertising. If no ad unit ID is set, the ad object will use a default + * ID that only receives test ads. + */ +@property (nonatomic, copy) NSString *adUnitId; + +/** + * A string representing a set of keywords that should be passed to the MoPub ad server to receive + * more relevant advertising. + * + * Keywords are typically used to target ad campaigns at specific user segments. They should be + * formatted as comma-separated key-value pairs (e.g. "marital:single,age:24"). + * + * On the MoPub website, keyword targeting options can be found under the "Advanced Targeting" + * section when managing campaigns. + */ +@property (nonatomic, copy) NSString *keywords; + +/** + * A `CLLocation` object representing a user's location that should be passed to the MoPub ad server + * to receive more relevant advertising. + */ +@property (nonatomic, copy) CLLocation *location; + +/** @name Enabling Test Mode */ + +/** + * A Boolean value that determines whether the interstitial ad object should request ads in test + * mode. + * + * The default value is NO. + * @warning **Important**: If you set this value to YES, make sure to reset it to NO before + * submitting your application to the App Store. + */ +@property (nonatomic, assign, getter=isTesting) BOOL testing; + +/** @name Loading an Interstitial Ad */ + +/** + * Begins loading ad content for the interstitial. + * + * You can implement the `interstitialDidLoadAd:` and `interstitialDidFailToLoadAd:` methods of + * `MPInterstitialAdControllerDelegate` if you would like to be notified as loading succeeds or + * fails. + */ +- (void)loadAd; + +/** @name Detecting Whether the Interstitial Ad Has Loaded */ + +/** + * A Boolean value that represents whether the interstitial ad has loaded an advertisement and is + * ready to be presented. + * + * After obtaining an interstitial ad object, you can use `loadAd` to tell the object to begin + * loading ad content. Once the content has been loaded, the value of this property will be YES. + * + * The value of this property can be NO if the ad content has not finished loading, has already + * been presented, or has expired. The expiration condition only applies for ads from certain + * third-party ad networks. See `MPInterstitialAdControllerDelegate` for more details. + */ +@property (nonatomic, assign, readonly) BOOL ready; + +/** @name Presenting an Interstitial Ad */ + +/** + * Presents the interstitial ad modally from the specified view controller. + * + * This method will do nothing if the interstitial ad has not been loaded (i.e. the value of its + * `ready` property is NO). + * + * `MPInterstitialAdControllerDelegate` provides optional methods that you may implement to stay + * informed about when an interstitial takes over or relinquishes the screen. + * + * @param controller The view controller that should be used to present the interstitial ad. + */ +- (void)showFromViewController:(UIViewController *)controller; + +/** @name Disposing of an Interstitial Ad */ + +/** + * Removes the given interstitial object from the shared pool of interstitials available to your + * application. + * + * This method removes the mapping from the interstitial's ad unit ID to the interstitial ad + * object. In other words, you will receive a different ad object if you subsequently call + * `interstitialAdControllerForAdUnitId:` for the same ad unit ID. + * + * @warning **Important**: This method is intended to be used for deallocating the interstitial + * ad object when it is no longer needed. You should `nil` out any references you have to the + * object after calling this method. + * + * @param controller The interstitial ad object that should be disposed. + */ ++ (void)removeSharedInterstitialAdController:(MPInterstitialAdController *)controller; + +/* + * Returns the shared pool of interstitial objects for your application. + */ ++ (NSMutableArray *)sharedInterstitialAdControllers; + +@end + +#pragma mark - + +/** + * The delegate of an `MPInterstitialAdController` object must adopt the + * `MPInterstitialAdControllerDelegate` protocol. + * + * The optional methods of this protocol allow the delegate to be notified of interstitial state + * changes, such as when an ad has loaded, when an ad has been presented or dismissed from the + * screen, and when an ad has expired. + */ + +@protocol MPInterstitialAdControllerDelegate + +@optional + +/** @name Detecting When an Interstitial Ad is Loaded */ + +/** + * Sent when an interstitial ad object successfully loads an ad. + * + * @param interstitial The interstitial ad object sending the message. + */ +- (void)interstitialDidLoadAd:(MPInterstitialAdController *)interstitial; + +/** + * Sent when an interstitial ad object fails to load an ad. + * + * @param interstitial The interstitial ad object sending the message. + */ +- (void)interstitialDidFailToLoadAd:(MPInterstitialAdController *)interstitial; + +/** @name Detecting When an Interstitial Ad is Presented */ + +/** + * Sent immediately before an interstitial ad object is presented on the screen. + * + * Your implementation of this method should pause any application activity that requires user + * interaction. + * + * @param interstitial The interstitial ad object sending the message. + */ +- (void)interstitialWillAppear:(MPInterstitialAdController *)interstitial; + +/** + * Sent after an interstitial ad object has been presented on the screen. + * + * @param interstitial The interstitial ad object sending the message. + */ +- (void)interstitialDidAppear:(MPInterstitialAdController *)interstitial; + +/** @name Detecting When an Interstitial Ad is Dismissed */ + +/** + * Sent immediately before an interstitial ad object will be dismissed from the screen. + * + * @param interstitial The interstitial ad object sending the message. + */ +- (void)interstitialWillDisappear:(MPInterstitialAdController *)interstitial; + +/** + * Sent after an interstitial ad object has been dismissed from the screen, returning control + * to your application. + * + * Your implementation of this method should resume any application activity that was paused + * prior to the interstitial being presented on-screen. + * + * @param interstitial The interstitial ad object sending the message. + */ +- (void)interstitialDidDisappear:(MPInterstitialAdController *)interstitial; + +/** @name Detecting When an Interstitial Ad Expires */ + +/** + * Sent when a loaded interstitial ad is no longer eligible to be displayed. + * + * Interstitial ads from certain networks may expire their content at any time, + * even if the content is currently on-screen. This method notifies you when the currently- + * loaded interstitial has expired and is no longer eligible for display. + * + * If the ad was on-screen when it expired, you can expect that the ad will already have been + * dismissed by the time this message is sent. + * + * Your implementation may include a call to `loadAd` to fetch a new ad, if desired. + * + * @param interstitial The interstitial ad object sending the message. + */ +- (void)interstitialDidExpire:(MPInterstitialAdController *)interstitial; + +/** + * Sent when the user taps the interstitial ad and the ad is about to perform its target action. + * + * This action may include displaying a modal or leaving your application. Certain ad networks + * may not expose a "tapped" callback so you should not rely on this callback to perform + * critical tasks. + * + * @param interstitial The interstitial ad object sending the message. + */ +- (void)interstitialDidReceiveTapEvent:(MPInterstitialAdController *)interstitial; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/MPInterstitialAdController.m b/iphone/Maps/3party/MoPubSDK/MPInterstitialAdController.m new file mode 100644 index 00000000000..785a56b61ef --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/MPInterstitialAdController.m @@ -0,0 +1,195 @@ +// +// MPInterstitialAdController.m +// MoPub +// +// Copyright (c) 2012 MoPub, Inc. All rights reserved. +// + +#import "MPInterstitialAdController.h" + +#import "MPLogging.h" +#import "MPInstanceProvider.h" +#import "MPInterstitialAdManager.h" +#import "MPInterstitialAdManagerDelegate.h" + +@interface MPInterstitialAdController () + +@property (nonatomic, strong) MPInterstitialAdManager *manager; + ++ (NSMutableArray *)sharedInterstitials; +- (id)initWithAdUnitId:(NSString *)adUnitId; + +@end + +@implementation MPInterstitialAdController + +@synthesize manager = _manager; +@synthesize delegate = _delegate; +@synthesize adUnitId = _adUnitId; +@synthesize keywords = _keywords; +@synthesize location = _location; +@synthesize testing = _testing; + +- (id)initWithAdUnitId:(NSString *)adUnitId +{ + if (self = [super init]) { + self.manager = [[MPInstanceProvider sharedProvider] buildMPInterstitialAdManagerWithDelegate:self]; + self.adUnitId = adUnitId; + } + return self; +} + +- (void)dealloc +{ + [self.manager setDelegate:nil]; +} + +#pragma mark - Public + ++ (MPInterstitialAdController *)interstitialAdControllerForAdUnitId:(NSString *)adUnitId +{ + NSMutableArray *interstitials = [[self class] sharedInterstitials]; + + @synchronized(self) { + // Find the correct ad controller based on the ad unit ID. + MPInterstitialAdController *interstitial = nil; + for (MPInterstitialAdController *currentInterstitial in interstitials) { + if ([currentInterstitial.adUnitId isEqualToString:adUnitId]) { + interstitial = currentInterstitial; + break; + } + } + + // Create a new ad controller for this ad unit ID if one doesn't already exist. + if (!interstitial) { + interstitial = [[[self class] alloc] initWithAdUnitId:adUnitId]; + [interstitials addObject:interstitial]; + } + + return interstitial; + } +} + +- (BOOL)ready +{ + return self.manager.ready; +} + +- (void)loadAd +{ + [self.manager loadInterstitialWithAdUnitID:self.adUnitId + keywords:self.keywords + location:self.location + testing:self.testing]; +} + +- (void)showFromViewController:(UIViewController *)controller +{ + if (!controller) { + MPLogWarn(@"The interstitial could not be shown: " + @"a nil view controller was passed to -showFromViewController:."); + return; + } + + if (![controller.view.window isKeyWindow]) { + MPLogWarn(@"Attempted to present an interstitial ad in non-key window. The ad may not render properly"); + } + + [self.manager presentInterstitialFromViewController:controller]; +} + +#pragma mark - Internal + ++ (NSMutableArray *)sharedInterstitials +{ + static NSMutableArray *sharedInterstitials; + + @synchronized(self) { + if (!sharedInterstitials) { + sharedInterstitials = [NSMutableArray array]; + } + } + + return sharedInterstitials; +} + +#pragma mark - MPInterstitialAdManagerDelegate + +- (MPInterstitialAdController *)interstitialAdController +{ + return self; +} + +- (id)interstitialDelegate +{ + return self.delegate; +} + +- (void)managerDidLoadInterstitial:(MPInterstitialAdManager *)manager +{ + if ([self.delegate respondsToSelector:@selector(interstitialDidLoadAd:)]) { + [self.delegate interstitialDidLoadAd:self]; + } +} + +- (void)manager:(MPInterstitialAdManager *)manager + didFailToLoadInterstitialWithError:(NSError *)error +{ + if ([self.delegate respondsToSelector:@selector(interstitialDidFailToLoadAd:)]) { + [self.delegate interstitialDidFailToLoadAd:self]; + } +} + +- (void)managerWillPresentInterstitial:(MPInterstitialAdManager *)manager +{ + if ([self.delegate respondsToSelector:@selector(interstitialWillAppear:)]) { + [self.delegate interstitialWillAppear:self]; + } +} + +- (void)managerDidPresentInterstitial:(MPInterstitialAdManager *)manager +{ + if ([self.delegate respondsToSelector:@selector(interstitialDidAppear:)]) { + [self.delegate interstitialDidAppear:self]; + } +} + +- (void)managerWillDismissInterstitial:(MPInterstitialAdManager *)manager +{ + if ([self.delegate respondsToSelector:@selector(interstitialWillDisappear:)]) { + [self.delegate interstitialWillDisappear:self]; + } +} + +- (void)managerDidDismissInterstitial:(MPInterstitialAdManager *)manager +{ + if ([self.delegate respondsToSelector:@selector(interstitialDidDisappear:)]) { + [self.delegate interstitialDidDisappear:self]; + } +} + +- (void)managerDidExpireInterstitial:(MPInterstitialAdManager *)manager +{ + if ([self.delegate respondsToSelector:@selector(interstitialDidExpire:)]) { + [self.delegate interstitialDidExpire:self]; + } +} + +- (void)managerDidReceiveTapEventFromInterstitial:(MPInterstitialAdManager *)manager +{ + if ([self.delegate respondsToSelector:@selector(interstitialDidReceiveTapEvent:)]) { + [self.delegate interstitialDidReceiveTapEvent:self]; + } +} + ++ (NSMutableArray *)sharedInterstitialAdControllers +{ + return [[self class] sharedInterstitials]; +} + ++ (void)removeSharedInterstitialAdController:(MPInterstitialAdController *)controller +{ + [[[self class] sharedInterstitials] removeObject:controller]; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/MPInterstitialCustomEvent.h b/iphone/Maps/3party/MoPubSDK/MPInterstitialCustomEvent.h new file mode 100644 index 00000000000..dc76b68f8f8 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/MPInterstitialCustomEvent.h @@ -0,0 +1,85 @@ +// +// MPInterstitialCustomEvent.h +// MoPub +// +// Copyright (c) 2012 MoPub, Inc. All rights reserved. +// + +#import +#import "MPInterstitialCustomEventDelegate.h" + +/** + * The MoPub iOS SDK mediates third party Ad Networks using custom events. The custom events are + * responsible for instantiating and manipulating objects in the third party SDK and translating + * and communicating events from those objects back to the MoPub SDK by notifying a delegate. + * + * `MPInterstitialCustomEvent` is a base class for custom events that support full-screen interstitial ads. + * By implementing subclasses of `MPInterstitialCustomEvent` you can enable the MoPub SDK to + * natively support a wide variety of third-party ad networks. + * + * At runtime, the MoPub SDK will find and instantiate an `MPInterstitialCustomEvent` subclass as needed and + * invoke its `-requestInterstitialWithCustomEventInfo:` method. + */ + + +@interface MPInterstitialCustomEvent : NSObject + +/** @name Requesting and Displaying an Interstitial Ad */ + +/** + * Called when the MoPub SDK requires a new interstitial ad. + * + * When the MoPub SDK receives a response indicating it should load a custom event, it will send + * this message to your custom event class. Your implementation of this method should load an + * interstitial ad from a third-party ad network. It must also notify the + * `MPInterstitialCustomEventDelegate` of certain lifecycle events. + * + * @param info A dictionary containing additional custom data associated with a given custom event + * request. This data is configurable on the MoPub website, and may be used to pass dynamic information, such as publisher IDs. + */ + +- (void)requestInterstitialWithCustomEventInfo:(NSDictionary *)info; + +/** + * Called when the interstitial should be displayed. + * + * This message is sent sometime after an interstitial has been successfully loaded, as a result + * of your code calling `-[MPInterstitialAdController showFromViewController:]`. Your implementation + * of this method should present the interstitial ad from the specified view controller. + * + * If you decide to [opt out of automatic impression tracking](enableAutomaticImpressionAndClickTracking), you should place your + * manual calls to [-trackImpression]([MPInterstitialCustomEventDelegate trackImpression]) in this method to ensure correct metrics. + * + * @param rootViewController The controller to use to present the interstitial modally. + * + */ +- (void)showInterstitialFromRootViewController:(UIViewController *)rootViewController; + +/** @name Impression and Click Tracking */ + +/** + * Override to opt out of automatic impression and click tracking. + * + * By default, the MPInterstitialCustomEventDelegate will automatically record impressions and clicks in + * response to the appropriate callbacks. You may override this behavior by implementing this method + * to return `NO`. + * + * @warning **Important**: If you do this, you are responsible for calling the `[-trackImpression]([MPInterstitialCustomEventDelegate trackImpression])` and + * `[-trackClick]([MPInterstitialCustomEventDelegate trackClick])` methods on the custom event delegate. Additionally, you should make sure that these + * methods are only called **once** per ad. + */ +- (BOOL)enableAutomaticImpressionAndClickTracking; + +/** @name Communicating with the MoPub SDK */ + +/** + * The `MPInterstitialCustomEventDelegate` to send messages to as events occur. + * + * The `delegate` object defines several methods that you should call in order to inform both MoPub + * and your `MPInterstitialAdController`'s delegate of the progress of your custom event. + * + */ + +@property (nonatomic, weak) id delegate; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/MPInterstitialCustomEvent.m b/iphone/Maps/3party/MoPubSDK/MPInterstitialCustomEvent.m new file mode 100644 index 00000000000..7a36ec1058f --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/MPInterstitialCustomEvent.m @@ -0,0 +1,33 @@ +// +// MPInterstitialCustomEvent.m +// MoPub +// +// Copyright (c) 2012 MoPub, Inc. All rights reserved. +// + +#import "MPInterstitialCustomEvent.h" + +@implementation MPInterstitialCustomEvent + +@synthesize delegate; + +- (void)requestInterstitialWithCustomEventInfo:(NSDictionary *)info +{ + // The default implementation of this method does nothing. Subclasses must override this method + // and implement code to load an interstitial here. +} + +- (BOOL)enableAutomaticImpressionAndClickTracking +{ + // Subclasses may override this method to return NO to perform impression and click tracking + // manually. + return YES; +} + +- (void)showInterstitialFromRootViewController:(UIViewController *)rootViewController +{ + // The default implementation of this method does nothing. Subclasses must override this method + // and implement code to display an interstitial here. +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/MPInterstitialCustomEventDelegate.h b/iphone/Maps/3party/MoPubSDK/MPInterstitialCustomEventDelegate.h new file mode 100644 index 00000000000..d634f2227a8 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/MPInterstitialCustomEventDelegate.h @@ -0,0 +1,183 @@ +// +// MPInterstitialCustomEventDelegate.h +// MoPub +// +// Copyright (c) 2012 MoPub, Inc. All rights reserved. +// + +#import +#import + +@class MPInterstitialCustomEvent; + +/** + * Instances of your custom subclass of `MPInterstitialCustomEvent` will have an `MPInterstitialCustomEventDelegate` delegate. + * You use this delegate to communicate events ad events back to the MoPub SDK. + * + * When mediating a third party ad network it is important to call as many of these methods + * as accurately as possible. Not all ad networks support all these events, and some support + * different events. It is your responsibility to find an appropriate mapping betwen the ad + * network's events and the callbacks defined on `MPInterstitialCustomEventDelegate`. + */ + +@protocol MPInterstitialCustomEventDelegate + +/** + * The user's current location. + * + * @return This method provides the location that was passed into the parent `MPInterstitialAdController`. The MoPub + * SDK does **not** automatically request the user's location. It is your responsibility to pass the location + * into `MPInterstitialAdController`. + * + * You may use this to inform third-party ad networks of the user's location. + */ +- (CLLocation *)location; + +/** @name Interstitial Ad Event Callbacks - Fetching Ads */ + +/** + * Call this method immediately after an ad loads succesfully. + * + * @param customEvent You should pass `self` to allow the MoPub SDK to associate this event with the correct + * instance of your custom event. + * + * @param ad (*optional*) An object that represents the ad that was retrieved. The MoPub SDK does not + * do anything with this optional parameter. + * + * @warning **Important**: Your custom event subclass **must** call this method when it successfully loads an ad. + * Failure to do so will disrupt the mediation waterfall and cause future ad requests to stall. + */ +- (void)interstitialCustomEvent:(MPInterstitialCustomEvent *)customEvent + didLoadAd:(id)ad; + +/** + * Call this method immediately after an ad fails to load. + * + * @param customEvent You should pass `self` to allow the MoPub SDK to associate this event with the correct + * instance of your custom event. + * + * @param error (*optional*) You may pass an error describing the failure. + * + * @warning **Important**: Your custom event subclass **must** call this method when it fails to load an ad. + * Failure to do so will disrupt the mediation waterfall and cause future ad requests to stall. + */ +- (void)interstitialCustomEvent:(MPInterstitialCustomEvent *)customEvent + didFailToLoadAdWithError:(NSError *)error; + +/** + * Call this method if a previously loaded interstitial should no longer be eligible for presentation. + * + * Some third-party networks will mark interstitials as expired (indicating they should not be + * presented) *after* they have loaded. You may use this method to inform the MoPub SDK that a + * previously loaded interstitial has expired and that a new interstitial should be obtained. + * + * @param customEvent You should pass `self` to allow the MoPub SDK to associate this event with the correct + * instance of your custom event. + */ +- (void)interstitialCustomEventDidExpire:(MPInterstitialCustomEvent *)customEvent; + +/** @name Interstitial Ad Event Callbacks - Presenting and Dismissing Ads */ + +/** + * Call this method when an ad is about to appear. + * + * @param customEvent You should pass `self` to allow the MoPub SDK to associate this event with the correct + * instance of your custom event. + * + * @warning **Important**: Your custom event subclass **must** call this method when it is about to present the interstitial. + * Failure to do so will disrupt the mediation waterfall and cause future ad requests to stall. + * + */ +- (void)interstitialCustomEventWillAppear:(MPInterstitialCustomEvent *)customEvent; + +/** + * Call this method when an ad has finished appearing. + * + * @param customEvent You should pass `self` to allow the MoPub SDK to associate this event with the correct + * instance of your custom event. + * + * @warning **Important**: Your custom event subclass **must** call this method when it is finished presenting the interstitial. + * Failure to do so will disrupt the mediation waterfall and cause future ad requests to stall. + * + * **Note**: if it is not possible to know when the interstitial *finished* appearing, you should call + * this immediately after calling `-interstitialCustomEventWillAppear:`. + */ +- (void)interstitialCustomEventDidAppear:(MPInterstitialCustomEvent *)customEvent; + +/** + * Call this method when an ad is about to disappear. + * + * @param customEvent You should pass `self` to allow the MoPub SDK to associate this event with the correct + * instance of your custom event. + * + * @warning **Important**: Your custom event subclass **must** call this method when it is about to dismiss the interstitial. + * Failure to do so will disrupt the mediation waterfall and cause future ad requests to stall. + * + */ +- (void)interstitialCustomEventWillDisappear:(MPInterstitialCustomEvent *)customEvent; + +/** + * Call this method when an ad has finished disappearing. + * + * @param customEvent You should pass `self` to allow the MoPub SDK to associate this event with the correct + * instance of your custom event. + * + * @warning **Important**: Your custom event subclass **must** call this method when it is finished with dismissing the interstitial. + * Failure to do so will disrupt the mediation waterfall and cause future ad requests to stall. + * + * **Note**: if it is not possible to know when the interstitial *finished* dismissing, you should call + * this immediately after calling `-interstitialCustomEventDidDisappear:`. + */ +- (void)interstitialCustomEventDidDisappear:(MPInterstitialCustomEvent *)customEvent; + +/** @name Interstitial Ad Event Callbacks - User Interaction */ + +/** + * Call this method when the user taps on the interstitial ad. + * + * This method is optional. When automatic click and impression tracking is enabled (the default) + * this method will track a click (the click is guaranteed to only be tracked once per ad). + * + * **Note**: some third-party networks provide a "will leave application" callback instead of/in + * addition to a "user did click" callback. You should call this method in response to either of + * those callbacks (since leaving the application is generally an indicator of a user tap). + * + * @param customEvent You should pass `self` to allow the MoPub SDK to associate this event with the correct + * instance of your custom event. + * + */ +- (void)interstitialCustomEventDidReceiveTapEvent:(MPInterstitialCustomEvent *)customEvent; + +/** + * Call this method when the interstitial ad will cause the user to leave the application. + * + * For example, the user may have tapped on a link to visit the App Store or Safari. + * + * @param customEvent You should pass `self` to allow the MoPub SDK to associate this event with the correct + * instance of your custom event. + */ +- (void)interstitialCustomEventWillLeaveApplication:(MPInterstitialCustomEvent *)customEvent; + +/** @name Impression and Click Tracking */ + +/** + * Call this method to track an impression. + * + * @warning **Important**: You should **only** call this method if you have [opted out of automatic click and impression tracking]([MPInterstitialCustomEvent enableAutomaticImpressionAndClickTracking]). + * By default the MoPub SDK automatically tracks impressions. + * + * **Important**: In order to obtain accurate metrics, it is your responsibility to call `trackImpression` only **once** per ad. + */ +- (void)trackImpression; + +/** + * Call this method to track a click. + * + * @warning **Important**: You should **only** call this method if you have [opted out of automatic click and impression tracking]([MPInterstitialCustomEvent enableAutomaticImpressionAndClickTracking]). + * By default the MoPub SDK automatically tracks clicks. + * + * **Important**: In order to obtain accurate metrics, it is your responsibility to call `trackClick` only **once** per ad. + */ +- (void)trackClick; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/MoPub-Bridging-Header.h b/iphone/Maps/3party/MoPubSDK/MoPub-Bridging-Header.h new file mode 100644 index 00000000000..474eec53630 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/MoPub-Bridging-Header.h @@ -0,0 +1,51 @@ +// +// MoPub-Bridging-Header.h +// MoPubSDK +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import "MoPub.h" + +#import "MPAdConversionTracker.h" +#import "MPAdView.h" +#import "MPBannerCustomEvent.h" +#import "MPBannerCustomEventDelegate.h" +#import "MPConstants.h" +#import "MPInterstitialAdController.h" +#import "MPInterstitialCustomEvent.h" +#import "MPInterstitialCustomEventDelegate.h" + +#if MP_HAS_NATIVE_PACKAGE + +#import "MPNativeAd.h" +#import "MPNativeAdAdapter.h" +#import "MPNativeAdConstants.h" +#import "MPNativeCustomEvent.h" +#import "MPNativeCustomEventDelegate.h" +#import "MPNativeAdDelegate.h" +#import "MPNativeAdError.h" +#import "MPNativeAdRendering.h" +#import "MPNativeAdRequest.h" +#import "MPNativeAdRequestTargeting.h" +#import "MPStaticNativeAdRendererSettings.h" +#import "MPNativeAdRendererConfiguration.h" +#import "MPNativeAdRendererSettings.h" +#import "MPNativeAdRenderer.h" +#import "MPStaticNativeAdRenderer.h" +#import "MOPUBNativeVideoAdRendererSettings.h" +#import "MOPUBNativeVideoAdRenderer.h" +#import "MPNativeAdRenderingImageLoader.h" +#import "MPClientAdPositioning.h" +#import "MPServerAdPositioning.h" +#import "MPCollectionViewAdPlacer.h" +#import "MPTableViewAdPlacer.h" + +#endif + + +#import "MPMediationSettingsProtocol.h" +#import "MPRewardedVideo.h" +#import "MPRewardedVideoReward.h" +#import "MPRewardedVideoCustomEvent.h" +#import "MPRewardedVideoError.h" diff --git a/iphone/Maps/3party/MoPubSDK/MoPub.h b/iphone/Maps/3party/MoPubSDK/MoPub.h new file mode 100644 index 00000000000..b815d895614 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/MoPub.h @@ -0,0 +1,122 @@ +// +// MoPub.h +// MoPub +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import "MPConstants.h" + +#import "MPAdConversionTracker.h" +#import "MPAdView.h" +#import "MPBannerCustomEvent.h" +#import "MPBannerCustomEventDelegate.h" +#import "MPInterstitialAdController.h" +#import "MPInterstitialCustomEvent.h" +#import "MPInterstitialCustomEventDelegate.h" +#import "MPMediationSettingsProtocol.h" +#import "MPRewardedVideo.h" +#import "MPRewardedVideoReward.h" +#import "MPRewardedVideoCustomEvent.h" +#import "MPRewardedVideoError.h" + +#if MP_HAS_NATIVE_PACKAGE +#import "MPNativeAd.h" +#import "MPNativeAdAdapter.h" +#import "MPNativeAdConstants.h" +#import "MPNativeCustomEvent.h" +#import "MPNativeCustomEventDelegate.h" +#import "MPNativeAdError.h" +#import "MPNativeAdRendering.h" +#import "MPNativeAdRequest.h" +#import "MPNativeAdRequestTargeting.h" +#import "MPCollectionViewAdPlacer.h" +#import "MPTableViewAdPlacer.h" +#import "MPClientAdPositioning.h" +#import "MPServerAdPositioning.h" +#import "MPNativeAdDelegate.h" +#import "MPStaticNativeAdRendererSettings.h" +#import "MPNativeAdRendererConfiguration.h" +#import "MPNativeAdRendererSettings.h" +#import "MPNativeAdRenderer.h" +#import "MPStaticNativeAdRenderer.h" +#import "MOPUBNativeVideoAdRendererSettings.h" +#import "MOPUBNativeVideoAdRenderer.h" +#import "MPNativeAdRenderingImageLoader.h" +#endif + +// Import these frameworks for module support. +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import + +#define MoPubKit [MoPub sharedInstance] + +@interface MoPub : NSObject + +/** + * Returns the MoPub singleton object. + * + * @return The MoPub singleton object. + */ ++ (MoPub *)sharedInstance; + +/** + * A Boolean value indicating whether the MoPub SDK should use Core Location APIs to automatically + * derive targeting information for location-based ads. + * + * When set to NO, the SDK will not attempt to determine device location. When set to YES, the + * SDK will periodically try to listen for location updates in order to request location-based ads. + * This only occurs if location services are enabled and the user has already authorized the use + * of location services for the application. The default value is YES. + * + * @param enabled A Boolean value indicating whether the SDK should listen for location updates. + */ +@property (nonatomic, assign) BOOL locationUpdatesEnabled; + + +/** + * A Boolean value indicating whether the MoPub SDK should create a MoPub ID that can be used + * for frequency capping when Limit ad tracking is on & the IDFA we get is + * 00000000-0000-0000-0000-000000000000. + * + * When set to NO, the SDK will not create a MoPub ID in the above case. When set to YES, the + * SDK will generate a MoPub ID. The default value is YES. + * + */ +@property (nonatomic) BOOL frequencyCappingIdUsageEnabled; + +/** @name Rewarded Video */ +/** + * Initializes the rewarded video system. + * + * This method should only be called once. It should also be called prior to requesting any rewarded video ads. + * Once the global mediation settings and delegate are set, they cannot be changed. + * + * @param globalMediationSettings Global configurations for all rewarded video ad networks your app supports. + * + * @param delegate The delegate that will receive all events related to rewarded video. + */ +- (void)initializeRewardedVideoWithGlobalMediationSettings:(NSArray *)globalMediationSettings delegate:(id)delegate; + +/** + * Retrieves the global mediation settings for a given class type. +* + * @param aClass The type of mediation settings object you want to receive from the collection. + */ +- (id)globalMediationSettingsForClass:(Class)aClass; + +- (void)start; +- (NSString *)version; +- (NSString *)bundleIdentifier; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/MoPub.m b/iphone/Maps/3party/MoPubSDK/MoPub.m new file mode 100644 index 00000000000..9662cf797cb --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/MoPub.m @@ -0,0 +1,89 @@ +// +// MoPub.m +// MoPub +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import "MoPub.h" +#import "MPConstants.h" +#import "MPCoreInstanceProvider.h" +#import "MPGeolocationProvider.h" +#import "MPRewardedVideo.h" +#import "MPIdentityProvider.h" + +@interface MoPub () + +@property (nonatomic, strong) NSArray *globalMediationSettings; + +@end + +@implementation MoPub + ++ (MoPub *)sharedInstance +{ + static MoPub *sharedInstance = nil; + static dispatch_once_t initOnceToken; + dispatch_once(&initOnceToken, ^{ + sharedInstance = [[MoPub alloc] init]; + }); + return sharedInstance; +} + +- (void)setLocationUpdatesEnabled:(BOOL)locationUpdatesEnabled +{ + [[[MPCoreInstanceProvider sharedProvider] sharedMPGeolocationProvider] setLocationUpdatesEnabled:locationUpdatesEnabled]; +} + +- (BOOL)locationUpdatesEnabled +{ + return [[MPCoreInstanceProvider sharedProvider] sharedMPGeolocationProvider].locationUpdatesEnabled; +} + +- (void)setFrequencyCappingIdUsageEnabled:(BOOL)frequencyCappingIdUsageEnabled +{ + [MPIdentityProvider setFrequencyCappingIdUsageEnabled:frequencyCappingIdUsageEnabled]; +} + +- (BOOL)frequencyCappingIdUsageEnabled +{ + return [MPIdentityProvider frequencyCappingIdUsageEnabled]; +} + +- (void)start +{ + +} + +// Keep -version and -bundleIdentifier methods around for Fabric backwards compatibility. +- (NSString *)version +{ + return MP_SDK_VERSION; +} + +- (NSString *)bundleIdentifier +{ + return MP_BUNDLE_IDENTIFIER; +} + +- (void)initializeRewardedVideoWithGlobalMediationSettings:(NSArray *)globalMediationSettings delegate:(id)delegate +{ + // initializeWithDelegate: is a known private initialization method on MPRewardedVideo. So we forward the initialization call to that class. + [MPRewardedVideo performSelector:@selector(initializeWithDelegate:) withObject:delegate]; + self.globalMediationSettings = globalMediationSettings; +} + +- (id)globalMediationSettingsForClass:(Class)aClass +{ + NSArray *mediationSettingsCollection = self.globalMediationSettings; + + for (id settings in mediationSettingsCollection) { + if ([settings isKindOfClass:aClass]) { + return settings; + } + } + + return nil; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Mopub.xcodeproj/project.pbxproj b/iphone/Maps/3party/MoPubSDK/Mopub.xcodeproj/project.pbxproj new file mode 100644 index 00000000000..1a4b6d01f33 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Mopub.xcodeproj/project.pbxproj @@ -0,0 +1,1426 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 34F408E51E9E1DA400E57AC0 /* FacebookBannerCustomEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F407611E9E1DA400E57AC0 /* FacebookBannerCustomEvent.m */; }; + 34F408E61E9E1DA400E57AC0 /* FacebookInterstitialCustomEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F407631E9E1DA400E57AC0 /* FacebookInterstitialCustomEvent.m */; }; + 34F408E71E9E1DA400E57AC0 /* FacebookNativeAdAdapter.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F407651E9E1DA400E57AC0 /* FacebookNativeAdAdapter.m */; }; + 34F408E81E9E1DA400E57AC0 /* FacebookNativeCustomEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F407671E9E1DA400E57AC0 /* FacebookNativeCustomEvent.m */; }; + 34F408E91E9E1DA400E57AC0 /* FBAudienceNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 34F407691E9E1DA400E57AC0 /* FBAudienceNetwork.framework */; }; + 34F408EB1E9E1DA400E57AC0 /* MPBannerAdManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F4076E1E9E1DA400E57AC0 /* MPBannerAdManager.m */; }; + 34F408EC1E9E1DA400E57AC0 /* MPBannerCustomEventAdapter.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F407711E9E1DA400E57AC0 /* MPBannerCustomEventAdapter.m */; }; + 34F408ED1E9E1DA400E57AC0 /* MPBaseBannerAdapter.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F407731E9E1DA400E57AC0 /* MPBaseBannerAdapter.m */; }; + 34F408EE1E9E1DA400E57AC0 /* MPAdAlertGestureRecognizer.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F407781E9E1DA400E57AC0 /* MPAdAlertGestureRecognizer.m */; }; + 34F408EF1E9E1DA400E57AC0 /* MPAdAlertManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F4077A1E9E1DA400E57AC0 /* MPAdAlertManager.m */; }; + 34F408F01E9E1DA400E57AC0 /* MPActivityViewControllerHelper+TweetShare.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F4077C1E9E1DA400E57AC0 /* MPActivityViewControllerHelper+TweetShare.m */; }; + 34F408F11E9E1DA400E57AC0 /* MPActivityViewControllerHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F4077E1E9E1DA400E57AC0 /* MPActivityViewControllerHelper.m */; }; + 34F408F21E9E1DA400E57AC0 /* MPAdBrowserController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F407801E9E1DA400E57AC0 /* MPAdBrowserController.m */; }; + 34F408F31E9E1DA400E57AC0 /* MPAdConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F407831E9E1DA400E57AC0 /* MPAdConfiguration.m */; }; + 34F408F41E9E1DA400E57AC0 /* MPAdDestinationDisplayAgent.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F407851E9E1DA400E57AC0 /* MPAdDestinationDisplayAgent.m */; }; + 34F408F51E9E1DA400E57AC0 /* MPAdServerCommunicator.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F407871E9E1DA400E57AC0 /* MPAdServerCommunicator.m */; }; + 34F408F61E9E1DA400E57AC0 /* MPAdServerURLBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F407891E9E1DA400E57AC0 /* MPAdServerURLBuilder.m */; }; + 34F408F71E9E1DA400E57AC0 /* MPAPIEndpoints.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F4078B1E9E1DA400E57AC0 /* MPAPIEndpoints.m */; }; + 34F408F81E9E1DA400E57AC0 /* MPClosableView.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F4078D1E9E1DA400E57AC0 /* MPClosableView.m */; }; + 34F408F91E9E1DA400E57AC0 /* MPCountdownTimerView.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F4078F1E9E1DA400E57AC0 /* MPCountdownTimerView.m */; }; + 34F408FA1E9E1DA400E57AC0 /* MPEnhancedDeeplinkRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F407911E9E1DA400E57AC0 /* MPEnhancedDeeplinkRequest.m */; }; + 34F408FB1E9E1DA400E57AC0 /* MPFacebookKeywordProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F407931E9E1DA400E57AC0 /* MPFacebookKeywordProvider.m */; }; + 34F408FC1E9E1DA400E57AC0 /* MPLastResortDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F407961E9E1DA400E57AC0 /* MPLastResortDelegate.m */; }; + 34F408FD1E9E1DA400E57AC0 /* MPProgressOverlayView.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F407981E9E1DA400E57AC0 /* MPProgressOverlayView.m */; }; + 34F408FE1E9E1DA400E57AC0 /* MPURLActionInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F4079A1E9E1DA400E57AC0 /* MPURLActionInfo.m */; }; + 34F408FF1E9E1DA400E57AC0 /* MPURLResolver.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F4079C1E9E1DA400E57AC0 /* MPURLResolver.m */; }; + 34F409001E9E1DA400E57AC0 /* MPVideoConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F4079E1E9E1DA400E57AC0 /* MPVideoConfig.m */; }; + 34F409011E9E1DA400E57AC0 /* MPXMLParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F407A01E9E1DA400E57AC0 /* MPXMLParser.m */; }; + 34F409021E9E1DA400E57AC0 /* MPLogEvent+NativeVideo.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F407A31E9E1DA400E57AC0 /* MPLogEvent+NativeVideo.m */; }; + 34F409031E9E1DA400E57AC0 /* MPLogEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F407A51E9E1DA400E57AC0 /* MPLogEvent.m */; }; + 34F409041E9E1DA400E57AC0 /* MPLogEventCommunicator.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F407A71E9E1DA400E57AC0 /* MPLogEventCommunicator.m */; }; + 34F409051E9E1DA400E57AC0 /* MPLogEventRecorder.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F407A91E9E1DA400E57AC0 /* MPLogEventRecorder.m */; }; + 34F409061E9E1DA400E57AC0 /* MPNetworkManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F407AB1E9E1DA400E57AC0 /* MPNetworkManager.m */; }; + 34F409071E9E1DA400E57AC0 /* MPQRunLoopOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F407AD1E9E1DA400E57AC0 /* MPQRunLoopOperation.m */; }; + 34F409081E9E1DA400E57AC0 /* MPRetryingHTTPOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F407AF1E9E1DA400E57AC0 /* MPRetryingHTTPOperation.m */; }; + 34F409091E9E1DA400E57AC0 /* MPAdWebViewAgent.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F407B21E9E1DA400E57AC0 /* MPAdWebViewAgent.m */; }; + 34F4090A1E9E1DA400E57AC0 /* MPHTMLBannerCustomEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F407B41E9E1DA400E57AC0 /* MPHTMLBannerCustomEvent.m */; }; + 34F4090B1E9E1DA400E57AC0 /* MPHTMLInterstitialCustomEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F407B61E9E1DA400E57AC0 /* MPHTMLInterstitialCustomEvent.m */; }; + 34F4090C1E9E1DA400E57AC0 /* MPHTMLInterstitialViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F407B81E9E1DA400E57AC0 /* MPHTMLInterstitialViewController.m */; }; + 34F4090D1E9E1DA400E57AC0 /* MPWebView.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F407BA1E9E1DA400E57AC0 /* MPWebView.m */; }; + 34F4090E1E9E1DA400E57AC0 /* MPBaseInterstitialAdapter.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F407BD1E9E1DA400E57AC0 /* MPBaseInterstitialAdapter.m */; }; + 34F4090F1E9E1DA400E57AC0 /* MPInterstitialAdManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F407BF1E9E1DA400E57AC0 /* MPInterstitialAdManager.m */; }; + 34F409101E9E1DA400E57AC0 /* MPInterstitialCustomEventAdapter.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F407C21E9E1DA400E57AC0 /* MPInterstitialCustomEventAdapter.m */; }; + 34F409111E9E1DA400E57AC0 /* MPInterstitialViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F407C41E9E1DA400E57AC0 /* MPInterstitialViewController.m */; }; + 34F409121E9E1DA400E57AC0 /* MPCoreInstanceProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F407C71E9E1DA400E57AC0 /* MPCoreInstanceProvider.m */; }; + 34F409131E9E1DA400E57AC0 /* MPInstanceProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F407C91E9E1DA400E57AC0 /* MPInstanceProvider.m */; }; + 34F409141E9E1DA400E57AC0 /* MPVASTTracking.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F407CB1E9E1DA400E57AC0 /* MPVASTTracking.m */; }; + 34F409151E9E1DA400E57AC0 /* MPMRAIDBannerCustomEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F407D01E9E1DA400E57AC0 /* MPMRAIDBannerCustomEvent.m */; }; + 34F409161E9E1DA400E57AC0 /* MPMRAIDInterstitialCustomEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F407D21E9E1DA400E57AC0 /* MPMRAIDInterstitialCustomEvent.m */; }; + 34F409171E9E1DA400E57AC0 /* MPMRAIDInterstitialViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F407D41E9E1DA400E57AC0 /* MPMRAIDInterstitialViewController.m */; }; + 34F409181E9E1DA400E57AC0 /* MRBridge.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F407D61E9E1DA400E57AC0 /* MRBridge.m */; }; + 34F409191E9E1DA400E57AC0 /* MRBundleManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F407D81E9E1DA400E57AC0 /* MRBundleManager.m */; }; + 34F4091A1E9E1DA400E57AC0 /* MRCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F407DA1E9E1DA400E57AC0 /* MRCommand.m */; }; + 34F4091B1E9E1DA400E57AC0 /* MRConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F407DC1E9E1DA400E57AC0 /* MRConstants.m */; }; + 34F4091C1E9E1DA400E57AC0 /* MRController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F407DE1E9E1DA400E57AC0 /* MRController.m */; }; + 34F4091D1E9E1DA400E57AC0 /* MRError.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F407E01E9E1DA400E57AC0 /* MRError.m */; }; + 34F4091E1E9E1DA400E57AC0 /* MRExpandModalViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F407E21E9E1DA400E57AC0 /* MRExpandModalViewController.m */; }; + 34F4091F1E9E1DA400E57AC0 /* MRNativeCommandHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F407E41E9E1DA400E57AC0 /* MRNativeCommandHandler.m */; }; + 34F409201E9E1DA400E57AC0 /* MRProperty.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F407E61E9E1DA400E57AC0 /* MRProperty.m */; }; + 34F409211E9E1DA400E57AC0 /* MRVideoPlayerManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F407E81E9E1DA400E57AC0 /* MRVideoPlayerManager.m */; }; + 34F409221E9E1DA400E57AC0 /* NSBundle+MPAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F407EC1E9E1DA400E57AC0 /* NSBundle+MPAdditions.m */; }; + 34F409231E9E1DA400E57AC0 /* NSHTTPURLResponse+MPAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F407EE1E9E1DA400E57AC0 /* NSHTTPURLResponse+MPAdditions.m */; }; + 34F409241E9E1DA400E57AC0 /* NSJSONSerialization+MPAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F407F01E9E1DA400E57AC0 /* NSJSONSerialization+MPAdditions.m */; }; + 34F409251E9E1DA400E57AC0 /* NSURL+MPAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F407F21E9E1DA400E57AC0 /* NSURL+MPAdditions.m */; }; + 34F409261E9E1DA400E57AC0 /* UIButton+MPAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F407F41E9E1DA400E57AC0 /* UIButton+MPAdditions.m */; }; + 34F409271E9E1DA400E57AC0 /* UIColor+MPAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F407F61E9E1DA400E57AC0 /* UIColor+MPAdditions.m */; }; + 34F409281E9E1DA400E57AC0 /* UIView+MPAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F407F81E9E1DA400E57AC0 /* UIView+MPAdditions.m */; }; + 34F409291E9E1DA400E57AC0 /* UIWebView+MPAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F407FA1E9E1DA400E57AC0 /* UIWebView+MPAdditions.m */; }; + 34F4092A1E9E1DA400E57AC0 /* MPAnalyticsTracker.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F407FC1E9E1DA400E57AC0 /* MPAnalyticsTracker.m */; }; + 34F4092B1E9E1DA400E57AC0 /* MPError.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F407FE1E9E1DA400E57AC0 /* MPError.m */; }; + 34F4092C1E9E1DA400E57AC0 /* MPGeolocationProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F408001E9E1DA400E57AC0 /* MPGeolocationProvider.m */; }; + 34F4092D1E9E1DA400E57AC0 /* MPGlobal.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F408021E9E1DA400E57AC0 /* MPGlobal.m */; }; + 34F4092E1E9E1DA400E57AC0 /* MPIdentityProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F408041E9E1DA400E57AC0 /* MPIdentityProvider.m */; }; + 34F4092F1E9E1DA400E57AC0 /* MPInternalUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F408061E9E1DA400E57AC0 /* MPInternalUtils.m */; }; + 34F409301E9E1DA400E57AC0 /* MPLogging.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F408081E9E1DA400E57AC0 /* MPLogging.m */; }; + 34F409311E9E1DA400E57AC0 /* MPLogProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F4080A1E9E1DA400E57AC0 /* MPLogProvider.m */; }; + 34F409321E9E1DA400E57AC0 /* MPReachability.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F4080C1E9E1DA400E57AC0 /* MPReachability.m */; }; + 34F409331E9E1DA400E57AC0 /* MPSessionTracker.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F4080E1E9E1DA400E57AC0 /* MPSessionTracker.m */; }; + 34F409341E9E1DA400E57AC0 /* MPStoreKitProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F408101E9E1DA400E57AC0 /* MPStoreKitProvider.m */; }; + 34F409351E9E1DA400E57AC0 /* MPTimer.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F408121E9E1DA400E57AC0 /* MPTimer.m */; }; + 34F409361E9E1DA400E57AC0 /* MPUserInteractionGestureRecognizer.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F408141E9E1DA400E57AC0 /* MPUserInteractionGestureRecognizer.m */; }; + 34F409371E9E1DA400E57AC0 /* MPVASTAd.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F408171E9E1DA400E57AC0 /* MPVASTAd.m */; }; + 34F409381E9E1DA400E57AC0 /* MPVASTCompanionAd.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F408191E9E1DA400E57AC0 /* MPVASTCompanionAd.m */; }; + 34F409391E9E1DA400E57AC0 /* MPVASTCreative.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F4081B1E9E1DA400E57AC0 /* MPVASTCreative.m */; }; + 34F4093A1E9E1DA400E57AC0 /* MPVASTDurationOffset.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F4081D1E9E1DA400E57AC0 /* MPVASTDurationOffset.m */; }; + 34F4093B1E9E1DA400E57AC0 /* MPVASTIndustryIcon.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F4081F1E9E1DA400E57AC0 /* MPVASTIndustryIcon.m */; }; + 34F4093C1E9E1DA400E57AC0 /* MPVASTInline.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F408211E9E1DA400E57AC0 /* MPVASTInline.m */; }; + 34F4093D1E9E1DA400E57AC0 /* MPVASTLinearAd.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F408231E9E1DA400E57AC0 /* MPVASTLinearAd.m */; }; + 34F4093E1E9E1DA400E57AC0 /* MPVASTMacroProcessor.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F408251E9E1DA400E57AC0 /* MPVASTMacroProcessor.m */; }; + 34F4093F1E9E1DA400E57AC0 /* MPVASTManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F408271E9E1DA400E57AC0 /* MPVASTManager.m */; }; + 34F409401E9E1DA400E57AC0 /* MPVASTMediaFile.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F408291E9E1DA400E57AC0 /* MPVASTMediaFile.m */; }; + 34F409411E9E1DA400E57AC0 /* MPVASTModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F4082B1E9E1DA400E57AC0 /* MPVASTModel.m */; }; + 34F409421E9E1DA400E57AC0 /* MPVASTResource.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F4082D1E9E1DA400E57AC0 /* MPVASTResource.m */; }; + 34F409431E9E1DA400E57AC0 /* MPVASTResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F4082F1E9E1DA400E57AC0 /* MPVASTResponse.m */; }; + 34F409441E9E1DA400E57AC0 /* MPVASTStringUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F408311E9E1DA400E57AC0 /* MPVASTStringUtilities.m */; }; + 34F409451E9E1DA400E57AC0 /* MPVASTTrackingEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F408331E9E1DA400E57AC0 /* MPVASTTrackingEvent.m */; }; + 34F409461E9E1DA400E57AC0 /* MPVASTWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F408351E9E1DA400E57AC0 /* MPVASTWrapper.m */; }; + 34F409471E9E1DA400E57AC0 /* MoPub.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F408381E9E1DA400E57AC0 /* MoPub.m */; }; + 34F409481E9E1DA400E57AC0 /* MPAdConversionTracker.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F4083A1E9E1DA400E57AC0 /* MPAdConversionTracker.m */; }; + 34F409491E9E1DA400E57AC0 /* MPAdView.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F4083C1E9E1DA400E57AC0 /* MPAdView.m */; }; + 34F4094A1E9E1DA400E57AC0 /* MPBannerCustomEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F4083E1E9E1DA400E57AC0 /* MPBannerCustomEvent.m */; }; + 34F4094B1E9E1DA400E57AC0 /* MPConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F408411E9E1DA400E57AC0 /* MPConstants.m */; }; + 34F4094C1E9E1DA400E57AC0 /* MPInterstitialAdController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F408431E9E1DA400E57AC0 /* MPInterstitialAdController.m */; }; + 34F4094D1E9E1DA400E57AC0 /* MPInterstitialCustomEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F408451E9E1DA400E57AC0 /* MPInterstitialCustomEvent.m */; }; + 34F4094E1E9E1DA400E57AC0 /* MPAdPlacerInvocation.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F4084C1E9E1DA400E57AC0 /* MPAdPlacerInvocation.m */; }; + 34F4094F1E9E1DA400E57AC0 /* MPCollectionViewAdPlacerCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F4084E1E9E1DA400E57AC0 /* MPCollectionViewAdPlacerCell.m */; }; + 34F409501E9E1DA400E57AC0 /* MPDiskLRUCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F408501E9E1DA400E57AC0 /* MPDiskLRUCache.m */; }; + 34F409511E9E1DA400E57AC0 /* MPImageDownloadQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F408521E9E1DA400E57AC0 /* MPImageDownloadQueue.m */; }; + 34F409521E9E1DA400E57AC0 /* MPMoPubNativeAdAdapter.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F408541E9E1DA400E57AC0 /* MPMoPubNativeAdAdapter.m */; }; + 34F409531E9E1DA400E57AC0 /* MPMoPubNativeCustomEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F408561E9E1DA400E57AC0 /* MPMoPubNativeCustomEvent.m */; }; + 34F409541E9E1DA400E57AC0 /* MPNativeAd+Internal.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F408581E9E1DA400E57AC0 /* MPNativeAd+Internal.m */; }; + 34F409551E9E1DA400E57AC0 /* MPNativeAdRendererImageHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F4085B1E9E1DA400E57AC0 /* MPNativeAdRendererImageHandler.m */; }; + 34F409561E9E1DA400E57AC0 /* MPNativeAdSourceQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F4085D1E9E1DA400E57AC0 /* MPNativeAdSourceQueue.m */; }; + 34F409571E9E1DA400E57AC0 /* MPNativeAdUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F4085F1E9E1DA400E57AC0 /* MPNativeAdUtils.m */; }; + 34F409581E9E1DA400E57AC0 /* MPNativeCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F408611E9E1DA400E57AC0 /* MPNativeCache.m */; }; + 34F409591E9E1DA400E57AC0 /* MPNativePositionResponseDeserializer.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F408631E9E1DA400E57AC0 /* MPNativePositionResponseDeserializer.m */; }; + 34F4095A1E9E1DA400E57AC0 /* MPNativePositionSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F408651E9E1DA400E57AC0 /* MPNativePositionSource.m */; }; + 34F4095B1E9E1DA400E57AC0 /* MPNativeView.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F408671E9E1DA400E57AC0 /* MPNativeView.m */; }; + 34F4095C1E9E1DA400E57AC0 /* MPStaticNativeAdImpressionTimer.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F408691E9E1DA400E57AC0 /* MPStaticNativeAdImpressionTimer.m */; }; + 34F4095D1E9E1DA400E57AC0 /* MPTableViewAdPlacerCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F4086B1E9E1DA400E57AC0 /* MPTableViewAdPlacerCell.m */; }; + 34F4095E1E9E1DA400E57AC0 /* MPTableViewCellImpressionTracker.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F4086D1E9E1DA400E57AC0 /* MPTableViewCellImpressionTracker.m */; }; + 34F4095F1E9E1DA400E57AC0 /* MPAdPositioning.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F4086F1E9E1DA400E57AC0 /* MPAdPositioning.m */; }; + 34F409601E9E1DA400E57AC0 /* MPClientAdPositioning.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F408711E9E1DA400E57AC0 /* MPClientAdPositioning.m */; }; + 34F409611E9E1DA400E57AC0 /* MPCollectionViewAdPlacer.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F408731E9E1DA400E57AC0 /* MPCollectionViewAdPlacer.m */; }; + 34F409621E9E1DA400E57AC0 /* MPNativeAd.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F408751E9E1DA400E57AC0 /* MPNativeAd.m */; }; + 34F409631E9E1DA400E57AC0 /* MPNativeAdConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F408781E9E1DA400E57AC0 /* MPNativeAdConstants.m */; }; + 34F409641E9E1DA400E57AC0 /* MPNativeAdData.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F4087A1E9E1DA400E57AC0 /* MPNativeAdData.m */; }; + 34F409651E9E1DA400E57AC0 /* MPNativeAdError.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F4087D1E9E1DA400E57AC0 /* MPNativeAdError.m */; }; + 34F409661E9E1DA400E57AC0 /* MPNativeAdRendererConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F408801E9E1DA400E57AC0 /* MPNativeAdRendererConfiguration.m */; }; + 34F409671E9E1DA400E57AC0 /* MPNativeAdRenderingImageLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F408841E9E1DA400E57AC0 /* MPNativeAdRenderingImageLoader.m */; }; + 34F409681E9E1DA400E57AC0 /* MPNativeAdRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F408861E9E1DA400E57AC0 /* MPNativeAdRequest.m */; }; + 34F409691E9E1DA400E57AC0 /* MPNativeAdRequestTargeting.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F408881E9E1DA400E57AC0 /* MPNativeAdRequestTargeting.m */; }; + 34F4096A1E9E1DA400E57AC0 /* MPNativeAdSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F4088A1E9E1DA400E57AC0 /* MPNativeAdSource.m */; }; + 34F4096B1E9E1DA400E57AC0 /* MPNativeCustomEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F4088D1E9E1DA400E57AC0 /* MPNativeCustomEvent.m */; }; + 34F4096C1E9E1DA400E57AC0 /* MPServerAdPositioning.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F408901E9E1DA400E57AC0 /* MPServerAdPositioning.m */; }; + 34F4096D1E9E1DA400E57AC0 /* MPStaticNativeAdRenderer.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F408921E9E1DA400E57AC0 /* MPStaticNativeAdRenderer.m */; }; + 34F4096E1E9E1DA400E57AC0 /* MPStaticNativeAdRendererSettings.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F408941E9E1DA400E57AC0 /* MPStaticNativeAdRendererSettings.m */; }; + 34F4096F1E9E1DA400E57AC0 /* MPStreamAdPlacementData.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F408961E9E1DA400E57AC0 /* MPStreamAdPlacementData.m */; }; + 34F409701E9E1DA400E57AC0 /* MPStreamAdPlacer.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F408981E9E1DA400E57AC0 /* MPStreamAdPlacer.m */; }; + 34F409711E9E1DA400E57AC0 /* MPTableViewAdPlacer.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F4089A1E9E1DA400E57AC0 /* MPTableViewAdPlacer.m */; }; + 34F409721E9E1DA400E57AC0 /* MOPUBActivityIndicatorView.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F4089E1E9E1DA400E57AC0 /* MOPUBActivityIndicatorView.m */; }; + 34F409731E9E1DA400E57AC0 /* MOPUBAVPlayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F408A01E9E1DA400E57AC0 /* MOPUBAVPlayer.m */; }; + 34F409741E9E1DA400E57AC0 /* MOPUBAVPlayerView.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F408A21E9E1DA400E57AC0 /* MOPUBAVPlayerView.m */; }; + 34F409751E9E1DA400E57AC0 /* MOPUBFullscreenPlayerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F408A41E9E1DA400E57AC0 /* MOPUBFullscreenPlayerViewController.m */; }; + 34F409761E9E1DA400E57AC0 /* MOPUBNativeVideoAdAdapter.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F408A61E9E1DA400E57AC0 /* MOPUBNativeVideoAdAdapter.m */; }; + 34F409771E9E1DA400E57AC0 /* MOPUBNativeVideoAdConfigValues.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F408A81E9E1DA400E57AC0 /* MOPUBNativeVideoAdConfigValues.m */; }; + 34F409781E9E1DA400E57AC0 /* MOPUBNativeVideoCustomEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F408AA1E9E1DA400E57AC0 /* MOPUBNativeVideoCustomEvent.m */; }; + 34F409791E9E1DA400E57AC0 /* MOPUBNativeVideoImpressionAgent.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F408AC1E9E1DA400E57AC0 /* MOPUBNativeVideoImpressionAgent.m */; }; + 34F4097A1E9E1DA400E57AC0 /* MOPUBPlayerManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F408AE1E9E1DA400E57AC0 /* MOPUBPlayerManager.m */; }; + 34F4097B1E9E1DA400E57AC0 /* MOPUBPlayerView.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F408B01E9E1DA400E57AC0 /* MOPUBPlayerView.m */; }; + 34F4097C1E9E1DA400E57AC0 /* MOPUBPlayerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F408B21E9E1DA400E57AC0 /* MOPUBPlayerViewController.m */; }; + 34F4097D1E9E1DA400E57AC0 /* MOPUBReplayView.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F408B41E9E1DA400E57AC0 /* MOPUBReplayView.m */; }; + 34F4097E1E9E1DA400E57AC0 /* MOPUBNativeVideoAdRenderer.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F408B61E9E1DA400E57AC0 /* MOPUBNativeVideoAdRenderer.m */; }; + 34F4097F1E9E1DA400E57AC0 /* MOPUBNativeVideoAdRendererSettings.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F408B81E9E1DA400E57AC0 /* MOPUBNativeVideoAdRendererSettings.m */; }; + 34F409801E9E1DA400E57AC0 /* MPMoPubRewardedPlayableCustomEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F408D11E9E1DA400E57AC0 /* MPMoPubRewardedPlayableCustomEvent.m */; }; + 34F409811E9E1DA400E57AC0 /* MPMoPubRewardedVideoCustomEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F408D31E9E1DA400E57AC0 /* MPMoPubRewardedVideoCustomEvent.m */; }; + 34F409821E9E1DA400E57AC0 /* MPRewardedVideoAdapter.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F408D71E9E1DA400E57AC0 /* MPRewardedVideoAdapter.m */; }; + 34F409831E9E1DA400E57AC0 /* MPRewardedVideoAdManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F408D91E9E1DA400E57AC0 /* MPRewardedVideoAdManager.m */; }; + 34F409841E9E1DA400E57AC0 /* MPRewardedVideoConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F408DB1E9E1DA400E57AC0 /* MPRewardedVideoConnection.m */; }; + 34F409851E9E1DA400E57AC0 /* MPRewardedVideo.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F408DE1E9E1DA400E57AC0 /* MPRewardedVideo.m */; }; + 34F409861E9E1DA400E57AC0 /* MPRewardedVideoCustomEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F408E01E9E1DA400E57AC0 /* MPRewardedVideoCustomEvent.m */; }; + 34F409871E9E1DA400E57AC0 /* MPRewardedVideoError.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F408E21E9E1DA400E57AC0 /* MPRewardedVideoError.m */; }; + 34F409881E9E1DA400E57AC0 /* MPRewardedVideoReward.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F408E41E9E1DA400E57AC0 /* MPRewardedVideoReward.m */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 34F4074A1E9E1D3500E57AC0 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = "include/$(PRODUCT_NAME)"; + dstSubfolderSpec = 16; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 34F4074C1E9E1D3500E57AC0 /* libMopub.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libMopub.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 34F407601E9E1DA400E57AC0 /* FacebookBannerCustomEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FacebookBannerCustomEvent.h; sourceTree = ""; }; + 34F407611E9E1DA400E57AC0 /* FacebookBannerCustomEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FacebookBannerCustomEvent.m; sourceTree = ""; }; + 34F407621E9E1DA400E57AC0 /* FacebookInterstitialCustomEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FacebookInterstitialCustomEvent.h; sourceTree = ""; }; + 34F407631E9E1DA400E57AC0 /* FacebookInterstitialCustomEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FacebookInterstitialCustomEvent.m; sourceTree = ""; }; + 34F407641E9E1DA400E57AC0 /* FacebookNativeAdAdapter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FacebookNativeAdAdapter.h; sourceTree = ""; }; + 34F407651E9E1DA400E57AC0 /* FacebookNativeAdAdapter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FacebookNativeAdAdapter.m; sourceTree = ""; }; + 34F407661E9E1DA400E57AC0 /* FacebookNativeCustomEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FacebookNativeCustomEvent.h; sourceTree = ""; }; + 34F407671E9E1DA400E57AC0 /* FacebookNativeCustomEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FacebookNativeCustomEvent.m; sourceTree = ""; }; + 34F407691E9E1DA400E57AC0 /* FBAudienceNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = FBAudienceNetwork.framework; sourceTree = ""; }; + 34F4076D1E9E1DA400E57AC0 /* MPBannerAdManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPBannerAdManager.h; sourceTree = ""; }; + 34F4076E1E9E1DA400E57AC0 /* MPBannerAdManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPBannerAdManager.m; sourceTree = ""; }; + 34F4076F1E9E1DA400E57AC0 /* MPBannerAdManagerDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPBannerAdManagerDelegate.h; sourceTree = ""; }; + 34F407701E9E1DA400E57AC0 /* MPBannerCustomEventAdapter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPBannerCustomEventAdapter.h; sourceTree = ""; }; + 34F407711E9E1DA400E57AC0 /* MPBannerCustomEventAdapter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPBannerCustomEventAdapter.m; sourceTree = ""; }; + 34F407721E9E1DA400E57AC0 /* MPBaseBannerAdapter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPBaseBannerAdapter.h; sourceTree = ""; }; + 34F407731E9E1DA400E57AC0 /* MPBaseBannerAdapter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPBaseBannerAdapter.m; sourceTree = ""; }; + 34F407741E9E1DA400E57AC0 /* MPPrivateBannerCustomEventDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPPrivateBannerCustomEventDelegate.h; sourceTree = ""; }; + 34F407771E9E1DA400E57AC0 /* MPAdAlertGestureRecognizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAdAlertGestureRecognizer.h; sourceTree = ""; }; + 34F407781E9E1DA400E57AC0 /* MPAdAlertGestureRecognizer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAdAlertGestureRecognizer.m; sourceTree = ""; }; + 34F407791E9E1DA400E57AC0 /* MPAdAlertManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAdAlertManager.h; sourceTree = ""; }; + 34F4077A1E9E1DA400E57AC0 /* MPAdAlertManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAdAlertManager.m; sourceTree = ""; }; + 34F4077B1E9E1DA400E57AC0 /* MPActivityViewControllerHelper+TweetShare.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MPActivityViewControllerHelper+TweetShare.h"; sourceTree = ""; }; + 34F4077C1E9E1DA400E57AC0 /* MPActivityViewControllerHelper+TweetShare.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MPActivityViewControllerHelper+TweetShare.m"; sourceTree = ""; }; + 34F4077D1E9E1DA400E57AC0 /* MPActivityViewControllerHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPActivityViewControllerHelper.h; sourceTree = ""; }; + 34F4077E1E9E1DA400E57AC0 /* MPActivityViewControllerHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPActivityViewControllerHelper.m; sourceTree = ""; }; + 34F4077F1E9E1DA400E57AC0 /* MPAdBrowserController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAdBrowserController.h; sourceTree = ""; }; + 34F407801E9E1DA400E57AC0 /* MPAdBrowserController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAdBrowserController.m; sourceTree = ""; }; + 34F407811E9E1DA400E57AC0 /* MPAdBrowserController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MPAdBrowserController.xib; sourceTree = ""; }; + 34F407821E9E1DA400E57AC0 /* MPAdConfiguration.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAdConfiguration.h; sourceTree = ""; }; + 34F407831E9E1DA400E57AC0 /* MPAdConfiguration.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAdConfiguration.m; sourceTree = ""; }; + 34F407841E9E1DA400E57AC0 /* MPAdDestinationDisplayAgent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAdDestinationDisplayAgent.h; sourceTree = ""; }; + 34F407851E9E1DA400E57AC0 /* MPAdDestinationDisplayAgent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAdDestinationDisplayAgent.m; sourceTree = ""; }; + 34F407861E9E1DA400E57AC0 /* MPAdServerCommunicator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAdServerCommunicator.h; sourceTree = ""; }; + 34F407871E9E1DA400E57AC0 /* MPAdServerCommunicator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAdServerCommunicator.m; sourceTree = ""; }; + 34F407881E9E1DA400E57AC0 /* MPAdServerURLBuilder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAdServerURLBuilder.h; sourceTree = ""; }; + 34F407891E9E1DA400E57AC0 /* MPAdServerURLBuilder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAdServerURLBuilder.m; sourceTree = ""; }; + 34F4078A1E9E1DA400E57AC0 /* MPAPIEndpoints.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAPIEndpoints.h; sourceTree = ""; }; + 34F4078B1E9E1DA400E57AC0 /* MPAPIEndpoints.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAPIEndpoints.m; sourceTree = ""; }; + 34F4078C1E9E1DA400E57AC0 /* MPClosableView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPClosableView.h; sourceTree = ""; }; + 34F4078D1E9E1DA400E57AC0 /* MPClosableView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPClosableView.m; sourceTree = ""; }; + 34F4078E1E9E1DA400E57AC0 /* MPCountdownTimerView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPCountdownTimerView.h; sourceTree = ""; }; + 34F4078F1E9E1DA400E57AC0 /* MPCountdownTimerView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPCountdownTimerView.m; sourceTree = ""; }; + 34F407901E9E1DA400E57AC0 /* MPEnhancedDeeplinkRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPEnhancedDeeplinkRequest.h; sourceTree = ""; }; + 34F407911E9E1DA400E57AC0 /* MPEnhancedDeeplinkRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPEnhancedDeeplinkRequest.m; sourceTree = ""; }; + 34F407921E9E1DA400E57AC0 /* MPFacebookKeywordProvider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPFacebookKeywordProvider.h; sourceTree = ""; }; + 34F407931E9E1DA400E57AC0 /* MPFacebookKeywordProvider.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPFacebookKeywordProvider.m; sourceTree = ""; }; + 34F407941E9E1DA400E57AC0 /* MPKeywordProvider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPKeywordProvider.h; sourceTree = ""; }; + 34F407951E9E1DA400E57AC0 /* MPLastResortDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPLastResortDelegate.h; sourceTree = ""; }; + 34F407961E9E1DA400E57AC0 /* MPLastResortDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPLastResortDelegate.m; sourceTree = ""; }; + 34F407971E9E1DA400E57AC0 /* MPProgressOverlayView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPProgressOverlayView.h; sourceTree = ""; }; + 34F407981E9E1DA400E57AC0 /* MPProgressOverlayView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPProgressOverlayView.m; sourceTree = ""; }; + 34F407991E9E1DA400E57AC0 /* MPURLActionInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPURLActionInfo.h; sourceTree = ""; }; + 34F4079A1E9E1DA400E57AC0 /* MPURLActionInfo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPURLActionInfo.m; sourceTree = ""; }; + 34F4079B1E9E1DA400E57AC0 /* MPURLResolver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPURLResolver.h; sourceTree = ""; }; + 34F4079C1E9E1DA400E57AC0 /* MPURLResolver.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPURLResolver.m; sourceTree = ""; }; + 34F4079D1E9E1DA400E57AC0 /* MPVideoConfig.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPVideoConfig.h; sourceTree = ""; }; + 34F4079E1E9E1DA400E57AC0 /* MPVideoConfig.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPVideoConfig.m; sourceTree = ""; }; + 34F4079F1E9E1DA400E57AC0 /* MPXMLParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPXMLParser.h; sourceTree = ""; }; + 34F407A01E9E1DA400E57AC0 /* MPXMLParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPXMLParser.m; sourceTree = ""; }; + 34F407A21E9E1DA400E57AC0 /* MPLogEvent+NativeVideo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MPLogEvent+NativeVideo.h"; sourceTree = ""; }; + 34F407A31E9E1DA400E57AC0 /* MPLogEvent+NativeVideo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MPLogEvent+NativeVideo.m"; sourceTree = ""; }; + 34F407A41E9E1DA400E57AC0 /* MPLogEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPLogEvent.h; sourceTree = ""; }; + 34F407A51E9E1DA400E57AC0 /* MPLogEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPLogEvent.m; sourceTree = ""; }; + 34F407A61E9E1DA400E57AC0 /* MPLogEventCommunicator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPLogEventCommunicator.h; sourceTree = ""; }; + 34F407A71E9E1DA400E57AC0 /* MPLogEventCommunicator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPLogEventCommunicator.m; sourceTree = ""; }; + 34F407A81E9E1DA400E57AC0 /* MPLogEventRecorder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPLogEventRecorder.h; sourceTree = ""; }; + 34F407A91E9E1DA400E57AC0 /* MPLogEventRecorder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPLogEventRecorder.m; sourceTree = ""; }; + 34F407AA1E9E1DA400E57AC0 /* MPNetworkManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPNetworkManager.h; sourceTree = ""; }; + 34F407AB1E9E1DA400E57AC0 /* MPNetworkManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPNetworkManager.m; sourceTree = ""; }; + 34F407AC1E9E1DA400E57AC0 /* MPQRunLoopOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPQRunLoopOperation.h; sourceTree = ""; }; + 34F407AD1E9E1DA400E57AC0 /* MPQRunLoopOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPQRunLoopOperation.m; sourceTree = ""; }; + 34F407AE1E9E1DA400E57AC0 /* MPRetryingHTTPOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPRetryingHTTPOperation.h; sourceTree = ""; }; + 34F407AF1E9E1DA400E57AC0 /* MPRetryingHTTPOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPRetryingHTTPOperation.m; sourceTree = ""; }; + 34F407B11E9E1DA400E57AC0 /* MPAdWebViewAgent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAdWebViewAgent.h; sourceTree = ""; }; + 34F407B21E9E1DA400E57AC0 /* MPAdWebViewAgent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAdWebViewAgent.m; sourceTree = ""; }; + 34F407B31E9E1DA400E57AC0 /* MPHTMLBannerCustomEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPHTMLBannerCustomEvent.h; sourceTree = ""; }; + 34F407B41E9E1DA400E57AC0 /* MPHTMLBannerCustomEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPHTMLBannerCustomEvent.m; sourceTree = ""; }; + 34F407B51E9E1DA400E57AC0 /* MPHTMLInterstitialCustomEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPHTMLInterstitialCustomEvent.h; sourceTree = ""; }; + 34F407B61E9E1DA400E57AC0 /* MPHTMLInterstitialCustomEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPHTMLInterstitialCustomEvent.m; sourceTree = ""; }; + 34F407B71E9E1DA400E57AC0 /* MPHTMLInterstitialViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPHTMLInterstitialViewController.h; sourceTree = ""; }; + 34F407B81E9E1DA400E57AC0 /* MPHTMLInterstitialViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPHTMLInterstitialViewController.m; sourceTree = ""; }; + 34F407B91E9E1DA400E57AC0 /* MPWebView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPWebView.h; sourceTree = ""; }; + 34F407BA1E9E1DA400E57AC0 /* MPWebView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPWebView.m; sourceTree = ""; }; + 34F407BC1E9E1DA400E57AC0 /* MPBaseInterstitialAdapter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPBaseInterstitialAdapter.h; sourceTree = ""; }; + 34F407BD1E9E1DA400E57AC0 /* MPBaseInterstitialAdapter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPBaseInterstitialAdapter.m; sourceTree = ""; }; + 34F407BE1E9E1DA400E57AC0 /* MPInterstitialAdManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPInterstitialAdManager.h; sourceTree = ""; }; + 34F407BF1E9E1DA400E57AC0 /* MPInterstitialAdManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPInterstitialAdManager.m; sourceTree = ""; }; + 34F407C01E9E1DA400E57AC0 /* MPInterstitialAdManagerDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPInterstitialAdManagerDelegate.h; sourceTree = ""; }; + 34F407C11E9E1DA400E57AC0 /* MPInterstitialCustomEventAdapter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPInterstitialCustomEventAdapter.h; sourceTree = ""; }; + 34F407C21E9E1DA400E57AC0 /* MPInterstitialCustomEventAdapter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPInterstitialCustomEventAdapter.m; sourceTree = ""; }; + 34F407C31E9E1DA400E57AC0 /* MPInterstitialViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPInterstitialViewController.h; sourceTree = ""; }; + 34F407C41E9E1DA400E57AC0 /* MPInterstitialViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPInterstitialViewController.m; sourceTree = ""; }; + 34F407C51E9E1DA400E57AC0 /* MPPrivateInterstitialCustomEventDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPPrivateInterstitialCustomEventDelegate.h; sourceTree = ""; }; + 34F407C61E9E1DA400E57AC0 /* MPCoreInstanceProvider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPCoreInstanceProvider.h; sourceTree = ""; }; + 34F407C71E9E1DA400E57AC0 /* MPCoreInstanceProvider.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPCoreInstanceProvider.m; sourceTree = ""; }; + 34F407C81E9E1DA400E57AC0 /* MPInstanceProvider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPInstanceProvider.h; sourceTree = ""; }; + 34F407C91E9E1DA400E57AC0 /* MPInstanceProvider.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPInstanceProvider.m; sourceTree = ""; }; + 34F407CA1E9E1DA400E57AC0 /* MPVASTTracking.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPVASTTracking.h; sourceTree = ""; }; + 34F407CB1E9E1DA400E57AC0 /* MPVASTTracking.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPVASTTracking.m; sourceTree = ""; }; + 34F407CD1E9E1DA400E57AC0 /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; + 34F407CE1E9E1DA400E57AC0 /* MPForceableOrientationProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPForceableOrientationProtocol.h; sourceTree = ""; }; + 34F407CF1E9E1DA400E57AC0 /* MPMRAIDBannerCustomEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPMRAIDBannerCustomEvent.h; sourceTree = ""; }; + 34F407D01E9E1DA400E57AC0 /* MPMRAIDBannerCustomEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPMRAIDBannerCustomEvent.m; sourceTree = ""; }; + 34F407D11E9E1DA400E57AC0 /* MPMRAIDInterstitialCustomEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPMRAIDInterstitialCustomEvent.h; sourceTree = ""; }; + 34F407D21E9E1DA400E57AC0 /* MPMRAIDInterstitialCustomEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPMRAIDInterstitialCustomEvent.m; sourceTree = ""; }; + 34F407D31E9E1DA400E57AC0 /* MPMRAIDInterstitialViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPMRAIDInterstitialViewController.h; sourceTree = ""; }; + 34F407D41E9E1DA400E57AC0 /* MPMRAIDInterstitialViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPMRAIDInterstitialViewController.m; sourceTree = ""; }; + 34F407D51E9E1DA400E57AC0 /* MRBridge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MRBridge.h; sourceTree = ""; }; + 34F407D61E9E1DA400E57AC0 /* MRBridge.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MRBridge.m; sourceTree = ""; }; + 34F407D71E9E1DA400E57AC0 /* MRBundleManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MRBundleManager.h; sourceTree = ""; }; + 34F407D81E9E1DA400E57AC0 /* MRBundleManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MRBundleManager.m; sourceTree = ""; }; + 34F407D91E9E1DA400E57AC0 /* MRCommand.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MRCommand.h; sourceTree = ""; }; + 34F407DA1E9E1DA400E57AC0 /* MRCommand.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MRCommand.m; sourceTree = ""; }; + 34F407DB1E9E1DA400E57AC0 /* MRConstants.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MRConstants.h; sourceTree = ""; }; + 34F407DC1E9E1DA400E57AC0 /* MRConstants.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MRConstants.m; sourceTree = ""; }; + 34F407DD1E9E1DA400E57AC0 /* MRController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MRController.h; sourceTree = ""; }; + 34F407DE1E9E1DA400E57AC0 /* MRController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MRController.m; sourceTree = ""; }; + 34F407DF1E9E1DA400E57AC0 /* MRError.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MRError.h; sourceTree = ""; }; + 34F407E01E9E1DA400E57AC0 /* MRError.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MRError.m; sourceTree = ""; }; + 34F407E11E9E1DA400E57AC0 /* MRExpandModalViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MRExpandModalViewController.h; sourceTree = ""; }; + 34F407E21E9E1DA400E57AC0 /* MRExpandModalViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MRExpandModalViewController.m; sourceTree = ""; }; + 34F407E31E9E1DA400E57AC0 /* MRNativeCommandHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MRNativeCommandHandler.h; sourceTree = ""; }; + 34F407E41E9E1DA400E57AC0 /* MRNativeCommandHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MRNativeCommandHandler.m; sourceTree = ""; }; + 34F407E51E9E1DA400E57AC0 /* MRProperty.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MRProperty.h; sourceTree = ""; }; + 34F407E61E9E1DA400E57AC0 /* MRProperty.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MRProperty.m; sourceTree = ""; }; + 34F407E71E9E1DA400E57AC0 /* MRVideoPlayerManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MRVideoPlayerManager.h; sourceTree = ""; }; + 34F407E81E9E1DA400E57AC0 /* MRVideoPlayerManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MRVideoPlayerManager.m; sourceTree = ""; }; + 34F407EB1E9E1DA400E57AC0 /* NSBundle+MPAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSBundle+MPAdditions.h"; sourceTree = ""; }; + 34F407EC1E9E1DA400E57AC0 /* NSBundle+MPAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSBundle+MPAdditions.m"; sourceTree = ""; }; + 34F407ED1E9E1DA400E57AC0 /* NSHTTPURLResponse+MPAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSHTTPURLResponse+MPAdditions.h"; sourceTree = ""; }; + 34F407EE1E9E1DA400E57AC0 /* NSHTTPURLResponse+MPAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSHTTPURLResponse+MPAdditions.m"; sourceTree = ""; }; + 34F407EF1E9E1DA400E57AC0 /* NSJSONSerialization+MPAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSJSONSerialization+MPAdditions.h"; sourceTree = ""; }; + 34F407F01E9E1DA400E57AC0 /* NSJSONSerialization+MPAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSJSONSerialization+MPAdditions.m"; sourceTree = ""; }; + 34F407F11E9E1DA400E57AC0 /* NSURL+MPAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSURL+MPAdditions.h"; sourceTree = ""; }; + 34F407F21E9E1DA400E57AC0 /* NSURL+MPAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSURL+MPAdditions.m"; sourceTree = ""; }; + 34F407F31E9E1DA400E57AC0 /* UIButton+MPAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIButton+MPAdditions.h"; sourceTree = ""; }; + 34F407F41E9E1DA400E57AC0 /* UIButton+MPAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIButton+MPAdditions.m"; sourceTree = ""; }; + 34F407F51E9E1DA400E57AC0 /* UIColor+MPAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIColor+MPAdditions.h"; sourceTree = ""; }; + 34F407F61E9E1DA400E57AC0 /* UIColor+MPAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIColor+MPAdditions.m"; sourceTree = ""; }; + 34F407F71E9E1DA400E57AC0 /* UIView+MPAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIView+MPAdditions.h"; sourceTree = ""; }; + 34F407F81E9E1DA400E57AC0 /* UIView+MPAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIView+MPAdditions.m"; sourceTree = ""; }; + 34F407F91E9E1DA400E57AC0 /* UIWebView+MPAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIWebView+MPAdditions.h"; sourceTree = ""; }; + 34F407FA1E9E1DA400E57AC0 /* UIWebView+MPAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIWebView+MPAdditions.m"; sourceTree = ""; }; + 34F407FB1E9E1DA400E57AC0 /* MPAnalyticsTracker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAnalyticsTracker.h; sourceTree = ""; }; + 34F407FC1E9E1DA400E57AC0 /* MPAnalyticsTracker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAnalyticsTracker.m; sourceTree = ""; }; + 34F407FD1E9E1DA400E57AC0 /* MPError.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPError.h; sourceTree = ""; }; + 34F407FE1E9E1DA400E57AC0 /* MPError.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPError.m; sourceTree = ""; }; + 34F407FF1E9E1DA400E57AC0 /* MPGeolocationProvider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPGeolocationProvider.h; sourceTree = ""; }; + 34F408001E9E1DA400E57AC0 /* MPGeolocationProvider.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPGeolocationProvider.m; sourceTree = ""; }; + 34F408011E9E1DA400E57AC0 /* MPGlobal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPGlobal.h; sourceTree = ""; }; + 34F408021E9E1DA400E57AC0 /* MPGlobal.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPGlobal.m; sourceTree = ""; }; + 34F408031E9E1DA400E57AC0 /* MPIdentityProvider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPIdentityProvider.h; sourceTree = ""; }; + 34F408041E9E1DA400E57AC0 /* MPIdentityProvider.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPIdentityProvider.m; sourceTree = ""; }; + 34F408051E9E1DA400E57AC0 /* MPInternalUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPInternalUtils.h; sourceTree = ""; }; + 34F408061E9E1DA400E57AC0 /* MPInternalUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPInternalUtils.m; sourceTree = ""; }; + 34F408071E9E1DA400E57AC0 /* MPLogging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPLogging.h; sourceTree = ""; }; + 34F408081E9E1DA400E57AC0 /* MPLogging.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPLogging.m; sourceTree = ""; }; + 34F408091E9E1DA400E57AC0 /* MPLogProvider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPLogProvider.h; sourceTree = ""; }; + 34F4080A1E9E1DA400E57AC0 /* MPLogProvider.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPLogProvider.m; sourceTree = ""; }; + 34F4080B1E9E1DA400E57AC0 /* MPReachability.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPReachability.h; sourceTree = ""; }; + 34F4080C1E9E1DA400E57AC0 /* MPReachability.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPReachability.m; sourceTree = ""; }; + 34F4080D1E9E1DA400E57AC0 /* MPSessionTracker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPSessionTracker.h; sourceTree = ""; }; + 34F4080E1E9E1DA400E57AC0 /* MPSessionTracker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPSessionTracker.m; sourceTree = ""; }; + 34F4080F1E9E1DA400E57AC0 /* MPStoreKitProvider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPStoreKitProvider.h; sourceTree = ""; }; + 34F408101E9E1DA400E57AC0 /* MPStoreKitProvider.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPStoreKitProvider.m; sourceTree = ""; }; + 34F408111E9E1DA400E57AC0 /* MPTimer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPTimer.h; sourceTree = ""; }; + 34F408121E9E1DA400E57AC0 /* MPTimer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPTimer.m; sourceTree = ""; }; + 34F408131E9E1DA400E57AC0 /* MPUserInteractionGestureRecognizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPUserInteractionGestureRecognizer.h; sourceTree = ""; }; + 34F408141E9E1DA400E57AC0 /* MPUserInteractionGestureRecognizer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPUserInteractionGestureRecognizer.m; sourceTree = ""; }; + 34F408161E9E1DA400E57AC0 /* MPVASTAd.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPVASTAd.h; sourceTree = ""; }; + 34F408171E9E1DA400E57AC0 /* MPVASTAd.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPVASTAd.m; sourceTree = ""; }; + 34F408181E9E1DA400E57AC0 /* MPVASTCompanionAd.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPVASTCompanionAd.h; sourceTree = ""; }; + 34F408191E9E1DA400E57AC0 /* MPVASTCompanionAd.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPVASTCompanionAd.m; sourceTree = ""; }; + 34F4081A1E9E1DA400E57AC0 /* MPVASTCreative.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPVASTCreative.h; sourceTree = ""; }; + 34F4081B1E9E1DA400E57AC0 /* MPVASTCreative.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPVASTCreative.m; sourceTree = ""; }; + 34F4081C1E9E1DA400E57AC0 /* MPVASTDurationOffset.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPVASTDurationOffset.h; sourceTree = ""; }; + 34F4081D1E9E1DA400E57AC0 /* MPVASTDurationOffset.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPVASTDurationOffset.m; sourceTree = ""; }; + 34F4081E1E9E1DA400E57AC0 /* MPVASTIndustryIcon.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPVASTIndustryIcon.h; sourceTree = ""; }; + 34F4081F1E9E1DA400E57AC0 /* MPVASTIndustryIcon.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPVASTIndustryIcon.m; sourceTree = ""; }; + 34F408201E9E1DA400E57AC0 /* MPVASTInline.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPVASTInline.h; sourceTree = ""; }; + 34F408211E9E1DA400E57AC0 /* MPVASTInline.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPVASTInline.m; sourceTree = ""; }; + 34F408221E9E1DA400E57AC0 /* MPVASTLinearAd.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPVASTLinearAd.h; sourceTree = ""; }; + 34F408231E9E1DA400E57AC0 /* MPVASTLinearAd.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPVASTLinearAd.m; sourceTree = ""; }; + 34F408241E9E1DA400E57AC0 /* MPVASTMacroProcessor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPVASTMacroProcessor.h; sourceTree = ""; }; + 34F408251E9E1DA400E57AC0 /* MPVASTMacroProcessor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPVASTMacroProcessor.m; sourceTree = ""; }; + 34F408261E9E1DA400E57AC0 /* MPVASTManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPVASTManager.h; sourceTree = ""; }; + 34F408271E9E1DA400E57AC0 /* MPVASTManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPVASTManager.m; sourceTree = ""; }; + 34F408281E9E1DA400E57AC0 /* MPVASTMediaFile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPVASTMediaFile.h; sourceTree = ""; }; + 34F408291E9E1DA400E57AC0 /* MPVASTMediaFile.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPVASTMediaFile.m; sourceTree = ""; }; + 34F4082A1E9E1DA400E57AC0 /* MPVASTModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPVASTModel.h; sourceTree = ""; }; + 34F4082B1E9E1DA400E57AC0 /* MPVASTModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPVASTModel.m; sourceTree = ""; }; + 34F4082C1E9E1DA400E57AC0 /* MPVASTResource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPVASTResource.h; sourceTree = ""; }; + 34F4082D1E9E1DA400E57AC0 /* MPVASTResource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPVASTResource.m; sourceTree = ""; }; + 34F4082E1E9E1DA400E57AC0 /* MPVASTResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPVASTResponse.h; sourceTree = ""; }; + 34F4082F1E9E1DA400E57AC0 /* MPVASTResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPVASTResponse.m; sourceTree = ""; }; + 34F408301E9E1DA400E57AC0 /* MPVASTStringUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPVASTStringUtilities.h; sourceTree = ""; }; + 34F408311E9E1DA400E57AC0 /* MPVASTStringUtilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPVASTStringUtilities.m; sourceTree = ""; }; + 34F408321E9E1DA400E57AC0 /* MPVASTTrackingEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPVASTTrackingEvent.h; sourceTree = ""; }; + 34F408331E9E1DA400E57AC0 /* MPVASTTrackingEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPVASTTrackingEvent.m; sourceTree = ""; }; + 34F408341E9E1DA400E57AC0 /* MPVASTWrapper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPVASTWrapper.h; sourceTree = ""; }; + 34F408351E9E1DA400E57AC0 /* MPVASTWrapper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPVASTWrapper.m; sourceTree = ""; }; + 34F408361E9E1DA400E57AC0 /* MoPub-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MoPub-Bridging-Header.h"; sourceTree = ""; }; + 34F408371E9E1DA400E57AC0 /* MoPub.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MoPub.h; sourceTree = ""; }; + 34F408381E9E1DA400E57AC0 /* MoPub.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MoPub.m; sourceTree = ""; }; + 34F408391E9E1DA400E57AC0 /* MPAdConversionTracker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAdConversionTracker.h; sourceTree = ""; }; + 34F4083A1E9E1DA400E57AC0 /* MPAdConversionTracker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAdConversionTracker.m; sourceTree = ""; }; + 34F4083B1E9E1DA400E57AC0 /* MPAdView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAdView.h; sourceTree = ""; }; + 34F4083C1E9E1DA400E57AC0 /* MPAdView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAdView.m; sourceTree = ""; }; + 34F4083D1E9E1DA400E57AC0 /* MPBannerCustomEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPBannerCustomEvent.h; sourceTree = ""; }; + 34F4083E1E9E1DA400E57AC0 /* MPBannerCustomEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPBannerCustomEvent.m; sourceTree = ""; }; + 34F4083F1E9E1DA400E57AC0 /* MPBannerCustomEventDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPBannerCustomEventDelegate.h; sourceTree = ""; }; + 34F408401E9E1DA400E57AC0 /* MPConstants.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPConstants.h; sourceTree = ""; }; + 34F408411E9E1DA400E57AC0 /* MPConstants.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPConstants.m; sourceTree = ""; }; + 34F408421E9E1DA400E57AC0 /* MPInterstitialAdController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPInterstitialAdController.h; sourceTree = ""; }; + 34F408431E9E1DA400E57AC0 /* MPInterstitialAdController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPInterstitialAdController.m; sourceTree = ""; }; + 34F408441E9E1DA400E57AC0 /* MPInterstitialCustomEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPInterstitialCustomEvent.h; sourceTree = ""; }; + 34F408451E9E1DA400E57AC0 /* MPInterstitialCustomEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPInterstitialCustomEvent.m; sourceTree = ""; }; + 34F408461E9E1DA400E57AC0 /* MPInterstitialCustomEventDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPInterstitialCustomEventDelegate.h; sourceTree = ""; }; + 34F4084A1E9E1DA400E57AC0 /* MPNativeAdRequest+MPNativeAdSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MPNativeAdRequest+MPNativeAdSource.h"; sourceTree = ""; }; + 34F4084B1E9E1DA400E57AC0 /* MPAdPlacerInvocation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAdPlacerInvocation.h; sourceTree = ""; }; + 34F4084C1E9E1DA400E57AC0 /* MPAdPlacerInvocation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAdPlacerInvocation.m; sourceTree = ""; }; + 34F4084D1E9E1DA400E57AC0 /* MPCollectionViewAdPlacerCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPCollectionViewAdPlacerCell.h; sourceTree = ""; }; + 34F4084E1E9E1DA400E57AC0 /* MPCollectionViewAdPlacerCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPCollectionViewAdPlacerCell.m; sourceTree = ""; }; + 34F4084F1E9E1DA400E57AC0 /* MPDiskLRUCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPDiskLRUCache.h; sourceTree = ""; }; + 34F408501E9E1DA400E57AC0 /* MPDiskLRUCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPDiskLRUCache.m; sourceTree = ""; }; + 34F408511E9E1DA400E57AC0 /* MPImageDownloadQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPImageDownloadQueue.h; sourceTree = ""; }; + 34F408521E9E1DA400E57AC0 /* MPImageDownloadQueue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPImageDownloadQueue.m; sourceTree = ""; }; + 34F408531E9E1DA400E57AC0 /* MPMoPubNativeAdAdapter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPMoPubNativeAdAdapter.h; sourceTree = ""; }; + 34F408541E9E1DA400E57AC0 /* MPMoPubNativeAdAdapter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPMoPubNativeAdAdapter.m; sourceTree = ""; }; + 34F408551E9E1DA400E57AC0 /* MPMoPubNativeCustomEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPMoPubNativeCustomEvent.h; sourceTree = ""; }; + 34F408561E9E1DA400E57AC0 /* MPMoPubNativeCustomEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPMoPubNativeCustomEvent.m; sourceTree = ""; }; + 34F408571E9E1DA400E57AC0 /* MPNativeAd+Internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MPNativeAd+Internal.h"; sourceTree = ""; }; + 34F408581E9E1DA400E57AC0 /* MPNativeAd+Internal.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MPNativeAd+Internal.m"; sourceTree = ""; }; + 34F408591E9E1DA400E57AC0 /* MPNativeAdRendererConstants.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPNativeAdRendererConstants.h; sourceTree = ""; }; + 34F4085A1E9E1DA400E57AC0 /* MPNativeAdRendererImageHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPNativeAdRendererImageHandler.h; sourceTree = ""; }; + 34F4085B1E9E1DA400E57AC0 /* MPNativeAdRendererImageHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPNativeAdRendererImageHandler.m; sourceTree = ""; }; + 34F4085C1E9E1DA400E57AC0 /* MPNativeAdSourceQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPNativeAdSourceQueue.h; sourceTree = ""; }; + 34F4085D1E9E1DA400E57AC0 /* MPNativeAdSourceQueue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPNativeAdSourceQueue.m; sourceTree = ""; }; + 34F4085E1E9E1DA400E57AC0 /* MPNativeAdUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPNativeAdUtils.h; sourceTree = ""; }; + 34F4085F1E9E1DA400E57AC0 /* MPNativeAdUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPNativeAdUtils.m; sourceTree = ""; }; + 34F408601E9E1DA400E57AC0 /* MPNativeCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPNativeCache.h; sourceTree = ""; }; + 34F408611E9E1DA400E57AC0 /* MPNativeCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPNativeCache.m; sourceTree = ""; }; + 34F408621E9E1DA400E57AC0 /* MPNativePositionResponseDeserializer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPNativePositionResponseDeserializer.h; sourceTree = ""; }; + 34F408631E9E1DA400E57AC0 /* MPNativePositionResponseDeserializer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPNativePositionResponseDeserializer.m; sourceTree = ""; }; + 34F408641E9E1DA400E57AC0 /* MPNativePositionSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPNativePositionSource.h; sourceTree = ""; }; + 34F408651E9E1DA400E57AC0 /* MPNativePositionSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPNativePositionSource.m; sourceTree = ""; }; + 34F408661E9E1DA400E57AC0 /* MPNativeView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPNativeView.h; sourceTree = ""; }; + 34F408671E9E1DA400E57AC0 /* MPNativeView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPNativeView.m; sourceTree = ""; }; + 34F408681E9E1DA400E57AC0 /* MPStaticNativeAdImpressionTimer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPStaticNativeAdImpressionTimer.h; sourceTree = ""; }; + 34F408691E9E1DA400E57AC0 /* MPStaticNativeAdImpressionTimer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPStaticNativeAdImpressionTimer.m; sourceTree = ""; }; + 34F4086A1E9E1DA400E57AC0 /* MPTableViewAdPlacerCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPTableViewAdPlacerCell.h; sourceTree = ""; }; + 34F4086B1E9E1DA400E57AC0 /* MPTableViewAdPlacerCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPTableViewAdPlacerCell.m; sourceTree = ""; }; + 34F4086C1E9E1DA400E57AC0 /* MPTableViewCellImpressionTracker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPTableViewCellImpressionTracker.h; sourceTree = ""; }; + 34F4086D1E9E1DA400E57AC0 /* MPTableViewCellImpressionTracker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPTableViewCellImpressionTracker.m; sourceTree = ""; }; + 34F4086E1E9E1DA400E57AC0 /* MPAdPositioning.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAdPositioning.h; sourceTree = ""; }; + 34F4086F1E9E1DA400E57AC0 /* MPAdPositioning.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAdPositioning.m; sourceTree = ""; }; + 34F408701E9E1DA400E57AC0 /* MPClientAdPositioning.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPClientAdPositioning.h; sourceTree = ""; }; + 34F408711E9E1DA400E57AC0 /* MPClientAdPositioning.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPClientAdPositioning.m; sourceTree = ""; }; + 34F408721E9E1DA400E57AC0 /* MPCollectionViewAdPlacer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPCollectionViewAdPlacer.h; sourceTree = ""; }; + 34F408731E9E1DA400E57AC0 /* MPCollectionViewAdPlacer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPCollectionViewAdPlacer.m; sourceTree = ""; }; + 34F408741E9E1DA400E57AC0 /* MPNativeAd.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPNativeAd.h; sourceTree = ""; }; + 34F408751E9E1DA400E57AC0 /* MPNativeAd.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPNativeAd.m; sourceTree = ""; }; + 34F408761E9E1DA400E57AC0 /* MPNativeAdAdapter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPNativeAdAdapter.h; sourceTree = ""; }; + 34F408771E9E1DA400E57AC0 /* MPNativeAdConstants.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPNativeAdConstants.h; sourceTree = ""; }; + 34F408781E9E1DA400E57AC0 /* MPNativeAdConstants.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPNativeAdConstants.m; sourceTree = ""; }; + 34F408791E9E1DA400E57AC0 /* MPNativeAdData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPNativeAdData.h; sourceTree = ""; }; + 34F4087A1E9E1DA400E57AC0 /* MPNativeAdData.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPNativeAdData.m; sourceTree = ""; }; + 34F4087B1E9E1DA400E57AC0 /* MPNativeAdDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPNativeAdDelegate.h; sourceTree = ""; }; + 34F4087C1E9E1DA400E57AC0 /* MPNativeAdError.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPNativeAdError.h; sourceTree = ""; }; + 34F4087D1E9E1DA400E57AC0 /* MPNativeAdError.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPNativeAdError.m; sourceTree = ""; }; + 34F4087E1E9E1DA400E57AC0 /* MPNativeAdRenderer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPNativeAdRenderer.h; sourceTree = ""; }; + 34F4087F1E9E1DA400E57AC0 /* MPNativeAdRendererConfiguration.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPNativeAdRendererConfiguration.h; sourceTree = ""; }; + 34F408801E9E1DA400E57AC0 /* MPNativeAdRendererConfiguration.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPNativeAdRendererConfiguration.m; sourceTree = ""; }; + 34F408811E9E1DA400E57AC0 /* MPNativeAdRendererSettings.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPNativeAdRendererSettings.h; sourceTree = ""; }; + 34F408821E9E1DA400E57AC0 /* MPNativeAdRendering.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPNativeAdRendering.h; sourceTree = ""; }; + 34F408831E9E1DA400E57AC0 /* MPNativeAdRenderingImageLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPNativeAdRenderingImageLoader.h; sourceTree = ""; }; + 34F408841E9E1DA400E57AC0 /* MPNativeAdRenderingImageLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPNativeAdRenderingImageLoader.m; sourceTree = ""; }; + 34F408851E9E1DA400E57AC0 /* MPNativeAdRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPNativeAdRequest.h; sourceTree = ""; }; + 34F408861E9E1DA400E57AC0 /* MPNativeAdRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPNativeAdRequest.m; sourceTree = ""; }; + 34F408871E9E1DA400E57AC0 /* MPNativeAdRequestTargeting.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPNativeAdRequestTargeting.h; sourceTree = ""; }; + 34F408881E9E1DA400E57AC0 /* MPNativeAdRequestTargeting.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPNativeAdRequestTargeting.m; sourceTree = ""; }; + 34F408891E9E1DA400E57AC0 /* MPNativeAdSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPNativeAdSource.h; sourceTree = ""; }; + 34F4088A1E9E1DA400E57AC0 /* MPNativeAdSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPNativeAdSource.m; sourceTree = ""; }; + 34F4088B1E9E1DA400E57AC0 /* MPNativeAdSourceDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPNativeAdSourceDelegate.h; sourceTree = ""; }; + 34F4088C1E9E1DA400E57AC0 /* MPNativeCustomEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPNativeCustomEvent.h; sourceTree = ""; }; + 34F4088D1E9E1DA400E57AC0 /* MPNativeCustomEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPNativeCustomEvent.m; sourceTree = ""; }; + 34F4088E1E9E1DA400E57AC0 /* MPNativeCustomEventDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPNativeCustomEventDelegate.h; sourceTree = ""; }; + 34F4088F1E9E1DA400E57AC0 /* MPServerAdPositioning.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPServerAdPositioning.h; sourceTree = ""; }; + 34F408901E9E1DA400E57AC0 /* MPServerAdPositioning.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPServerAdPositioning.m; sourceTree = ""; }; + 34F408911E9E1DA400E57AC0 /* MPStaticNativeAdRenderer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPStaticNativeAdRenderer.h; sourceTree = ""; }; + 34F408921E9E1DA400E57AC0 /* MPStaticNativeAdRenderer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPStaticNativeAdRenderer.m; sourceTree = ""; }; + 34F408931E9E1DA400E57AC0 /* MPStaticNativeAdRendererSettings.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPStaticNativeAdRendererSettings.h; sourceTree = ""; }; + 34F408941E9E1DA400E57AC0 /* MPStaticNativeAdRendererSettings.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPStaticNativeAdRendererSettings.m; sourceTree = ""; }; + 34F408951E9E1DA400E57AC0 /* MPStreamAdPlacementData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPStreamAdPlacementData.h; sourceTree = ""; }; + 34F408961E9E1DA400E57AC0 /* MPStreamAdPlacementData.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPStreamAdPlacementData.m; sourceTree = ""; }; + 34F408971E9E1DA400E57AC0 /* MPStreamAdPlacer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPStreamAdPlacer.h; sourceTree = ""; }; + 34F408981E9E1DA400E57AC0 /* MPStreamAdPlacer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPStreamAdPlacer.m; sourceTree = ""; }; + 34F408991E9E1DA400E57AC0 /* MPTableViewAdPlacer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPTableViewAdPlacer.h; sourceTree = ""; }; + 34F4089A1E9E1DA400E57AC0 /* MPTableViewAdPlacer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPTableViewAdPlacer.m; sourceTree = ""; }; + 34F4089D1E9E1DA400E57AC0 /* MOPUBActivityIndicatorView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MOPUBActivityIndicatorView.h; sourceTree = ""; }; + 34F4089E1E9E1DA400E57AC0 /* MOPUBActivityIndicatorView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MOPUBActivityIndicatorView.m; sourceTree = ""; }; + 34F4089F1E9E1DA400E57AC0 /* MOPUBAVPlayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MOPUBAVPlayer.h; sourceTree = ""; }; + 34F408A01E9E1DA400E57AC0 /* MOPUBAVPlayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MOPUBAVPlayer.m; sourceTree = ""; }; + 34F408A11E9E1DA400E57AC0 /* MOPUBAVPlayerView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MOPUBAVPlayerView.h; sourceTree = ""; }; + 34F408A21E9E1DA400E57AC0 /* MOPUBAVPlayerView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MOPUBAVPlayerView.m; sourceTree = ""; }; + 34F408A31E9E1DA400E57AC0 /* MOPUBFullscreenPlayerViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MOPUBFullscreenPlayerViewController.h; sourceTree = ""; }; + 34F408A41E9E1DA400E57AC0 /* MOPUBFullscreenPlayerViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MOPUBFullscreenPlayerViewController.m; sourceTree = ""; }; + 34F408A51E9E1DA400E57AC0 /* MOPUBNativeVideoAdAdapter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MOPUBNativeVideoAdAdapter.h; sourceTree = ""; }; + 34F408A61E9E1DA400E57AC0 /* MOPUBNativeVideoAdAdapter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MOPUBNativeVideoAdAdapter.m; sourceTree = ""; }; + 34F408A71E9E1DA400E57AC0 /* MOPUBNativeVideoAdConfigValues.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MOPUBNativeVideoAdConfigValues.h; sourceTree = ""; }; + 34F408A81E9E1DA400E57AC0 /* MOPUBNativeVideoAdConfigValues.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MOPUBNativeVideoAdConfigValues.m; sourceTree = ""; }; + 34F408A91E9E1DA400E57AC0 /* MOPUBNativeVideoCustomEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MOPUBNativeVideoCustomEvent.h; sourceTree = ""; }; + 34F408AA1E9E1DA400E57AC0 /* MOPUBNativeVideoCustomEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MOPUBNativeVideoCustomEvent.m; sourceTree = ""; }; + 34F408AB1E9E1DA400E57AC0 /* MOPUBNativeVideoImpressionAgent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MOPUBNativeVideoImpressionAgent.h; sourceTree = ""; }; + 34F408AC1E9E1DA400E57AC0 /* MOPUBNativeVideoImpressionAgent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MOPUBNativeVideoImpressionAgent.m; sourceTree = ""; }; + 34F408AD1E9E1DA400E57AC0 /* MOPUBPlayerManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MOPUBPlayerManager.h; sourceTree = ""; }; + 34F408AE1E9E1DA400E57AC0 /* MOPUBPlayerManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MOPUBPlayerManager.m; sourceTree = ""; }; + 34F408AF1E9E1DA400E57AC0 /* MOPUBPlayerView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MOPUBPlayerView.h; sourceTree = ""; }; + 34F408B01E9E1DA400E57AC0 /* MOPUBPlayerView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MOPUBPlayerView.m; sourceTree = ""; }; + 34F408B11E9E1DA400E57AC0 /* MOPUBPlayerViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MOPUBPlayerViewController.h; sourceTree = ""; }; + 34F408B21E9E1DA400E57AC0 /* MOPUBPlayerViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MOPUBPlayerViewController.m; sourceTree = ""; }; + 34F408B31E9E1DA400E57AC0 /* MOPUBReplayView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MOPUBReplayView.h; sourceTree = ""; }; + 34F408B41E9E1DA400E57AC0 /* MOPUBReplayView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MOPUBReplayView.m; sourceTree = ""; }; + 34F408B51E9E1DA400E57AC0 /* MOPUBNativeVideoAdRenderer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MOPUBNativeVideoAdRenderer.h; sourceTree = ""; }; + 34F408B61E9E1DA400E57AC0 /* MOPUBNativeVideoAdRenderer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MOPUBNativeVideoAdRenderer.m; sourceTree = ""; }; + 34F408B71E9E1DA400E57AC0 /* MOPUBNativeVideoAdRendererSettings.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MOPUBNativeVideoAdRendererSettings.h; sourceTree = ""; }; + 34F408B81E9E1DA400E57AC0 /* MOPUBNativeVideoAdRendererSettings.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MOPUBNativeVideoAdRendererSettings.m; sourceTree = ""; }; + 34F408BA1E9E1DA400E57AC0 /* MPCloseBtn.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = MPCloseBtn.png; sourceTree = ""; }; + 34F408BB1E9E1DA400E57AC0 /* MPCloseBtn@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "MPCloseBtn@2x.png"; sourceTree = ""; }; + 34F408BC1E9E1DA400E57AC0 /* MPCloseBtn@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "MPCloseBtn@3x.png"; sourceTree = ""; }; + 34F408BD1E9E1DA400E57AC0 /* MPCloseButtonX.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = MPCloseButtonX.png; sourceTree = ""; }; + 34F408BE1E9E1DA400E57AC0 /* MPCloseButtonX@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "MPCloseButtonX@2x.png"; sourceTree = ""; }; + 34F408BF1E9E1DA400E57AC0 /* MPCloseButtonX@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "MPCloseButtonX@3x.png"; sourceTree = ""; }; + 34F408C01E9E1DA400E57AC0 /* MPCountdownTimer.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = MPCountdownTimer.html; sourceTree = ""; }; + 34F408C11E9E1DA400E57AC0 /* MPDAAIcon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = MPDAAIcon.png; sourceTree = ""; }; + 34F408C21E9E1DA400E57AC0 /* MPDAAIcon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "MPDAAIcon@2x.png"; sourceTree = ""; }; + 34F408C31E9E1DA400E57AC0 /* MPDAAIcon@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "MPDAAIcon@3x.png"; sourceTree = ""; }; + 34F408C41E9E1DA400E57AC0 /* MPMutedBtn.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = MPMutedBtn.png; sourceTree = ""; }; + 34F408C51E9E1DA400E57AC0 /* MPMutedBtn@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "MPMutedBtn@2x.png"; sourceTree = ""; }; + 34F408C61E9E1DA400E57AC0 /* MPMutedBtn@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "MPMutedBtn@3x.png"; sourceTree = ""; }; + 34F408C71E9E1DA400E57AC0 /* MPPlayBtn.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = MPPlayBtn.png; sourceTree = ""; }; + 34F408C81E9E1DA400E57AC0 /* MPPlayBtn@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "MPPlayBtn@2x.png"; sourceTree = ""; }; + 34F408C91E9E1DA400E57AC0 /* MPPlayBtn@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "MPPlayBtn@3x.png"; sourceTree = ""; }; + 34F408CA1E9E1DA400E57AC0 /* MPUnmutedBtn.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = MPUnmutedBtn.png; sourceTree = ""; }; + 34F408CB1E9E1DA400E57AC0 /* MPUnmutedBtn@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "MPUnmutedBtn@2x.png"; sourceTree = ""; }; + 34F408CC1E9E1DA400E57AC0 /* MPUnmutedBtn@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "MPUnmutedBtn@3x.png"; sourceTree = ""; }; + 34F408CD1E9E1DA400E57AC0 /* MRAID.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = MRAID.bundle; sourceTree = ""; }; + 34F408D01E9E1DA400E57AC0 /* MPMoPubRewardedPlayableCustomEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPMoPubRewardedPlayableCustomEvent.h; sourceTree = ""; }; + 34F408D11E9E1DA400E57AC0 /* MPMoPubRewardedPlayableCustomEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPMoPubRewardedPlayableCustomEvent.m; sourceTree = ""; }; + 34F408D21E9E1DA400E57AC0 /* MPMoPubRewardedVideoCustomEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPMoPubRewardedVideoCustomEvent.h; sourceTree = ""; }; + 34F408D31E9E1DA400E57AC0 /* MPMoPubRewardedVideoCustomEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPMoPubRewardedVideoCustomEvent.m; sourceTree = ""; }; + 34F408D41E9E1DA400E57AC0 /* MPPrivateRewardedVideoCustomEventDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPPrivateRewardedVideoCustomEventDelegate.h; sourceTree = ""; }; + 34F408D51E9E1DA400E57AC0 /* MPRewardedVideo+Internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MPRewardedVideo+Internal.h"; sourceTree = ""; }; + 34F408D61E9E1DA400E57AC0 /* MPRewardedVideoAdapter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPRewardedVideoAdapter.h; sourceTree = ""; }; + 34F408D71E9E1DA400E57AC0 /* MPRewardedVideoAdapter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPRewardedVideoAdapter.m; sourceTree = ""; }; + 34F408D81E9E1DA400E57AC0 /* MPRewardedVideoAdManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPRewardedVideoAdManager.h; sourceTree = ""; }; + 34F408D91E9E1DA400E57AC0 /* MPRewardedVideoAdManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPRewardedVideoAdManager.m; sourceTree = ""; }; + 34F408DA1E9E1DA400E57AC0 /* MPRewardedVideoConnection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPRewardedVideoConnection.h; sourceTree = ""; }; + 34F408DB1E9E1DA400E57AC0 /* MPRewardedVideoConnection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPRewardedVideoConnection.m; sourceTree = ""; }; + 34F408DC1E9E1DA400E57AC0 /* MPMediationSettingsProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPMediationSettingsProtocol.h; sourceTree = ""; }; + 34F408DD1E9E1DA400E57AC0 /* MPRewardedVideo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPRewardedVideo.h; sourceTree = ""; }; + 34F408DE1E9E1DA400E57AC0 /* MPRewardedVideo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPRewardedVideo.m; sourceTree = ""; }; + 34F408DF1E9E1DA400E57AC0 /* MPRewardedVideoCustomEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPRewardedVideoCustomEvent.h; sourceTree = ""; }; + 34F408E01E9E1DA400E57AC0 /* MPRewardedVideoCustomEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPRewardedVideoCustomEvent.m; sourceTree = ""; }; + 34F408E11E9E1DA400E57AC0 /* MPRewardedVideoError.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPRewardedVideoError.h; sourceTree = ""; }; + 34F408E21E9E1DA400E57AC0 /* MPRewardedVideoError.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPRewardedVideoError.m; sourceTree = ""; }; + 34F408E31E9E1DA400E57AC0 /* MPRewardedVideoReward.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPRewardedVideoReward.h; sourceTree = ""; }; + 34F408E41E9E1DA400E57AC0 /* MPRewardedVideoReward.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPRewardedVideoReward.m; sourceTree = ""; }; + 34F4098E1E9E27DB00E57AC0 /* common-debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "common-debug.xcconfig"; path = "../../../../xcode/common-debug.xcconfig"; sourceTree = ""; }; + 34F4098F1E9E27DB00E57AC0 /* common-release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "common-release.xcconfig"; path = "../../../../xcode/common-release.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 34F407491E9E1D3500E57AC0 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 34F408E91E9E1DA400E57AC0 /* FBAudienceNetwork.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 34F407431E9E1D3500E57AC0 = { + isa = PBXGroup; + children = ( + 34F4098E1E9E27DB00E57AC0 /* common-debug.xcconfig */, + 34F4098F1E9E27DB00E57AC0 /* common-release.xcconfig */, + 34F4075E1E9E1DA400E57AC0 /* AdNetworkSupport */, + 34F4076B1E9E1DA400E57AC0 /* Internal */, + 34F408361E9E1DA400E57AC0 /* MoPub-Bridging-Header.h */, + 34F408371E9E1DA400E57AC0 /* MoPub.h */, + 34F408381E9E1DA400E57AC0 /* MoPub.m */, + 34F408391E9E1DA400E57AC0 /* MPAdConversionTracker.h */, + 34F4083A1E9E1DA400E57AC0 /* MPAdConversionTracker.m */, + 34F4083B1E9E1DA400E57AC0 /* MPAdView.h */, + 34F4083C1E9E1DA400E57AC0 /* MPAdView.m */, + 34F4083D1E9E1DA400E57AC0 /* MPBannerCustomEvent.h */, + 34F4083E1E9E1DA400E57AC0 /* MPBannerCustomEvent.m */, + 34F4083F1E9E1DA400E57AC0 /* MPBannerCustomEventDelegate.h */, + 34F408401E9E1DA400E57AC0 /* MPConstants.h */, + 34F408411E9E1DA400E57AC0 /* MPConstants.m */, + 34F408421E9E1DA400E57AC0 /* MPInterstitialAdController.h */, + 34F408431E9E1DA400E57AC0 /* MPInterstitialAdController.m */, + 34F408441E9E1DA400E57AC0 /* MPInterstitialCustomEvent.h */, + 34F408451E9E1DA400E57AC0 /* MPInterstitialCustomEvent.m */, + 34F408461E9E1DA400E57AC0 /* MPInterstitialCustomEventDelegate.h */, + 34F408471E9E1DA400E57AC0 /* Native Ads */, + 34F4089B1E9E1DA400E57AC0 /* NativeVideo */, + 34F408B91E9E1DA400E57AC0 /* Resources */, + 34F408CE1E9E1DA400E57AC0 /* RewardedVideo */, + 34F4074D1E9E1D3500E57AC0 /* Products */, + ); + sourceTree = ""; + }; + 34F4074D1E9E1D3500E57AC0 /* Products */ = { + isa = PBXGroup; + children = ( + 34F4074C1E9E1D3500E57AC0 /* libMopub.a */, + ); + name = Products; + sourceTree = ""; + }; + 34F4075E1E9E1DA400E57AC0 /* AdNetworkSupport */ = { + isa = PBXGroup; + children = ( + 34F4075F1E9E1DA400E57AC0 /* Facebook */, + ); + path = AdNetworkSupport; + sourceTree = ""; + }; + 34F4075F1E9E1DA400E57AC0 /* Facebook */ = { + isa = PBXGroup; + children = ( + 34F407601E9E1DA400E57AC0 /* FacebookBannerCustomEvent.h */, + 34F407611E9E1DA400E57AC0 /* FacebookBannerCustomEvent.m */, + 34F407621E9E1DA400E57AC0 /* FacebookInterstitialCustomEvent.h */, + 34F407631E9E1DA400E57AC0 /* FacebookInterstitialCustomEvent.m */, + 34F407641E9E1DA400E57AC0 /* FacebookNativeAdAdapter.h */, + 34F407651E9E1DA400E57AC0 /* FacebookNativeAdAdapter.m */, + 34F407661E9E1DA400E57AC0 /* FacebookNativeCustomEvent.h */, + 34F407671E9E1DA400E57AC0 /* FacebookNativeCustomEvent.m */, + 34F407681E9E1DA400E57AC0 /* SDK */, + ); + path = Facebook; + sourceTree = ""; + }; + 34F407681E9E1DA400E57AC0 /* SDK */ = { + isa = PBXGroup; + children = ( + 34F407691E9E1DA400E57AC0 /* FBAudienceNetwork.framework */, + ); + path = SDK; + sourceTree = ""; + }; + 34F4076B1E9E1DA400E57AC0 /* Internal */ = { + isa = PBXGroup; + children = ( + 34F4076C1E9E1DA400E57AC0 /* Banners */, + 34F407751E9E1DA400E57AC0 /* Common */, + 34F407A11E9E1DA400E57AC0 /* Event Logging */, + 34F407B01E9E1DA400E57AC0 /* HTML */, + 34F407BB1E9E1DA400E57AC0 /* Interstitials */, + 34F407C61E9E1DA400E57AC0 /* MPCoreInstanceProvider.h */, + 34F407C71E9E1DA400E57AC0 /* MPCoreInstanceProvider.m */, + 34F407C81E9E1DA400E57AC0 /* MPInstanceProvider.h */, + 34F407C91E9E1DA400E57AC0 /* MPInstanceProvider.m */, + 34F407CA1E9E1DA400E57AC0 /* MPVASTTracking.h */, + 34F407CB1E9E1DA400E57AC0 /* MPVASTTracking.m */, + 34F407CC1E9E1DA400E57AC0 /* MRAID */, + 34F407E91E9E1DA400E57AC0 /* Utility */, + 34F408151E9E1DA400E57AC0 /* VAST */, + ); + path = Internal; + sourceTree = ""; + }; + 34F4076C1E9E1DA400E57AC0 /* Banners */ = { + isa = PBXGroup; + children = ( + 34F4076D1E9E1DA400E57AC0 /* MPBannerAdManager.h */, + 34F4076E1E9E1DA400E57AC0 /* MPBannerAdManager.m */, + 34F4076F1E9E1DA400E57AC0 /* MPBannerAdManagerDelegate.h */, + 34F407701E9E1DA400E57AC0 /* MPBannerCustomEventAdapter.h */, + 34F407711E9E1DA400E57AC0 /* MPBannerCustomEventAdapter.m */, + 34F407721E9E1DA400E57AC0 /* MPBaseBannerAdapter.h */, + 34F407731E9E1DA400E57AC0 /* MPBaseBannerAdapter.m */, + 34F407741E9E1DA400E57AC0 /* MPPrivateBannerCustomEventDelegate.h */, + ); + path = Banners; + sourceTree = ""; + }; + 34F407751E9E1DA400E57AC0 /* Common */ = { + isa = PBXGroup; + children = ( + 34F407761E9E1DA400E57AC0 /* AdAlerts */, + 34F4077B1E9E1DA400E57AC0 /* MPActivityViewControllerHelper+TweetShare.h */, + 34F4077C1E9E1DA400E57AC0 /* MPActivityViewControllerHelper+TweetShare.m */, + 34F4077D1E9E1DA400E57AC0 /* MPActivityViewControllerHelper.h */, + 34F4077E1E9E1DA400E57AC0 /* MPActivityViewControllerHelper.m */, + 34F4077F1E9E1DA400E57AC0 /* MPAdBrowserController.h */, + 34F407801E9E1DA400E57AC0 /* MPAdBrowserController.m */, + 34F407811E9E1DA400E57AC0 /* MPAdBrowserController.xib */, + 34F407821E9E1DA400E57AC0 /* MPAdConfiguration.h */, + 34F407831E9E1DA400E57AC0 /* MPAdConfiguration.m */, + 34F407841E9E1DA400E57AC0 /* MPAdDestinationDisplayAgent.h */, + 34F407851E9E1DA400E57AC0 /* MPAdDestinationDisplayAgent.m */, + 34F407861E9E1DA400E57AC0 /* MPAdServerCommunicator.h */, + 34F407871E9E1DA400E57AC0 /* MPAdServerCommunicator.m */, + 34F407881E9E1DA400E57AC0 /* MPAdServerURLBuilder.h */, + 34F407891E9E1DA400E57AC0 /* MPAdServerURLBuilder.m */, + 34F4078A1E9E1DA400E57AC0 /* MPAPIEndpoints.h */, + 34F4078B1E9E1DA400E57AC0 /* MPAPIEndpoints.m */, + 34F4078C1E9E1DA400E57AC0 /* MPClosableView.h */, + 34F4078D1E9E1DA400E57AC0 /* MPClosableView.m */, + 34F4078E1E9E1DA400E57AC0 /* MPCountdownTimerView.h */, + 34F4078F1E9E1DA400E57AC0 /* MPCountdownTimerView.m */, + 34F407901E9E1DA400E57AC0 /* MPEnhancedDeeplinkRequest.h */, + 34F407911E9E1DA400E57AC0 /* MPEnhancedDeeplinkRequest.m */, + 34F407921E9E1DA400E57AC0 /* MPFacebookKeywordProvider.h */, + 34F407931E9E1DA400E57AC0 /* MPFacebookKeywordProvider.m */, + 34F407941E9E1DA400E57AC0 /* MPKeywordProvider.h */, + 34F407951E9E1DA400E57AC0 /* MPLastResortDelegate.h */, + 34F407961E9E1DA400E57AC0 /* MPLastResortDelegate.m */, + 34F407971E9E1DA400E57AC0 /* MPProgressOverlayView.h */, + 34F407981E9E1DA400E57AC0 /* MPProgressOverlayView.m */, + 34F407991E9E1DA400E57AC0 /* MPURLActionInfo.h */, + 34F4079A1E9E1DA400E57AC0 /* MPURLActionInfo.m */, + 34F4079B1E9E1DA400E57AC0 /* MPURLResolver.h */, + 34F4079C1E9E1DA400E57AC0 /* MPURLResolver.m */, + 34F4079D1E9E1DA400E57AC0 /* MPVideoConfig.h */, + 34F4079E1E9E1DA400E57AC0 /* MPVideoConfig.m */, + 34F4079F1E9E1DA400E57AC0 /* MPXMLParser.h */, + 34F407A01E9E1DA400E57AC0 /* MPXMLParser.m */, + ); + path = Common; + sourceTree = ""; + }; + 34F407761E9E1DA400E57AC0 /* AdAlerts */ = { + isa = PBXGroup; + children = ( + 34F407771E9E1DA400E57AC0 /* MPAdAlertGestureRecognizer.h */, + 34F407781E9E1DA400E57AC0 /* MPAdAlertGestureRecognizer.m */, + 34F407791E9E1DA400E57AC0 /* MPAdAlertManager.h */, + 34F4077A1E9E1DA400E57AC0 /* MPAdAlertManager.m */, + ); + path = AdAlerts; + sourceTree = ""; + }; + 34F407A11E9E1DA400E57AC0 /* Event Logging */ = { + isa = PBXGroup; + children = ( + 34F407A21E9E1DA400E57AC0 /* MPLogEvent+NativeVideo.h */, + 34F407A31E9E1DA400E57AC0 /* MPLogEvent+NativeVideo.m */, + 34F407A41E9E1DA400E57AC0 /* MPLogEvent.h */, + 34F407A51E9E1DA400E57AC0 /* MPLogEvent.m */, + 34F407A61E9E1DA400E57AC0 /* MPLogEventCommunicator.h */, + 34F407A71E9E1DA400E57AC0 /* MPLogEventCommunicator.m */, + 34F407A81E9E1DA400E57AC0 /* MPLogEventRecorder.h */, + 34F407A91E9E1DA400E57AC0 /* MPLogEventRecorder.m */, + 34F407AA1E9E1DA400E57AC0 /* MPNetworkManager.h */, + 34F407AB1E9E1DA400E57AC0 /* MPNetworkManager.m */, + 34F407AC1E9E1DA400E57AC0 /* MPQRunLoopOperation.h */, + 34F407AD1E9E1DA400E57AC0 /* MPQRunLoopOperation.m */, + 34F407AE1E9E1DA400E57AC0 /* MPRetryingHTTPOperation.h */, + 34F407AF1E9E1DA400E57AC0 /* MPRetryingHTTPOperation.m */, + ); + path = "Event Logging"; + sourceTree = ""; + }; + 34F407B01E9E1DA400E57AC0 /* HTML */ = { + isa = PBXGroup; + children = ( + 34F407B11E9E1DA400E57AC0 /* MPAdWebViewAgent.h */, + 34F407B21E9E1DA400E57AC0 /* MPAdWebViewAgent.m */, + 34F407B31E9E1DA400E57AC0 /* MPHTMLBannerCustomEvent.h */, + 34F407B41E9E1DA400E57AC0 /* MPHTMLBannerCustomEvent.m */, + 34F407B51E9E1DA400E57AC0 /* MPHTMLInterstitialCustomEvent.h */, + 34F407B61E9E1DA400E57AC0 /* MPHTMLInterstitialCustomEvent.m */, + 34F407B71E9E1DA400E57AC0 /* MPHTMLInterstitialViewController.h */, + 34F407B81E9E1DA400E57AC0 /* MPHTMLInterstitialViewController.m */, + 34F407B91E9E1DA400E57AC0 /* MPWebView.h */, + 34F407BA1E9E1DA400E57AC0 /* MPWebView.m */, + ); + path = HTML; + sourceTree = ""; + }; + 34F407BB1E9E1DA400E57AC0 /* Interstitials */ = { + isa = PBXGroup; + children = ( + 34F407BC1E9E1DA400E57AC0 /* MPBaseInterstitialAdapter.h */, + 34F407BD1E9E1DA400E57AC0 /* MPBaseInterstitialAdapter.m */, + 34F407BE1E9E1DA400E57AC0 /* MPInterstitialAdManager.h */, + 34F407BF1E9E1DA400E57AC0 /* MPInterstitialAdManager.m */, + 34F407C01E9E1DA400E57AC0 /* MPInterstitialAdManagerDelegate.h */, + 34F407C11E9E1DA400E57AC0 /* MPInterstitialCustomEventAdapter.h */, + 34F407C21E9E1DA400E57AC0 /* MPInterstitialCustomEventAdapter.m */, + 34F407C31E9E1DA400E57AC0 /* MPInterstitialViewController.h */, + 34F407C41E9E1DA400E57AC0 /* MPInterstitialViewController.m */, + 34F407C51E9E1DA400E57AC0 /* MPPrivateInterstitialCustomEventDelegate.h */, + ); + path = Interstitials; + sourceTree = ""; + }; + 34F407CC1E9E1DA400E57AC0 /* MRAID */ = { + isa = PBXGroup; + children = ( + 34F407CD1E9E1DA400E57AC0 /* LICENSE */, + 34F407CE1E9E1DA400E57AC0 /* MPForceableOrientationProtocol.h */, + 34F407CF1E9E1DA400E57AC0 /* MPMRAIDBannerCustomEvent.h */, + 34F407D01E9E1DA400E57AC0 /* MPMRAIDBannerCustomEvent.m */, + 34F407D11E9E1DA400E57AC0 /* MPMRAIDInterstitialCustomEvent.h */, + 34F407D21E9E1DA400E57AC0 /* MPMRAIDInterstitialCustomEvent.m */, + 34F407D31E9E1DA400E57AC0 /* MPMRAIDInterstitialViewController.h */, + 34F407D41E9E1DA400E57AC0 /* MPMRAIDInterstitialViewController.m */, + 34F407D51E9E1DA400E57AC0 /* MRBridge.h */, + 34F407D61E9E1DA400E57AC0 /* MRBridge.m */, + 34F407D71E9E1DA400E57AC0 /* MRBundleManager.h */, + 34F407D81E9E1DA400E57AC0 /* MRBundleManager.m */, + 34F407D91E9E1DA400E57AC0 /* MRCommand.h */, + 34F407DA1E9E1DA400E57AC0 /* MRCommand.m */, + 34F407DB1E9E1DA400E57AC0 /* MRConstants.h */, + 34F407DC1E9E1DA400E57AC0 /* MRConstants.m */, + 34F407DD1E9E1DA400E57AC0 /* MRController.h */, + 34F407DE1E9E1DA400E57AC0 /* MRController.m */, + 34F407DF1E9E1DA400E57AC0 /* MRError.h */, + 34F407E01E9E1DA400E57AC0 /* MRError.m */, + 34F407E11E9E1DA400E57AC0 /* MRExpandModalViewController.h */, + 34F407E21E9E1DA400E57AC0 /* MRExpandModalViewController.m */, + 34F407E31E9E1DA400E57AC0 /* MRNativeCommandHandler.h */, + 34F407E41E9E1DA400E57AC0 /* MRNativeCommandHandler.m */, + 34F407E51E9E1DA400E57AC0 /* MRProperty.h */, + 34F407E61E9E1DA400E57AC0 /* MRProperty.m */, + 34F407E71E9E1DA400E57AC0 /* MRVideoPlayerManager.h */, + 34F407E81E9E1DA400E57AC0 /* MRVideoPlayerManager.m */, + ); + path = MRAID; + sourceTree = ""; + }; + 34F407E91E9E1DA400E57AC0 /* Utility */ = { + isa = PBXGroup; + children = ( + 34F407EA1E9E1DA400E57AC0 /* Categories */, + 34F407FB1E9E1DA400E57AC0 /* MPAnalyticsTracker.h */, + 34F407FC1E9E1DA400E57AC0 /* MPAnalyticsTracker.m */, + 34F407FD1E9E1DA400E57AC0 /* MPError.h */, + 34F407FE1E9E1DA400E57AC0 /* MPError.m */, + 34F407FF1E9E1DA400E57AC0 /* MPGeolocationProvider.h */, + 34F408001E9E1DA400E57AC0 /* MPGeolocationProvider.m */, + 34F408011E9E1DA400E57AC0 /* MPGlobal.h */, + 34F408021E9E1DA400E57AC0 /* MPGlobal.m */, + 34F408031E9E1DA400E57AC0 /* MPIdentityProvider.h */, + 34F408041E9E1DA400E57AC0 /* MPIdentityProvider.m */, + 34F408051E9E1DA400E57AC0 /* MPInternalUtils.h */, + 34F408061E9E1DA400E57AC0 /* MPInternalUtils.m */, + 34F408071E9E1DA400E57AC0 /* MPLogging.h */, + 34F408081E9E1DA400E57AC0 /* MPLogging.m */, + 34F408091E9E1DA400E57AC0 /* MPLogProvider.h */, + 34F4080A1E9E1DA400E57AC0 /* MPLogProvider.m */, + 34F4080B1E9E1DA400E57AC0 /* MPReachability.h */, + 34F4080C1E9E1DA400E57AC0 /* MPReachability.m */, + 34F4080D1E9E1DA400E57AC0 /* MPSessionTracker.h */, + 34F4080E1E9E1DA400E57AC0 /* MPSessionTracker.m */, + 34F4080F1E9E1DA400E57AC0 /* MPStoreKitProvider.h */, + 34F408101E9E1DA400E57AC0 /* MPStoreKitProvider.m */, + 34F408111E9E1DA400E57AC0 /* MPTimer.h */, + 34F408121E9E1DA400E57AC0 /* MPTimer.m */, + 34F408131E9E1DA400E57AC0 /* MPUserInteractionGestureRecognizer.h */, + 34F408141E9E1DA400E57AC0 /* MPUserInteractionGestureRecognizer.m */, + ); + path = Utility; + sourceTree = ""; + }; + 34F407EA1E9E1DA400E57AC0 /* Categories */ = { + isa = PBXGroup; + children = ( + 34F407EB1E9E1DA400E57AC0 /* NSBundle+MPAdditions.h */, + 34F407EC1E9E1DA400E57AC0 /* NSBundle+MPAdditions.m */, + 34F407ED1E9E1DA400E57AC0 /* NSHTTPURLResponse+MPAdditions.h */, + 34F407EE1E9E1DA400E57AC0 /* NSHTTPURLResponse+MPAdditions.m */, + 34F407EF1E9E1DA400E57AC0 /* NSJSONSerialization+MPAdditions.h */, + 34F407F01E9E1DA400E57AC0 /* NSJSONSerialization+MPAdditions.m */, + 34F407F11E9E1DA400E57AC0 /* NSURL+MPAdditions.h */, + 34F407F21E9E1DA400E57AC0 /* NSURL+MPAdditions.m */, + 34F407F31E9E1DA400E57AC0 /* UIButton+MPAdditions.h */, + 34F407F41E9E1DA400E57AC0 /* UIButton+MPAdditions.m */, + 34F407F51E9E1DA400E57AC0 /* UIColor+MPAdditions.h */, + 34F407F61E9E1DA400E57AC0 /* UIColor+MPAdditions.m */, + 34F407F71E9E1DA400E57AC0 /* UIView+MPAdditions.h */, + 34F407F81E9E1DA400E57AC0 /* UIView+MPAdditions.m */, + 34F407F91E9E1DA400E57AC0 /* UIWebView+MPAdditions.h */, + 34F407FA1E9E1DA400E57AC0 /* UIWebView+MPAdditions.m */, + ); + path = Categories; + sourceTree = ""; + }; + 34F408151E9E1DA400E57AC0 /* VAST */ = { + isa = PBXGroup; + children = ( + 34F408161E9E1DA400E57AC0 /* MPVASTAd.h */, + 34F408171E9E1DA400E57AC0 /* MPVASTAd.m */, + 34F408181E9E1DA400E57AC0 /* MPVASTCompanionAd.h */, + 34F408191E9E1DA400E57AC0 /* MPVASTCompanionAd.m */, + 34F4081A1E9E1DA400E57AC0 /* MPVASTCreative.h */, + 34F4081B1E9E1DA400E57AC0 /* MPVASTCreative.m */, + 34F4081C1E9E1DA400E57AC0 /* MPVASTDurationOffset.h */, + 34F4081D1E9E1DA400E57AC0 /* MPVASTDurationOffset.m */, + 34F4081E1E9E1DA400E57AC0 /* MPVASTIndustryIcon.h */, + 34F4081F1E9E1DA400E57AC0 /* MPVASTIndustryIcon.m */, + 34F408201E9E1DA400E57AC0 /* MPVASTInline.h */, + 34F408211E9E1DA400E57AC0 /* MPVASTInline.m */, + 34F408221E9E1DA400E57AC0 /* MPVASTLinearAd.h */, + 34F408231E9E1DA400E57AC0 /* MPVASTLinearAd.m */, + 34F408241E9E1DA400E57AC0 /* MPVASTMacroProcessor.h */, + 34F408251E9E1DA400E57AC0 /* MPVASTMacroProcessor.m */, + 34F408261E9E1DA400E57AC0 /* MPVASTManager.h */, + 34F408271E9E1DA400E57AC0 /* MPVASTManager.m */, + 34F408281E9E1DA400E57AC0 /* MPVASTMediaFile.h */, + 34F408291E9E1DA400E57AC0 /* MPVASTMediaFile.m */, + 34F4082A1E9E1DA400E57AC0 /* MPVASTModel.h */, + 34F4082B1E9E1DA400E57AC0 /* MPVASTModel.m */, + 34F4082C1E9E1DA400E57AC0 /* MPVASTResource.h */, + 34F4082D1E9E1DA400E57AC0 /* MPVASTResource.m */, + 34F4082E1E9E1DA400E57AC0 /* MPVASTResponse.h */, + 34F4082F1E9E1DA400E57AC0 /* MPVASTResponse.m */, + 34F408301E9E1DA400E57AC0 /* MPVASTStringUtilities.h */, + 34F408311E9E1DA400E57AC0 /* MPVASTStringUtilities.m */, + 34F408321E9E1DA400E57AC0 /* MPVASTTrackingEvent.h */, + 34F408331E9E1DA400E57AC0 /* MPVASTTrackingEvent.m */, + 34F408341E9E1DA400E57AC0 /* MPVASTWrapper.h */, + 34F408351E9E1DA400E57AC0 /* MPVASTWrapper.m */, + ); + path = VAST; + sourceTree = ""; + }; + 34F408471E9E1DA400E57AC0 /* Native Ads */ = { + isa = PBXGroup; + children = ( + 34F408481E9E1DA400E57AC0 /* Internal */, + 34F4086E1E9E1DA400E57AC0 /* MPAdPositioning.h */, + 34F4086F1E9E1DA400E57AC0 /* MPAdPositioning.m */, + 34F408701E9E1DA400E57AC0 /* MPClientAdPositioning.h */, + 34F408711E9E1DA400E57AC0 /* MPClientAdPositioning.m */, + 34F408721E9E1DA400E57AC0 /* MPCollectionViewAdPlacer.h */, + 34F408731E9E1DA400E57AC0 /* MPCollectionViewAdPlacer.m */, + 34F408741E9E1DA400E57AC0 /* MPNativeAd.h */, + 34F408751E9E1DA400E57AC0 /* MPNativeAd.m */, + 34F408761E9E1DA400E57AC0 /* MPNativeAdAdapter.h */, + 34F408771E9E1DA400E57AC0 /* MPNativeAdConstants.h */, + 34F408781E9E1DA400E57AC0 /* MPNativeAdConstants.m */, + 34F408791E9E1DA400E57AC0 /* MPNativeAdData.h */, + 34F4087A1E9E1DA400E57AC0 /* MPNativeAdData.m */, + 34F4087B1E9E1DA400E57AC0 /* MPNativeAdDelegate.h */, + 34F4087C1E9E1DA400E57AC0 /* MPNativeAdError.h */, + 34F4087D1E9E1DA400E57AC0 /* MPNativeAdError.m */, + 34F4087E1E9E1DA400E57AC0 /* MPNativeAdRenderer.h */, + 34F4087F1E9E1DA400E57AC0 /* MPNativeAdRendererConfiguration.h */, + 34F408801E9E1DA400E57AC0 /* MPNativeAdRendererConfiguration.m */, + 34F408811E9E1DA400E57AC0 /* MPNativeAdRendererSettings.h */, + 34F408821E9E1DA400E57AC0 /* MPNativeAdRendering.h */, + 34F408831E9E1DA400E57AC0 /* MPNativeAdRenderingImageLoader.h */, + 34F408841E9E1DA400E57AC0 /* MPNativeAdRenderingImageLoader.m */, + 34F408851E9E1DA400E57AC0 /* MPNativeAdRequest.h */, + 34F408861E9E1DA400E57AC0 /* MPNativeAdRequest.m */, + 34F408871E9E1DA400E57AC0 /* MPNativeAdRequestTargeting.h */, + 34F408881E9E1DA400E57AC0 /* MPNativeAdRequestTargeting.m */, + 34F408891E9E1DA400E57AC0 /* MPNativeAdSource.h */, + 34F4088A1E9E1DA400E57AC0 /* MPNativeAdSource.m */, + 34F4088B1E9E1DA400E57AC0 /* MPNativeAdSourceDelegate.h */, + 34F4088C1E9E1DA400E57AC0 /* MPNativeCustomEvent.h */, + 34F4088D1E9E1DA400E57AC0 /* MPNativeCustomEvent.m */, + 34F4088E1E9E1DA400E57AC0 /* MPNativeCustomEventDelegate.h */, + 34F4088F1E9E1DA400E57AC0 /* MPServerAdPositioning.h */, + 34F408901E9E1DA400E57AC0 /* MPServerAdPositioning.m */, + 34F408911E9E1DA400E57AC0 /* MPStaticNativeAdRenderer.h */, + 34F408921E9E1DA400E57AC0 /* MPStaticNativeAdRenderer.m */, + 34F408931E9E1DA400E57AC0 /* MPStaticNativeAdRendererSettings.h */, + 34F408941E9E1DA400E57AC0 /* MPStaticNativeAdRendererSettings.m */, + 34F408951E9E1DA400E57AC0 /* MPStreamAdPlacementData.h */, + 34F408961E9E1DA400E57AC0 /* MPStreamAdPlacementData.m */, + 34F408971E9E1DA400E57AC0 /* MPStreamAdPlacer.h */, + 34F408981E9E1DA400E57AC0 /* MPStreamAdPlacer.m */, + 34F408991E9E1DA400E57AC0 /* MPTableViewAdPlacer.h */, + 34F4089A1E9E1DA400E57AC0 /* MPTableViewAdPlacer.m */, + ); + path = "Native Ads"; + sourceTree = ""; + }; + 34F408481E9E1DA400E57AC0 /* Internal */ = { + isa = PBXGroup; + children = ( + 34F408491E9E1DA400E57AC0 /* Categories */, + 34F4084B1E9E1DA400E57AC0 /* MPAdPlacerInvocation.h */, + 34F4084C1E9E1DA400E57AC0 /* MPAdPlacerInvocation.m */, + 34F4084D1E9E1DA400E57AC0 /* MPCollectionViewAdPlacerCell.h */, + 34F4084E1E9E1DA400E57AC0 /* MPCollectionViewAdPlacerCell.m */, + 34F4084F1E9E1DA400E57AC0 /* MPDiskLRUCache.h */, + 34F408501E9E1DA400E57AC0 /* MPDiskLRUCache.m */, + 34F408511E9E1DA400E57AC0 /* MPImageDownloadQueue.h */, + 34F408521E9E1DA400E57AC0 /* MPImageDownloadQueue.m */, + 34F408531E9E1DA400E57AC0 /* MPMoPubNativeAdAdapter.h */, + 34F408541E9E1DA400E57AC0 /* MPMoPubNativeAdAdapter.m */, + 34F408551E9E1DA400E57AC0 /* MPMoPubNativeCustomEvent.h */, + 34F408561E9E1DA400E57AC0 /* MPMoPubNativeCustomEvent.m */, + 34F408571E9E1DA400E57AC0 /* MPNativeAd+Internal.h */, + 34F408581E9E1DA400E57AC0 /* MPNativeAd+Internal.m */, + 34F408591E9E1DA400E57AC0 /* MPNativeAdRendererConstants.h */, + 34F4085A1E9E1DA400E57AC0 /* MPNativeAdRendererImageHandler.h */, + 34F4085B1E9E1DA400E57AC0 /* MPNativeAdRendererImageHandler.m */, + 34F4085C1E9E1DA400E57AC0 /* MPNativeAdSourceQueue.h */, + 34F4085D1E9E1DA400E57AC0 /* MPNativeAdSourceQueue.m */, + 34F4085E1E9E1DA400E57AC0 /* MPNativeAdUtils.h */, + 34F4085F1E9E1DA400E57AC0 /* MPNativeAdUtils.m */, + 34F408601E9E1DA400E57AC0 /* MPNativeCache.h */, + 34F408611E9E1DA400E57AC0 /* MPNativeCache.m */, + 34F408621E9E1DA400E57AC0 /* MPNativePositionResponseDeserializer.h */, + 34F408631E9E1DA400E57AC0 /* MPNativePositionResponseDeserializer.m */, + 34F408641E9E1DA400E57AC0 /* MPNativePositionSource.h */, + 34F408651E9E1DA400E57AC0 /* MPNativePositionSource.m */, + 34F408661E9E1DA400E57AC0 /* MPNativeView.h */, + 34F408671E9E1DA400E57AC0 /* MPNativeView.m */, + 34F408681E9E1DA400E57AC0 /* MPStaticNativeAdImpressionTimer.h */, + 34F408691E9E1DA400E57AC0 /* MPStaticNativeAdImpressionTimer.m */, + 34F4086A1E9E1DA400E57AC0 /* MPTableViewAdPlacerCell.h */, + 34F4086B1E9E1DA400E57AC0 /* MPTableViewAdPlacerCell.m */, + 34F4086C1E9E1DA400E57AC0 /* MPTableViewCellImpressionTracker.h */, + 34F4086D1E9E1DA400E57AC0 /* MPTableViewCellImpressionTracker.m */, + ); + path = Internal; + sourceTree = ""; + }; + 34F408491E9E1DA400E57AC0 /* Categories */ = { + isa = PBXGroup; + children = ( + 34F4084A1E9E1DA400E57AC0 /* MPNativeAdRequest+MPNativeAdSource.h */, + ); + path = Categories; + sourceTree = ""; + }; + 34F4089B1E9E1DA400E57AC0 /* NativeVideo */ = { + isa = PBXGroup; + children = ( + 34F4089C1E9E1DA400E57AC0 /* Internal */, + 34F408B51E9E1DA400E57AC0 /* MOPUBNativeVideoAdRenderer.h */, + 34F408B61E9E1DA400E57AC0 /* MOPUBNativeVideoAdRenderer.m */, + 34F408B71E9E1DA400E57AC0 /* MOPUBNativeVideoAdRendererSettings.h */, + 34F408B81E9E1DA400E57AC0 /* MOPUBNativeVideoAdRendererSettings.m */, + ); + path = NativeVideo; + sourceTree = ""; + }; + 34F4089C1E9E1DA400E57AC0 /* Internal */ = { + isa = PBXGroup; + children = ( + 34F4089D1E9E1DA400E57AC0 /* MOPUBActivityIndicatorView.h */, + 34F4089E1E9E1DA400E57AC0 /* MOPUBActivityIndicatorView.m */, + 34F4089F1E9E1DA400E57AC0 /* MOPUBAVPlayer.h */, + 34F408A01E9E1DA400E57AC0 /* MOPUBAVPlayer.m */, + 34F408A11E9E1DA400E57AC0 /* MOPUBAVPlayerView.h */, + 34F408A21E9E1DA400E57AC0 /* MOPUBAVPlayerView.m */, + 34F408A31E9E1DA400E57AC0 /* MOPUBFullscreenPlayerViewController.h */, + 34F408A41E9E1DA400E57AC0 /* MOPUBFullscreenPlayerViewController.m */, + 34F408A51E9E1DA400E57AC0 /* MOPUBNativeVideoAdAdapter.h */, + 34F408A61E9E1DA400E57AC0 /* MOPUBNativeVideoAdAdapter.m */, + 34F408A71E9E1DA400E57AC0 /* MOPUBNativeVideoAdConfigValues.h */, + 34F408A81E9E1DA400E57AC0 /* MOPUBNativeVideoAdConfigValues.m */, + 34F408A91E9E1DA400E57AC0 /* MOPUBNativeVideoCustomEvent.h */, + 34F408AA1E9E1DA400E57AC0 /* MOPUBNativeVideoCustomEvent.m */, + 34F408AB1E9E1DA400E57AC0 /* MOPUBNativeVideoImpressionAgent.h */, + 34F408AC1E9E1DA400E57AC0 /* MOPUBNativeVideoImpressionAgent.m */, + 34F408AD1E9E1DA400E57AC0 /* MOPUBPlayerManager.h */, + 34F408AE1E9E1DA400E57AC0 /* MOPUBPlayerManager.m */, + 34F408AF1E9E1DA400E57AC0 /* MOPUBPlayerView.h */, + 34F408B01E9E1DA400E57AC0 /* MOPUBPlayerView.m */, + 34F408B11E9E1DA400E57AC0 /* MOPUBPlayerViewController.h */, + 34F408B21E9E1DA400E57AC0 /* MOPUBPlayerViewController.m */, + 34F408B31E9E1DA400E57AC0 /* MOPUBReplayView.h */, + 34F408B41E9E1DA400E57AC0 /* MOPUBReplayView.m */, + ); + path = Internal; + sourceTree = ""; + }; + 34F408B91E9E1DA400E57AC0 /* Resources */ = { + isa = PBXGroup; + children = ( + 34F408BA1E9E1DA400E57AC0 /* MPCloseBtn.png */, + 34F408BB1E9E1DA400E57AC0 /* MPCloseBtn@2x.png */, + 34F408BC1E9E1DA400E57AC0 /* MPCloseBtn@3x.png */, + 34F408BD1E9E1DA400E57AC0 /* MPCloseButtonX.png */, + 34F408BE1E9E1DA400E57AC0 /* MPCloseButtonX@2x.png */, + 34F408BF1E9E1DA400E57AC0 /* MPCloseButtonX@3x.png */, + 34F408C01E9E1DA400E57AC0 /* MPCountdownTimer.html */, + 34F408C11E9E1DA400E57AC0 /* MPDAAIcon.png */, + 34F408C21E9E1DA400E57AC0 /* MPDAAIcon@2x.png */, + 34F408C31E9E1DA400E57AC0 /* MPDAAIcon@3x.png */, + 34F408C41E9E1DA400E57AC0 /* MPMutedBtn.png */, + 34F408C51E9E1DA400E57AC0 /* MPMutedBtn@2x.png */, + 34F408C61E9E1DA400E57AC0 /* MPMutedBtn@3x.png */, + 34F408C71E9E1DA400E57AC0 /* MPPlayBtn.png */, + 34F408C81E9E1DA400E57AC0 /* MPPlayBtn@2x.png */, + 34F408C91E9E1DA400E57AC0 /* MPPlayBtn@3x.png */, + 34F408CA1E9E1DA400E57AC0 /* MPUnmutedBtn.png */, + 34F408CB1E9E1DA400E57AC0 /* MPUnmutedBtn@2x.png */, + 34F408CC1E9E1DA400E57AC0 /* MPUnmutedBtn@3x.png */, + 34F408CD1E9E1DA400E57AC0 /* MRAID.bundle */, + ); + path = Resources; + sourceTree = ""; + }; + 34F408CE1E9E1DA400E57AC0 /* RewardedVideo */ = { + isa = PBXGroup; + children = ( + 34F408CF1E9E1DA400E57AC0 /* Internal */, + 34F408DC1E9E1DA400E57AC0 /* MPMediationSettingsProtocol.h */, + 34F408DD1E9E1DA400E57AC0 /* MPRewardedVideo.h */, + 34F408DE1E9E1DA400E57AC0 /* MPRewardedVideo.m */, + 34F408DF1E9E1DA400E57AC0 /* MPRewardedVideoCustomEvent.h */, + 34F408E01E9E1DA400E57AC0 /* MPRewardedVideoCustomEvent.m */, + 34F408E11E9E1DA400E57AC0 /* MPRewardedVideoError.h */, + 34F408E21E9E1DA400E57AC0 /* MPRewardedVideoError.m */, + 34F408E31E9E1DA400E57AC0 /* MPRewardedVideoReward.h */, + 34F408E41E9E1DA400E57AC0 /* MPRewardedVideoReward.m */, + ); + path = RewardedVideo; + sourceTree = ""; + }; + 34F408CF1E9E1DA400E57AC0 /* Internal */ = { + isa = PBXGroup; + children = ( + 34F408D01E9E1DA400E57AC0 /* MPMoPubRewardedPlayableCustomEvent.h */, + 34F408D11E9E1DA400E57AC0 /* MPMoPubRewardedPlayableCustomEvent.m */, + 34F408D21E9E1DA400E57AC0 /* MPMoPubRewardedVideoCustomEvent.h */, + 34F408D31E9E1DA400E57AC0 /* MPMoPubRewardedVideoCustomEvent.m */, + 34F408D41E9E1DA400E57AC0 /* MPPrivateRewardedVideoCustomEventDelegate.h */, + 34F408D51E9E1DA400E57AC0 /* MPRewardedVideo+Internal.h */, + 34F408D61E9E1DA400E57AC0 /* MPRewardedVideoAdapter.h */, + 34F408D71E9E1DA400E57AC0 /* MPRewardedVideoAdapter.m */, + 34F408D81E9E1DA400E57AC0 /* MPRewardedVideoAdManager.h */, + 34F408D91E9E1DA400E57AC0 /* MPRewardedVideoAdManager.m */, + 34F408DA1E9E1DA400E57AC0 /* MPRewardedVideoConnection.h */, + 34F408DB1E9E1DA400E57AC0 /* MPRewardedVideoConnection.m */, + ); + path = Internal; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 34F4074B1E9E1D3500E57AC0 /* Mopub */ = { + isa = PBXNativeTarget; + buildConfigurationList = 34F407551E9E1D3500E57AC0 /* Build configuration list for PBXNativeTarget "Mopub" */; + buildPhases = ( + 34F407481E9E1D3500E57AC0 /* Sources */, + 34F407491E9E1D3500E57AC0 /* Frameworks */, + 34F4074A1E9E1D3500E57AC0 /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Mopub; + productName = Mopub; + productReference = 34F4074C1E9E1D3500E57AC0 /* libMopub.a */; + productType = "com.apple.product-type.library.static"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 34F407441E9E1D3500E57AC0 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0830; + ORGANIZATIONNAME = maps.me; + TargetAttributes = { + 34F4074B1E9E1D3500E57AC0 = { + CreatedOnToolsVersion = 8.3.1; + DevelopmentTeam = 3T6FSDE8C7; + ProvisioningStyle = Automatic; + }; + }; + }; + buildConfigurationList = 34F407471E9E1D3500E57AC0 /* Build configuration list for PBXProject "Mopub" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = 34F407431E9E1D3500E57AC0; + productRefGroup = 34F4074D1E9E1D3500E57AC0 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 34F4074B1E9E1D3500E57AC0 /* Mopub */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXSourcesBuildPhase section */ + 34F407481E9E1D3500E57AC0 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 34F4095C1E9E1DA400E57AC0 /* MPStaticNativeAdImpressionTimer.m in Sources */, + 34F409831E9E1DA400E57AC0 /* MPRewardedVideoAdManager.m in Sources */, + 34F409451E9E1DA400E57AC0 /* MPVASTTrackingEvent.m in Sources */, + 34F409771E9E1DA400E57AC0 /* MOPUBNativeVideoAdConfigValues.m in Sources */, + 34F408F31E9E1DA400E57AC0 /* MPAdConfiguration.m in Sources */, + 34F409581E9E1DA400E57AC0 /* MPNativeCache.m in Sources */, + 34F409511E9E1DA400E57AC0 /* MPImageDownloadQueue.m in Sources */, + 34F409781E9E1DA400E57AC0 /* MOPUBNativeVideoCustomEvent.m in Sources */, + 34F409371E9E1DA400E57AC0 /* MPVASTAd.m in Sources */, + 34F409871E9E1DA400E57AC0 /* MPRewardedVideoError.m in Sources */, + 34F4096E1E9E1DA400E57AC0 /* MPStaticNativeAdRendererSettings.m in Sources */, + 34F409571E9E1DA400E57AC0 /* MPNativeAdUtils.m in Sources */, + 34F409501E9E1DA400E57AC0 /* MPDiskLRUCache.m in Sources */, + 34F408FB1E9E1DA400E57AC0 /* MPFacebookKeywordProvider.m in Sources */, + 34F409441E9E1DA400E57AC0 /* MPVASTStringUtilities.m in Sources */, + 34F4091E1E9E1DA400E57AC0 /* MRExpandModalViewController.m in Sources */, + 34F408F01E9E1DA400E57AC0 /* MPActivityViewControllerHelper+TweetShare.m in Sources */, + 34F408F71E9E1DA400E57AC0 /* MPAPIEndpoints.m in Sources */, + 34F4094A1E9E1DA400E57AC0 /* MPBannerCustomEvent.m in Sources */, + 34F4093D1E9E1DA400E57AC0 /* MPVASTLinearAd.m in Sources */, + 34F409521E9E1DA400E57AC0 /* MPMoPubNativeAdAdapter.m in Sources */, + 34F4091B1E9E1DA400E57AC0 /* MRConstants.m in Sources */, + 34F4093F1E9E1DA400E57AC0 /* MPVASTManager.m in Sources */, + 34F408E81E9E1DA400E57AC0 /* FacebookNativeCustomEvent.m in Sources */, + 34F409141E9E1DA400E57AC0 /* MPVASTTracking.m in Sources */, + 34F4092E1E9E1DA400E57AC0 /* MPIdentityProvider.m in Sources */, + 34F4094F1E9E1DA400E57AC0 /* MPCollectionViewAdPlacerCell.m in Sources */, + 34F408F41E9E1DA400E57AC0 /* MPAdDestinationDisplayAgent.m in Sources */, + 34F409051E9E1DA400E57AC0 /* MPLogEventRecorder.m in Sources */, + 34F408EB1E9E1DA400E57AC0 /* MPBannerAdManager.m in Sources */, + 34F409181E9E1DA400E57AC0 /* MRBridge.m in Sources */, + 34F409291E9E1DA400E57AC0 /* UIWebView+MPAdditions.m in Sources */, + 34F409711E9E1DA400E57AC0 /* MPTableViewAdPlacer.m in Sources */, + 34F409661E9E1DA400E57AC0 /* MPNativeAdRendererConfiguration.m in Sources */, + 34F4094D1E9E1DA400E57AC0 /* MPInterstitialCustomEvent.m in Sources */, + 34F408FC1E9E1DA400E57AC0 /* MPLastResortDelegate.m in Sources */, + 34F408FF1E9E1DA400E57AC0 /* MPURLResolver.m in Sources */, + 34F409721E9E1DA400E57AC0 /* MOPUBActivityIndicatorView.m in Sources */, + 34F409751E9E1DA400E57AC0 /* MOPUBFullscreenPlayerViewController.m in Sources */, + 34F4091F1E9E1DA400E57AC0 /* MRNativeCommandHandler.m in Sources */, + 34F4091D1E9E1DA400E57AC0 /* MRError.m in Sources */, + 34F4097D1E9E1DA400E57AC0 /* MOPUBReplayView.m in Sources */, + 34F409631E9E1DA400E57AC0 /* MPNativeAdConstants.m in Sources */, + 34F408E71E9E1DA400E57AC0 /* FacebookNativeAdAdapter.m in Sources */, + 34F4096C1E9E1DA400E57AC0 /* MPServerAdPositioning.m in Sources */, + 34F408EC1E9E1DA400E57AC0 /* MPBannerCustomEventAdapter.m in Sources */, + 34F409421E9E1DA400E57AC0 /* MPVASTResource.m in Sources */, + 34F409701E9E1DA400E57AC0 /* MPStreamAdPlacer.m in Sources */, + 34F4094E1E9E1DA400E57AC0 /* MPAdPlacerInvocation.m in Sources */, + 34F4090D1E9E1DA400E57AC0 /* MPWebView.m in Sources */, + 34F409481E9E1DA400E57AC0 /* MPAdConversionTracker.m in Sources */, + 34F409261E9E1DA400E57AC0 /* UIButton+MPAdditions.m in Sources */, + 34F408FE1E9E1DA400E57AC0 /* MPURLActionInfo.m in Sources */, + 34F409361E9E1DA400E57AC0 /* MPUserInteractionGestureRecognizer.m in Sources */, + 34F409861E9E1DA400E57AC0 /* MPRewardedVideoCustomEvent.m in Sources */, + 34F409391E9E1DA400E57AC0 /* MPVASTCreative.m in Sources */, + 34F409561E9E1DA400E57AC0 /* MPNativeAdSourceQueue.m in Sources */, + 34F409811E9E1DA400E57AC0 /* MPMoPubRewardedVideoCustomEvent.m in Sources */, + 34F409461E9E1DA400E57AC0 /* MPVASTWrapper.m in Sources */, + 34F409621E9E1DA400E57AC0 /* MPNativeAd.m in Sources */, + 34F408FA1E9E1DA400E57AC0 /* MPEnhancedDeeplinkRequest.m in Sources */, + 34F4097F1E9E1DA400E57AC0 /* MOPUBNativeVideoAdRendererSettings.m in Sources */, + 34F409301E9E1DA400E57AC0 /* MPLogging.m in Sources */, + 34F408EF1E9E1DA400E57AC0 /* MPAdAlertManager.m in Sources */, + 34F4095F1E9E1DA400E57AC0 /* MPAdPositioning.m in Sources */, + 34F409231E9E1DA400E57AC0 /* NSHTTPURLResponse+MPAdditions.m in Sources */, + 34F4090B1E9E1DA400E57AC0 /* MPHTMLInterstitialCustomEvent.m in Sources */, + 34F4093C1E9E1DA400E57AC0 /* MPVASTInline.m in Sources */, + 34F409741E9E1DA400E57AC0 /* MOPUBAVPlayerView.m in Sources */, + 34F4095A1E9E1DA400E57AC0 /* MPNativePositionSource.m in Sources */, + 34F409211E9E1DA400E57AC0 /* MRVideoPlayerManager.m in Sources */, + 34F4092D1E9E1DA400E57AC0 /* MPGlobal.m in Sources */, + 34F409431E9E1DA400E57AC0 /* MPVASTResponse.m in Sources */, + 34F409381E9E1DA400E57AC0 /* MPVASTCompanionAd.m in Sources */, + 34F4095E1E9E1DA400E57AC0 /* MPTableViewCellImpressionTracker.m in Sources */, + 34F4096D1E9E1DA400E57AC0 /* MPStaticNativeAdRenderer.m in Sources */, + 34F409111E9E1DA400E57AC0 /* MPInterstitialViewController.m in Sources */, + 34F4095D1E9E1DA400E57AC0 /* MPTableViewAdPlacerCell.m in Sources */, + 34F4097C1E9E1DA400E57AC0 /* MOPUBPlayerViewController.m in Sources */, + 34F409091E9E1DA400E57AC0 /* MPAdWebViewAgent.m in Sources */, + 34F408F81E9E1DA400E57AC0 /* MPClosableView.m in Sources */, + 34F409061E9E1DA400E57AC0 /* MPNetworkManager.m in Sources */, + 34F409101E9E1DA400E57AC0 /* MPInterstitialCustomEventAdapter.m in Sources */, + 34F409131E9E1DA400E57AC0 /* MPInstanceProvider.m in Sources */, + 34F4094C1E9E1DA400E57AC0 /* MPInterstitialAdController.m in Sources */, + 34F4092F1E9E1DA400E57AC0 /* MPInternalUtils.m in Sources */, + 34F4096B1E9E1DA400E57AC0 /* MPNativeCustomEvent.m in Sources */, + 34F4097E1E9E1DA400E57AC0 /* MOPUBNativeVideoAdRenderer.m in Sources */, + 34F409801E9E1DA400E57AC0 /* MPMoPubRewardedPlayableCustomEvent.m in Sources */, + 34F409491E9E1DA400E57AC0 /* MPAdView.m in Sources */, + 34F409401E9E1DA400E57AC0 /* MPVASTMediaFile.m in Sources */, + 34F4090E1E9E1DA400E57AC0 /* MPBaseInterstitialAdapter.m in Sources */, + 34F409611E9E1DA400E57AC0 /* MPCollectionViewAdPlacer.m in Sources */, + 34F409011E9E1DA400E57AC0 /* MPXMLParser.m in Sources */, + 34F409031E9E1DA400E57AC0 /* MPLogEvent.m in Sources */, + 34F408F21E9E1DA400E57AC0 /* MPAdBrowserController.m in Sources */, + 34F408E51E9E1DA400E57AC0 /* FacebookBannerCustomEvent.m in Sources */, + 34F408F11E9E1DA400E57AC0 /* MPActivityViewControllerHelper.m in Sources */, + 34F408FD1E9E1DA400E57AC0 /* MPProgressOverlayView.m in Sources */, + 34F409681E9E1DA400E57AC0 /* MPNativeAdRequest.m in Sources */, + 34F4097A1E9E1DA400E57AC0 /* MOPUBPlayerManager.m in Sources */, + 34F409671E9E1DA400E57AC0 /* MPNativeAdRenderingImageLoader.m in Sources */, + 34F409071E9E1DA400E57AC0 /* MPQRunLoopOperation.m in Sources */, + 34F409321E9E1DA400E57AC0 /* MPReachability.m in Sources */, + 34F409241E9E1DA400E57AC0 /* NSJSONSerialization+MPAdditions.m in Sources */, + 34F409821E9E1DA400E57AC0 /* MPRewardedVideoAdapter.m in Sources */, + 34F409251E9E1DA400E57AC0 /* NSURL+MPAdditions.m in Sources */, + 34F409601E9E1DA400E57AC0 /* MPClientAdPositioning.m in Sources */, + 34F409041E9E1DA400E57AC0 /* MPLogEventCommunicator.m in Sources */, + 34F4094B1E9E1DA400E57AC0 /* MPConstants.m in Sources */, + 34F409151E9E1DA400E57AC0 /* MPMRAIDBannerCustomEvent.m in Sources */, + 34F409851E9E1DA400E57AC0 /* MPRewardedVideo.m in Sources */, + 34F409691E9E1DA400E57AC0 /* MPNativeAdRequestTargeting.m in Sources */, + 34F4096F1E9E1DA400E57AC0 /* MPStreamAdPlacementData.m in Sources */, + 34F409191E9E1DA400E57AC0 /* MRBundleManager.m in Sources */, + 34F4092C1E9E1DA400E57AC0 /* MPGeolocationProvider.m in Sources */, + 34F409021E9E1DA400E57AC0 /* MPLogEvent+NativeVideo.m in Sources */, + 34F4093A1E9E1DA400E57AC0 /* MPVASTDurationOffset.m in Sources */, + 34F4091C1E9E1DA400E57AC0 /* MRController.m in Sources */, + 34F409271E9E1DA400E57AC0 /* UIColor+MPAdditions.m in Sources */, + 34F408EE1E9E1DA400E57AC0 /* MPAdAlertGestureRecognizer.m in Sources */, + 34F409841E9E1DA400E57AC0 /* MPRewardedVideoConnection.m in Sources */, + 34F409881E9E1DA400E57AC0 /* MPRewardedVideoReward.m in Sources */, + 34F409311E9E1DA400E57AC0 /* MPLogProvider.m in Sources */, + 34F409651E9E1DA400E57AC0 /* MPNativeAdError.m in Sources */, + 34F409221E9E1DA400E57AC0 /* NSBundle+MPAdditions.m in Sources */, + 34F4093E1E9E1DA400E57AC0 /* MPVASTMacroProcessor.m in Sources */, + 34F4097B1E9E1DA400E57AC0 /* MOPUBPlayerView.m in Sources */, + 34F4090C1E9E1DA400E57AC0 /* MPHTMLInterstitialViewController.m in Sources */, + 34F408F91E9E1DA400E57AC0 /* MPCountdownTimerView.m in Sources */, + 34F409281E9E1DA400E57AC0 /* UIView+MPAdditions.m in Sources */, + 34F4093B1E9E1DA400E57AC0 /* MPVASTIndustryIcon.m in Sources */, + 34F4092A1E9E1DA400E57AC0 /* MPAnalyticsTracker.m in Sources */, + 34F409761E9E1DA400E57AC0 /* MOPUBNativeVideoAdAdapter.m in Sources */, + 34F4091A1E9E1DA400E57AC0 /* MRCommand.m in Sources */, + 34F409001E9E1DA400E57AC0 /* MPVideoConfig.m in Sources */, + 34F4090A1E9E1DA400E57AC0 /* MPHTMLBannerCustomEvent.m in Sources */, + 34F4096A1E9E1DA400E57AC0 /* MPNativeAdSource.m in Sources */, + 34F409541E9E1DA400E57AC0 /* MPNativeAd+Internal.m in Sources */, + 34F4095B1E9E1DA400E57AC0 /* MPNativeView.m in Sources */, + 34F409351E9E1DA400E57AC0 /* MPTimer.m in Sources */, + 34F409161E9E1DA400E57AC0 /* MPMRAIDInterstitialCustomEvent.m in Sources */, + 34F408F51E9E1DA400E57AC0 /* MPAdServerCommunicator.m in Sources */, + 34F409411E9E1DA400E57AC0 /* MPVASTModel.m in Sources */, + 34F4092B1E9E1DA400E57AC0 /* MPError.m in Sources */, + 34F409471E9E1DA400E57AC0 /* MoPub.m in Sources */, + 34F409731E9E1DA400E57AC0 /* MOPUBAVPlayer.m in Sources */, + 34F409121E9E1DA400E57AC0 /* MPCoreInstanceProvider.m in Sources */, + 34F409591E9E1DA400E57AC0 /* MPNativePositionResponseDeserializer.m in Sources */, + 34F409791E9E1DA400E57AC0 /* MOPUBNativeVideoImpressionAgent.m in Sources */, + 34F409341E9E1DA400E57AC0 /* MPStoreKitProvider.m in Sources */, + 34F409331E9E1DA400E57AC0 /* MPSessionTracker.m in Sources */, + 34F408E61E9E1DA400E57AC0 /* FacebookInterstitialCustomEvent.m in Sources */, + 34F409551E9E1DA400E57AC0 /* MPNativeAdRendererImageHandler.m in Sources */, + 34F408F61E9E1DA400E57AC0 /* MPAdServerURLBuilder.m in Sources */, + 34F409081E9E1DA400E57AC0 /* MPRetryingHTTPOperation.m in Sources */, + 34F409531E9E1DA400E57AC0 /* MPMoPubNativeCustomEvent.m in Sources */, + 34F409171E9E1DA400E57AC0 /* MPMRAIDInterstitialViewController.m in Sources */, + 34F408ED1E9E1DA400E57AC0 /* MPBaseBannerAdapter.m in Sources */, + 34F409201E9E1DA400E57AC0 /* MRProperty.m in Sources */, + 34F4090F1E9E1DA400E57AC0 /* MPInterstitialAdManager.m in Sources */, + 34F409641E9E1DA400E57AC0 /* MPNativeAdData.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 34F407531E9E1D3500E57AC0 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 34F4098E1E9E27DB00E57AC0 /* common-debug.xcconfig */; + buildSettings = { + BUILD_DIR = "$(OMIM_ROOT)/../../../omim-build/xcode"; + GCC_WARN_INHIBIT_ALL_WARNINGS = YES; + MACH_O_TYPE = staticlib; + }; + name = Debug; + }; + 34F407541E9E1D3500E57AC0 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 34F4098F1E9E27DB00E57AC0 /* common-release.xcconfig */; + buildSettings = { + BUILD_DIR = "$(OMIM_ROOT)/../../../omim-build/xcode"; + GCC_WARN_INHIBIT_ALL_WARNINGS = YES; + MACH_O_TYPE = staticlib; + }; + name = Release; + }; + 34F407561E9E1D3500E57AC0 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/AdNetworkSupport/Facebook/SDK", + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 34F407571E9E1D3500E57AC0 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/AdNetworkSupport/Facebook/SDK", + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 34F407471E9E1D3500E57AC0 /* Build configuration list for PBXProject "Mopub" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 34F407531E9E1D3500E57AC0 /* Debug */, + 34F407541E9E1D3500E57AC0 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 34F407551E9E1D3500E57AC0 /* Build configuration list for PBXNativeTarget "Mopub" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 34F407561E9E1D3500E57AC0 /* Debug */, + 34F407571E9E1D3500E57AC0 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 34F407441E9E1D3500E57AC0 /* Project object */; +} diff --git a/iphone/Maps/3party/MoPubSDK/Mopub.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/iphone/Maps/3party/MoPubSDK/Mopub.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000000..c332ab746fe --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Mopub.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/Categories/MPNativeAdRequest+MPNativeAdSource.h b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/Categories/MPNativeAdRequest+MPNativeAdSource.h new file mode 100644 index 00000000000..8c18dca23dd --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/Categories/MPNativeAdRequest+MPNativeAdSource.h @@ -0,0 +1,14 @@ +// +// MPNativeAdRequest+MPNativeAdSource.h +// MoPubSDK +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import "MPNativeAdRequest.h" + +@interface MPNativeAdRequest (MPNativeAdSource) + +- (void)startForAdSequence:(NSInteger)adSequence withCompletionHandler:(MPNativeAdRequestHandler)handler; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPAdPlacerInvocation.h b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPAdPlacerInvocation.h new file mode 100644 index 00000000000..13030c181fa --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPAdPlacerInvocation.h @@ -0,0 +1,124 @@ +// +// MPAdPlacerInvocation.h +// MoPubSDK +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import + +@class MPStreamAdPlacer; + +/** + * A convenience class that handles a lot of the common logic when implementing a wrapper for the delegate / data source of a UI collection object + * (e.g. UITableView or UICollectionView). + * + * When implementing wrapper object methods, you will often have to take the following into consideration: + * + * - Handling cells that contain ads differently from regular content cells. For example, you may want to disable moving an ad cell while allowing + * regular content cells to move. + * + * - Calling through to the original object when handling regular content cells. The original delegate / data source only knows about regular content + * cells. Thus, you need to translate the given index path, that indexes ads and content, to an original index path that only indexes content. + * You can use the original translated index path to have the original delegate / data source process logic on the correct cell. + * + * - Providing the default behavior when the original object doesn't respond to the specific method and an ad doesn't occupy the given index path. + * + * This class takes care of all the work in two steps. Set up and invoke an NSInvocation object by calling invokeForTarget:. If you wish to return + * a value based on the invocation, you may pass the returned invocation to one of the result methods (e.g. boolResultForInvocation:defaultValue:) and + * provide a defaultValue that will be returned if an ad occupies the cell or the original delegate / data source doesn't respond to the selector. + * If an ad is not at the given index path and the original delegate / data source responds to the selector, invokeForTarget: will translate the index + * path for you and pass it to the original delegate / data source. The result method will then return the result from the invocation. + */ + +@interface MPAdPlacerInvocation : NSObject + +/** + * Creates an NSInvocation object with the given parameters and invokes the object. + * This will return nil if there is an ad at the index path or the target doesn't respond to the selector. + * + * @param target The object's original data source or delegate. + * @param with2ArgSelector The method we want to execute on the target if an ad doesn't exist. + * @param firstArg The first argument to the selector. + * @param secondArg The second argument to the selector. + * @param streamAdPlacer The MPStreamAdPlacer backing your UI collection that can translate index paths to their originals. + * + * @return The invocation with all the parameters passed into the method. + */ ++ (NSInvocation *)invokeForTarget:(id)target + with2ArgSelector:(SEL)selector + firstArg:(id)arg1 + secondArg:(NSIndexPath *)indexPath + streamAdPlacer:(MPStreamAdPlacer *)streamAdPlacer; + +/** + * Creates an NSInvocation object with the given parameters and invokes the object. + * This will return nil if there is an ad at the index path or the target doesn't respond to the selector. + * + * @param target The object's original data source or delegate. + * @param with3ArgSelector The method we want to execute on the target if an ad doesn't exist. + * @param firstArg The first argument to the selector. + * @param secondArg The second argument to the selector. + * @param thirdArg The third argument to the selector. + * @param streamAdPlacer The MPStreamAdPlacer backing your UI collection that can translate index paths to their originals. + * + * @return The invocation with all the parameters passed into the method. + */ ++ (NSInvocation *)invokeForTarget:(id)target + with3ArgSelector:(SEL)selector + firstArg:(id)arg1 + secondArg:(id)arg2 + thirdArg:(NSIndexPath *)indexPath + streamAdPlacer:(MPStreamAdPlacer *)streamAdPlacer; + +/** + * Creates an NSInvocation object with the given parameters and invokes the object. + * This will return nil if there is an ad at the index path or the target doesn't respond to the selector. + * + * @param target The object's original data source or delegate. + * @param with3ArgSelector The method we want to execute on the target if an ad doesn't exist. + * @param firstArg The first argument to the selector. + * @param secondArg The second argument to the selector. + * @param thirdArg The third argument to the selector. + * @param streamAdPlacer The MPStreamAdPlacer backing your UI collection that can translate index paths to their originals. + * + * @return The invocation with all the parameters passed into the method. + */ ++ (NSInvocation *)invokeForTarget:(id)target + with3ArgIntSelector:(SEL)selector + firstArg:(id)arg1 + secondArg:(NSInteger)arg2 + thirdArg:(NSIndexPath *)indexPath + streamAdPlacer:(MPStreamAdPlacer *)streamAdPlacer; + +/** + * Returns the result for an invocation. Will return defaultReturnValue if invocation is nil. + * + * @param invocation The invocation that was returned from invokeForTarget:. + * @param defaultReturnValue What to return when the invocation is nil. + * + * @return defaultReturnValue or the invocation's return value. + */ ++ (BOOL)boolResultForInvocation:(NSInvocation *)invocation defaultValue:(BOOL)defaultReturnValue; + +/** + * Returns the result for an invocation. Will return defaultReturnValue if invocation is nil. + * + * @param invocation The invocation that was returned from invokeForTarget:. + * @param defaultReturnValue What to return when the invocation is nil. + * + * @return defaultReturnValue or the invocation's return value. + */ ++ (NSInteger)integerResultForInvocation:(NSInvocation *)invocation defaultValue:(NSInteger)defaultReturnValue; + +/** + * Returns the result for an invocation. Will return defaultReturnValue if invocation is nil. + * + * @param invocation The invocation that was returned from invokeForTarget:. + * @param defaultReturnValue What to return when the invocation is nil. + * + * @return defaultReturnValue or the invocation's return value. + */ ++ (id)resultForInvocation:(NSInvocation *)invocation defaultValue:(id)defaultReturnValue; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPAdPlacerInvocation.m b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPAdPlacerInvocation.m new file mode 100644 index 00000000000..8fe0af42e98 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPAdPlacerInvocation.m @@ -0,0 +1,129 @@ +// +// MPAdPlacerInvocation.m +// MoPubSDK +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import "MPAdPlacerInvocation.h" +#import "MPStreamAdPlacer.h" + +@implementation MPAdPlacerInvocation + ++ (NSInvocation *)invocationForTarget:(id)target + selector:(SEL)selector + indexPath:(NSIndexPath *)indexPath + streamAdPlacer:(MPStreamAdPlacer *)streamAdPlacer +{ + if (![target respondsToSelector:selector]) { + return nil; + } + + // No invocations for ad rows. + if ([streamAdPlacer isAdAtIndexPath:indexPath]) { + return nil; + } + + // Create the invocation. + NSMethodSignature *signature = [target methodSignatureForSelector:selector]; + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature]; + [invocation setTarget:target]; + [invocation setSelector:selector]; + return invocation; +} + ++ (NSInvocation *)invokeForTarget:(id)target + with2ArgSelector:(SEL)selector + firstArg:(id)arg1 + secondArg:(NSIndexPath *)indexPath + streamAdPlacer:(MPStreamAdPlacer *)streamAdPlacer +{ + NSInvocation *invocation = [MPAdPlacerInvocation invocationForTarget:target + selector:selector + indexPath:indexPath + streamAdPlacer:streamAdPlacer]; + if (invocation) { + NSIndexPath *origPath = [streamAdPlacer originalIndexPathForAdjustedIndexPath:indexPath]; + [invocation setArgument:&(arg1) atIndex:2]; + [invocation setArgument:&(origPath) atIndex:3]; + [invocation invoke]; + } + return invocation; +} + ++ (NSInvocation *)invokeForTarget:(id)target + with3ArgSelector:(SEL)selector + firstArg:(id)arg1 + secondArg:(id)arg2 + thirdArg:(NSIndexPath *)indexPath + streamAdPlacer:(MPStreamAdPlacer *)streamAdPlacer +{ + NSInvocation *invocation = [MPAdPlacerInvocation invocationForTarget:target + selector:selector + indexPath:indexPath + streamAdPlacer:streamAdPlacer]; + if (invocation) { + NSIndexPath *origPath = [streamAdPlacer originalIndexPathForAdjustedIndexPath:indexPath]; + [invocation setArgument:&(arg1) atIndex:2]; + [invocation setArgument:&(arg2) atIndex:3]; + [invocation setArgument:&(origPath) atIndex:4]; + [invocation invoke]; + } + return invocation; +} + ++ (NSInvocation *)invokeForTarget:(id)target + with3ArgIntSelector:(SEL)selector + firstArg:(id)arg1 + secondArg:(NSInteger)arg2 + thirdArg:(NSIndexPath *)indexPath + streamAdPlacer:(MPStreamAdPlacer *)streamAdPlacer +{ + NSInvocation *invocation = [MPAdPlacerInvocation invocationForTarget:target + selector:selector + indexPath:indexPath + streamAdPlacer:streamAdPlacer]; + if (invocation) { + NSIndexPath *origPath = [streamAdPlacer originalIndexPathForAdjustedIndexPath:indexPath]; + [invocation setArgument:&(arg1) atIndex:2]; + [invocation setArgument:&(arg2) atIndex:3]; + [invocation setArgument:&(origPath) atIndex:4]; + [invocation invoke]; + } + return invocation; +} + ++ (BOOL)boolResultForInvocation:(NSInvocation *)invocation defaultValue:(BOOL)defaultReturnValue +{ + if (!invocation) { + return defaultReturnValue; + } + + BOOL returnValue; + [invocation getReturnValue:&returnValue]; + return returnValue; +} + ++ (id)resultForInvocation:(NSInvocation *)invocation defaultValue:(id)defaultReturnValue +{ + if (!invocation) { + return defaultReturnValue; + } + + __unsafe_unretained id returnValue; + [invocation getReturnValue:&returnValue]; + return returnValue; +} + ++ (NSInteger)integerResultForInvocation:(NSInvocation *)invocation defaultValue:(NSInteger)defaultReturnValue +{ + if (!invocation) { + return defaultReturnValue; + } + + NSInteger returnValue; + [invocation getReturnValue:&returnValue]; + return returnValue; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPCollectionViewAdPlacerCell.h b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPCollectionViewAdPlacerCell.h new file mode 100644 index 00000000000..def7c329882 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPCollectionViewAdPlacerCell.h @@ -0,0 +1,12 @@ +// +// MPCollectionViewAdPlacerCell.h +// MoPubSDK +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import + +@interface MPCollectionViewAdPlacerCell : UICollectionViewCell + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPCollectionViewAdPlacerCell.m b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPCollectionViewAdPlacerCell.m new file mode 100644 index 00000000000..60e9cfb8f00 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPCollectionViewAdPlacerCell.m @@ -0,0 +1,12 @@ +// +// MPCollectionViewAdPlacerCell.m +// MoPubSDK +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import "MPCollectionViewAdPlacerCell.h" + +@implementation MPCollectionViewAdPlacerCell + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPDiskLRUCache.h b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPDiskLRUCache.h new file mode 100755 index 00000000000..7eab0ab1e6d --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPDiskLRUCache.h @@ -0,0 +1,21 @@ +// +// MPDiskLRUCache.h +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import + +@interface MPDiskLRUCache : NSObject + ++ (MPDiskLRUCache *)sharedDiskCache; + +/* + * Do NOT call any of the following methods on the main thread, potentially lengthy wait for disk IO + */ +- (BOOL)cachedDataExistsForKey:(NSString *)key; +- (NSData *)retrieveDataForKey:(NSString *)key; +- (void)storeData:(NSData *)data forKey:(NSString *)key; +- (void)removeAllCachedFiles; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPDiskLRUCache.m b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPDiskLRUCache.m new file mode 100755 index 00000000000..8736ae13128 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPDiskLRUCache.m @@ -0,0 +1,266 @@ +// +// MPDiskLRUCache.m +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import "MPDiskLRUCache.h" +#import "MPGlobal.h" +#import "MPLogging.h" + +#import + +// cached files that have not been access since kCacheFileMaxAge ago will be evicted +#define kCacheFileMaxAge (7 * 24 * 60 * 60) // 1 week + +// once the cache hits this size AND we've added at least kCacheBytesStoredBeforeSizeCheck bytes, +// cached files will be evicted (LRU) until the total size drops below this limit +#define kCacheSoftMaxSize (100 * 1024 * 1024) // 100 MB + +#define kCacheBytesStoredBeforeSizeCheck (kCacheSoftMaxSize / 10) // 10% of kCacheSoftMaxSize + +@interface MPDiskLRUCacheFile : NSObject + +@property (nonatomic, copy) NSString *filePath; +@property (nonatomic, assign) NSTimeInterval lastModTimestamp; +@property (nonatomic, assign) uint64_t fileSize; + +@end + +@implementation MPDiskLRUCacheFile + + +@end + +@interface MPDiskLRUCache () + +#if !OS_OBJECT_USE_OBJC +@property (nonatomic, assign) dispatch_queue_t diskIOQueue; +#else +@property (nonatomic, strong) dispatch_queue_t diskIOQueue; +#endif +@property (nonatomic, copy) NSString *diskCachePath; +@property (atomic, assign) uint64_t numBytesStoredForSizeCheck; + +@end + +@implementation MPDiskLRUCache + ++ (MPDiskLRUCache *)sharedDiskCache +{ + static dispatch_once_t once; + static MPDiskLRUCache *sharedDiskCache; + dispatch_once(&once, ^{ + sharedDiskCache = [self new]; + }); + return sharedDiskCache; +} + +- (id)init +{ + self = [super init]; + if (self != nil) { + _diskIOQueue = dispatch_queue_create("com.mopub.diskCacheIOQueue", DISPATCH_QUEUE_SERIAL); + + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); + if (paths.count > 0) { + _diskCachePath = [[[paths objectAtIndex:0] stringByAppendingPathComponent:@"com.mopub.diskCache"] copy]; + + NSFileManager *fileManager = [NSFileManager defaultManager]; + if (![fileManager fileExistsAtPath:_diskCachePath]) { + [fileManager createDirectoryAtPath:_diskCachePath withIntermediateDirectories:YES attributes:nil error:nil]; + } + } + + // check cache size on startup + [self ensureCacheSizeLimit]; + } + + return self; +} + +- (void)dealloc +{ +#if !OS_OBJECT_USE_OBJC + dispatch_release(_diskIOQueue); +#endif +} + +#pragma mark Public + +- (void)removeAllCachedFiles +{ + dispatch_sync(self.diskIOQueue, ^{ + NSFileManager *fileManager = [NSFileManager defaultManager]; + + NSArray *allFiles = [self cacheFilesSortedByModDate]; + for (MPDiskLRUCacheFile *file in allFiles) { + [fileManager removeItemAtPath:file.filePath error:nil]; + } + }); +} + +- (BOOL)cachedDataExistsForKey:(NSString *)key +{ + __block BOOL result = NO; + + dispatch_sync(self.diskIOQueue, ^{ + NSFileManager *fileManager = [NSFileManager defaultManager]; + result = [fileManager fileExistsAtPath:[self cacheFilePathForKey:key]]; + }); + + return result; +} + +- (NSData *)retrieveDataForKey:(NSString *)key +{ + __block NSData *data = nil; + + dispatch_sync(self.diskIOQueue, ^{ + NSString *cachedFilePath = [self cacheFilePathForKey:key]; + + NSFileManager *fileManager = [NSFileManager defaultManager]; + BOOL isDirectory = NO; + if ([fileManager fileExistsAtPath:cachedFilePath isDirectory:&isDirectory]) { + data = [NSData dataWithContentsOfFile:cachedFilePath]; + + // "touch" file to mark access since NSFileManager doesn't return a last accessed date + [fileManager setAttributes:[NSDictionary dictionaryWithObject:[NSDate date] forKey:NSFileModificationDate] ofItemAtPath:cachedFilePath error:nil]; + } + }); + + return data; +} + +- (void)storeData:(NSData *)data forKey:(NSString *)key +{ + dispatch_sync(self.diskIOQueue, ^{ + NSString *cacheFilePath = [self cacheFilePathForKey:key]; + NSFileManager *fileManager = [NSFileManager defaultManager]; + + if (![fileManager fileExistsAtPath:cacheFilePath]) { + [fileManager createFileAtPath:cacheFilePath contents:data attributes:nil]; + } else { + // overwrite existing file + [data writeToFile:cacheFilePath atomically:YES]; + } + }); + + self.numBytesStoredForSizeCheck += data.length; + + if (self.numBytesStoredForSizeCheck >= kCacheBytesStoredBeforeSizeCheck) { + [self ensureCacheSizeLimit]; + self.numBytesStoredForSizeCheck = 0; + } +} + +#pragma mark Private + +- (void)ensureCacheSizeLimit +{ + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ + MPLogDebug(@"Checking cache size..."); + + NSFileManager *fileManager = [NSFileManager defaultManager]; + + NSMutableArray *cacheFilesSortedByModDate = [self cacheFilesSortedByModDate]; + + dispatch_async(self.diskIOQueue, ^{ + @autoreleasepool { + // verify age + NSArray *expiredFiles = [self expiredCachedFilesInArray:cacheFilesSortedByModDate]; + for (MPDiskLRUCacheFile *file in expiredFiles) { + MPLogDebug(@"Trying to remove %@ from cache due to expiration", file.filePath); + + [fileManager removeItemAtPath:file.filePath error:nil]; + [cacheFilesSortedByModDate removeObject:file]; + } + + // verify size + while ([self sizeOfCacheFilesInArray:cacheFilesSortedByModDate] >= kCacheSoftMaxSize && cacheFilesSortedByModDate.count > 0) { + NSString *oldestFilePath = ((MPDiskLRUCacheFile *)[cacheFilesSortedByModDate objectAtIndex:0]).filePath; + + MPLogDebug(@"Trying to remove %@ from cache due to size", oldestFilePath); + + [fileManager removeItemAtPath:oldestFilePath error:nil]; + [cacheFilesSortedByModDate removeObjectAtIndex:0]; + } + } + }); + }); +} + +- (NSArray *)expiredCachedFilesInArray:(NSArray *)cachedFiles +{ + NSMutableArray *result = [NSMutableArray array]; + + NSTimeInterval now = [[NSDate date] timeIntervalSinceReferenceDate]; + + for (MPDiskLRUCacheFile *file in cachedFiles) { + if (now - file.lastModTimestamp >= kCacheFileMaxAge) { + [result addObject:file]; + } + } + + return result; +} + +- (NSMutableArray *)cacheFilesSortedByModDate +{ + NSFileManager *fileManager = [NSFileManager defaultManager]; + + NSArray *cachedFiles = [fileManager contentsOfDirectoryAtPath:self.diskCachePath error:nil]; + NSArray *sortedFiles = [cachedFiles sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) { + NSString *fileName1 = [self.diskCachePath stringByAppendingPathComponent:(NSString *)obj1]; + NSString *fileName2 = [self.diskCachePath stringByAppendingPathComponent:(NSString *)obj2]; + + NSDictionary *fileAttrs1 = [fileManager attributesOfItemAtPath:fileName1 error:nil]; + NSDictionary *fileAttrs2 = [fileManager attributesOfItemAtPath:fileName2 error:nil]; + + NSDate *lastModDate1 = [fileAttrs1 fileModificationDate]; + NSDate *lastModDate2 = [fileAttrs2 fileModificationDate]; + + return [lastModDate1 compare:lastModDate2]; + }]; + + NSMutableArray *result = [NSMutableArray array]; + + for (NSString *fileName in sortedFiles) { + if ([fileName hasPrefix:@"."]) { + continue; + } + + MPDiskLRUCacheFile *cacheFile = [[MPDiskLRUCacheFile alloc] init]; + cacheFile.filePath = [self.diskCachePath stringByAppendingPathComponent:fileName]; + + NSDictionary *fileAttrs = [fileManager attributesOfItemAtPath:cacheFile.filePath error:nil]; + cacheFile.fileSize = [fileAttrs fileSize]; + cacheFile.lastModTimestamp = [[fileAttrs fileModificationDate] timeIntervalSinceReferenceDate]; + + [result addObject:cacheFile]; + } + + return result; +} + +- (uint64_t)sizeOfCacheFilesInArray:(NSArray *)files +{ + uint64_t currentSize = 0; + + for (MPDiskLRUCacheFile *file in files) { + currentSize += file.fileSize; + } + + MPLogDebug(@"Current cache size %qu bytes", currentSize); + + return currentSize; +} + +- (NSString *)cacheFilePathForKey:(NSString *)key +{ + NSString *hashedKey = MPSHA1Digest(key); + NSString *cachedFilePath = [self.diskCachePath stringByAppendingPathComponent:hashedKey]; + return cachedFilePath; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPImageDownloadQueue.h b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPImageDownloadQueue.h new file mode 100755 index 00000000000..d9c5793380a --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPImageDownloadQueue.h @@ -0,0 +1,19 @@ +// +// MPImageDownloadQueue.h +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import + +typedef void (^MPImageDownloadQueueCompletionBlock)(NSArray *errors); + +@interface MPImageDownloadQueue : NSObject + +// pass useCachedImage:NO to force download of images. default is YES, cached images will not be re-downloaded +- (void)addDownloadImageURLs:(NSArray *)imageURLs completionBlock:(MPImageDownloadQueueCompletionBlock)completionBlock; +- (void)addDownloadImageURLs:(NSArray *)imageURLs completionBlock:(MPImageDownloadQueueCompletionBlock)completionBlock useCachedImage:(BOOL)useCachedImage; + +- (void)cancelAllDownloads; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPImageDownloadQueue.m b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPImageDownloadQueue.m new file mode 100755 index 00000000000..783a283d428 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPImageDownloadQueue.m @@ -0,0 +1,105 @@ +// +// MPImageDownloadQueue.m +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import "MPImageDownloadQueue.h" +#import "MPNativeAdError.h" +#import "MPLogging.h" +#import "MPNativeCache.h" + +@interface MPImageDownloadQueue () + +@property (atomic, strong) NSOperationQueue *imageDownloadQueue; +@property (atomic, assign) BOOL isCanceled; + +@end + +@implementation MPImageDownloadQueue + +- (id)init +{ + self = [super init]; + + if (self != nil) { + _imageDownloadQueue = [[NSOperationQueue alloc] init]; + [_imageDownloadQueue setMaxConcurrentOperationCount:1]; // serial queue + } + + return self; +} + +- (void)dealloc +{ + [_imageDownloadQueue cancelAllOperations]; +} + +- (void)addDownloadImageURLs:(NSArray *)imageURLs completionBlock:(MPImageDownloadQueueCompletionBlock)completionBlock +{ + [self addDownloadImageURLs:imageURLs completionBlock:completionBlock useCachedImage:YES]; +} + +- (void)addDownloadImageURLs:(NSArray *)imageURLs completionBlock:(MPImageDownloadQueueCompletionBlock)completionBlock useCachedImage:(BOOL)useCachedImage +{ + __block NSMutableArray *errors = nil; + + for (NSURL *imageURL in imageURLs) { + [self.imageDownloadQueue addOperationWithBlock:^{ + @autoreleasepool { + if (![[MPNativeCache sharedCache] cachedDataExistsForKey:imageURL.absoluteString] || !useCachedImage) { + MPLogDebug(@"Downloading %@", imageURL); + + NSURLResponse *response = nil; + NSError *error = nil; + NSData *data = [NSURLConnection sendSynchronousRequest:[NSURLRequest requestWithURL:imageURL] + returningResponse:&response + error:&error]; + + BOOL validImageDownloaded = data != nil; + if (validImageDownloaded) { + UIImage *downloadedImage = [UIImage imageWithData:data]; + if (downloadedImage != nil) { + [[MPNativeCache sharedCache] storeData:data forKey:imageURL.absoluteString]; + } else { + if (downloadedImage == nil) { + MPLogDebug(@"Error: invalid image data downloaded"); + } + + validImageDownloaded = NO; + } + } + + if (!validImageDownloaded) { + if (error == nil) { + error = MPNativeAdNSErrorForImageDownloadFailure(); + } + + if (errors == nil) { + errors = [NSMutableArray array]; + } + + [errors addObject:error]; + } + } + } + }]; + } + + // after all images have been downloaded, invoke callback on main thread + [self.imageDownloadQueue addOperationWithBlock:^{ + dispatch_async(dispatch_get_main_queue(), ^{ + if (!self.isCanceled) { + completionBlock(errors); + } + }); + }]; +} + +- (void)cancelAllDownloads +{ + self.isCanceled = YES; + [self.imageDownloadQueue cancelAllOperations]; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPMoPubNativeAdAdapter.h b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPMoPubNativeAdAdapter.h new file mode 100644 index 00000000000..c48f7f58164 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPMoPubNativeAdAdapter.h @@ -0,0 +1,20 @@ +// +// MPMoPubNativeAdAdapter.h +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import "MPNativeAdAdapter.h" + +@class MPAdConfiguration; + +@interface MPMoPubNativeAdAdapter : NSObject + +@property (nonatomic, weak) id delegate; +@property (nonatomic, readonly) NSArray *impressionTrackerURLs; +@property (nonatomic, readonly) NSArray *clickTrackerURLs; +@property (nonatomic) MPAdConfiguration *adConfiguration; + +- (instancetype)initWithAdProperties:(NSMutableDictionary *)properties; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPMoPubNativeAdAdapter.m b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPMoPubNativeAdAdapter.m new file mode 100644 index 00000000000..a8d6c6aa5ba --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPMoPubNativeAdAdapter.m @@ -0,0 +1,156 @@ +// +// MPMoPubNativeAdAdapter.m +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import "MPMoPubNativeAdAdapter.h" +#import "MPNativeAdError.h" +#import "MPAdDestinationDisplayAgent.h" +#import "MPCoreInstanceProvider.h" +#import "MPStaticNativeAdImpressionTimer.h" +#import "MPNativeAdConstants.h" +#import "MPGlobal.h" + +static const NSTimeInterval kMoPubRequiredSecondsForImpression = 1.0; +static const CGFloat kMoPubRequiredViewVisibilityPercentage = 0.5; + +@interface MPMoPubNativeAdAdapter () + +@property (nonatomic) MPStaticNativeAdImpressionTimer *impressionTimer; +@property (nonatomic, readonly) MPAdDestinationDisplayAgent *destinationDisplayAgent; + +@end + +@implementation MPMoPubNativeAdAdapter + +@synthesize properties = _properties; +@synthesize defaultActionURL = _defaultActionURL; + +- (instancetype)initWithAdProperties:(NSMutableDictionary *)properties +{ + if (self = [super init]) { + + // Let's make sure the data types of all the provided native ad properties are strings before creating the adapter + + NSArray *keysToCheck = @[kAdIconImageKey, kAdMainImageKey, kAdTextKey, kAdTitleKey, kAdCTATextKey]; + + for (NSString *key in keysToCheck) { + id value = properties[key]; + if (value != nil && ![value isKindOfClass:[NSString class]]) { + return nil; + } + } + + BOOL valid = YES; + NSArray *impressionTrackers = [properties objectForKey:kImpressionTrackerURLsKey]; + if (![impressionTrackers isKindOfClass:[NSArray class]] || [impressionTrackers count] < 1) { + valid = NO; + } else { + _impressionTrackerURLs = MPConvertStringArrayToURLArray(impressionTrackers); + } + + NSObject *clickTracker = [properties objectForKey:kClickTrackerURLKey]; + + // The click tracker could either be a single URL or an array of URLS. + if ([clickTracker isKindOfClass:[NSArray class]]) { + _clickTrackerURLs = MPConvertStringArrayToURLArray((NSArray *)clickTracker); + } else if ([clickTracker isKindOfClass:[NSString class]]) { + NSURL *url = [NSURL URLWithString:(NSString *)clickTracker]; + if (url) { + _clickTrackerURLs = @[ url ]; + } else { + valid = NO; + } + } else { + valid = NO; + } + + _defaultActionURL = [NSURL URLWithString:[properties objectForKey:kDefaultActionURLKey]]; + + [properties removeObjectsForKeys:[NSArray arrayWithObjects:kImpressionTrackerURLsKey, kClickTrackerURLKey, kDefaultActionURLKey, nil]]; + _properties = properties; + + if (!valid) { + return nil; + } + + // Add the DAA icon settings to our properties dictionary. + NSString * daaIconImagePath = MPResourcePathForResource(kDAAIconImageName); + if (daaIconImagePath != nil) { + [properties setObject:daaIconImagePath forKey:kAdDAAIconImageKey]; + } + + _destinationDisplayAgent = [[MPCoreInstanceProvider sharedProvider] buildMPAdDestinationDisplayAgentWithDelegate:self]; + _impressionTimer = [[MPStaticNativeAdImpressionTimer alloc] initWithRequiredSecondsForImpression:kMoPubRequiredSecondsForImpression requiredViewVisibilityPercentage:kMoPubRequiredViewVisibilityPercentage]; + _impressionTimer.delegate = self; + } + + return self; +} + +- (void)dealloc +{ + [_destinationDisplayAgent cancel]; + [_destinationDisplayAgent setDelegate:nil]; +} + +#pragma mark - + +- (void)willAttachToView:(UIView *)view +{ + [self.impressionTimer startTrackingView:view]; +} + +- (void)displayContentForURL:(NSURL *)URL rootViewController:(UIViewController *)controller +{ + if (!controller) { + return; + } + + if (!URL || ![URL isKindOfClass:[NSURL class]] || ![URL.absoluteString length]) { + return; + } + + [self.destinationDisplayAgent displayDestinationForURL:URL]; +} + +#pragma mark - DAA Icon + +- (void)displayContentForDAAIconTap +{ + [self.destinationDisplayAgent displayDestinationForURL:[NSURL URLWithString:kDAAIconTapDestinationURL]]; +} + +#pragma mark - + +- (void)trackImpression +{ + [self.delegate nativeAdWillLogImpression:self]; +} + +#pragma mark - + +- (UIViewController *)viewControllerForPresentingModalView +{ + return [self.delegate viewControllerForPresentingModalView]; +} + +- (void)displayAgentWillPresentModal +{ + [self.delegate nativeAdWillPresentModalForAdapter:self]; +} + +- (void)displayAgentWillLeaveApplication +{ + [self.delegate nativeAdWillLeaveApplicationFromAdapter:self]; +} + +- (void)displayAgentDidDismissModal +{ + [self.delegate nativeAdDidDismissModalForAdapter:self]; +} + +// - (MPAdConfiguration *)adConfiguration delegate method is automatically implemented via the adConfiguration property declaration. + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPMoPubNativeCustomEvent.h b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPMoPubNativeCustomEvent.h new file mode 100644 index 00000000000..a62bf50e3f2 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPMoPubNativeCustomEvent.h @@ -0,0 +1,12 @@ +// +// MPMoPubNativeCustomEvent.h +// MoPubSDK +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import "MPNativeCustomEvent.h" + +@interface MPMoPubNativeCustomEvent : MPNativeCustomEvent + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPMoPubNativeCustomEvent.m b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPMoPubNativeCustomEvent.m new file mode 100644 index 00000000000..d444c2c3ff1 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPMoPubNativeCustomEvent.m @@ -0,0 +1,50 @@ +// +// MPMoPubNativeCustomEvent.m +// MoPubSDK +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import "MPMoPubNativeCustomEvent.h" +#import "MPMoPubNativeAdAdapter.h" +#import "MPNativeAd+Internal.h" +#import "MPNativeAdError.h" +#import "MPLogging.h" +#import "MPNativeAdUtils.h" + +@implementation MPMoPubNativeCustomEvent + +- (void)requestAdWithCustomEventInfo:(NSDictionary *)info +{ + MPMoPubNativeAdAdapter *adAdapter = [[MPMoPubNativeAdAdapter alloc] initWithAdProperties:[info mutableCopy]]; + + if (adAdapter.properties) { + MPNativeAd *interfaceAd = [[MPNativeAd alloc] initWithAdAdapter:adAdapter]; + [interfaceAd.impressionTrackerURLs addObjectsFromArray:adAdapter.impressionTrackerURLs]; + [interfaceAd.clickTrackerURLs addObjectsFromArray:adAdapter.clickTrackerURLs]; + + // Get the image urls so we can download them prior to returning the ad. + NSMutableArray *imageURLs = [NSMutableArray array]; + for (NSString *key in [info allKeys]) { + if ([[key lowercaseString] hasSuffix:@"image"] && [[info objectForKey:key] isKindOfClass:[NSString class]]) { + if (![MPNativeAdUtils addURLString:[info objectForKey:key] toURLArray:imageURLs]) { + [self.delegate nativeCustomEvent:self didFailToLoadAdWithError:MPNativeAdNSErrorForInvalidImageURL()]; + } + } + } + + [super precacheImagesWithURLs:imageURLs completionBlock:^(NSArray *errors) { + if (errors) { + MPLogDebug(@"%@", errors); + [self.delegate nativeCustomEvent:self didFailToLoadAdWithError:MPNativeAdNSErrorForImageDownloadFailure()]; + } else { + [self.delegate nativeCustomEvent:self didLoadAd:interfaceAd]; + } + }]; + } else { + [self.delegate nativeCustomEvent:self didFailToLoadAdWithError:MPNativeAdNSErrorForInvalidAdServerResponse(nil)]; + } + +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPNativeAd+Internal.h b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPNativeAd+Internal.h new file mode 100644 index 00000000000..571ed5f5844 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPNativeAd+Internal.h @@ -0,0 +1,40 @@ +// +// MPNativeAd+Internal.h +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import "MPNativeAd.h" + +@class MPNativeView; + +@interface MPNativeAd (Internal) + +@property (nonatomic, readonly) NSDate *creationDate; +@property (nonatomic) MPNativeView *associatedView; +@property (nonatomic, readwrite, strong) id renderer; +@property (nonatomic, readonly) NSMutableSet *clickTrackerURLs; +@property (nonatomic, readonly) NSMutableSet *impressionTrackerURLs; +@property (nonatomic, readonly, strong) id adAdapter; + +/** + * This method is called by the ad placers when the sizes of the ad placer stream + * view's have changed. The ad placer will get the size from the renderer and just + * pass it through to the mpnativead to update the view size since the ad is the only one + * who has access to the ad view. +*/ +- (void)updateAdViewSize:(CGSize)size; + +/** + * Retrieves the custom ad view with its frame set to the would-be containing native view. Unlike + * `retrieveAdViewWithError:`, this method does not have side effects of changing the view hierarchy + * and is only intended for size calculation purposes. + * + * @param error A pointer to an error object. If an error occurs, this pointer will be set to an + * actual error object containing the error information. + * + * @return If successful, the method will return the rendered ad. The method will + * return nil if it cannot render the ad data to a view. + */ +- (UIView *)retrieveAdViewForSizeCalculationWithError:(NSError **)error; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPNativeAd+Internal.m b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPNativeAd+Internal.m new file mode 100644 index 00000000000..813f1453f97 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPNativeAd+Internal.m @@ -0,0 +1,38 @@ +// +// MPNativeAd+Internal.m +// MoPubSDK +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import +#import "MPNativeAd+Internal.h" +#import "MPNativeAdRenderer.h" +#import "MPNativeView.h" + +@implementation MPNativeAd (Internal) + +@dynamic impressionTrackerURLs; +@dynamic clickTrackerURLs; +@dynamic creationDate; +@dynamic renderer; +@dynamic associatedView; +@dynamic adAdapter; + +- (void)updateAdViewSize:(CGSize)size +{ + self.associatedView.frame = CGRectMake(0, 0, size.width, size.height); +} + +- (UIView *)retrieveAdViewForSizeCalculationWithError:(NSError **)error +{ + // retrieve the ad and apply the frame of the associatedView (superview of the adView) so the + // adView can calculate its own size. It's important that we don't add adView to the associatedView + // because this can mess up expectations in `retrieveAdViewWithError:` especially around hydrating + // image views asynchronously + UIView *adView = [self.renderer retrieveViewWithAdapter:self.adAdapter error:error]; + adView.frame = self.associatedView.bounds; + return adView; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPNativeAdRendererConstants.h b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPNativeAdRendererConstants.h new file mode 100644 index 00000000000..bc1e9af486e --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPNativeAdRendererConstants.h @@ -0,0 +1,14 @@ +// +// MPNativeAdRendererConstants.h +// MoPubSDK +// +// Copyright (c) 2016 MoPub. All rights reserved. +// + +/** + * Return this value from `MPNativeViewSizeHandler` when you want to display ad content that could + * have variable height and needs to be calculated only after ad properties are available. The + * implementation of ad view conforming to the `MPNativeAdRendering` protocol should implement + * `sizeThatFits:` and handle layout changes appropriately. + */ +FOUNDATION_EXPORT const CGFloat MPNativeViewDynamicDimension; diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPNativeAdRendererImageHandler.h b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPNativeAdRendererImageHandler.h new file mode 100644 index 00000000000..36ea61a1b18 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPNativeAdRendererImageHandler.h @@ -0,0 +1,22 @@ +// +// MPNativeAdRendererImageHandler.h +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import +#import + +@protocol MPNativeAdRendererImageHandlerDelegate + +- (BOOL)nativeAdViewInViewHierarchy; + +@end + +@interface MPNativeAdRendererImageHandler : NSObject + + +@property (nonatomic, weak) id delegate; + +- (void)loadImageForURL:(NSURL *)imageURL intoImageView:(UIImageView *)imageView; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPNativeAdRendererImageHandler.m b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPNativeAdRendererImageHandler.m new file mode 100644 index 00000000000..4214e570f8b --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPNativeAdRendererImageHandler.m @@ -0,0 +1,96 @@ +// +// MPNativeAdRendererImageHandler.m +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import "MPNativeAdRendererImageHandler.h" +#import "MPLogging.h" +#import "MPNativeCache.h" +#import "MPImageDownloadQueue.h" + +@interface MPNativeAdRendererImageHandler() + +@property (nonatomic) MPImageDownloadQueue *imageDownloadQueue; + +@end + +@implementation MPNativeAdRendererImageHandler + +- (instancetype)init +{ + if (self = [super init]) { + _imageDownloadQueue = [[MPImageDownloadQueue alloc] init]; + } + return self; +} + +- (void)loadImageForURL:(NSURL *)imageURL intoImageView:(UIImageView *)imageView +{ + imageView.image = nil; + + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ + __block BOOL isAdViewInHierarchy = NO; + + // Try to prevent unnecessary work if the ad view is not currently in the view hierarchy. + // Note that this doesn't prevent 100% of the cases as the ad view can still be recycled after this passes. + // We have an additional 100% accurate check in safeMainQueueSetImage to ensure that we don't overwrite. + + dispatch_sync(dispatch_get_main_queue(), ^{ + isAdViewInHierarchy = [self.delegate nativeAdViewInViewHierarchy]; + }); + + if (!isAdViewInHierarchy) { + MPLogDebug(@"Cell was recycled. Don't bother rendering the image."); + return; + } + + NSData *cachedImageData = [[MPNativeCache sharedCache] retrieveDataForKey:imageURL.absoluteString]; + UIImage *image = [UIImage imageWithData:cachedImageData]; + + if (image) { + // By default, the image data isn't decompressed until set on a UIImageView, on the main thread. This + // can result in poor scrolling performance. To fix this, we force decompression in the background before + // assignment to a UIImageView. + UIGraphicsBeginImageContext(CGSizeMake(1, 1)); + [image drawAtPoint:CGPointZero]; + UIGraphicsEndImageContext(); + + [self safeMainQueueSetImage:image intoImageView:imageView]; + } else if (imageURL) { + MPLogDebug(@"Cache miss on %@. Re-downloading...", imageURL); + + __weak typeof(self) weakSelf = self; + [self.imageDownloadQueue addDownloadImageURLs:@[imageURL] + completionBlock:^(NSArray *errors) { + __strong typeof(self) strongSelf = weakSelf; + if (strongSelf) { + if (errors.count == 0) { + UIImage *image = [UIImage imageWithData:[[MPNativeCache sharedCache] retrieveDataForKey:imageURL.absoluteString]]; + + [strongSelf safeMainQueueSetImage:image intoImageView:imageView]; + } else { + MPLogDebug(@"Failed to download %@ on cache miss. Giving up for now.", imageURL); + } + } else { + MPLogInfo(@"MPNativeAd deallocated before loadImageForURL:intoImageView: download completion block was called"); + } + }]; + } + }); +} + +- (void)safeMainQueueSetImage:(UIImage *)image intoImageView:(UIImageView *)imageView +{ + dispatch_async(dispatch_get_main_queue(), ^{ + if (![self.delegate nativeAdViewInViewHierarchy]) { + MPLogDebug(@"Cell was recycled. Don't bother setting the image."); + return; + } + + if (image) { + imageView.image = image; + } + }); +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPNativeAdSourceQueue.h b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPNativeAdSourceQueue.h new file mode 100644 index 00000000000..af870c27b5c --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPNativeAdSourceQueue.h @@ -0,0 +1,31 @@ +// +// MPNativeAdSourceQueue.h +// MoPub +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import +@class MPNativeAdRequestTargeting; +@class MPNativeAd; + +@protocol MPNativeAdSourceQueueDelegate; + +@interface MPNativeAdSourceQueue : NSObject + +@property (nonatomic, weak) id delegate; + + +- (instancetype)initWithAdUnitIdentifier:(NSString *)identifier rendererConfigurations:(NSArray *)rendererConfigurations andTargeting:(MPNativeAdRequestTargeting *)targeting; +- (MPNativeAd *)dequeueAdWithMaxAge:(NSTimeInterval)age; +- (NSUInteger)count; +- (void)loadAds; +- (void)cancelRequests; + +@end + +@protocol MPNativeAdSourceQueueDelegate + +- (void)adSourceQueueAdIsAvailable:(MPNativeAdSourceQueue *)source; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPNativeAdSourceQueue.m b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPNativeAdSourceQueue.m new file mode 100644 index 00000000000..a8796f41586 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPNativeAdSourceQueue.m @@ -0,0 +1,152 @@ +// +// MPNativeAdSourceQueue.m +// MoPub +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import "MPNativeAdSourceQueue.h" +#import "MPNativeAd+Internal.h" +#import "MPNativeAdRequestTargeting.h" +#import "MPNativeAdRequest+MPNativeAdSource.h" +#import "MPLogging.h" +#import "MPNativeAdError.h" + +static NSUInteger const kCacheSizeLimit = 1; +static NSTimeInterval const kAdFetchRetryTimes[] = {1, 3, 5, 25, 60, 300}; +// Calculate the number of elements inside the array by taking the size divided by the size of one element. +static NSUInteger const kMaxRetries = sizeof(kAdFetchRetryTimes)/sizeof(kAdFetchRetryTimes[0]); + +@interface MPNativeAdSourceQueue () + +@property (nonatomic) NSMutableArray *adQueue; +@property (nonatomic, assign) NSUInteger adFetchRetryCounter; +@property (nonatomic, assign) NSUInteger currentSequence; +@property (nonatomic, copy) NSString *adUnitIdentifier; +@property (nonatomic) MPNativeAdRequestTargeting *targeting; +@property (nonatomic) NSArray *rendererConfigurations; +@property (nonatomic, assign) BOOL isAdLoading; + +@end + +@implementation MPNativeAdSourceQueue + +#pragma mark - Object Lifecycle + +- (instancetype)initWithAdUnitIdentifier:(NSString *)identifier rendererConfigurations:(NSArray *)rendererConfigurations andTargeting:(MPNativeAdRequestTargeting *)targeting +{ + self = [super init]; + if (self) { + _adUnitIdentifier = [identifier copy]; + _rendererConfigurations = rendererConfigurations; + _targeting = targeting; + _adQueue = [[NSMutableArray alloc] init]; + } + return self; +} + + +#pragma mark - Public Methods + +- (MPNativeAd *)dequeueAd +{ + MPNativeAd *nextAd = [self.adQueue firstObject]; + [self.adQueue removeObject:nextAd]; + [self loadAds]; + return nextAd; +} + +- (MPNativeAd *)dequeueAdWithMaxAge:(NSTimeInterval)age +{ + MPNativeAd *nextAd = [self dequeueAd]; + + while (nextAd && ![self isAdAgeValid:nextAd withMaxAge:age]) { + nextAd = [self dequeueAd]; + } + + return nextAd; +} + +- (void)addNativeAd:(MPNativeAd *)nativeAd +{ + [self.adQueue addObject:nativeAd]; +} + +- (NSUInteger)count +{ + return [self.adQueue count]; +} + +- (void)cancelRequests +{ + [self resetBackoff]; +} + +#pragma mark - Internal Logic + +- (BOOL)isAdAgeValid:(MPNativeAd *)ad withMaxAge:(NSTimeInterval)maxAge +{ + NSTimeInterval adAge = [ad.creationDate timeIntervalSinceNow]; + + return fabs(adAge) < maxAge; +} + +#pragma mark - Ad Requests + +- (void)resetBackoff +{ + [NSObject cancelPreviousPerformRequestsWithTarget:self]; + self.adFetchRetryCounter = 0; +} + +- (void)loadAds +{ + if (self.adFetchRetryCounter == 0) { + [self replenishCache]; + } +} + +- (void)replenishCache +{ + if ([self count] >= kCacheSizeLimit || self.isAdLoading) { + return; + } + + self.isAdLoading = YES; + + MPNativeAdRequest *adRequest = [MPNativeAdRequest requestWithAdUnitIdentifier:self.adUnitIdentifier rendererConfigurations:self.rendererConfigurations]; + adRequest.targeting = self.targeting; + + [adRequest startForAdSequence:self.currentSequence withCompletionHandler:^(MPNativeAdRequest *request, MPNativeAd *response, NSError *error) { + if (response && !error) { + self.adFetchRetryCounter = 0; + + [self addNativeAd:response]; + self.currentSequence++; + if ([self count] == 1) { + [self.delegate adSourceQueueAdIsAvailable:self]; + } + } else { + MPLogDebug(@"%@", error); + //increment in this failure case to prevent retrying a request that wasn't bid on. + //currently under discussion on whether we do this or not. + if (error.code == MPNativeAdErrorNoInventory) { + self.currentSequence++; + } + + if (self.adFetchRetryCounter < kMaxRetries) { + NSTimeInterval retryTime = kAdFetchRetryTimes[self.adFetchRetryCounter]; + self.adFetchRetryCounter++; + [self performSelector:@selector(replenishCache) withObject:nil afterDelay:retryTime]; + MPLogDebug(@"Will re-attempt to replenish the ad cache in %.1f seconds.", retryTime); + } else { + // Don't try to fetch anymore ads after we have tried kMaxRetries times. + MPLogDebug(@"Replenishing the cache has timed out."); + } + } + self.isAdLoading = NO; + [self loadAds]; + }]; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPNativeAdUtils.h b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPNativeAdUtils.h new file mode 100644 index 00000000000..4f9d49ac90d --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPNativeAdUtils.h @@ -0,0 +1,16 @@ +// +// MPNativeAdUtils.h +// MoPubSDK +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import + +extern NSTimeInterval const kUpdateVisibleCellsInterval; + +@interface MPNativeAdUtils : NSObject + ++ (BOOL)addURLString:(NSString *)urlString toURLArray:(NSMutableArray *)urlArray; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPNativeAdUtils.m b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPNativeAdUtils.m new file mode 100644 index 00000000000..506231c077f --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPNativeAdUtils.m @@ -0,0 +1,28 @@ +// +// MPNativeAdUtils.m +// MoPubSDK +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import "MPNativeAdUtils.h" + +NSTimeInterval const kUpdateVisibleCellsInterval = 0.25; + +@implementation MPNativeAdUtils + ++ (BOOL)addURLString:(NSString *)urlString toURLArray:(NSMutableArray *)urlArray +{ + if (urlString.length == 0) { + return NO; + } + + NSURL *url = [NSURL URLWithString:urlString]; + if (url) { + [urlArray addObject:url]; + return YES; + } else { + return NO; + } +} +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPNativeCache.h b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPNativeCache.h new file mode 100644 index 00000000000..10762e72ec6 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPNativeCache.h @@ -0,0 +1,23 @@ +// +// MPNativeCache.h +// MoPub +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import + +@interface MPNativeCache : NSObject + ++ (instancetype)sharedCache; + +/* + * Do NOT call any of the following methods on the main thread, potentially lengthy wait for disk IO + */ +- (BOOL)cachedDataExistsForKey:(NSString *)key; +- (NSData *)retrieveDataForKey:(NSString *)key; +- (void)storeData:(NSData *)data forKey:(NSString *)key; +- (void)removeAllDataFromCache; +- (void)setInMemoryCacheEnabled:(BOOL)enabled; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPNativeCache.m b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPNativeCache.m new file mode 100644 index 00000000000..7babede42d4 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPNativeCache.m @@ -0,0 +1,185 @@ +// +// MPNativeCache.m +// MoPub +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import "MPNativeCache.h" +#import "MPDiskLRUCache.h" +#import "MPLogging.h" + +typedef enum { + MPNativeCacheMethodDisk = 0, + MPNativeCacheMethodDiskAndMemory = 1 << 0 +} MPNativeCacheMethod; + +@interface MPNativeCache () + +@property (nonatomic, strong) NSCache *memoryCache; +@property (nonatomic, strong) MPDiskLRUCache *diskCache; +@property (nonatomic, assign) MPNativeCacheMethod cacheMethod; + +- (BOOL)cachedDataExistsForKey:(NSString *)key withCacheMethod:(MPNativeCacheMethod)cacheMethod; +- (NSData *)retrieveDataForKey:(NSString *)key withCacheMethod:(MPNativeCacheMethod)cacheMethod; +- (void)storeData:(id)data forKey:(NSString *)key withCacheMethod:(MPNativeCacheMethod)cacheMethod; +- (void)removeAllDataFromMemory; +- (void)removeAllDataFromDisk; + +@end + +@implementation MPNativeCache + ++ (instancetype)sharedCache; +{ + static dispatch_once_t once; + static MPNativeCache *sharedCache; + dispatch_once(&once, ^{ + sharedCache = [[self alloc] init]; + }); + return sharedCache; +} + +- (id)init +{ + self = [super init]; + if (self != nil) { + _memoryCache = [[NSCache alloc] init]; + _memoryCache.delegate = self; + + _diskCache = [[MPDiskLRUCache alloc] init]; + + _cacheMethod = MPNativeCacheMethodDiskAndMemory; + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:[UIApplication sharedApplication]]; + } + + return self; +} + +- (void)dealloc +{ + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; +} + +#pragma mark - Public Cache Interactions + +- (void)setInMemoryCacheEnabled:(BOOL)enabled +{ + if (enabled) { + self.cacheMethod = MPNativeCacheMethodDiskAndMemory; + } else { + self.cacheMethod = MPNativeCacheMethodDisk; + [self.memoryCache removeAllObjects]; + } +} + +- (BOOL)cachedDataExistsForKey:(NSString *)key +{ + return [self cachedDataExistsForKey:key withCacheMethod:self.cacheMethod]; +} + +- (NSData *)retrieveDataForKey:(NSString *)key +{ + return [self retrieveDataForKey:key withCacheMethod:self.cacheMethod]; +} + +- (void)storeData:(NSData *)data forKey:(NSString *)key +{ + [self storeData:data forKey:key withCacheMethod:self.cacheMethod]; +} + +- (void)removeAllDataFromCache +{ + [self removeAllDataFromMemory]; + [self removeAllDataFromDisk]; +} + +#pragma mark - Private Cache Implementation + +- (BOOL)cachedDataExistsForKey:(NSString *)key withCacheMethod:(MPNativeCacheMethod)cacheMethod +{ + BOOL dataExists = NO; + if (cacheMethod & MPNativeCacheMethodDiskAndMemory) { + dataExists = [self.memoryCache objectForKey:key] != nil; + } + + if (!dataExists) { + dataExists = [self.diskCache cachedDataExistsForKey:key]; + } + + return dataExists; +} + +- (id)retrieveDataForKey:(NSString *)key withCacheMethod:(MPNativeCacheMethod)cacheMethod +{ + id data = nil; + + if (cacheMethod & MPNativeCacheMethodDiskAndMemory) { + data = [self.memoryCache objectForKey:key]; + } + + if (data) { + MPLogDebug(@"RETRIEVE FROM MEMORY: %@", key); + } + + + if (data == nil) { + data = [self.diskCache retrieveDataForKey:key]; + + if (data && cacheMethod & MPNativeCacheMethodDiskAndMemory) { + MPLogDebug(@"RETRIEVE FROM DISK: %@", key); + + [self.memoryCache setObject:data forKey:key]; + MPLogDebug(@"STORED IN MEMORY: %@", key); + } + } + + if (data == nil) { + MPLogDebug(@"RETRIEVE FAILED: %@", key); + } + + return data; +} + +- (void)storeData:(id)data forKey:(NSString *)key withCacheMethod:(MPNativeCacheMethod)cacheMethod +{ + if (data == nil) { + return; + } + + if (cacheMethod & MPNativeCacheMethodDiskAndMemory) { + [self.memoryCache setObject:data forKey:key]; + MPLogDebug(@"STORED IN MEMORY: %@", key); + } + + [self.diskCache storeData:data forKey:key]; + MPLogDebug(@"STORED ON DISK: %@", key); +} + +- (void)removeAllDataFromMemory +{ + [self.memoryCache removeAllObjects]; +} + +- (void)removeAllDataFromDisk +{ + [self.diskCache removeAllCachedFiles]; +} + +#pragma mark - Notifications + +- (void)didReceiveMemoryWarning:(NSNotification *)notification +{ + [self.memoryCache removeAllObjects]; +} + +#pragma mark - NSCacheDelegate + +- (void)cache:(NSCache *)cache willEvictObject:(id)obj +{ + MPLogDebug(@"Evicting Object"); +} + + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPNativePositionResponseDeserializer.h b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPNativePositionResponseDeserializer.h new file mode 100644 index 00000000000..fdbf00e2b3b --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPNativePositionResponseDeserializer.h @@ -0,0 +1,51 @@ +// +// MPNativePositionResponseDeserializer.h +// MoPub +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import +#import + +@class MPClientAdPositioning; + +typedef enum : NSUInteger { + MPNativePositionResponseDataIsEmpty, + MPNativePositionResponseIsNotValidJSON, + MPNativePositionResponseJSONHasInvalidPositionData, +} MPNativePositionResponseDeserializationErrorCode; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * The `MPNativePositionResponseDeserializer` class is used to convert HTTP response data + * containing positioning information into ad positioning objects that may be used by various + * native ad placers. + */ +@interface MPNativePositionResponseDeserializer : NSObject + +/** + * Creates and returns an object that can deserialize HTTP response data into ad positioning + * objects. + * + * @return The newly created deserializer. + */ ++ (instancetype)deserializer; + +/** + * Returns an ad positioning object given a data object. + * + * If an error occurs during the data conversion, this method will return an empty positioning + * object containing no desired ad positions. + * + * @param data A data object containing positioning information. + * @param error A pointer to an error object. If an error occurs, this pointer will be set to an + * actual error object containing the error information. + * + * @return An `MPClientAdPositioning` object. This is guaranteed to be non-nil; if an error occurs + * during deserialization, the return value will still be a positioning object with no ad positions. + */ +- (MPClientAdPositioning *)clientPositioningForData:(NSData *)data error:(NSError **)error; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPNativePositionResponseDeserializer.m b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPNativePositionResponseDeserializer.m new file mode 100644 index 00000000000..3e931c99394 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPNativePositionResponseDeserializer.m @@ -0,0 +1,250 @@ +// +// MPNativePositionResponseDeserializer.m +// MoPub +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import "MPNativePositionResponseDeserializer.h" +#import "MPClientAdPositioning.h" +#import "NSJSONSerialization+MPAdditions.h" + +static NSString * const MPNativePositionResponseDeserializationErrorDomain = @"com.mopub.iossdk.position.deserialization"; +static NSString * const MPNativePositionResponseFixedPositionsKey = @"fixed"; +static NSString * const MPNativePositionResponseSectionKey = @"section"; +static NSString * const MPNativePositionResponsePositionKey = @"position"; +static NSString * const MPNativePositionResponseRepeatingKey = @"repeating"; +static NSString * const MPNativePositionResponseIntervalKey = @"interval"; +static NSInteger const MPMinRepeatingInterval = 2; +static NSInteger const MPMaxRepeatingInterval = 1 << 16; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation MPNativePositionResponseDeserializer + ++ (instancetype)deserializer +{ + return [[[self class] alloc] init]; +} + +- (MPClientAdPositioning *)clientPositioningForData:(NSData *)data error:(NSError **)error +{ + MPClientAdPositioning *positioning = [MPClientAdPositioning positioning]; + + if (!data || [data length] == 0) { + [self safeAssignError:error code:MPNativePositionResponseDataIsEmpty description:@"Positioning cannot be created from nil or empty data."]; + return [MPClientAdPositioning positioning]; + } + + NSError *deserializationError = nil; + NSDictionary *positionDictionary = [NSJSONSerialization mp_JSONObjectWithData:data options:0 clearNullObjects:YES error:&deserializationError]; + + if (deserializationError) { + [self safeAssignError:error code:MPNativePositionResponseIsNotValidJSON description:@"Failed to deserialize JSON." underlyingError:deserializationError]; + return [MPClientAdPositioning positioning]; + } + + NSError *fixedPositionsError = nil; + NSArray *fixedPositions = [self parseFixedPositionsObject:[positionDictionary objectForKey:MPNativePositionResponseFixedPositionsKey] error:&fixedPositionsError]; + + if (fixedPositionsError) { + if (error) { + *error = fixedPositionsError; + } + return [MPClientAdPositioning positioning]; + } + + NSError *repeatingIntervalError = nil; + NSInteger repeatingInterval = [self parseRepeatingIntervalObject:[positionDictionary objectForKey:MPNativePositionResponseRepeatingKey] error:&repeatingIntervalError]; + + if (repeatingIntervalError) { + if (error) { + *error = repeatingIntervalError; + } + return [MPClientAdPositioning positioning]; + } + + if ([fixedPositions count] == 0 && repeatingInterval <= 0) { + [self safeAssignError:error code:MPNativePositionResponseJSONHasInvalidPositionData description:@"Positioning object must have either fixed positions or a repeating interval."]; + return [MPClientAdPositioning positioning]; + } + + [fixedPositions enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) { + [positioning addFixedIndexPath:indexPath]; + }]; + [positioning enableRepeatingPositionsWithInterval:repeatingInterval]; + return positioning; +} + +#pragma mark - Parsing and validation + +- (NSArray *)parseFixedPositionsObject:(id)positionsObject error:(NSError **)error +{ + NSMutableArray *parsedPositions = [NSMutableArray array]; + + if (positionsObject && ![positionsObject isKindOfClass:[NSArray class]]) { + [self safeAssignError:error code:MPNativePositionResponseJSONHasInvalidPositionData description:[NSString stringWithFormat:@"Expected object for key \"%@\" to be an array. Actual: %@", MPNativePositionResponseFixedPositionsKey, positionsObject]]; + return nil; + } + + __block NSError *fixedPositionError = nil; + [positionsObject enumerateObjectsUsingBlock:^(id positionObj, NSUInteger idx, BOOL *stop) { + if (![self validatePositionObject:positionObj error:&fixedPositionError]) { + *stop = YES; + return; + } + + NSInteger section = [self integerFromDictionary:positionObj forKey:MPNativePositionResponseSectionKey defaultValue:0]; + NSInteger position = [self integerFromDictionary:positionObj forKey:MPNativePositionResponsePositionKey defaultValue:0]; + NSIndexPath *indexPath = [NSIndexPath indexPathForRow:position inSection:section]; + [parsedPositions addObject:indexPath]; + }]; + + if (fixedPositionError) { + if (error) { + *error = fixedPositionError; + } + return nil; + } + + return parsedPositions; +} + +- (NSInteger)parseRepeatingIntervalObject:(id)repeatingIntervalObject error:(NSError **)error +{ + if (!repeatingIntervalObject) { + return 0; + } + + NSError *repeatingIntervalError = nil; + if (![self validateRepeatingIntervalObject:repeatingIntervalObject error:&repeatingIntervalError]) { + if (error) { + *error = repeatingIntervalError; + } + return 0; + } + + return [self integerFromDictionary:repeatingIntervalObject forKey:MPNativePositionResponseIntervalKey defaultValue:0]; +} + +- (BOOL)validatePositionObject:(id)positionObject error:(NSError **)error +{ + if (![positionObject isKindOfClass:[NSDictionary class]]) { + [self safeAssignError:error code:MPNativePositionResponseJSONHasInvalidPositionData description:[NSString stringWithFormat:@"Position object is not a dictionary: %@.", positionObject]]; + return NO; + } + + // Section number is not required. If it's present, we have to check that it's non-negative; + // if it isn't there, we assign a section number of 0. + NSInteger section = [positionObject objectForKey:MPNativePositionResponseSectionKey] ? [self integerFromDictionary:positionObject forKey:MPNativePositionResponseSectionKey defaultValue:-1] : 0; + if (section < 0) { + [self safeAssignError:error code:MPNativePositionResponseJSONHasInvalidPositionData description:[NSString stringWithFormat:@"Position object has an invalid \"%@\" value or is not a positive number: %ld.", MPNativePositionResponseSectionKey, (long)section]]; + return NO; + } + + // Unlike section, position is required. It also must be a non-negative number. + NSInteger position = [self integerFromDictionary:positionObject forKey:MPNativePositionResponsePositionKey defaultValue:-1]; + if (position < 0) { + [self safeAssignError:error code:MPNativePositionResponseJSONHasInvalidPositionData description:[NSString stringWithFormat:@"Position object has an invalid \"%@\" value or is not a positive number: %ld.", MPNativePositionResponsePositionKey, (long)position]]; + return NO; + } + + return YES; +} + +- (BOOL)validateRepeatingIntervalObject:(id)repeatingIntervalObject error:(NSError **)error +{ + if (![repeatingIntervalObject isKindOfClass:[NSDictionary class]]) { + [self safeAssignError:error code:MPNativePositionResponseJSONHasInvalidPositionData description:[NSString stringWithFormat:@"Repeating interval object is not a dictionary: %@.", repeatingIntervalObject]]; + return NO; + } + + // The object must contain a value between MPMinRepeatingInterval and MPMaxRepeatingInterval. + NSInteger interval = [self integerFromDictionary:repeatingIntervalObject forKey:MPNativePositionResponseIntervalKey defaultValue:0]; + if (interval < MPMinRepeatingInterval || interval > MPMaxRepeatingInterval) { + [self safeAssignError:error code:MPNativePositionResponseJSONHasInvalidPositionData description:[NSString stringWithFormat:@"\"%@\" value in repeating interval object needs to be between %ld and %ld: %ld.", MPNativePositionResponseIntervalKey, (long)MPMinRepeatingInterval, (long)MPMaxRepeatingInterval, (long)interval]]; + return NO; + } + + return YES; +} + +#pragma mark - Dictionary helpers + +/** + * Returns an `NSInteger` value associated with a certain key in a dictionary, or a specified + * default value if the key is not associated with a valid integer representation. + * + * Valid integer representations include `NSNumber` objects and `NSString` objects that + * consist only of integer or sign characters. + * + * @param dictionary A dictionary containing keys and values. + * @param key The key for which to return an integer value. + * @param defaultValue A value that should be returned if `key` is not associated with an object + * that contains an integer representation. + * + * @return The integer value associated with `key`, or `defaultValue` if the object is not an + * `NSNumber` or an `NSString` representing an integer. + */ +- (NSInteger)integerFromDictionary:(NSDictionary *)dictionary forKey:(NSString *)key defaultValue:(NSInteger)defaultValue +{ + static NSCharacterSet *nonIntegerCharacterSet; + + id object = [dictionary objectForKey:key]; + + if ([object isKindOfClass:[NSNumber class]]) { + return [object integerValue]; + } else if ([object isKindOfClass:[NSString class]]) { + if (!nonIntegerCharacterSet) { + nonIntegerCharacterSet = [[NSCharacterSet characterSetWithCharactersInString:@"0123456789-"] invertedSet]; + } + + // If the string consists of all digits, we'll call -integerValue. Otherwise, return the + // default value. + if ([object rangeOfCharacterFromSet:nonIntegerCharacterSet].location == NSNotFound) { + return [object integerValue]; + } else { + return defaultValue; + } + } else { + return defaultValue; + } +} + +#pragma mark - Error helpers + +- (void)safeAssignError:(NSError **)error code:(MPNativePositionResponseDeserializationErrorCode)code userInfo:(NSDictionary *)userInfo +{ + if (error) { + *error = [self deserializationErrorWithCode:code userInfo:userInfo]; + } +} + +- (void)safeAssignError:(NSError **)error code:(MPNativePositionResponseDeserializationErrorCode)code description:(NSString *)description +{ + [self safeAssignError:error code:code description:description underlyingError:nil]; +} + +- (void)safeAssignError:(NSError **)error code:(MPNativePositionResponseDeserializationErrorCode)code description:(NSString *)description underlyingError:(NSError *)underlyingError +{ + NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; + + if (description) { + [userInfo setObject:description forKey:NSLocalizedDescriptionKey]; + } + + if (underlyingError) { + [userInfo setObject:underlyingError forKey:NSUnderlyingErrorKey]; + } + + [self safeAssignError:error code:code userInfo:userInfo]; +} + +- (NSError *)deserializationErrorWithCode:(MPNativePositionResponseDeserializationErrorCode)code userInfo:(NSDictionary *)userInfo +{ + return [NSError errorWithDomain:MPNativePositionResponseDeserializationErrorDomain + code:code + userInfo:userInfo]; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPNativePositionSource.h b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPNativePositionSource.h new file mode 100644 index 00000000000..f03593bcbee --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPNativePositionSource.h @@ -0,0 +1,26 @@ +// +// MPNativePositionSource.h +// MoPub +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import + +@class MPAdPositioning; + +typedef enum : NSUInteger { + MPNativePositionSourceInvalidAdUnitIdentifier, + MPNativePositionSourceEmptyResponse, + MPNativePositionSourceDeserializationFailed, + MPNativePositionSourceConnectionFailed, +} MPNativePositionSourceErrorCode; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface MPNativePositionSource : NSObject + +- (void)loadPositionsWithAdUnitIdentifier:(NSString *)identifier completionHandler:(void (^)(MPAdPositioning *positioning, NSError *error))completionHandler; +- (void)cancel; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPNativePositionSource.m b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPNativePositionSource.m new file mode 100644 index 00000000000..dfb662e5a44 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPNativePositionSource.m @@ -0,0 +1,169 @@ +// +// MPNativePositionSource.m +// MoPub +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import "MPNativePositionSource.h" +#import "MPConstants.h" +#import "MPIdentityProvider.h" +#import "MPAdPositioning.h" +#import "MPClientAdPositioning.h" +#import "MPLogging.h" +#import "MPNativePositionResponseDeserializer.h" +#import "MPAPIEndpoints.h" + +static NSString * const kPositioningSourceErrorDomain = @"com.mopub.iossdk.positioningsource"; +static const NSTimeInterval kMaximumRetryInterval = 60.0; +static const NSTimeInterval kMinimumRetryInterval = 1.0; +static const CGFloat kRetryIntervalBackoffMultiplier = 2.0; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface MPNativePositionSource () + +@property (nonatomic, assign) BOOL loading; +@property (nonatomic, copy) NSString *adUnitIdentifier; +@property (nonatomic, strong) NSURLConnection *connection; +@property (nonatomic, strong) NSMutableData *data; +@property (nonatomic, copy) void (^completionHandler)(MPAdPositioning *positioning, NSError *error); +@property (nonatomic, assign) NSTimeInterval maximumRetryInterval; +@property (nonatomic, assign) NSTimeInterval minimumRetryInterval; +@property (nonatomic, assign) NSTimeInterval retryInterval; +@property (nonatomic, assign) NSUInteger retryCount; + +- (NSURL *)serverURLWithAdUnitIdentifier:(NSString *)identifier; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation MPNativePositionSource + +- (id)init +{ + if (self) { + self.maximumRetryInterval = kMaximumRetryInterval; + self.minimumRetryInterval = kMinimumRetryInterval; + self.retryInterval = self.minimumRetryInterval; + } + return self; +} + +- (void)dealloc +{ + [_connection cancel]; +} + +#pragma mark - Public + +- (void)loadPositionsWithAdUnitIdentifier:(NSString *)identifier completionHandler:(void (^)(MPAdPositioning *positioning, NSError *error))completionHandler +{ + NSAssert(completionHandler != nil, @"A completion handler is required to load positions."); + + if (![identifier length]) { + NSError *invalidIDError = [NSError errorWithDomain:kPositioningSourceErrorDomain code:MPNativePositionSourceInvalidAdUnitIdentifier userInfo:nil]; + completionHandler(nil, invalidIDError); + return; + } + + self.adUnitIdentifier = identifier; + self.completionHandler = completionHandler; + self.retryCount = 0; + self.retryInterval = self.minimumRetryInterval; + + MPLogInfo(@"Requesting ad positions for native ad unit (%@).", identifier); + + NSURLRequest *request = [NSURLRequest requestWithURL:[self serverURLWithAdUnitIdentifier:identifier]]; + [self.connection cancel]; + [self.data setLength:0]; + self.connection = [NSURLConnection connectionWithRequest:request delegate:self]; +} + +- (void)cancel +{ + // Cancel any connection currently in flight. + [self.connection cancel]; + + // Cancel any queued retry requests. + [NSObject cancelPreviousPerformRequestsWithTarget:self]; +} + +#pragma mark - Internal + +- (NSURL *)serverURLWithAdUnitIdentifier:(NSString *)identifier +{ + NSString *URLString = [NSString stringWithFormat:@"%@?id=%@&v=%@&nsv=%@&udid=%@", + [MPAPIEndpoints baseURLStringWithPath:MOPUB_API_PATH_NATIVE_POSITIONING testing:NO], + identifier, + MP_SERVER_VERSION, + MP_SDK_VERSION, + [MPIdentityProvider identifier]]; + return [NSURL URLWithString:URLString]; +} + +- (void)retryLoadingPositions +{ + self.retryCount++; + + MPLogInfo(@"Retrying positions (retry attempt #%lu).", (unsigned long)self.retryCount); + + NSURLRequest *request = [NSURLRequest requestWithURL:[self serverURLWithAdUnitIdentifier:self.adUnitIdentifier]]; + [self.connection cancel]; + [self.data setLength:0]; + self.connection = [NSURLConnection connectionWithRequest:request delegate:self]; +} + +#pragma mark - + +- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error +{ + if (self.retryInterval >= self.maximumRetryInterval) { + self.completionHandler(nil, error); + self.completionHandler = nil; + } else { + [self performSelector:@selector(retryLoadingPositions) withObject:nil afterDelay:self.retryInterval]; + self.retryInterval = MIN(self.retryInterval * kRetryIntervalBackoffMultiplier, self.maximumRetryInterval); + } +} + +- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data +{ + if (!self.data) { + self.data = [NSMutableData data]; + } + + [self.data appendData:data]; +} + +- (void)connectionDidFinishLoading:(NSURLConnection *)connection +{ + NSError *deserializationError = nil; + MPClientAdPositioning *positioning = [[MPNativePositionResponseDeserializer deserializer] clientPositioningForData:self.data error:&deserializationError]; + + if (deserializationError) { + MPLogDebug(@"Position deserialization failed with error: %@", deserializationError); + + NSError *underlyingError = [[deserializationError userInfo] objectForKey:NSUnderlyingErrorKey]; + if ([underlyingError code] == MPNativePositionResponseDataIsEmpty) { + // Empty response data means the developer hasn't assigned any ad positions for the ad + // unit. No point in retrying the request. + self.completionHandler(nil, [NSError errorWithDomain:kPositioningSourceErrorDomain code:MPNativePositionSourceEmptyResponse userInfo:nil]); + self.completionHandler = nil; + } else if (self.retryInterval >= self.maximumRetryInterval) { + self.completionHandler(nil, deserializationError); + self.completionHandler = nil; + } else { + [self performSelector:@selector(retryLoadingPositions) withObject:nil afterDelay:self.retryInterval]; + self.retryInterval = MIN(self.retryInterval * kRetryIntervalBackoffMultiplier, self.maximumRetryInterval); + } + + return; + } + + self.completionHandler(positioning, nil); + self.completionHandler = nil; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPNativeView.h b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPNativeView.h new file mode 100644 index 00000000000..46b2c28825c --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPNativeView.h @@ -0,0 +1,24 @@ +// +// MPNativeView.h +// MoPubSDK +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import + +@protocol MPNativeViewDelegate; + +@interface MPNativeView : UIView + +@property (nonatomic, weak) id delegate; + +@end + +@protocol MPNativeViewDelegate + +@required + +- (void)nativeViewWillMoveToSuperview:(UIView *)superview; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPNativeView.m b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPNativeView.m new file mode 100644 index 00000000000..1ea7aa33bb2 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPNativeView.m @@ -0,0 +1,17 @@ +// +// MPNativeView.m +// MoPubSDK +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import "MPNativeView.h" + +@implementation MPNativeView + +- (void)willMoveToSuperview:(UIView *)superview +{ + [self.delegate nativeViewWillMoveToSuperview:superview]; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPStaticNativeAdImpressionTimer.h b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPStaticNativeAdImpressionTimer.h new file mode 100644 index 00000000000..f89d938061b --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPStaticNativeAdImpressionTimer.h @@ -0,0 +1,48 @@ +// +// MPStaticNativeAdImpressionTimer.h +// MoPubSDK +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import + +@protocol MPStaticNativeAdImpressionTimerDelegate; + +@interface MPStaticNativeAdImpressionTimer : NSObject + +@property (nonatomic, weak) id delegate; + +/* + * Initializes and returns an object that will tell its delegate when the a certain percentage of the view + * has been visible for a given amount of time. + * + * visibilityPercentage is a float between 0.0 and 1.0. For example, 0.7 represents 70%. + */ + +- (instancetype)initWithRequiredSecondsForImpression:(NSTimeInterval)requiredSecondsForImpression requiredViewVisibilityPercentage:(CGFloat)visibilityPercentage; + +/* + * Begins monitoring the view to meet the impression tracking criteria set up in -initWithRequiredSecondsForImpression:requiredViewVisibilityPercentage:. + * + * The current visibility duration is not reset when calling this method. If the first view + * was visible for 1 second, the internal state will maintain that the view its tracking has + * been visible for 1 second even if this method is called again with a different view. + */ + +- (void)startTrackingView:(UIView *)view; + +@end + +@protocol MPStaticNativeAdImpressionTimerDelegate + +@required + +/* + * Called when the required visibility time has been satisfied. This delegate method + * will only be called once for the lifetime of this object. + */ + +- (void)trackImpression; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPStaticNativeAdImpressionTimer.m b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPStaticNativeAdImpressionTimer.m new file mode 100644 index 00000000000..32d5fa5eb25 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPStaticNativeAdImpressionTimer.m @@ -0,0 +1,80 @@ +// +// MPStaticNativeAdImpressionTimer.m +// MoPubSDK +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import "MPStaticNativeAdImpressionTimer.h" + +#import "MPTimer.h" +#import "MPGlobal.h" +#import "MPNativeAdConstants.h" + +static const CGFloat kImpressionTimerInterval = 0.25; +static const NSTimeInterval kFirstVisibilityTimestampNone = -1; + +@interface MPStaticNativeAdImpressionTimer () + +@property (nonatomic) UIView *adView; +@property (nonatomic) MPTimer *viewVisibilityTimer; +@property (nonatomic, assign) NSTimeInterval firstVisibilityTimestamp; +@property (nonatomic, assign) CGFloat requiredViewVisibilityPercentage; +@property (nonatomic, readonly) NSTimeInterval requiredSecondsForImpression; + +@end + +@implementation MPStaticNativeAdImpressionTimer + +- (instancetype)initWithRequiredSecondsForImpression:(NSTimeInterval)requiredSecondsForImpression requiredViewVisibilityPercentage:(CGFloat)visibilityPercentage +{ + if (self = [super init]) { + _viewVisibilityTimer = [MPTimer timerWithTimeInterval:kImpressionTimerInterval target:self selector:@selector(tick:) repeats:YES]; + _viewVisibilityTimer.runLoopMode = NSRunLoopCommonModes; + _requiredSecondsForImpression = requiredSecondsForImpression; + _requiredViewVisibilityPercentage = visibilityPercentage; + _firstVisibilityTimestamp = kFirstVisibilityTimestampNone; + } + + return self; +} + +- (void)dealloc +{ + [_viewVisibilityTimer invalidate]; + _delegate = nil; + _viewVisibilityTimer = nil; +} + +- (void)startTrackingView:(UIView *)view +{ + self.adView = view; + + if (!self.viewVisibilityTimer.isScheduled) { + [self.viewVisibilityTimer scheduleNow]; + } +} + +- (void)tick:(NSTimer *)timer +{ + BOOL visible = MPViewIsVisible(self.adView) && MPViewIntersectsParentWindowWithPercent(self.adView, self.requiredViewVisibilityPercentage) && ([[UIApplication sharedApplication] applicationState] == UIApplicationStateActive); + + if (visible) { + NSTimeInterval now = [[NSDate date] timeIntervalSinceReferenceDate]; + + if (self.firstVisibilityTimestamp == kFirstVisibilityTimestampNone) { + self.firstVisibilityTimestamp = now; + } else if (now - self.firstVisibilityTimestamp >= self.requiredSecondsForImpression) { + // Invalidate the timer and tell the delegate it should track an impression. + self.firstVisibilityTimestamp = kFirstVisibilityTimestampNone; + [self.viewVisibilityTimer invalidate]; + self.viewVisibilityTimer = nil; + + [self.delegate trackImpression]; + } + } else { + self.firstVisibilityTimestamp = kFirstVisibilityTimestampNone; + } +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPTableViewAdPlacerCell.h b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPTableViewAdPlacerCell.h new file mode 100644 index 00000000000..bebf859e493 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPTableViewAdPlacerCell.h @@ -0,0 +1,12 @@ +// +// MPTableViewAdPlacerCell.h +// MoPubSDK +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import + +@interface MPTableViewAdPlacerCell : UITableViewCell + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPTableViewAdPlacerCell.m b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPTableViewAdPlacerCell.m new file mode 100644 index 00000000000..4f6489e0d71 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPTableViewAdPlacerCell.m @@ -0,0 +1,12 @@ +// +// MPTableViewAdPlacerCell.m +// MoPubSDK +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import "MPTableViewAdPlacerCell.h" + +@implementation MPTableViewAdPlacerCell + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPTableViewCellImpressionTracker.h b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPTableViewCellImpressionTracker.h new file mode 100644 index 00000000000..b7baa85768b --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPTableViewCellImpressionTracker.h @@ -0,0 +1,23 @@ +// +// MPTableViewCellImpressionTracker.h +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import +#import + +@protocol MPTableViewCellImpressionTrackerDelegate; + +@interface MPTableViewCellImpressionTracker : NSObject + +- (id)initWithTableView:(UITableView *)tableView delegate:(id)delegate; +- (void)startTracking; +- (void)stopTracking; + +@end + +@protocol MPTableViewCellImpressionTrackerDelegate + +- (void)tracker:(MPTableViewCellImpressionTracker *)tracker didDetectVisibleRowsAtIndexPaths:(NSArray *)indexPaths; + +@end \ No newline at end of file diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPTableViewCellImpressionTracker.m b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPTableViewCellImpressionTracker.m new file mode 100644 index 00000000000..37bc44b9dc7 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/Internal/MPTableViewCellImpressionTracker.m @@ -0,0 +1,82 @@ +// +// MPTableViewCellImpressionTracker.m +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import "MPTableViewCellImpressionTracker.h" + +@interface MPTableViewCellImpressionTracker () + +@property (nonatomic, strong) UITableView *tableView; +@property (nonatomic, weak) id delegate; +@property (nonatomic, strong) NSTimer *timer; + +@end + +#define MPTableViewCellImpressionTrackerTimeInterval 0.25 + +@implementation MPTableViewCellImpressionTracker + +- (id)initWithTableView:(UITableView *)tableView delegate:(id)delegate +{ + self = [super init]; + if (self) { + _tableView = tableView; + _delegate = delegate; + } + return self; +} + +- (void)dealloc +{ + [_timer invalidate]; +} + +- (void)startTracking +{ + [self.timer invalidate]; + self.timer = [NSTimer timerWithTimeInterval:MPTableViewCellImpressionTrackerTimeInterval target:self selector:@selector(tick:) userInfo:nil repeats:YES]; + + [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes]; +} + +- (void)stopTracking +{ + [self.timer invalidate]; + self.timer = nil; +} + +#pragma mark - Internal + +- (void)tick:(NSTimer *)timer +{ + NSMutableArray *indexPathsForVisibleRows = [[self.tableView indexPathsForVisibleRows] mutableCopy]; + NSUInteger rowCount = [indexPathsForVisibleRows count]; + + // For our purposes, "visible" means that more than half of the cell is on-screen. + // Filter -indexPathsForVisibleRows to fit this definition. + if (rowCount > 1) { + NSIndexPath *firstVisibleRow = [indexPathsForVisibleRows objectAtIndex:0]; + if (![self isMajorityOfCellAtIndexPathVisible:firstVisibleRow]) { + [indexPathsForVisibleRows removeObjectAtIndex:0]; + } + + NSIndexPath *lastVisibleRow = [indexPathsForVisibleRows lastObject]; + if (![self isMajorityOfCellAtIndexPathVisible:lastVisibleRow]) { + [indexPathsForVisibleRows removeLastObject]; + } + } + + if ([indexPathsForVisibleRows count]) { + [self.delegate tracker:self didDetectVisibleRowsAtIndexPaths:[NSArray arrayWithArray:indexPathsForVisibleRows]]; + } +} + +- (BOOL)isMajorityOfCellAtIndexPathVisible:(NSIndexPath *)indexPath +{ + CGRect cellRect = [self.tableView rectForRowAtIndexPath:indexPath]; + CGPoint cellRectMidY = CGPointMake(0, CGRectGetMidY(cellRect)); + return CGRectContainsPoint(self.tableView.bounds, cellRectMidY); +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/MPAdPositioning.h b/iphone/Maps/3party/MoPubSDK/Native Ads/MPAdPositioning.h new file mode 100644 index 00000000000..805f97ee434 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/MPAdPositioning.h @@ -0,0 +1,15 @@ +// +// MPAdPositioning.h +// MoPub +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import + +@interface MPAdPositioning : NSObject + +@property (nonatomic, assign) NSUInteger repeatingInterval; +@property (nonatomic, strong, readonly) NSMutableOrderedSet *fixedPositions; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/MPAdPositioning.m b/iphone/Maps/3party/MoPubSDK/Native Ads/MPAdPositioning.m new file mode 100644 index 00000000000..7d75027644c --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/MPAdPositioning.m @@ -0,0 +1,39 @@ +// +// MPAdPositioning.m +// MoPub +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import "MPAdPositioning.h" + +@interface MPAdPositioning () + +@property (nonatomic, strong) NSMutableOrderedSet *fixedPositions; + +@end + +@implementation MPAdPositioning + +- (id)init +{ + self = [super init]; + if (self) { + _fixedPositions = [[NSMutableOrderedSet alloc] init]; + } + + return self; +} + + +#pragma mark - + +- (id)copyWithZone:(NSZone *)zone +{ + MPAdPositioning *newPositioning = [[[self class] allocWithZone:zone] init]; + newPositioning.fixedPositions = [self.fixedPositions copyWithZone:zone]; + newPositioning.repeatingInterval = self.repeatingInterval; + return newPositioning; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/MPClientAdPositioning.h b/iphone/Maps/3party/MoPubSDK/Native Ads/MPClientAdPositioning.h new file mode 100644 index 00000000000..7bfa7a3c014 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/MPClientAdPositioning.h @@ -0,0 +1,64 @@ +// +// MPClientAdPositioning.h +// MoPub +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import +#import "MPAdPositioning.h" + +/** + * The `MPClientAdPositioning` class is a model that allows you to control the positions where + * native advertisements should appear within a stream. A positioning object works in conjunction + * with an ad placer, giving the ad placer the information it needs to configure the positions and + * frequency of ads. You can specify that ads should appear at fixed index paths and/or at equally + * spaced intervals throughout your content. + * + * Unlike with `MPServerAdPositioning`, which tells an ad placer to obtain its positioning + * information from the MoPub ad server, client ad positioning does not allow you to control your ad + * positions via the MoPub website. + */ + +@interface MPClientAdPositioning : MPAdPositioning + +/** @name Creating a Client Positioning Object */ + +/** + * Creates and returns an empty positioning object. In order for ads to display in a stream, the + * positioning object must either have at least one fixed position or have repeating positions + * enabled. + * + * @return The newly created positioning object. + */ ++ (instancetype)positioning; + +/** + * Tells the positioning object that an ad should be placed at the specified position. + * + * Positions are passed in as absolute index paths within a stream. For example, if you place an + * ad in a table view at a fixed index path with row 1, an ad will appear in row 1, which may shift + * other content items to higher row indexes. + * + * Note: this method uses `NSIndexPath` objects to accommodate streams with multiple sections. If + * your stream does not contain multiple sections, you should pass in index paths with a section + * value of 0. + * + * @param indexPath An index path representing a position for an ad. + */ +- (void)addFixedIndexPath:(NSIndexPath *)indexPath; + +/** + * Tells the positioning object that ads should be displayed evenly throughout a stream using the + * specified interval. + * + * Repeating ads will only appear within a single section. If the receiver has fixed positions, + * the sequence of repeating ads will start to appear following the last registered fixed position. + * If the receiver does not have any fixed positions, ads will appear regularly starting at + * `interval`, within the first section. + * + * @param interval The frequency at which to display ads. This must be a value greater than 1. + */ +- (void)enableRepeatingPositionsWithInterval:(NSUInteger)interval; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/MPClientAdPositioning.m b/iphone/Maps/3party/MoPubSDK/Native Ads/MPClientAdPositioning.m new file mode 100644 index 00000000000..00310356d7a --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/MPClientAdPositioning.m @@ -0,0 +1,33 @@ +// +// MPClientAdPositioning.m +// MoPub +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import "MPClientAdPositioning.h" +#import "MPLogging.h" + +@implementation MPClientAdPositioning + ++ (instancetype)positioning +{ + return [[self alloc] init]; +} + +- (void)addFixedIndexPath:(NSIndexPath *)indexPath +{ + [self.fixedPositions addObject:indexPath]; +} + +- (void)enableRepeatingPositionsWithInterval:(NSUInteger)interval +{ + if (interval > 1) { + self.repeatingInterval = interval; + } else { + MPLogWarn(@"Repeating positions will not be enabled: the provided interval must be greater " + @"than 1."); + } +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/MPCollectionViewAdPlacer.h b/iphone/Maps/3party/MoPubSDK/Native Ads/MPCollectionViewAdPlacer.h new file mode 100644 index 00000000000..2e0ad58d9e3 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/MPCollectionViewAdPlacer.h @@ -0,0 +1,387 @@ +// +// MPCollectionViewAdPlacer.h +// MoPub +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import +#import +#import "MPClientAdPositioning.h" +#import "MPServerAdPositioning.h" + +@class MPNativeAdRequestTargeting; +@protocol MPCollectionViewAdPlacerDelegate; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * The `MPCollectionViewAdPlacer` class allows you to request native ads from the MoPub ad server + * and place them into a `UICollectionView` object. + * + * When an instance of this class is initialized with a collection view, it wraps the collection + * view's data source and delegate in order to insert ads and adjust the positions of your regular + * content cells. + */ + +@interface MPCollectionViewAdPlacer : NSObject + +@property (nonatomic, weak) id delegate; + +/** @name Initializing a Collection View Ad Placer */ + +/** + * Creates and returns an ad placer that will insert ads into a collection view at positions that can + * be configured dynamically on the MoPub website. + * + * When you make an ad request, the ad placer will ask the MoPub ad server for the positions where + * ads should be inserted into the collection view. You can configure these positioning values by + * editing your ad unit's settings on the MoPub website. + * + * Using this method is equivalent to calling + * +placerWithCollectionView:viewController:adPositioning:defaultAdRenderingClass: and passing in an + * `MPServerAdPositioning` object as the `positioning` parameter. + * + * @param collectionView The collection view in which to insert ads. + * @param controller The view controller which should be used to present modal content. + * @param rendererConfigurations An array of MPNativeAdRendererConfiguration objects that control how + * the native ad is rendered. + * + * @return An `MPCollectionViewAdPlacer` object. + */ ++ (instancetype)placerWithCollectionView:(UICollectionView *)collectionView viewController:(UIViewController *)controller rendererConfigurations:(NSArray *)rendererConfigurations; + +/** + * Creates and returns an ad placer that will insert ads into a collection view. + * + * When using this method, there are two options for controlling the positions where ads appear + * within the collection view. + * + * First, you may pass an `MPServerAdPositioning` object as the `positioning` parameter, which tells + * the ad placer to obtain positioning information dynamically from the ad server, which you can + * configure on the MoPub website. In many cases, this is the preferred approach, since it allows + * you to modify the positions without rebuilding your application. Note that calling the + * convenience method +placerWithCollectionView:viewController:defaultAdRenderingClass: accomplishes + * this as well. + * + * Alternatively, if you wish to hard-code your positions, you may pass an `MPClientAdPositioning` + * object instead. + * + * @param collectionView The collection view in which to insert ads. + * @param controller The view controller which should be used to present modal content. + * @param positioning The positioning object that specifies where ads should be shown in the stream. + * @param rendererConfigurations An array of MPNativeAdRendererConfiguration objects that control how + * the native ad is rendered. + * + * @return An `MPCollectionViewAdPlacer` object. + */ ++ (instancetype)placerWithCollectionView:(UICollectionView *)collectionView viewController:(UIViewController *)controller adPositioning:(MPAdPositioning *)positioning rendererConfigurations:(NSArray *)rendererConfigurations; + +/** @name Requesting Ads */ + +/** + * Requests ads from the MoPub ad server using the specified ad unit ID. + * + * @param adUnitID A string representing a MoPub ad unit ID. + */ +- (void)loadAdsForAdUnitID:(NSString *)adUnitID; + +/** + * Requests ads from the MoPub ad server using the specified ad unit ID and targeting parameters. + * + * @param adUnitID A string representing a MoPub ad unit ID. + * @param targeting An object containing targeting information, such as geolocation data. + */ +- (void)loadAdsForAdUnitID:(NSString *)adUnitID targeting:(MPNativeAdRequestTargeting *)targeting; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * The MoPub SDK adds interfaces to the `UICollectionView` class to help your application with + * responsibilities related to `MPCollectionViewAdPlacer`. These APIs include methods to help notify + * the ad placer of all modifications to the original collection view, as well as to simplify your + * application code such that it does not need to perform index path manipulations to account for + * the presence of ads. + * + * Since the ad placer replaces the original data source and delegate objects of your collection + * view, the SDK also provides new methods for you to set these properties such that the ad placer + * remains aware of the changes. + */ + +@interface UICollectionView (MPCollectionViewAdPlacer) + +- (void)mp_setAdPlacer:(MPCollectionViewAdPlacer *)placer; + +/** @name Obtaining the Collection View Ad Placer */ + +/** + * Returns the ad placer currently being used for this collection view. + * + * @return An ad placer object or `nil` if no ad placer is being used. + */ +- (MPCollectionViewAdPlacer *)mp_adPlacer; + +/** @name Setting and Getting the Delegate and Data Source */ + +/** + * Sets the collection view's data source. + * + * If your application needs to change a collection view's data source after it has instantiated an + * ad placer using that collection view, use this method rather than + * -[UICollectionView setDataSource:]. + * + * @param dataSource The new collection view data source. + */ +- (void)mp_setDataSource:(id)dataSource; + +/** + * Returns the original data source of the collection view. + * + * When you instantiate an ad placer using a collection view, the ad placer replaces the collection + * view's original data source object. If your application needs to access the original data source, + * use this method instead of -[UICollectionView dataSource]. + * + * @return The original collection view data source. + */ +- (id)mp_dataSource; + +/** + * Sets the collection view's delegate. + * + * If your application needs to change a collection view's delegate after it has instantiated an ad + * placer using that collection view, use this method rather than -[UICollectionView setDelegate:]. + * + * @param delegate The new collection view delegate. + */ +- (void)mp_setDelegate:(id)delegate; + +/** + * Returns the original delegate of the collection view. + * + * When you instantiate an ad placer using a collection view, the ad placer replaces the collection + * view's original delegate object. If your application needs to access the original delegate, use + * this method instead of -[UICollectionView delegate]. + * + * @return The original collection view delegate. + */ +- (id)mp_delegate; + +/** @name Notifying the Collection View Ad Placer of Content Changes */ + +/** + * Reloads all of the data for the collection view. + */ +- (void)mp_reloadData; + +/** + * Inserts new items at the specified index paths, and informs the attached ad placer of the + * insertions. + * + * @param indexPaths An array of `NSIndexPath` objects, each of which contains a section index and + * item index at which to insert a new cell. This parameter must not be `nil`. + */ +- (void)mp_insertItemsAtIndexPaths:(NSArray *)indexPaths; + +/** + * Deletes the items at the specified index paths, and informs the attached ad placer of the + * deletions. + * + * @param indexPaths An array of `NSIndexPath` objects, each of which contains a section index and + * item index for the item you want to delete from the collection view. This parameter must not be + * `nil`. + */ +- (void)mp_deleteItemsAtIndexPaths:(NSArray *)indexPaths; + +/** + * Reloads just the items at the specified index paths. + * + * @param indexPaths An array of `NSIndexPath` objects identifying the items you want to update. + */ +- (void)mp_reloadItemsAtIndexPaths:(NSArray *)indexPaths; + +/** + * Moves an item from one location to another in the collection view, taking into account ads + * inserted by the ad placer. + * + * @param indexPath The index path of the item you want to move. This parameter must not be + * `nil`. + * @param newIndexPath The index path of the item’s new location. This parameter must not be `nil`. + */ +- (void)mp_moveItemAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath; + +/** + * Inserts new sections at the specified indexes, and informs the attached ad placer of the + * insertions. + * + * @param sections An index set containing the indexes of the sections you want to insert. This + * parameter must not be `nil`. + */ +- (void)mp_insertSections:(NSIndexSet *)sections; + +/** + * Deletes the sections at the specified indexes, and informs the attached ad placer of the + * deletions. + * + * @param sections The indexes of the sections you want to delete. This parameter must not be `nil`. + */ +- (void)mp_deleteSections:(NSIndexSet *)sections; + +/** + * Reloads the data in the specified sections of the collection view, and informs the attached ad + * placer that sections may have changed. + * + * @param sections The indexes of the sections to reload. + */ +- (void)mp_reloadSections:(NSIndexSet *)sections; + +/** + * Moves a section from one location to another in the collection view, and informs the attached ad + * placer. + * + * @param section The index path of the section you want to move. This parameter must not be + * `nil`. + * @param newSection The index path of the section’s new location. This parameter must not be `nil`. + */ +- (void)mp_moveSection:(NSInteger)section toSection:(NSInteger)newSection; + +/** @name Methods Involving Index Paths */ + +/** + * Returns the visible cell object at the specified index path. + * + * @param indexPath The index path that specifies the section and item number of the cell. + * + * @return The cell object at the corresponding index path or `nil` if the cell is not visible or + * *indexPath* is out of range. + */ +- (UICollectionViewCell *)mp_cellForItemAtIndexPath:(NSIndexPath *)indexPath; + +/** + * Returns a reusable cell object located by its identifier. + * + * @param identifier The reuse identifier for the specified cell. This parameter must not be `nil`. + * @param indexPath The index path specifying the location of the cell. The data source receives + * this information when it is asked for the cell and should just pass it along. This method uses + * the index path to perform additional configuration based on the cell’s position in the collection + * view. + * + * @return A valid `UICollectionReusableView` object. + */ +- (id)mp_dequeueReusableCellWithReuseIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath*)indexPath; + +/** + * Deselects the item at the specified index. + * + * @param indexPath The index path of the item to select. Specifying `nil` results in no change to + * the current selection. + * @param animated Specify YES to animate the change in the selection or NO to make the change + * without animating it. + */ +- (void)mp_deselectItemAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated; + +/** + * Returns the original index path for the given cell or `nil` if the cell is an ad cell. + * + * @param cell The cell object whose index path you want. + * + * @return The index path of the cell or `nil` if the specified cell contains an ad or is not in the + * collection view. + */ +- (NSIndexPath *)mp_indexPathForCell:(UICollectionViewCell *)cell; + +/** + * Returns the index path of the item at the specified point in the collection view. + * + * @param point A point in the collection view’s coordinate system. + * + * @return The index path of the item at the specified point or `nil` if either an ad or no item was + * found at the specified point. + */ +- (NSIndexPath *)mp_indexPathForItemAtPoint:(CGPoint)point; + +/** + * Returns the original index paths (as if no ads were inserted) for the selected items. + * + * @return An array of the original index paths for the selected items. + */ +- (NSArray *)mp_indexPathsForSelectedItems; + +/** + * Returns an array of original index paths each identifying a visible non-ad item in the collection + * view, calculated before any ads were inserted. + * + * @return An array of the original index paths representing visible non-ad items in the collection + * view. Returns `nil` if no items are visible. + */ +- (NSArray *)mp_indexPathsForVisibleItems; + +/** + * Returns the layout information for the item at the specified index path. + * + * @param indexPath The index path of the item. + * + * @return The layout attributes for the item or `nil` if no item exists at the specified path. + */ +- (UICollectionViewLayoutAttributes *)mp_layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath; + +/** + * Scrolls the collection view contents until the specified item is visible. + * + * @param indexPath The index path of the item to scroll into view. + * @param scrollPosition An option that specifies where the item should be positioned when scrolling + * finishes. + * @param animated Specify YES to animate the scrolling behavior or NO to adjust the scroll + * view’s visible content immediately. + */ +- (void)mp_scrollToItemAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(UICollectionViewScrollPosition)scrollPosition animated:(BOOL)animated; + +/** + * Selects the item at the specified index path and optionally scrolls it into view. + * + * @param indexPath The index path of the item to select. Specifying `nil` for this parameter + * clears the current selection. + * @param animated Specify YES to animate the change in the selection or NO to make the change + * without animating it. + * @param scrollPosition An option that specifies where the item should be positioned when scrolling + * finishes. + */ +- (void)mp_selectItemAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated scrollPosition:(UICollectionViewScrollPosition)scrollPosition; + +/** + * Returns an array of the non-ad cells that are visible in the collection view. + * + * @return An array of `UICollectionViewCell` objects, each representing a visible, non-ad cell in + * the receiving collection view. + */ +- (NSArray *)mp_visibleCells; + +@end + +@protocol MPCollectionViewAdPlacerDelegate + +@optional + +/* + * This method is called when a native ad, placed by the collection view ad placer, will present a modal view controller. + * + * @param placer The collection view ad placer that contains the ad displaying the modal. + */ +-(void)nativeAdWillPresentModalForCollectionViewAdPlacer:(MPCollectionViewAdPlacer *)placer; + +/* + * This method is called when a native ad, placed by the collection view ad placer, did dismiss its modal view controller. + * + * @param placer The collection view ad placer that contains the ad that dismissed the modal. + */ +-(void)nativeAdDidDismissModalForCollectionViewAdPlacer:(MPCollectionViewAdPlacer *)placer; + +/* + * This method is called when a native ad, placed by the collection view ad placer, will cause the app to background due to user interaction with the ad. + * + * @param placer The collection view ad placer that contains the ad causing the app to background. + */ +-(void)nativeAdWillLeaveApplicationFromCollectionViewAdPlacer:(MPCollectionViewAdPlacer *)placer; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/MPCollectionViewAdPlacer.m b/iphone/Maps/3party/MoPubSDK/Native Ads/MPCollectionViewAdPlacer.m new file mode 100644 index 00000000000..fc33d2454bd --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/MPCollectionViewAdPlacer.m @@ -0,0 +1,652 @@ +// +// MPCollectionViewAdPlacer.m +// MoPub +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import "MPCollectionViewAdPlacer.h" +#import "MPStreamAdPlacer.h" +#import "MPInstanceProvider.h" +#import "MPAdPlacerInvocation.h" +#import "MPTimer.h" +#import "MPNativeAdUtils.h" +#import "MPCollectionViewAdPlacerCell.h" +#import "MPNativeAdRendererConfiguration.h" +#import + +static NSString * const kCollectionViewAdPlacerReuseIdentifier = @"MPCollectionViewAdPlacerReuseIdentifier"; + +@protocol MPNativeAdRenderer; + +@interface MPCollectionViewAdPlacer () + +@property (nonatomic, strong) MPStreamAdPlacer *streamAdPlacer; +@property (nonatomic, strong) UICollectionView *collectionView; +@property (nonatomic, weak) id originalDataSource; +@property (nonatomic, weak) id originalDelegate; +@property (nonatomic, strong) MPTimer *insertionTimer; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation MPCollectionViewAdPlacer + ++ (instancetype)placerWithCollectionView:(UICollectionView *)collectionView viewController:(UIViewController *)controller rendererConfigurations:(NSArray *)rendererConfigurations +{ + return [[self class] placerWithCollectionView:collectionView viewController:controller adPositioning:[MPServerAdPositioning positioning] rendererConfigurations:rendererConfigurations]; +} + ++ (instancetype)placerWithCollectionView:(UICollectionView *)collectionView viewController:(UIViewController *)controller adPositioning:(MPAdPositioning *)positioning rendererConfigurations:(NSArray *)rendererConfigurations +{ + MPCollectionViewAdPlacer *collectionViewAdPlacer = [[MPCollectionViewAdPlacer alloc] initWithCollectionView:collectionView viewController:controller adPositioning:positioning rendererConfigurations:rendererConfigurations]; + return collectionViewAdPlacer; +} + +- (instancetype)initWithCollectionView:(UICollectionView *)collectionView viewController:(UIViewController *)controller adPositioning:(MPAdPositioning *)positioning rendererConfigurations:(NSArray *)rendererConfigurations +{ + for (id rendererConfiguration in rendererConfigurations) { + NSAssert([rendererConfiguration isKindOfClass:[MPNativeAdRendererConfiguration class]], @"A collection view ad placer must be instantiated with rendererConfigurations that are of type MPNativeAdRendererConfiguration."); + } + + if (self = [super init]) { + _collectionView = collectionView; + _streamAdPlacer = [[MPInstanceProvider sharedProvider] buildStreamAdPlacerWithViewController:controller adPositioning:positioning rendererConfigurations:rendererConfigurations]; + _streamAdPlacer.delegate = self; + + _insertionTimer = [MPTimer timerWithTimeInterval:kUpdateVisibleCellsInterval target:self selector:@selector(updateVisibleCells) repeats:YES]; + _insertionTimer.runLoopMode = NSRunLoopCommonModes; + [_insertionTimer scheduleNow]; + + _originalDataSource = collectionView.dataSource; + _originalDelegate = collectionView.delegate; + collectionView.dataSource = self; + collectionView.delegate = self; + + [_collectionView registerClass:[MPCollectionViewAdPlacerCell class] forCellWithReuseIdentifier:kCollectionViewAdPlacerReuseIdentifier]; + + [collectionView mp_setAdPlacer:self]; + } + + return self; +} + +- (void)dealloc +{ + [_insertionTimer invalidate]; +} + +#pragma mark - Public + +- (void)loadAdsForAdUnitID:(NSString *)adUnitID +{ + [self.streamAdPlacer loadAdsForAdUnitID:adUnitID]; +} + +- (void)loadAdsForAdUnitID:(NSString *)adUnitID targeting:(MPNativeAdRequestTargeting *)targeting +{ + [self.streamAdPlacer loadAdsForAdUnitID:adUnitID targeting:targeting]; +} + +#pragma mark - Ad Insertion + +- (void)updateVisibleCells +{ + NSArray *visiblePaths = self.collectionView.mp_indexPathsForVisibleItems; + + if ([visiblePaths count]) { + [self.streamAdPlacer setVisibleIndexPaths:visiblePaths]; + } +} + +#pragma mark - + +- (void)adPlacer:(MPStreamAdPlacer *)adPlacer didLoadAdAtIndexPath:(NSIndexPath *)indexPath +{ + BOOL animationsWereEnabled = [UIView areAnimationsEnabled]; + //We only want to enable animations if the index path is before or within our visible cells + BOOL animationsEnabled = ([(NSIndexPath *)[self.collectionView.indexPathsForVisibleItems lastObject] compare:indexPath] != NSOrderedAscending) && animationsWereEnabled; + + [UIView setAnimationsEnabled:animationsEnabled]; + + [self.collectionView insertItemsAtIndexPaths:@[indexPath]]; + + [UIView setAnimationsEnabled:animationsWereEnabled]; +} + +- (void)adPlacer:(MPStreamAdPlacer *)adPlacer didRemoveAdsAtIndexPaths:(NSArray *)indexPaths +{ + BOOL animationsWereEnabled = [UIView areAnimationsEnabled]; + [UIView setAnimationsEnabled:NO]; + + [self.collectionView performBatchUpdates:^{ + [self.collectionView deleteItemsAtIndexPaths:indexPaths]; + } completion:^(BOOL finished) { + [UIView setAnimationsEnabled:animationsWereEnabled]; + }]; +} + +- (void)nativeAdWillPresentModalForStreamAdPlacer:(MPStreamAdPlacer *)adPlacer +{ + if ([self.delegate respondsToSelector:@selector(nativeAdWillPresentModalForCollectionViewAdPlacer:)]) { + [self.delegate nativeAdWillPresentModalForCollectionViewAdPlacer:self]; + } +} + +- (void)nativeAdDidDismissModalForStreamAdPlacer:(MPStreamAdPlacer *)adPlacer +{ + if ([self.delegate respondsToSelector:@selector(nativeAdDidDismissModalForCollectionViewAdPlacer:)]) { + [self.delegate nativeAdDidDismissModalForCollectionViewAdPlacer:self]; + } +} + +- (void)nativeAdWillLeaveApplicationFromStreamAdPlacer:(MPStreamAdPlacer *)adPlacer +{ + if ([self.delegate respondsToSelector:@selector(nativeAdWillLeaveApplicationFromCollectionViewAdPlacer:)]) { + [self.delegate nativeAdWillLeaveApplicationFromCollectionViewAdPlacer:self]; + } +} + +#pragma mark - + +- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView +{ + if ([self.originalDataSource respondsToSelector:@selector(numberOfSectionsInCollectionView:)]) { + return [self.originalDataSource numberOfSectionsInCollectionView:collectionView]; + } + else { + return 1; + } +} + +- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section +{ + NSUInteger numberOfItems = [self.originalDataSource collectionView:collectionView numberOfItemsInSection:section]; + [self.streamAdPlacer setItemCount:numberOfItems forSection:section]; + return [self.streamAdPlacer adjustedNumberOfItems:numberOfItems inSection:section]; +} + +- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath +{ + if ([self.streamAdPlacer isAdAtIndexPath:indexPath]) { + MPCollectionViewAdPlacerCell *cell = (MPCollectionViewAdPlacerCell *)[collectionView dequeueReusableCellWithReuseIdentifier:kCollectionViewAdPlacerReuseIdentifier forIndexPath:indexPath]; + cell.clipsToBounds = YES; + + [self.streamAdPlacer renderAdAtIndexPath:indexPath inView:cell.contentView]; + return cell; + } + + NSIndexPath *originalIndexPath = [self.streamAdPlacer originalIndexPathForAdjustedIndexPath:indexPath]; + return [self.originalDataSource collectionView:collectionView cellForItemAtIndexPath:originalIndexPath]; +} + +#pragma mark - + +- (BOOL)collectionView:(UICollectionView *)collectionView canPerformAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender +{ + if ([self.streamAdPlacer isAdAtIndexPath:indexPath]) { + return NO; + } + + id delegate = self.originalDelegate; + if ([delegate respondsToSelector:@selector(collectionView:canPerformAction:forItemAtIndexPath:withSender:)]) { + NSIndexPath *originalPath = [self.streamAdPlacer originalIndexPathForAdjustedIndexPath:indexPath]; + return [delegate collectionView:collectionView canPerformAction:action forItemAtIndexPath:originalPath withSender:sender]; + } + + return NO; +} + +- (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath +{ + [MPAdPlacerInvocation invokeForTarget:self.originalDelegate with2ArgSelector:@selector(collectionView:didDeselectItemAtIndexPath:) firstArg:collectionView secondArg:indexPath streamAdPlacer:self.streamAdPlacer]; +} + +- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath +{ + [MPAdPlacerInvocation invokeForTarget:self.originalDelegate with3ArgSelector:@selector(collectionView:didEndDisplayingCell:forItemAtIndexPath:) firstArg:collectionView secondArg:cell thirdArg:indexPath streamAdPlacer:self.streamAdPlacer]; +} + +- (void)collectionView:(UICollectionView *)collectionView didHighlightItemAtIndexPath:(NSIndexPath *)indexPath +{ + [MPAdPlacerInvocation invokeForTarget:self.originalDelegate with2ArgSelector:@selector(collectionView:didHighlightItemAtIndexPath:) firstArg:collectionView secondArg:indexPath streamAdPlacer:self.streamAdPlacer]; +} + +- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath +{ + if ([self.streamAdPlacer isAdAtIndexPath:indexPath]) { + // The view inside the cell already has a gesture recognizer to handle the tap event. + [self.collectionView deselectItemAtIndexPath:indexPath animated:NO]; + return; + } + + id delegate = self.originalDelegate; + if ([delegate respondsToSelector:@selector(collectionView:didSelectItemAtIndexPath:)]) { + NSIndexPath *originalPath = [self.streamAdPlacer originalIndexPathForAdjustedIndexPath:indexPath]; + [delegate collectionView:collectionView didSelectItemAtIndexPath:originalPath]; + } +} + +- (void)collectionView:(UICollectionView *)collectionView didUnhighlightItemAtIndexPath:(NSIndexPath *)indexPath +{ + [MPAdPlacerInvocation invokeForTarget:self.originalDelegate with2ArgSelector:@selector(collectionView:didUnhighlightItemAtIndexPath:) firstArg:collectionView secondArg:indexPath streamAdPlacer:self.streamAdPlacer]; +} + +- (void)collectionView:(UICollectionView *)collectionView performAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender +{ + if ([self.streamAdPlacer isAdAtIndexPath:indexPath]) { + return; + } + + id delegate = self.originalDelegate; + if ([delegate respondsToSelector:@selector(collectionView:performAction:forItemAtIndexPath:withSender:)]) { + NSIndexPath *originalPath = [self.streamAdPlacer originalIndexPathForAdjustedIndexPath:indexPath]; + [delegate collectionView:collectionView performAction:action forItemAtIndexPath:originalPath withSender:sender]; + } +} + +- (BOOL)collectionView:(UICollectionView *)collectionView shouldDeselectItemAtIndexPath:(NSIndexPath *)indexPath +{ + NSInvocation *invocation = [MPAdPlacerInvocation invokeForTarget:self.originalDelegate with2ArgSelector:@selector(collectionView:shouldDeselectItemAtIndexPath:) firstArg:collectionView secondArg:indexPath streamAdPlacer:self.streamAdPlacer]; + + return [MPAdPlacerInvocation boolResultForInvocation:invocation defaultValue:YES]; +} + +- (BOOL)collectionView:(UICollectionView *)collectionView shouldHighlightItemAtIndexPath:(NSIndexPath *)indexPath +{ + NSInvocation *invocation = [MPAdPlacerInvocation invokeForTarget:self.originalDelegate with2ArgSelector:@selector(collectionView:shouldHighlightItemAtIndexPath:) firstArg:collectionView secondArg:indexPath streamAdPlacer:self.streamAdPlacer]; + + return [MPAdPlacerInvocation boolResultForInvocation:invocation defaultValue:YES]; +} + +- (BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath +{ + NSInvocation *invocation = [MPAdPlacerInvocation invokeForTarget:self.originalDelegate with2ArgSelector:@selector(collectionView:shouldSelectItemAtIndexPath:) firstArg:collectionView secondArg:indexPath streamAdPlacer:self.streamAdPlacer]; + + return [MPAdPlacerInvocation boolResultForInvocation:invocation defaultValue:collectionView.allowsSelection]; +} + +- (BOOL)collectionView:(UICollectionView *)collectionView shouldShowMenuForItemAtIndexPath:(NSIndexPath *)indexPath +{ + NSInvocation *invocation = [MPAdPlacerInvocation invokeForTarget:self.originalDelegate with2ArgSelector:@selector(collectionView:shouldShowMenuForItemAtIndexPath:) firstArg:collectionView secondArg:indexPath streamAdPlacer:self.streamAdPlacer]; + + return [MPAdPlacerInvocation boolResultForInvocation:invocation defaultValue:NO]; +} + +- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath +{ + if ([self.originalDelegate respondsToSelector:@selector(collectionView:willDisplayCell:forItemAtIndexPath:)]) { + [MPAdPlacerInvocation invokeForTarget:self.originalDelegate with3ArgSelector:@selector(collectionView:willDisplayCell:forItemAtIndexPath:) firstArg:collectionView secondArg:cell thirdArg:indexPath streamAdPlacer:self.streamAdPlacer]; + } +} + +#pragma mark - UICollectionViewDelegateFlowLayout + +- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath +{ + if ([self.streamAdPlacer isAdAtIndexPath:indexPath]) { + return [self.streamAdPlacer sizeForAdAtIndexPath:indexPath withMaximumWidth:CGRectGetWidth(self.collectionView.bounds)]; + } + + if ([self.originalDelegate respondsToSelector:@selector(collectionView:layout:sizeForItemAtIndexPath:)]) { + NSIndexPath *originalPath = [self.streamAdPlacer originalIndexPathForAdjustedIndexPath:indexPath]; + id flowLayout = (id)[self originalDelegate]; + return [flowLayout collectionView:collectionView layout:collectionViewLayout sizeForItemAtIndexPath:originalPath]; + } + return ((UICollectionViewFlowLayout *)collectionViewLayout).itemSize; +} + +#pragma mark - Method Forwarding + +- (BOOL)isKindOfClass:(Class)aClass { + return [super isKindOfClass:aClass] || + [self.originalDataSource isKindOfClass:aClass] || + [self.originalDelegate isKindOfClass:aClass]; +} + +- (BOOL)conformsToProtocol:(Protocol *)aProtocol +{ + return [super conformsToProtocol:aProtocol] || + [self.originalDelegate conformsToProtocol:aProtocol] || + [self.originalDataSource conformsToProtocol:aProtocol]; +} + +- (BOOL)respondsToSelector:(SEL)aSelector +{ + return [super respondsToSelector:aSelector] || + [self.originalDataSource respondsToSelector:aSelector] || + [self.originalDelegate respondsToSelector:aSelector]; +} + +- (id)forwardingTargetForSelector:(SEL)aSelector +{ + if ([self.originalDataSource respondsToSelector:aSelector]) { + return self.originalDataSource; + } else if ([self.originalDelegate respondsToSelector:aSelector]) { + return self.originalDelegate; + } else { + return [super forwardingTargetForSelector:aSelector]; + } +} + +@end + +@implementation UICollectionView (MPCollectionViewAdPlacer) + +static char kAdPlacerKey; + +- (void)mp_setAdPlacer:(MPCollectionViewAdPlacer *)placer +{ + objc_setAssociatedObject(self, &kAdPlacerKey, placer, OBJC_ASSOCIATION_ASSIGN); +} + +- (MPCollectionViewAdPlacer *)mp_adPlacer +{ + return objc_getAssociatedObject(self, &kAdPlacerKey); +} + +- (void)mp_setDelegate:(id)delegate +{ + MPCollectionViewAdPlacer *adPlacer = [self mp_adPlacer]; + + if (adPlacer) { + adPlacer.originalDelegate = delegate; + } else { + self.delegate = delegate; + } +} + +- (id)mp_delegate +{ + MPCollectionViewAdPlacer *adPlacer = [self mp_adPlacer]; + + if (adPlacer) { + return adPlacer.originalDelegate; + } else { + return self.delegate; + } +} + +- (void)mp_setDataSource:(id)dataSource +{ + MPCollectionViewAdPlacer *adPlacer = [self mp_adPlacer]; + + if (adPlacer) { + adPlacer.originalDataSource = dataSource; + } else { + self.dataSource = dataSource; + } +} + +- (id)mp_dataSource +{ + MPCollectionViewAdPlacer *adPlacer = [self mp_adPlacer]; + + if (adPlacer) { + return adPlacer.originalDataSource; + } else { + return self.dataSource; + } +} + +- (id)mp_dequeueReusableCellWithReuseIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath +{ + MPCollectionViewAdPlacer *adPlacer = [self mp_adPlacer]; + NSIndexPath *adjustedIndexPath = indexPath; + + if (adPlacer) { + adjustedIndexPath = [adPlacer.streamAdPlacer adjustedIndexPathForOriginalIndexPath:indexPath]; + } + + // Only pass nil through if developer passed it through + if (!indexPath || adjustedIndexPath) { + return [self dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:adjustedIndexPath]; + } else { + return nil; + } +} + +- (NSArray *)mp_indexPathsForSelectedItems +{ + MPCollectionViewAdPlacer *adPlacer = [self mp_adPlacer]; + NSArray *adjustedIndexPaths = [self indexPathsForSelectedItems]; + + if (adPlacer) { + adjustedIndexPaths = [adPlacer.streamAdPlacer originalIndexPathsForAdjustedIndexPaths:adjustedIndexPaths]; + } + + return adjustedIndexPaths; +} + +- (void)mp_selectItemAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated scrollPosition:(UICollectionViewScrollPosition)scrollPosition +{ + MPCollectionViewAdPlacer *adPlacer = [self mp_adPlacer]; + NSIndexPath *adjustedIndexPath = indexPath; + + if (adPlacer) { + adjustedIndexPath = [adPlacer.streamAdPlacer adjustedIndexPathForOriginalIndexPath:indexPath]; + } + + // Only pass nil through if developer passed it through + if (!indexPath || adjustedIndexPath) { + [self selectItemAtIndexPath:adjustedIndexPath animated:animated scrollPosition:scrollPosition]; + } +} + +- (void)mp_deselectItemAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated +{ + MPCollectionViewAdPlacer *adPlacer = [self mp_adPlacer]; + NSIndexPath *adjustedIndexPath = indexPath; + + if (adPlacer) { + adjustedIndexPath = [adPlacer.streamAdPlacer adjustedIndexPathForOriginalIndexPath:indexPath]; + } + + [self deselectItemAtIndexPath:adjustedIndexPath animated:animated]; +} + +- (void)mp_reloadData +{ + [self reloadData]; +} + +- (UICollectionViewLayoutAttributes *)mp_layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath +{ + MPCollectionViewAdPlacer *adPlacer = [self mp_adPlacer]; + NSIndexPath *adjustedIndexPath = indexPath; + + if (adPlacer) { + adjustedIndexPath = [adPlacer.streamAdPlacer adjustedIndexPathForOriginalIndexPath:indexPath]; + } + + return [self layoutAttributesForItemAtIndexPath:adjustedIndexPath]; +} + +- (NSIndexPath *)mp_indexPathForItemAtPoint:(CGPoint)point +{ + MPCollectionViewAdPlacer *adPlacer = [self mp_adPlacer]; + NSIndexPath *adjustedIndexPath = [self indexPathForItemAtPoint:point]; + + if (adPlacer) { + adjustedIndexPath = [adPlacer.streamAdPlacer originalIndexPathForAdjustedIndexPath:adjustedIndexPath]; + } + + return adjustedIndexPath; +} + +- (NSIndexPath *)mp_indexPathForCell:(UICollectionViewCell *)cell +{ + MPCollectionViewAdPlacer *adPlacer = [self mp_adPlacer]; + NSIndexPath *adjustedIndexPath = [self indexPathForCell:cell]; + + if (adPlacer) { + adjustedIndexPath = [adPlacer.streamAdPlacer originalIndexPathForAdjustedIndexPath:adjustedIndexPath]; + } + + return adjustedIndexPath; +} + +- (UICollectionViewCell *)mp_cellForItemAtIndexPath:(NSIndexPath *)indexPath +{ + MPCollectionViewAdPlacer *adPlacer = [self mp_adPlacer]; + NSIndexPath *adjustedIndexPath = indexPath; + + if (adPlacer) { + adjustedIndexPath = [adPlacer.streamAdPlacer adjustedIndexPathForOriginalIndexPath:adjustedIndexPath]; + } + + return [self cellForItemAtIndexPath:adjustedIndexPath]; +} + +- (NSArray *)mp_visibleCells +{ + MPCollectionViewAdPlacer *adPlacer = [self mp_adPlacer]; + + if (adPlacer) { + NSArray *indexPaths = [self mp_indexPathsForVisibleItems]; + NSMutableArray *visibleCells = [NSMutableArray array]; + for (NSIndexPath *indexPath in indexPaths) { + [visibleCells addObject:[self mp_cellForItemAtIndexPath:indexPath]]; + } + return visibleCells; + } else { + return [self visibleCells]; + } +} + +- (NSArray *)mp_indexPathsForVisibleItems +{ + MPCollectionViewAdPlacer *adPlacer = [self mp_adPlacer]; + NSArray *adjustedIndexPaths = [self indexPathsForVisibleItems]; + + if (adPlacer) { + adjustedIndexPaths = [adPlacer.streamAdPlacer originalIndexPathsForAdjustedIndexPaths:adjustedIndexPaths]; + } + + return adjustedIndexPaths; +} + +- (void)mp_scrollToItemAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(UICollectionViewScrollPosition)scrollPosition animated:(BOOL)animated +{ + MPCollectionViewAdPlacer *adPlacer = [self mp_adPlacer]; + NSIndexPath *adjustedIndexPath = indexPath; + + if (adPlacer) { + adjustedIndexPath = [adPlacer.streamAdPlacer adjustedIndexPathForOriginalIndexPath:adjustedIndexPath]; + } + + // Only pass nil through if developer passed it through + if (!indexPath || adjustedIndexPath) { + [self scrollToItemAtIndexPath:adjustedIndexPath atScrollPosition:scrollPosition animated:animated]; + } +} + +- (void)mp_insertSections:(NSIndexSet *)sections +{ + MPCollectionViewAdPlacer *adPlacer = [self mp_adPlacer]; + + if (adPlacer) { + [adPlacer.streamAdPlacer insertSections:sections]; + } + + [self insertSections:sections]; +} + +- (void)mp_deleteSections:(NSIndexSet *)sections +{ + MPCollectionViewAdPlacer *adPlacer = [self mp_adPlacer]; + + if (adPlacer) { + [adPlacer.streamAdPlacer deleteSections:sections]; + } + + [self deleteSections:sections]; +} + +- (void)mp_reloadSections:(NSIndexSet *)sections +{ + [self reloadSections:sections]; +} + +- (void)mp_moveSection:(NSInteger)section toSection:(NSInteger)newSection +{ + MPCollectionViewAdPlacer *adPlacer = [self mp_adPlacer]; + + if (adPlacer) { + [adPlacer.streamAdPlacer moveSection:section toSection:newSection]; + } + + [self moveSection:section toSection:newSection]; +} + +- (void)mp_insertItemsAtIndexPaths:(NSArray *)indexPaths +{ + MPCollectionViewAdPlacer *adPlacer = [self mp_adPlacer]; + NSArray *adjustedIndexPaths = indexPaths; + + if (adPlacer) { + [adPlacer.streamAdPlacer insertItemsAtIndexPaths:indexPaths]; + adjustedIndexPaths = [adPlacer.streamAdPlacer adjustedIndexPathsForOriginalIndexPaths:indexPaths]; + } + + // We perform the actual UI insertion AFTER updating the stream ad placer's + // data, because the insertion can trigger queries to the data source, which + // needs to reflect the post-insertion state. + [self insertItemsAtIndexPaths:adjustedIndexPaths]; +} + +- (void)mp_deleteItemsAtIndexPaths:(NSArray *)indexPaths +{ + MPCollectionViewAdPlacer *adPlacer = [self mp_adPlacer]; + [self performBatchUpdates:^{ + NSArray *adjustedIndexPaths = indexPaths; + + if (adPlacer) { + // We need to obtain the adjusted index paths to delete BEFORE we + // update the stream ad placer's data. + adjustedIndexPaths = [adPlacer.streamAdPlacer adjustedIndexPathsForOriginalIndexPaths:indexPaths]; + + [adPlacer.streamAdPlacer deleteItemsAtIndexPaths:indexPaths]; + } + + // We perform the actual UI deletion AFTER updating the stream ad placer's + // data, because the deletion can trigger queries to the data source, which + // needs to reflect the post-deletion state. + [self deleteItemsAtIndexPaths:adjustedIndexPaths]; + } completion:nil]; +} + +- (void)mp_reloadItemsAtIndexPaths:(NSArray *)indexPaths +{ + MPCollectionViewAdPlacer *adPlacer = [self mp_adPlacer]; + NSArray *adjustedIndexPaths = indexPaths; + + if (adPlacer) { + adjustedIndexPaths = [adPlacer.streamAdPlacer adjustedIndexPathsForOriginalIndexPaths:indexPaths]; + } + + [self reloadItemsAtIndexPaths:adjustedIndexPaths]; +} + +- (void)mp_moveItemAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath +{ + MPCollectionViewAdPlacer *adPlacer = [self mp_adPlacer]; + NSIndexPath *adjustedFrom = indexPath; + NSIndexPath *adjustedTo = newIndexPath; + + if (adPlacer) { + // We need to obtain the adjusted index paths to move BEFORE we + // update the stream ad placer's data. + adjustedFrom = [adPlacer.streamAdPlacer adjustedIndexPathForOriginalIndexPath:indexPath]; + adjustedTo = [adPlacer.streamAdPlacer adjustedIndexPathForOriginalIndexPath:newIndexPath]; + + [adPlacer.streamAdPlacer moveItemAtIndexPath:indexPath toIndexPath:newIndexPath]; + } + + // We perform the actual UI operation AFTER updating the stream ad placer's + // data, because the operation can trigger queries to the data source, which + // needs to reflect the post-operation state. + [self moveItemAtIndexPath:adjustedFrom toIndexPath:adjustedTo]; +} +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAd.h b/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAd.h new file mode 100644 index 00000000000..9cec3620605 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAd.h @@ -0,0 +1,51 @@ +// +// MPNativeAd.h +// Copyright (c) 2013 MoPub. All rights reserved. +// + +#import +#import + +@protocol MPNativeAdAdapter; +@protocol MPNativeAdDelegate; +@protocol MPNativeAdRenderer; +@class MPAdConfiguration; + +/** + * The `MPNativeAd` class is used to render and manage events for a native advertisement. The + * class provides methods for accessing native ad properties returned by the server, as well as + * convenience methods for URL navigation and metrics-gathering. + */ + +@interface MPNativeAd : NSObject + +/** @name Ad Resources */ + +/** + * The delegate of the `MPNativeAd` object. + */ +@property (nonatomic, weak) id delegate; + +/** + * A dictionary representing the native ad properties. + */ +@property (nonatomic, readonly) NSDictionary *properties; + +- (instancetype)initWithAdAdapter:(id)adAdapter; + +/** @name Retrieving Ad View */ + +/** + * Retrieves a rendered view containing the ad. + * + * @param error A pointer to an error object. If an error occurs, this pointer will be set to an + * actual error object containing the error information. + * + * @return If successful, the method will return a view containing the rendered ad. The method will + * return nil if it cannot render the ad data to a view. + */ +- (UIView *)retrieveAdViewWithError:(NSError **)error; + +- (void)trackMetricForURL:(NSURL *)URL; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAd.m b/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAd.m new file mode 100644 index 00000000000..ecffa2fccfc --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAd.m @@ -0,0 +1,235 @@ +// +// MPNativeAd.m +// Copyright (c) 2013 MoPub. All rights reserved. +// + +#import "MPNativeAd+Internal.h" +#import "MPAdConfiguration.h" +#import "MPCoreInstanceProvider.h" +#import "MPNativeAdError.h" +#import "MPLogging.h" +#import "MPNativeCache.h" +#import "MPNativeAdRendering.h" +#import "MPImageDownloadQueue.h" +#import "NSJSONSerialization+MPAdditions.h" +#import "MPNativeCustomEvent.h" +#import "MPNativeAdAdapter.h" +#import "MPNativeAdConstants.h" +#import "MPTimer.h" +#import "MPNativeAdRenderer.h" +#import "MPNativeAdDelegate.h" +#import "MPNativeView.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface MPNativeAd () + +@property (nonatomic, readwrite, strong) id renderer; + +@property (nonatomic, strong) NSDate *creationDate; + +@property (nonatomic, strong) NSMutableSet *clickTrackerURLs; +@property (nonatomic, strong) NSMutableSet *impressionTrackerURLs; + +@property (nonatomic, readonly, strong) id adAdapter; +@property (nonatomic, assign) BOOL hasTrackedImpression; +@property (nonatomic, assign) BOOL hasTrackedClick; + +@property (nonatomic, copy) NSString *adIdentifier; +@property (nonatomic) MPNativeView *associatedView; + +@property (nonatomic) BOOL hasAttachedToView; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation MPNativeAd + +- (instancetype)initWithAdAdapter:(id)adAdapter +{ + static int sequenceNumber = 0; + + self = [super init]; + if (self) { + _adAdapter = adAdapter; + if ([_adAdapter respondsToSelector:@selector(setDelegate:)]) { + [_adAdapter setDelegate:self]; + } + _adIdentifier = [[NSString stringWithFormat:@"%d", sequenceNumber++] copy]; + _impressionTrackerURLs = [[NSMutableSet alloc] init]; + _clickTrackerURLs = [[NSMutableSet alloc] init]; + _creationDate = [NSDate date]; + _associatedView = [[MPNativeView alloc] init]; + _associatedView.clipsToBounds = YES; + _associatedView.delegate = self; + + // Add a tap recognizer on top of the view if the ad network isn't handling clicks on its own. + if (!([_adAdapter respondsToSelector:@selector(enableThirdPartyClickTracking)] && [_adAdapter enableThirdPartyClickTracking])) { + UITapGestureRecognizer *recognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(adViewTapped)]; + [_associatedView addGestureRecognizer:recognizer]; + } + } + return self; +} + +#pragma mark - Public + +- (UIView *)retrieveAdViewWithError:(NSError **)error +{ + // We always return the same MPNativeView (self.associatedView) so we need to remove its subviews + // before attaching the new ad view to it. + [[self.associatedView subviews] makeObjectsPerformSelector:@selector(removeFromSuperview)]; + + UIView *adView = [self.renderer retrieveViewWithAdapter:self.adAdapter error:error]; + + if (adView) { + if (!self.hasAttachedToView) { + [self willAttachToView:self.associatedView]; + self.hasAttachedToView = YES; + } + + adView.frame = self.associatedView.bounds; + [self.associatedView addSubview:adView]; + + return self.associatedView; + } else { + return nil; + } +} + +- (NSDictionary *)properties +{ + return self.adAdapter.properties; +} + +- (void)trackImpression +{ + if (self.hasTrackedImpression) { + MPLogDebug(@"Impression already tracked."); + return; + } + + MPLogDebug(@"Tracking an impression for %@.", self.adIdentifier); + self.hasTrackedImpression = YES; + [self trackMetricsForURLs:self.impressionTrackerURLs]; +} + +- (void)trackClick +{ + if (self.hasTrackedClick) { + MPLogDebug(@"Click already tracked."); + return; + } + + MPLogDebug(@"Tracking a click for %@.", self.adIdentifier); + self.hasTrackedClick = YES; + [self trackMetricsForURLs:self.clickTrackerURLs]; + + if ([self.adAdapter respondsToSelector:@selector(trackClick)] && ![self isThirdPartyHandlingClicks]) { + [self.adAdapter trackClick]; + } + +} + +- (void)trackMetricsForURLs:(NSSet *)URLs +{ + for (NSURL *URL in URLs) { + [self trackMetricForURL:URL]; + } +} + +- (void)trackMetricForURL:(NSURL *)URL +{ + NSMutableURLRequest *request = [[MPCoreInstanceProvider sharedProvider] buildConfiguredURLRequestWithURL:URL]; + request.cachePolicy = NSURLRequestReloadIgnoringCacheData; + [NSURLConnection connectionWithRequest:request delegate:nil]; +} + +#pragma mark - Internal + +- (void)willAttachToView:(UIView *)view +{ + if ([self.adAdapter respondsToSelector:@selector(willAttachToView:)]) { + [self.adAdapter willAttachToView:view]; + } +} + +- (BOOL)isThirdPartyHandlingClicks +{ + return [self.adAdapter respondsToSelector:@selector(enableThirdPartyClickTracking)] && [self.adAdapter enableThirdPartyClickTracking]; +} + +- (void)displayAdContent +{ + [self trackClick]; + + if ([self.adAdapter respondsToSelector:@selector(displayContentForURL:rootViewController:)]) { + [self.adAdapter displayContentForURL:self.adAdapter.defaultActionURL rootViewController:[self.delegate viewControllerForPresentingModalView]]; + } else { + // If this method is called, that means that the backing adapter should implement -displayContentForURL:rootViewController:completion:. + // If it doesn't, we'll log a warning. + MPLogWarn(@"Cannot display native ad content. -displayContentForURL:rootViewController:completion: not implemented by native ad adapter: %@", [self.adAdapter class]); + } +} + +#pragma mark - UITapGestureRecognizer + +- (void)adViewTapped +{ + [self displayAdContent]; + + if ([self.renderer respondsToSelector:@selector(nativeAdTapped)]) { + [self.renderer nativeAdTapped]; + } +} + +#pragma mark - MPNativeViewDelegate + +- (void)nativeViewWillMoveToSuperview:(UIView *)superview +{ + if ([self.renderer respondsToSelector:@selector(adViewWillMoveToSuperview:)]) + { + [self.renderer adViewWillMoveToSuperview:superview]; + } +} + +#pragma mark - MPNativeAdAdapterDelegate + +- (UIViewController *)viewControllerForPresentingModalView +{ + return [self.delegate viewControllerForPresentingModalView]; +} + +- (void)nativeAdWillLogImpression:(id)adAdapter +{ + [self trackImpression]; +} + +- (void)nativeAdDidClick:(id)adAdapter +{ + [self trackClick]; +} + +- (void)nativeAdWillPresentModalForAdapter:(id)adapter +{ + if ([self.delegate respondsToSelector:@selector(willPresentModalForNativeAd:)]) { + [self.delegate willPresentModalForNativeAd:self]; + } +} + +- (void)nativeAdDidDismissModalForAdapter:(id)adapter +{ + if ([self.delegate respondsToSelector:@selector(didDismissModalForNativeAd:)]) { + [self.delegate didDismissModalForNativeAd:self]; + } +} + +- (void)nativeAdWillLeaveApplicationFromAdapter:(id)adapter +{ + if ([self.delegate respondsToSelector:@selector(willLeaveApplicationFromNativeAd:)]) { + [self.delegate willLeaveApplicationFromNativeAd:self]; + } +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAdAdapter.h b/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAdAdapter.h new file mode 100644 index 00000000000..baed12ff6b4 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAdAdapter.h @@ -0,0 +1,184 @@ +// +// MPNativeAdAdapter.h +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import + +@protocol MPNativeAdAdapter; + +/** + * Classes that conform to the `MPNativeAdAdapter` protocol can have an + * `MPNativeAdAdapterDelegate` delegate object. You use this delegate to communicate + * native ad events (such as impressions and clicks occurring) back to the MoPub SDK. + */ +@protocol MPNativeAdAdapterDelegate + +@required + +/** + * Asks the delegate for a view controller to use for presenting modal content, such as the in-app + * browser that can appear when an ad is tapped. + * + * @return A view controller that should be used for presenting modal content. + */ +- (UIViewController *)viewControllerForPresentingModalView; + +/** + * You should call this method when your adapter's modal is about to be presented. + * + * @param adapter The adapter that will present the modal. + */ +- (void)nativeAdWillPresentModalForAdapter:(id)adapter; + +/** + * You should call this method when your adapter's modal has been dismissed. + * + * @param adapter The adapter that dismissed the modal. + */ +- (void)nativeAdDidDismissModalForAdapter:(id)adapter; + +/** + * You should call this method when your the user will leave the application due to interaction with the ad. + * + * @param adapter The adapter that represents the ad that caused the user to leave the application. + */ +- (void)nativeAdWillLeaveApplicationFromAdapter:(id)adapter; + +@optional + +/** + * This method is called before the backing native ad logs an impression. + * + * @param adAdapter You should pass `self` to allow the MoPub SDK to associate this event with the + * correct instance of your ad adapter. + */ +- (void)nativeAdWillLogImpression:(id)adAdapter; + +/** + * This method is called when the user interacts with the ad. + * + * @param adAdapter You should pass `self` to allow the MoPub SDK to associate this event with the + * correct instance of your ad adapter. + */ +- (void)nativeAdDidClick:(id)adAdapter; + +@end + +/** + * The `MPNativeAdAdapter` protocol allows the MoPub SDK to interact with native ad objects obtained + * from third-party ad networks. An object that adopts this protocol acts as a wrapper for a native + * ad object, translating its proprietary interface into a common one that the MoPub SDK can + * understand. + * + * An object that adopts this protocol must implement the `properties` property to specify a + * dictionary of assets, such as title and text, that should be rendered as part of a native ad. + * When possible, you should place values in the returned dictionary such that they correspond to + * the pre-defined keys in the MPNativeAdConstants header file. + * + * An adopting object must additionally implement -displayContentForURL:rootViewController:completion: + * to supply the behavior that should occur when the user interacts with the ad. + * + * Optional methods of the protocol allow the adopting object to define when and how impressions + * and interactions should be tracked. + */ +@protocol MPNativeAdAdapter + +@required + +/** @name Ad Resources */ + +/** + * Provides a dictionary of all publicly accessible assets (such as title and text) for the + * native ad. + * + * When possible, you should place values in the returned dictionary such that they correspond to + * the pre-defined keys in the MPNativeAdConstants header file. + */ +@property (nonatomic, readonly) NSDictionary *properties; + +/** + * The default click-through URL for the ad. + * + * This may safely be set to nil if your network doesn't expose this value (for example, it may only + * provide a method to handle a click, lacking another for retrieving the URL itself). + */ +@property (nonatomic, readonly) NSURL *defaultActionURL; + +/** @name Handling Ad Interactions */ + +@optional + +/** + * Tells the object to open the specified URL using an appropriate mechanism. + * + * @param URL The URL to be opened. + * @param controller The view controller that should be used to present the modal view controller. + * + * Your implementation of this method should either forward the request to the underlying + * third-party ad object (if it has built-in support for handling ad interactions), or open an + * in-application modal web browser or a modal App Store controller. + */ +- (void)displayContentForURL:(NSURL *)URL rootViewController:(UIViewController *)controller; + +/** + * Determines whether MPNativeAd should track clicks + * + * If not implemented, this will be assumed to return NO, and MPNativeAd will track clicks. + * If this returns YES, then MPNativeAd will defer to the MPNativeAdAdapterDelegate callbacks to + * track clicks. + */ +- (BOOL)enableThirdPartyClickTracking; + +/** + * Tracks a click for this ad. + * + * To avoid reporting discrepancies, you should only implement this method if the third-party ad + * network requires clicks to be reported manually. + */ +- (void)trackClick; + +/** + * The `MPNativeAdAdapterDelegate` to send messages to as events occur. + * + * The `delegate` object defines several methods that you should call in order to inform MoPub + * of interactions with the ad. This delegate needs to be implemented if third party impression and/or + * click tracking is enabled. + */ +@property (nonatomic, weak) id delegate; + +/** @name Responding to an Ad Being Attached to a View */ + +/** + * This method will be called when your ad's content is about to be loaded into a view. + * + * @param view A view that will contain the ad content. + * + * You should implement this method if the underlying third-party ad object needs to be informed + * of this event. + */ +- (void)willAttachToView:(UIView *)view; + +/** + * This method will be called if your implementation provides a DAA icon through the properties dictionary + * and the user has tapped the icon. + */ +- (void)displayContentForDAAIconTap; + +/** + * Return your ad's privacy information icon view. + * + * You should implement this method if your ad supplies its own view for its privacy information icon. + */ +- (UIView *)privacyInformationIconView; + +/** + * Return your ad's main media view. + * + * You should implement this method if your ad supplies its own view for the main media view which is typically + * an image or video. If you implement this method, the SDK will not make any other attempts at retrieving + * the main media asset. + */ +- (UIView *)mainMediaView; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAdConstants.h b/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAdConstants.h new file mode 100644 index 00000000000..6452cb1d32c --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAdConstants.h @@ -0,0 +1,31 @@ +// +// MPNativeAdConstants.h +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import +#import + +extern const CGFloat kUniversalStarRatingScale; +extern const CGFloat kStarRatingMaxValue; +extern const CGFloat kStarRatingMinValue; +extern const NSTimeInterval kDefaultRequiredSecondsForImpression; + +/** @name MPNativeAd asset keys */ + +extern NSString *const kAdTitleKey; +extern NSString *const kAdTextKey; +extern NSString *const kAdIconImageKey; +extern NSString *const kAdMainImageKey; +extern NSString *const kAdCTATextKey; +extern NSString *const kAdStarRatingKey; +extern NSString *const kVideoConfigKey; +extern NSString *const kVASTVideoKey; +extern NSString *const kNativeVideoAdConfigKey; +extern NSString *const kAdDAAIconImageKey; +extern NSString *const kDAAIconImageName; +extern NSString *const kDAAIconTapDestinationURL; +extern NSString *const kImpressionTrackerURLsKey; +extern NSString *const kDefaultActionURLKey; +extern NSString *const kClickTrackerURLKey; +extern NSString *const kLogEventRequestPropertiesKey; diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAdConstants.m b/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAdConstants.m new file mode 100644 index 00000000000..640b70a82f5 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAdConstants.m @@ -0,0 +1,28 @@ +// +// MPNativeAdConstants.m +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import "MPNativeAdConstants.h" + +const CGFloat kUniversalStarRatingScale = 5.0f; +const CGFloat kStarRatingMaxValue = 5.0f; +const CGFloat kStarRatingMinValue = 0.0f; +const NSTimeInterval kDefaultRequiredSecondsForImpression = 1.0; + +NSString *const kAdTitleKey = @"title"; +NSString *const kAdTextKey = @"text"; +NSString *const kAdIconImageKey = @"iconimage"; +NSString *const kAdMainImageKey = @"mainimage"; +NSString *const kAdCTATextKey = @"ctatext"; +NSString *const kAdStarRatingKey = @"starrating"; +NSString *const kVideoConfigKey = @"videoconfig"; +NSString *const kVASTVideoKey = @"video"; +NSString *const kNativeVideoAdConfigKey = @"nativevideoadconfig"; +NSString *const kAdDAAIconImageKey = @"daaicon"; +NSString *const kDAAIconImageName = @"MPDAAIcon.png"; +NSString *const kDAAIconTapDestinationURL = @"https://www.mopub.com/optout"; +NSString *const kImpressionTrackerURLsKey = @"imptracker"; +NSString *const kDefaultActionURLKey = @"clk"; +NSString *const kClickTrackerURLKey = @"clktracker"; +NSString *const kLogEventRequestPropertiesKey = @"logeventrequestproperties"; diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAdData.h b/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAdData.h new file mode 100644 index 00000000000..1f4f2b118f9 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAdData.h @@ -0,0 +1,17 @@ +// +// MPNativeAdData.h +// MoPub +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import + +@class MPNativeAd; + +@interface MPNativeAdData : NSObject + +@property (nonatomic, copy) NSString *adUnitID; +@property (nonatomic, strong) MPNativeAd *ad; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAdData.m b/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAdData.m new file mode 100644 index 00000000000..08cf9c8a095 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAdData.m @@ -0,0 +1,12 @@ +// +// MPNativeAdData.m +// MoPub +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import "MPNativeAdData.h" + +@implementation MPNativeAdData + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAdDelegate.h b/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAdDelegate.h new file mode 100644 index 00000000000..0702cc79fc8 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAdDelegate.h @@ -0,0 +1,51 @@ +// +// MPNativeAdDelegate.h +// MoPub +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +/** + * The delegate of an `MPNativeAd` object must adopt the `MPNativeAdDelegate` protocol. It must + * implement `viewControllerForPresentingModalView` to provide a root view controller from which + * the ad view should present modal content. + */ +@class MPNativeAd; +@protocol MPNativeAdDelegate + +@optional + +/** + * Sent when the native ad will present its modal content. + * + * @param nativeAd The native ad sending the message. + */ +- (void)willPresentModalForNativeAd:(MPNativeAd *)nativeAd; + +/** + * Sent when a native ad has dismissed its modal content, returning control to your application. + * + * @param nativeAd The native ad sending the message. + */ +- (void)didDismissModalForNativeAd:(MPNativeAd *)nativeAd; + +/** + * Sent when a user is about to leave your application as a result of tapping this native ad. + * + * @param nativeAd The native ad sending the message. + */ +- (void)willLeaveApplicationFromNativeAd:(MPNativeAd *)nativeAd; + +@required + +/** @name Managing Modal Content Presentation */ + +/** + * Asks the delegate for a view controller to use for presenting modal content, such as the in-app + * browser that can appear when an ad is tapped. + * + * @return A view controller that should be used for presenting modal content. + */ +- (UIViewController *)viewControllerForPresentingModalView; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAdError.h b/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAdError.h new file mode 100644 index 00000000000..7a346861d98 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAdError.h @@ -0,0 +1,35 @@ +// +// MPNativeAdError.h +// Copyright (c) 2013 MoPub. All rights reserved. +// + +#import + +typedef enum MPNativeAdErrorCode { + MPNativeAdErrorUnknown = -1, + + MPNativeAdErrorHTTPError = -1000, + MPNativeAdErrorInvalidServerResponse = -1001, + MPNativeAdErrorNoInventory = -1002, + MPNativeAdErrorImageDownloadFailed = -1003, + MPNativeAdErrorAdUnitWarmingUp = -1004, + MPNativeAdErrorVASTParsingFailed = -1005, + MPNativeAdErrorVideoConfigInvalid = -1006, + + MPNativeAdErrorContentDisplayError = -1100, + MPNativeAdErrorRenderError = -1200 +} MPNativeAdErrorCode; + +extern NSString * const MoPubNativeAdsSDKDomain; + +NSError *MPNativeAdNSErrorForInvalidAdServerResponse(NSString *reason); +NSError *MPNativeAdNSErrorForAdUnitWarmingUp(); +NSError *MPNativeAdNSErrorForNoInventory(); +NSError *MPNativeAdNSErrorForNetworkConnectionError(); +NSError *MPNativeAdNSErrorForInvalidImageURL(); +NSError *MPNativeAdNSErrorForImageDownloadFailure(); +NSError *MPNativeAdNSErrorForContentDisplayErrorMissingRootController(); +NSError *MPNativeAdNSErrorForContentDisplayErrorInvalidURL(); +NSError *MPNativeAdNSErrorForVASTParsingFailure(); +NSError *MPNativeAdNSErrorForVideoConfigInvalid(); +NSError *MPNativeAdNSErrorForRenderValueTypeError(); diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAdError.m b/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAdError.m new file mode 100644 index 00000000000..8f9c40c947a --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAdError.m @@ -0,0 +1,56 @@ +// +// MPNativeAdError.m +// Copyright (c) 2013 MoPub. All rights reserved. +// + +#import "MPNativeAdError.h" + +NSString * const MoPubNativeAdsSDKDomain = @"com.mopub.nativeads"; + +NSError *MPNativeAdNSErrorForInvalidAdServerResponse(NSString *reason) { + if (reason.length == 0) { + reason = @"Invalid ad server response"; + } + + return [NSError errorWithDomain:MoPubNativeAdsSDKDomain code:MPNativeAdErrorInvalidServerResponse userInfo:@{NSLocalizedDescriptionKey : [reason copy]}]; +} + +NSError *MPNativeAdNSErrorForAdUnitWarmingUp() { + return [NSError errorWithDomain:MoPubNativeAdsSDKDomain code:MPNativeAdErrorAdUnitWarmingUp userInfo:@{NSLocalizedDescriptionKey : @"Ad unit is warming up"}]; +} + +NSError *MPNativeAdNSErrorForNoInventory() { + return [NSError errorWithDomain:MoPubNativeAdsSDKDomain code:MPNativeAdErrorNoInventory userInfo:@{NSLocalizedDescriptionKey : @"Ad server returned no inventory"}]; +} + +NSError *MPNativeAdNSErrorForNetworkConnectionError() { + return [NSError errorWithDomain:MoPubNativeAdsSDKDomain code:MPNativeAdErrorHTTPError userInfo:@{NSLocalizedDescriptionKey : @"Connection error"}]; +} + +NSError *MPNativeAdNSErrorForInvalidImageURL() { + return MPNativeAdNSErrorForInvalidAdServerResponse(@"Invalid image URL"); +} + +NSError *MPNativeAdNSErrorForImageDownloadFailure() { + return [NSError errorWithDomain:MoPubNativeAdsSDKDomain code:MPNativeAdErrorImageDownloadFailed userInfo:@{NSLocalizedDescriptionKey : @"Failed to download images"}]; +} + +NSError *MPNativeAdNSErrorForVASTParsingFailure() { + return [NSError errorWithDomain:MoPubNativeAdsSDKDomain code:MPNativeAdErrorVASTParsingFailed userInfo:@{NSLocalizedDescriptionKey : @"Failed to parse VAST tag"}]; +} + +NSError *MPNativeAdNSErrorForVideoConfigInvalid() { + return [NSError errorWithDomain:MoPubNativeAdsSDKDomain code:MPNativeAdErrorVideoConfigInvalid userInfo:@{NSLocalizedDescriptionKey : @"Native Video Config Values in Adserver response are invalid"}]; +} + +NSError *MPNativeAdNSErrorForContentDisplayErrorMissingRootController() { + return [NSError errorWithDomain:MoPubNativeAdsSDKDomain code:MPNativeAdErrorContentDisplayError userInfo:@{NSLocalizedDescriptionKey : @"Cannot display content without a root view controller"}]; +} + +NSError *MPNativeAdNSErrorForContentDisplayErrorInvalidURL() { + return [NSError errorWithDomain:MoPubNativeAdsSDKDomain code:MPNativeAdErrorContentDisplayError userInfo:@{NSLocalizedDescriptionKey : @"Cannot display content without a valid URL"}]; +} + +NSError *MPNativeAdNSErrorForRenderValueTypeError() { + return [NSError errorWithDomain:MoPubNativeAdsSDKDomain code:MPNativeAdErrorRenderError userInfo:@{NSLocalizedDescriptionKey : @"Native ad property was an incorrect data type"}]; +} diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAdRenderer.h b/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAdRenderer.h new file mode 100644 index 00000000000..3e7356ecf64 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAdRenderer.h @@ -0,0 +1,106 @@ +// +// MPNativeAdRenderer.h +// MoPubSDK +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import +#import + +@protocol MPNativeAdAdapter; +@protocol MPNativeAdRendererSettings; +@class MPNativeAdRendererConfiguration; + +/** + * Provide an implementation of this handler for your renderer settings. + * + * @param maximumWidth Max width of the ad expected to be rendered as. + * + * @return Size of the view as rendered given the maximum width desired. + */ +typedef CGSize (^MPNativeViewSizeHandler)(CGFloat maximumWidth); + +/** + * The MoPub SDK has a concept of native ad renderer that allows you to render the ad however you want. It also gives you the + * ability to expose configurable properties through renderer settings objects to the application that influence how you render + * your native custom event's view. + * + * Your renderer should implement this protocol. Your renderer is responsible for rendering the network's ad data into a view + * when `-retrieveViewWithAdapter:error:` is called. Your renderer will be asked to render the native ad if your renderer configuration + * indicates that your renderer supports your specific native ad network. + * + * Finally, your renderer will live as long as the ad adapter so you may store data in your renderer if necessary. + */ +@protocol MPNativeAdRenderer + +@required + +/** + * You must construct and return an MPNativeAdRendererConfiguration object specific for your renderer. You must + * set all the properties on the configuration object. + * + * @param rendererSettings Application defined settings that you should store in the configuration object that you + * construct. + * + * @return A configuration object that allows the MoPub SDK to instantiate your renderer with the application + * settings and for the supported ad types. + */ ++ (MPNativeAdRendererConfiguration *)rendererConfigurationWithRendererSettings:(id)rendererSettings; + +/** + * This is the init method that will be called when the MoPub SDK initializes your renderer. + * + * @param rendererSettings The renderer settings object that corresponds to your renderer. + */ +- (instancetype)initWithRendererSettings:(id)rendererSettings; + +/** + * You must return a native ad view when `-retrieveViewWithAdapter:error:` is called. Ideally, you should create a native view + * each time this is called as holding onto the view may end up consuming a lot of memory when many ads are being shown. + * However, it is OK to hold a strong reference to the view if you must. + * + * @param adapter Your custom event's adapter class that contains the network specific data necessary to render the ad to + * a view. + * @param error If you can't construct a view for whatever reason, you must fill in this error object. + * + * @return If successful, the method will return a native view presenting the ad. If it + * is unsuccessful at retrieving a view, it will return nil and create + * an error object for the error parameter. + */ +- (UIView *)retrieveViewWithAdapter:(id)adapter error:(NSError **)error; + +@optional + +/** + * The viewSizeHandler is used to allow the app to configure its native ad view size + * given a maximum width when using ad placer solutions. This is not called when the + * app is manually integrating native ads. + * + * You should obtain the renderer's viewSizeHandler from the settings object in + * `-initWithRendererSettings:`. + */ +@property (nonatomic, readonly) MPNativeViewSizeHandler viewSizeHandler; + +/** + * The MoPubSDK will notify your renderer when your native ad's view has moved in + * the hierarchy. superview will be nil if the native ad's view has been removed + * from the view hierarchy. + * + * The view your renderer creates is attached to another view before being added + * to the view hierarchy. As a result, the superview argument will not be the renderer's ad view's superview. + * + * @param superview The app's view that contains the native ad view. There is an + * intermediate view between the renderer's ad view and the app's view. + */ +- (void)adViewWillMoveToSuperview:(UIView *)superview; + +/** + * + * The MoPubSDK will call this method when the user has tapped the ad and will + * invoke the clickthrough action. + * + */ +- (void)nativeAdTapped; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAdRendererConfiguration.h b/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAdRendererConfiguration.h new file mode 100644 index 00000000000..26e243399d8 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAdRendererConfiguration.h @@ -0,0 +1,38 @@ +// +// MPNativeAdRendererConfiguration.h +// MoPubSDK +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import + +@protocol MPNativeAdRendererSettings; + +/* + * All native ads loaded with the MoPub SDK take a renderer configuration object. This object links + * the necessary native ad objects together. + * + * The configuration stores the renderer settings that will eventually be used when initializing the + * render class. Furthermore, the configuration indicates what custom events the renderer class supports + * through the supportedCustomEvents property. + */ +@interface MPNativeAdRendererConfiguration : NSObject + +/* + * The settings that inform the ad renderer about how it should render the ad. + */ +@property (nonatomic, strong) id rendererSettings; + +/* + * The renderer class used to render supported custom events. + */ +@property (nonatomic, assign) Class rendererClass; + +/* + * An array of custom event class names (as strings) that the renderClass can + * render. + */ +@property (nonatomic, strong) NSArray *supportedCustomEvents; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAdRendererConfiguration.m b/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAdRendererConfiguration.m new file mode 100644 index 00000000000..b59a580e6d7 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAdRendererConfiguration.m @@ -0,0 +1,12 @@ +// +// MPNativeAdRendererConfiguration.m +// MoPubSDK +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import "MPNativeAdRendererConfiguration.h" + +@implementation MPNativeAdRendererConfiguration + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAdRendererSettings.h b/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAdRendererSettings.h new file mode 100644 index 00000000000..372b88a315f --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAdRendererSettings.h @@ -0,0 +1,33 @@ +// +// MPNativeAdRendererSettings.h +// MoPubSDK +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import +#import "MPNativeAdRenderer.h" + +/* + * Renderer settings are objects that allow you to expose configurable properties to the application. + * You renderer will be initialized with these settings. + * + * You should create a renderer settings object that adheres to this protocol and exposes configurable + * configurable properties for your renderer class. + */ +@protocol MPNativeAdRendererSettings + +@optional + +/** + * The viewSizeHandler is used to allow the app to configure its native ad view size + * given a maximum width when using ad placer solutions. This is not called when the + * app is manually integrating native ads. + * + * Your renderer settings object should expose a settable viewSizeHandler property so the + * application can choose how it wants to size its ad views. Your renderer will be able + * to use the view size handler from your settings object. + */ +@property (nonatomic, readwrite, copy) MPNativeViewSizeHandler viewSizeHandler; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAdRendering.h b/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAdRendering.h new file mode 100644 index 00000000000..532cc1b5b5b --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAdRendering.h @@ -0,0 +1,103 @@ +// +// MPNativeAdRendering.h +// Copyright (c) 2013 MoPub. All rights reserved. +// + +#import + +#import "MPNativeAd.h" + +@class MPNativeAdRenderingImageLoader; + +/** + * The MPNativeAdRendering protocol provides methods for displaying ad content in + * custom view classes. + * + * It can be used for both static native ads and native video ads. If you are serving + * native video ads, you need to implement nativeVideoView. + */ + +@protocol MPNativeAdRendering + +@optional + +/** + * Return the UILabel that your view is using for the main text. + * + * @return a UILabel that is used for the main text. + */ +- (UILabel *)nativeMainTextLabel; + +/** + * Return the UILabel that your view is using for the title text. + * + * @return a UILabel that is used for the title text. + */ +- (UILabel *)nativeTitleTextLabel; + +/** + * Return the UIImageView that your view is using for the icon image. + * + * @return a UIImageView that is used for the icon image. + */ +- (UIImageView *)nativeIconImageView; + +/** + * Return the UIImageView that your view is using for the main image. + * + * @return a UIImageView that is used for the main image. + */ +- (UIImageView *)nativeMainImageView; + +/** + * Return the UIView that your view is using for the video. + * You only need to implement this when you are serving video ads. + * + * @return a UIView that is used to hold the video. + */ + +- (UIView *)nativeVideoView; + +/** + * Returns the UILabel that your view is using for the call to action (cta) text. + * + * @return a UILabel that is used for the cta text. + */ +- (UILabel *)nativeCallToActionTextLabel; + +/** + * Returns the UIImageView that your view is using for the privacy information icon. + * + * @return a UIImageView that is used for the privacy information icon. + */ +- (UIImageView *)nativePrivacyInformationIconImageView; + +/** + * This method is called if the ad contains a star rating. + * + * Implement this method if you expect and wish to display a star rating. + * + * @param starRating An NSNumber that is a float in the range of 0.0f and 5.0f. + */ +- (void)layoutStarRating:(NSNumber *)starRating; + +/** + * This method allows you to insert your custom native ad elements into your view. + * + * This method will be called when your ad view is added to the view hierarchy. + * + * @param customProperties Dictionary that contains custom native ad elements. + * @param imageLoader Use this object to load your custom images by calling `loadImageForURL:intoImageView:`. + */ +- (void)layoutCustomAssetsWithProperties:(NSDictionary *)customProperties imageLoader:(MPNativeAdRenderingImageLoader *)imageLoader; + +/** + * Specifies a nib object containing a view that should be used to render ads. + * + * If you want to use a nib object to render ads, you must implement this method. + * + * @return an initialized UINib object. This is not allowed to be `nil`. + */ ++ (UINib *)nibForAd; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAdRenderingImageLoader.h b/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAdRenderingImageLoader.h new file mode 100644 index 00000000000..dd30a026903 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAdRenderingImageLoader.h @@ -0,0 +1,16 @@ +// +// MPNativeAdRenderingImageLoader.h +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import + +@class MPNativeAdRendererImageHandler; + +@interface MPNativeAdRenderingImageLoader : NSObject + +- (instancetype)initWithImageHandler:(MPNativeAdRendererImageHandler *)imageHandler; + +- (void)loadImageForURL:(NSURL *)url intoImageView:(UIImageView *)imageView; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAdRenderingImageLoader.m b/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAdRenderingImageLoader.m new file mode 100644 index 00000000000..919096a703b --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAdRenderingImageLoader.m @@ -0,0 +1,31 @@ +// +// MPNativeAdRenderingImageLoader.m +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import "MPNativeAdRenderingImageLoader.h" +#import "MPNativeAdRendererImageHandler.h" + +@interface MPNativeAdRenderingImageLoader () + +@property (nonatomic) MPNativeAdRendererImageHandler *imageHandler; + +@end + +@implementation MPNativeAdRenderingImageLoader + +- (instancetype)initWithImageHandler:(MPNativeAdRendererImageHandler *)imageHandler +{ + if (self = [super init]) { + _imageHandler = imageHandler; + } + + return self; +} + +- (void)loadImageForURL:(NSURL *)url intoImageView:(UIImageView *)imageView +{ + [self.imageHandler loadImageForURL:url intoImageView:imageView]; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAdRequest.h b/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAdRequest.h new file mode 100644 index 00000000000..f9f3d325fee --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAdRequest.h @@ -0,0 +1,60 @@ +// +// MPNativeAdRequest.h +// Copyright (c) 2013 MoPub. All rights reserved. +// + +#import + +@class MPNativeAd; +@class MPNativeAdRequest; +@class MPNativeAdRequestTargeting; + +typedef void(^MPNativeAdRequestHandler)(MPNativeAdRequest *request, + MPNativeAd *response, + NSError *error); + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * The `MPNativeAdRequest` class is used to manage individual requests to the MoPub ad server for + * native ads. + * + * @warning **Note:** This class is meant for one-off requests for which you intend to manually + * process the native ad response. If you are using `MPTableViewAdPlacer` or + * `MPCollectionViewAdPlacer` to display ads, there should be no need for you to use this class. + */ + +@interface MPNativeAdRequest : NSObject + +/** @name Targeting Information */ + +/** + * An object representing targeting parameters that can be passed to the MoPub ad server to + * serve more relevant advertising. + */ +@property (nonatomic, strong) MPNativeAdRequestTargeting *targeting; + +/** @name Initializing and Starting an Ad Request */ + +/** + * Initializes a request object. + * + * @param identifier The ad unit identifier for this request. An ad unit is a defined placement in + * your application set aside for advertising. Ad unit IDs are created on the MoPub website. + * + * @param rendererConfigurations An array of MPNativeAdRendererConfiguration objects that control how + * the native ad is rendered. + * + * @return An `MPNativeAdRequest` object. + */ ++ (MPNativeAdRequest *)requestWithAdUnitIdentifier:(NSString *)identifier rendererConfigurations:(NSArray *)rendererConfigurations; + +/** + * Executes a request to the MoPub ad server. + * + * @param handler A block to execute when the request finishes. The block includes as parameters the + * request itself and either a valid MPNativeAd or an NSError object indicating failure. + */ +- (void)startWithCompletionHandler:(MPNativeAdRequestHandler)handler; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAdRequest.m b/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAdRequest.m new file mode 100644 index 00000000000..3f64fb7cffa --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAdRequest.m @@ -0,0 +1,284 @@ +// +// MPNativeAdRequest.m +// Copyright (c) 2013 MoPub. All rights reserved. +// + +#import "MPNativeAdRequest.h" + +#import "MPAdServerURLBuilder.h" +#import "MPCoreInstanceProvider.h" +#import "MPNativeAdError.h" +#import "MPNativeAd+Internal.h" +#import "MPNativeAdRequestTargeting.h" +#import "MPLogEvent.h" +#import "MPLogging.h" +#import "MPImageDownloadQueue.h" +#import "MPConstants.h" +#import "MPNativeAdConstants.h" +#import "MPNativeCustomEventDelegate.h" +#import "MPNativeCustomEvent.h" +#import "MOPUBNativeVideoAdConfigValues.h" +#import "MOPUBNativeVideoCustomEvent.h" +#import "MPInstanceProvider.h" +#import "NSJSONSerialization+MPAdditions.h" +#import "MPAdServerCommunicator.h" +#import "MPNativeAdRenderer.h" +#import "MPMoPubNativeCustomEvent.h" +#import "MPNativeAdRendererConfiguration.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface MPNativeAdRequest () + +@property (nonatomic, copy) NSString *adUnitIdentifier; +@property (nonatomic) NSArray *rendererConfigurations; +@property (nonatomic, strong) NSURL *URL; +@property (nonatomic, strong) MPAdServerCommunicator *communicator; +@property (nonatomic, copy) MPNativeAdRequestHandler completionHandler; +@property (nonatomic, strong) MPNativeCustomEvent *nativeCustomEvent; +@property (nonatomic, strong) MPAdConfiguration *adConfiguration; +@property (nonatomic) id customEventRenderer; +@property (nonatomic, assign) BOOL loading; + +@end + +@implementation MPNativeAdRequest + +- (id)initWithAdUnitIdentifier:(NSString *)identifier rendererConfigurations:(NSArray *)rendererConfigurations +{ + self = [super init]; + if (self) { + _adUnitIdentifier = [identifier copy]; + _communicator = [[MPCoreInstanceProvider sharedProvider] buildMPAdServerCommunicatorWithDelegate:self]; + _rendererConfigurations = rendererConfigurations; + } + return self; +} + +- (void)dealloc +{ + [_communicator cancel]; + [_communicator setDelegate:nil]; + [_nativeCustomEvent setDelegate:nil]; +} + +#pragma mark - Public + ++ (MPNativeAdRequest *)requestWithAdUnitIdentifier:(NSString *)identifier rendererConfigurations:(NSArray *)rendererConfigurations +{ + return [[self alloc] initWithAdUnitIdentifier:identifier rendererConfigurations:rendererConfigurations]; +} + +- (void)startWithCompletionHandler:(MPNativeAdRequestHandler)handler +{ + if (handler) { + self.URL = [MPAdServerURLBuilder URLWithAdUnitID:self.adUnitIdentifier + keywords:self.targeting.keywords + location:self.targeting.location + versionParameterName:@"nsv" + version:MP_SDK_VERSION + testing:NO + desiredAssets:[self.targeting.desiredAssets allObjects]]; + + [self assignCompletionHandler:handler]; + + [self loadAdWithURL:self.URL]; + } else { + MPLogWarn(@"Native Ad Request did not start - requires completion handler block."); + } +} + +- (void)startForAdSequence:(NSInteger)adSequence withCompletionHandler:(MPNativeAdRequestHandler)handler +{ + if (handler) { + self.URL = [MPAdServerURLBuilder URLWithAdUnitID:self.adUnitIdentifier + keywords:self.targeting.keywords + location:self.targeting.location + versionParameterName:@"nsv" + version:MP_SDK_VERSION + testing:NO + desiredAssets:[self.targeting.desiredAssets allObjects] + adSequence:adSequence]; + + [self assignCompletionHandler:handler]; + + [self loadAdWithURL:self.URL]; + } else { + MPLogWarn(@"Native Ad Request did not start - requires completion handler block."); + } +} + +#pragma mark - Private + +- (void)assignCompletionHandler:(MPNativeAdRequestHandler)handler +{ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-retain-cycles" + // we explicitly create a block retain cycle here to prevent self from being deallocated if the developer + // removes his strong reference to the request. This retain cycle is broken in + // - (void)completeAdRequestWithAdObject:(MPNativeAd *)adObject error:(NSError *)error + // when self.completionHandler is set to nil. + self.completionHandler = ^(MPNativeAdRequest *request, MPNativeAd *response, NSError *error) { + handler(self, response, error); + }; +#pragma clang diagnostic pop +} + +- (void)loadAdWithURL:(NSURL *)URL +{ + if (self.loading) { + MPLogWarn(@"Native ad request is already loading an ad. Wait for previous load to finish."); + return; + } + + MPLogInfo(@"Starting ad request with URL: %@", self.URL); + + self.loading = YES; + [self.communicator loadURL:URL]; +} + +- (void)getAdWithConfiguration:(MPAdConfiguration *)configuration +{ + if (configuration.customEventClass) { + MPLogInfo(@"Looking for custom event class named %@.", configuration.customEventClass); + } + + // For MoPub native ads, set the classData to be the adResponseData + if ((configuration.customEventClass == [MPMoPubNativeCustomEvent class]) || (configuration.customEventClass == [MOPUBNativeVideoCustomEvent class])) { + NSError *error; + NSMutableDictionary *classData = [NSJSONSerialization mp_JSONObjectWithData:configuration.adResponseData + options:0 + clearNullObjects:YES + error:&error]; + if (configuration.customEventClass == [MOPUBNativeVideoCustomEvent class]) { + [classData setObject:[[MOPUBNativeVideoAdConfigValues alloc] + initWithPlayVisiblePercent:configuration.nativeVideoPlayVisiblePercent + pauseVisiblePercent:configuration.nativeVideoPauseVisiblePercent + impressionMinVisiblePercent:configuration.nativeVideoImpressionMinVisiblePercent + impressionVisible:configuration.nativeVideoImpressionVisible + maxBufferingTime:configuration.nativeVideoMaxBufferingTime + trackers:configuration.nativeVideoTrackers] forKey:kNativeVideoAdConfigKey]; + MPAdConfigurationLogEventProperties *logEventProperties = + [[MPAdConfigurationLogEventProperties alloc] initWithConfiguration:configuration]; + [classData setObject:logEventProperties forKey:kLogEventRequestPropertiesKey]; + } + + configuration.customEventClassData = classData; + } + + // See if we have a renderer that we can use for the custom event now so we can fail early. + NSString *customEventClassName = NSStringFromClass(configuration.customEventClass); + MPNativeAdRendererConfiguration *customEventRendererConfig = nil; + + for (MPNativeAdRendererConfiguration *rendererConfig in self.rendererConfigurations) { + NSArray *supportedCustomEvents = rendererConfig.supportedCustomEvents; + + if ([supportedCustomEvents containsObject:customEventClassName]) { + customEventRendererConfig = rendererConfig; + break; + } + } + + if (customEventRendererConfig) { + // Create a renderer from the config. + self.customEventRenderer = [[customEventRendererConfig.rendererClass alloc] initWithRendererSettings:customEventRendererConfig.rendererSettings]; + self.nativeCustomEvent = [[MPInstanceProvider sharedProvider] buildNativeCustomEventFromCustomClass:configuration.customEventClass delegate:self]; + } else { + MPLogError(@"Could not find renderer configuration for custom event class: %@", NSStringFromClass(configuration.customEventClass)); + } + + if (self.nativeCustomEvent) { + [self.nativeCustomEvent requestAdWithCustomEventInfo:configuration.customEventClassData]; + } else if ([[self.adConfiguration.failoverURL absoluteString] length]) { + self.loading = NO; + [self loadAdWithURL:self.adConfiguration.failoverURL]; + } else { + [self completeAdRequestWithAdObject:nil error:MPNativeAdNSErrorForInvalidAdServerResponse(nil)]; + } +} + +- (void)completeAdRequestWithAdObject:(MPNativeAd *)adObject error:(NSError *)error +{ + self.loading = NO; + + adObject.renderer = self.customEventRenderer; + + if ([(id)adObject.adAdapter respondsToSelector:@selector(setAdConfiguration:)]) { + [(id)adObject.adAdapter performSelector:@selector(setAdConfiguration:) withObject:self.adConfiguration]; + } + + if (!error) { + MPLogInfo(@"Successfully loaded native ad."); + } else { + MPLogError(@"Native ad failed to load with error: %@", error); + } + + if (self.completionHandler) { + self.completionHandler(self, adObject, error); + self.completionHandler = nil; + } +} + +#pragma mark - + +- (void)communicatorDidReceiveAdConfiguration:(MPAdConfiguration *)configuration +{ + self.adConfiguration = configuration; + + if (configuration.adUnitWarmingUp) { + MPLogInfo(kMPWarmingUpErrorLogFormatWithAdUnitID, self.adUnitIdentifier); + [self completeAdRequestWithAdObject:nil error:MPNativeAdNSErrorForAdUnitWarmingUp()]; + return; + } + + if ([configuration.networkType isEqualToString:kAdTypeClear]) { + MPLogInfo(kMPClearErrorLogFormatWithAdUnitID, self.adUnitIdentifier); + [self completeAdRequestWithAdObject:nil error:MPNativeAdNSErrorForNoInventory()]; + return; + } + + MPLogInfo(@"Received data from MoPub to construct native ad.\n"); + [self getAdWithConfiguration:configuration]; +} + +- (void)communicatorDidFailWithError:(NSError *)error +{ + MPLogDebug(@"Error: Couldn't retrieve an ad from MoPub. Message: %@", error); + + [self completeAdRequestWithAdObject:nil error:MPNativeAdNSErrorForNetworkConnectionError()]; +} + +#pragma mark - + +- (void)nativeCustomEvent:(MPNativeCustomEvent *)event didLoadAd:(MPNativeAd *)adObject +{ + // Add the click tracker url from the header to our set. + if (self.adConfiguration.clickTrackingURL) { + [adObject.clickTrackerURLs addObject:self.adConfiguration.clickTrackingURL]; + } + + // Add the impression tracker url from the header to our set. + if (self.adConfiguration.impressionTrackingURL) { + [adObject.impressionTrackerURLs addObject:self.adConfiguration.impressionTrackingURL]; + } + + // Error if we don't have click trackers or impression trackers. + if (adObject.clickTrackerURLs.count < 1 || adObject.impressionTrackerURLs.count < 1) { + [self completeAdRequestWithAdObject:nil error:MPNativeAdNSErrorForInvalidAdServerResponse(@"Invalid ad trackers")]; + } else { + [self completeAdRequestWithAdObject:adObject error:nil]; + } +} + +- (void)nativeCustomEvent:(MPNativeCustomEvent *)event didFailToLoadAdWithError:(NSError *)error +{ + if ([[self.adConfiguration.failoverURL absoluteString] length]) { + self.loading = NO; + [self loadAdWithURL:self.adConfiguration.failoverURL]; + } else { + [self completeAdRequestWithAdObject:nil error:error]; + } +} + + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAdRequestTargeting.h b/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAdRequestTargeting.h new file mode 100644 index 00000000000..5fb814a454d --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAdRequestTargeting.h @@ -0,0 +1,54 @@ +// +// MPNativeAdRequestTargeting.h +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import + +@class CLLocation; + +/** + * The `MPNativeAdRequestTargeting` class is used to attach targeting information to + * `MPNativeAdRequest` objects. + */ + +@interface MPNativeAdRequestTargeting : NSObject + +/** @name Creating a Targeting Object */ + +/** + * Creates and returns an empty MPNativeAdRequestTargeting object. + * + * @return A newly initialized MPNativeAdRequestTargeting object. + */ ++ (MPNativeAdRequestTargeting *)targeting; + +/** @name Targeting Parameters */ + +/** + * A string representing a set of keywords that should be passed to the MoPub ad server to receive + * more relevant advertising. + * + * Keywords are typically used to target ad campaigns at specific user segments. They should be + * formatted as comma-separated key-value pairs (e.g. "marital:single,age:24"). + * + * On the MoPub website, keyword targeting options can be found under the "Advanced Targeting" + * section when managing campaigns. + */ +@property (nonatomic, copy) NSString *keywords; + +/** + * A `CLLocation` object representing a user's location that should be passed to the MoPub ad server + * to receive more relevant advertising. + */ +@property (nonatomic, copy) CLLocation *location; + +/** + * A set of defined strings that correspond to assets for the intended native ad + * object. This set should contain only those assets that will be displayed in the ad. + * + * The MoPub ad server will attempt to only return the assets in desiredAssets. + */ +@property (nonatomic, strong) NSSet *desiredAssets; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAdRequestTargeting.m b/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAdRequestTargeting.m new file mode 100644 index 00000000000..fea34ceecbf --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAdRequestTargeting.m @@ -0,0 +1,35 @@ +// +// MPNativeAdRequestTargeting.m +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import "MPNativeAdRequestTargeting.h" +#import "MPNativeAdConstants.h" + +#import + +@implementation MPNativeAdRequestTargeting + ++ (MPNativeAdRequestTargeting *)targeting +{ + return [[MPNativeAdRequestTargeting alloc] init]; +} + +- (void)setDesiredAssets:(NSSet *)desiredAssets +{ + if (_desiredAssets != desiredAssets) { + + NSMutableSet *allowedAdAssets = [NSMutableSet setWithObjects:kAdTitleKey, + kAdTextKey, + kAdIconImageKey, + kAdMainImageKey, + kAdCTATextKey, + kAdStarRatingKey, + nil]; + [allowedAdAssets intersectSet:desiredAssets]; + _desiredAssets = allowedAdAssets; + } +} + + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAdSource.h b/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAdSource.h new file mode 100644 index 00000000000..b338a1b2c6f --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAdSource.h @@ -0,0 +1,22 @@ +// +// MPNativeAdSource.h +// MoPub +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import +#import "MPNativeAdSourceDelegate.h" +@class MPNativeAdRequestTargeting; + +@interface MPNativeAdSource : NSObject + +@property (nonatomic, weak) id delegate; + ++ (instancetype)source; +- (void)loadAdsWithAdUnitIdentifier:(NSString *)identifier rendererConfigurations:(NSArray *)rendererConfigurations andTargeting:(MPNativeAdRequestTargeting *)targeting; +- (void)deleteCacheForAdUnitIdentifier:(NSString *)identifier; +- (id)dequeueAdForAdUnitIdentifier:(NSString *)identifier; + + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAdSource.m b/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAdSource.m new file mode 100644 index 00000000000..1c0a61b5dd4 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAdSource.m @@ -0,0 +1,83 @@ +// +// MPNativeAdSource.m +// MoPub +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import "MPNativeAdSource.h" +#import "MPNativeAd.h" +#import "MPNativeAdRequestTargeting.h" +#import "MPNativeAdSourceQueue.h" + +static NSTimeInterval const kCacheTimeoutInterval = 900; //15 minutes + +@interface MPNativeAdSource () + +@property (nonatomic, strong) NSMutableDictionary *adQueueDictionary; + +@end + +@implementation MPNativeAdSource + +#pragma mark - Object Lifecycle + ++ (instancetype)source +{ + return [[MPNativeAdSource alloc] init]; +} + +- (instancetype)init +{ + self = [super init]; + if (self) { + _adQueueDictionary = [[NSMutableDictionary alloc] init]; + } + + return self; +} + +- (void)dealloc +{ + for (NSString *queueKey in [_adQueueDictionary allKeys]) { + [self deleteCacheForAdUnitIdentifier:queueKey]; + } +} + +#pragma mark - Ad Source Interface + +- (void)loadAdsWithAdUnitIdentifier:(NSString *)identifier rendererConfigurations:(NSArray *)rendererConfigurations andTargeting:(MPNativeAdRequestTargeting *)targeting +{ + [self deleteCacheForAdUnitIdentifier:identifier]; + + MPNativeAdSourceQueue *adQueue = [[MPNativeAdSourceQueue alloc] initWithAdUnitIdentifier:identifier rendererConfigurations:rendererConfigurations andTargeting:targeting]; + adQueue.delegate = self; + [self.adQueueDictionary setObject:adQueue forKey:identifier]; + + [adQueue loadAds]; +} + +- (id)dequeueAdForAdUnitIdentifier:(NSString *)identifier +{ + MPNativeAdSourceQueue *adQueue = [self.adQueueDictionary objectForKey:identifier]; + MPNativeAd *nextAd = [adQueue dequeueAdWithMaxAge:kCacheTimeoutInterval]; + return nextAd; +} + +- (void)deleteCacheForAdUnitIdentifier:(NSString *)identifier +{ + MPNativeAdSourceQueue *sourceQueue = [self.adQueueDictionary objectForKey:identifier]; + sourceQueue.delegate = nil; + [sourceQueue cancelRequests]; + + [self.adQueueDictionary removeObjectForKey:identifier]; +} + +#pragma mark - MPNativeAdSourceQueueDelegate + +- (void)adSourceQueueAdIsAvailable:(MPNativeAdSourceQueue *)source +{ + [self.delegate adSourceDidFinishRequest:self]; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAdSourceDelegate.h b/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAdSourceDelegate.h new file mode 100644 index 00000000000..eac0599349a --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeAdSourceDelegate.h @@ -0,0 +1,16 @@ +// +// MPNativeAdSourceDelegate.h +// MoPub +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import + +@class MPNativeAdSource; + +@protocol MPNativeAdSourceDelegate + +- (void)adSourceDidFinishRequest:(MPNativeAdSource *)source; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeCustomEvent.h b/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeCustomEvent.h new file mode 100644 index 00000000000..447295df907 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeCustomEvent.h @@ -0,0 +1,69 @@ +// +// MPNativeCustomEvent.h +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import "MPNativeCustomEventDelegate.h" + +/** + * The MoPub iOS SDK mediates third-party native ad networks using custom events. Custom events are + * responsible for instantiating and manipulating native ad objects in the third-party SDK and + * translating and communicating events from those objects back to the MoPub SDK by notifying a + * delegate. + * + * `MPNativeCustomEvent` is a base class for custom events that support native ads. By implementing + * subclasses of `MPNativeCustomEvent` you can enable the MoPub SDK to support a variety of + * third-party ad networks. + * + * Your implementation should create an `MPNativeAd` object using an appropriate `MPNativeAdAdapter` + * for your network. Your custom event should also call the appropriate + * `MPNativeCustomEventDelegate` methods. + * + * At runtime, the MoPub SDK will find and instantiate an `MPNativeCustomEvent` subclass as needed + * and invoke its `-requestAdWithCustomEventInfo:` method. + */ +@interface MPNativeCustomEvent : NSObject + +/** @name Requesting a Native Ad */ + +/** + * Called when the MoPub SDK requires a new native ad. + * + * When the MoPub SDK receives a response indicating it should load a custom event, it will send + * this message to your custom event class. Your implementation should load a native ad from a + * third-party ad network. + * + * @param info A dictionary containing additional custom data associated with a given custom event + * request. This data is configurable on the MoPub website, and may be used to pass dynamic + * information, such as publisher IDs. + */ +- (void)requestAdWithCustomEventInfo:(NSDictionary *)info; + +/** @name Caching Image Resources */ + +/** + * Downloads and pre-caches images. + * + * If your ad network does not provide built-in support for image caching, you may invoke this + * method in your custom event implementation to pre-cache image assets. If you do call this method, + * you should wait until the completion block is called before invoking the appropriate + * success or failure callbacks on the `MPNativeCustomEventDelegate`. + * + * @param imageURLs An array of `NSURL` objects representing image resource locations. + * @param completionBlock A block that will be called after all download operations are complete. + * If any image downloads do not complete successfully, the `errors` parameter will contain + * error information about the failures. + */ +- (void)precacheImagesWithURLs:(NSArray *)imageURLs completionBlock:(void (^)(NSArray *errors))completionBlock; + +/** @name Communicating with the MoPub SDK */ + +/** + * The `MPNativeCustomEventDelegate` receives messages concerning the status of loading a native ad. + * + * The `delegate` object defines several methods that you should call in order to inform MoPub + * of the progress of your custom event. + */ +@property (nonatomic, weak) id delegate; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeCustomEvent.m b/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeCustomEvent.m new file mode 100644 index 00000000000..aa7e11605f9 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeCustomEvent.m @@ -0,0 +1,49 @@ +// +// MPNativeCustomEvent.m +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import "MPNativeCustomEvent.h" +#import "MPNativeAdError.h" +#import "MPImageDownloadQueue.h" +#import "MPLogging.h" + +@interface MPNativeCustomEvent () + +@property (nonatomic, strong) MPImageDownloadQueue *imageDownloadQueue; + +@end + +@implementation MPNativeCustomEvent + +- (id)init +{ + self = [super init]; + if (self) { + _imageDownloadQueue = [[MPImageDownloadQueue alloc] init]; + } + + return self; +} + +- (void)precacheImagesWithURLs:(NSArray *)imageURLs completionBlock:(void (^)(NSArray *errors))completionBlock +{ + if (imageURLs.count > 0) { + [_imageDownloadQueue addDownloadImageURLs:imageURLs completionBlock:^(NSArray *errors) { + if (completionBlock) { + completionBlock(errors); + } + }]; + } else { + if (completionBlock) { + completionBlock(nil); + } + } +} + +- (void)requestAdWithCustomEventInfo:(NSDictionary *)info +{ + /*override with custom network behavior*/ +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeCustomEventDelegate.h b/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeCustomEventDelegate.h new file mode 100644 index 00000000000..ff0b0f39a21 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/MPNativeCustomEventDelegate.h @@ -0,0 +1,36 @@ +// +// MPNativeCustomEventDelegate.h +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import + +@class MPNativeAd; +@class MPNativeCustomEvent; + +/** + * Instances of your custom subclass of `MPNativeCustomEvent` will have an + * `MPNativeCustomEventDelegate` delegate object. You use this delegate to communicate progress + * (such as whether an ad has loaded successfully) back to the MoPub SDK. + */ +@protocol MPNativeCustomEventDelegate + +/** + * This method is called when the ad and all required ad assets are loaded. + * + * @param event You should pass `self` to allow the MoPub SDK to associate this event with the + * correct instance of your custom event. + * @param adObject An `MPNativeAd` object, representing the ad that was retrieved. + */ +- (void)nativeCustomEvent:(MPNativeCustomEvent *)event didLoadAd:(MPNativeAd *)adObject; + +/** + * This method is called when the ad or any required ad assets fail to load. + * + * @param event You should pass `self` to allow the MoPub SDK to associate this event with the + * correct instance of your custom event. + * @param error (*optional*) You may pass an error describing the failure. + */ +- (void)nativeCustomEvent:(MPNativeCustomEvent *)event didFailToLoadAdWithError:(NSError *)error; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/MPServerAdPositioning.h b/iphone/Maps/3party/MoPubSDK/Native Ads/MPServerAdPositioning.h new file mode 100644 index 00000000000..c2952f2df78 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/MPServerAdPositioning.h @@ -0,0 +1,38 @@ +// +// MPServerAdPositioning.h +// MoPub +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import "MPAdPositioning.h" + +/** + * The `MPServerAdPositioning` class is a model that allows you to control the positions where + * native advertisements should appear within a stream. A server positioning object works in + * conjunction with an ad placer, telling the ad placer that it should retrieve positioning + * information from the MoPub ad server. + * + * Unlike `MPClientAdPositioning`, which represents hard-coded positioning information, a server + * positioning object offers you the benefit of modifying your ad positions via the MoPub website, + * without rebuilding your application. + */ + +@interface MPServerAdPositioning : MPAdPositioning + +/** @name Creating a Server Positioning Object */ + +/** + * Creates and returns a server positioning object. + * + * When an ad placer is set to use server positioning, it will ask the MoPub ad server for the + * positions where ads should be inserted into a given stream. These positioning values are + * configurable on the MoPub website. + * + * @return The newly created positioning object. + * + * @see MPClientAdPositioning + */ ++ (instancetype)positioning; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/MPServerAdPositioning.m b/iphone/Maps/3party/MoPubSDK/Native Ads/MPServerAdPositioning.m new file mode 100644 index 00000000000..d75d8591260 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/MPServerAdPositioning.m @@ -0,0 +1,17 @@ +// +// MPServerAdPositioning.m +// MoPub +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import "MPServerAdPositioning.h" + +@implementation MPServerAdPositioning + ++ (instancetype)positioning +{ + return [[[self class] alloc] init]; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/MPStaticNativeAdRenderer.h b/iphone/Maps/3party/MoPubSDK/Native Ads/MPStaticNativeAdRenderer.h new file mode 100644 index 00000000000..666b42de0c6 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/MPStaticNativeAdRenderer.h @@ -0,0 +1,20 @@ +// +// MPStaticNativeAdRenderer.h +// MoPubSDK +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import +#import "MPNativeAdRenderer.h" + +@class MPNativeAdRendererConfiguration; +@class MPStaticNativeAdRendererSettings; + +@interface MPStaticNativeAdRenderer : NSObject + +@property (nonatomic, readonly) MPNativeViewSizeHandler viewSizeHandler; + ++ (MPNativeAdRendererConfiguration *)rendererConfigurationWithRendererSettings:(id)rendererSettings; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/MPStaticNativeAdRenderer.m b/iphone/Maps/3party/MoPubSDK/Native Ads/MPStaticNativeAdRenderer.m new file mode 100644 index 00000000000..024349aa5a0 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/MPStaticNativeAdRenderer.m @@ -0,0 +1,200 @@ +// +// MPStaticNativeAdRenderer.m +// MoPubSDK +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import "MPAdDestinationDisplayAgent.h" +#import "MPLogging.h" +#import "MPNativeAdAdapter.h" +#import "MPNativeAdConstants.h" +#import "MPNativeAdError.h" +#import "MPNativeAdRenderer.h" +#import "MPNativeAdRendererConfiguration.h" +#import "MPNativeAdRendererConstants.h" +#import "MPNativeAdRendererImageHandler.h" +#import "MPNativeAdRendering.h" +#import "MPNativeAdRenderingImageLoader.h" +#import "MPNativeCache.h" +#import "MPNativeView.h" +#import "MPStaticNativeAdRenderer.h" +#import "MPStaticNativeAdRendererSettings.h" + +/** + * -1.0 is somewhat significant because this also happens to be what `UITableViewAutomaticDimension` + * is so it makes for easier migration to use `UITableViewAutomaticDimension` on iOS 8+ later but is not + * currently passed back in `-tableView:shouldIndentWhileEditingRowAtIndexPath:` directly so it can + * be any abitrary value. + */ +const CGFloat MPNativeViewDynamicDimension = -1.0; + +@interface MPStaticNativeAdRenderer () + +@property (nonatomic) UIView *adView; +@property (nonatomic) id adapter; +@property (nonatomic) BOOL adViewInViewHierarchy; +@property (nonatomic) Class renderingViewClass; +@property (nonatomic) MPNativeAdRendererImageHandler *rendererImageHandler; + +@end + +@implementation MPStaticNativeAdRenderer + ++ (MPNativeAdRendererConfiguration *)rendererConfigurationWithRendererSettings:(id)rendererSettings +{ + MPNativeAdRendererConfiguration *config = [[MPNativeAdRendererConfiguration alloc] init]; + config.rendererClass = [self class]; + config.rendererSettings = rendererSettings; + config.supportedCustomEvents = @[@"MPMoPubNativeCustomEvent", @"FacebookNativeCustomEvent", @"FlurryNativeCustomEvent", @"InMobiNativeCustomEvent", @"MillennialNativeCustomEvent", @"MPGoogleAdMobNativeCustomEvent"]; + + return config; +} + +- (instancetype)initWithRendererSettings:(id)rendererSettings +{ + if (self = [super init]) { + MPStaticNativeAdRendererSettings *settings = (MPStaticNativeAdRendererSettings *)rendererSettings; + _renderingViewClass = settings.renderingViewClass; + _viewSizeHandler = [settings.viewSizeHandler copy]; + _rendererImageHandler = [MPNativeAdRendererImageHandler new]; + _rendererImageHandler.delegate = self; + } + + return self; +} + +- (UIView *)retrieveViewWithAdapter:(id)adapter error:(NSError **)error +{ + if (!adapter) { + if (error) { + *error = MPNativeAdNSErrorForRenderValueTypeError(); + } + + return nil; + } + + self.adapter = adapter; + + if ([self.renderingViewClass respondsToSelector:@selector(nibForAd)]) { + self.adView = (UIView *)[[[self.renderingViewClass nibForAd] instantiateWithOwner:nil options:nil] firstObject]; + } else { + self.adView = [[self.renderingViewClass alloc] init]; + } + + self.adView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; + + // We only load text here. We delay loading of images until the view is added to the view hierarchy + // so we don't unnecessarily load images from the cache if the user is scrolling fast. So we will + // just store the image URLs for now. + if ([self.adView respondsToSelector:@selector(nativeMainTextLabel)]) { + self.adView.nativeMainTextLabel.text = [adapter.properties objectForKey:kAdTextKey]; + } + + if ([self.adView respondsToSelector:@selector(nativeTitleTextLabel)]) { + self.adView.nativeTitleTextLabel.text = [adapter.properties objectForKey:kAdTitleKey]; + } + + if ([self.adView respondsToSelector:@selector(nativeCallToActionTextLabel)] && self.adView.nativeCallToActionTextLabel) { + self.adView.nativeCallToActionTextLabel.text = [adapter.properties objectForKey:kAdCTATextKey]; + } + + if ([self.adView respondsToSelector:@selector(nativePrivacyInformationIconImageView)]) { + // MoPub ads pass the privacy information icon key through the properties dictionary. + NSString *daaIconImageLoc = [adapter.properties objectForKey:kAdDAAIconImageKey]; + if (daaIconImageLoc) { + UIImageView *imageView = self.adView.nativePrivacyInformationIconImageView; + imageView.hidden = NO; + + UIImage *daaIconImage = [UIImage imageNamed:daaIconImageLoc]; + imageView.image = daaIconImage; + + // Attach a gesture recognizer to handle loading the daa icon URL. + UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(DAAIconTapped)]; + imageView.userInteractionEnabled = YES; + [imageView addGestureRecognizer:tapRecognizer]; + } else if ([adapter respondsToSelector:@selector(privacyInformationIconView)]) { + // The ad network may provide its own view for its privacy information icon. We assume the ad handles the tap on the icon as well. + UIView *privacyIconAdView = [adapter privacyInformationIconView]; + privacyIconAdView.frame = self.adView.nativePrivacyInformationIconImageView.bounds; + privacyIconAdView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; + self.adView.nativePrivacyInformationIconImageView.userInteractionEnabled = YES; + [self.adView.nativePrivacyInformationIconImageView addSubview:privacyIconAdView]; + self.adView.nativePrivacyInformationIconImageView.hidden = NO; + } else { + self.adView.nativePrivacyInformationIconImageView.userInteractionEnabled = NO; + self.adView.nativePrivacyInformationIconImageView.hidden = YES; + } + } + + if ([self shouldLoadMediaView]) { + UIView *mediaView = [self.adapter mainMediaView]; + UIView *mainImageView = [self.adView nativeMainImageView]; + + mediaView.frame = mainImageView.bounds; + mediaView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; + mainImageView.userInteractionEnabled = YES; + + [mainImageView addSubview:mediaView]; + } + + // See if the ad contains a star rating and notify the view if it does. + if ([self.adView respondsToSelector:@selector(layoutStarRating:)]) { + NSNumber *starRatingNum = [adapter.properties objectForKey:kAdStarRatingKey]; + + if ([starRatingNum isKindOfClass:[NSNumber class]] && starRatingNum.floatValue >= kStarRatingMinValue && starRatingNum.floatValue <= kStarRatingMaxValue) { + [self.adView layoutStarRating:starRatingNum]; + } + } + + return self.adView; +} + +- (BOOL)shouldLoadMediaView +{ + return [self.adapter respondsToSelector:@selector(mainMediaView)] + && [self.adapter mainMediaView] + && [self.adView respondsToSelector:@selector(nativeMainImageView)]; +} + +- (void)DAAIconTapped +{ + if ([self.adapter respondsToSelector:@selector(displayContentForDAAIconTap)]) { + [self.adapter displayContentForDAAIconTap]; + } +} + +- (void)adViewWillMoveToSuperview:(UIView *)superview +{ + self.adViewInViewHierarchy = (superview != nil); + + if (superview) { + // We'll start asychronously loading the native ad images now. + if ([self.adapter.properties objectForKey:kAdIconImageKey] && [self.adView respondsToSelector:@selector(nativeIconImageView)]) { + [self.rendererImageHandler loadImageForURL:[NSURL URLWithString:[self.adapter.properties objectForKey:kAdIconImageKey]] intoImageView:self.adView.nativeIconImageView]; + } + + // Only handle the loading of the main image if the adapter doesn't already have a view for it. + if (!([self.adapter respondsToSelector:@selector(mainMediaView)] && [self.adapter mainMediaView])) { + if ([self.adapter.properties objectForKey:kAdMainImageKey] && [self.adView respondsToSelector:@selector(nativeMainImageView)]) { + [self.rendererImageHandler loadImageForURL:[NSURL URLWithString:[self.adapter.properties objectForKey:kAdMainImageKey]] intoImageView:self.adView.nativeMainImageView]; + } + } + + // Layout custom assets here as the custom assets may contain images that need to be loaded. + if ([self.adView respondsToSelector:@selector(layoutCustomAssetsWithProperties:imageLoader:)]) { + // Create a simplified image loader for the ad view to use. + MPNativeAdRenderingImageLoader *imageLoader = [[MPNativeAdRenderingImageLoader alloc] initWithImageHandler:self.rendererImageHandler]; + [self.adView layoutCustomAssetsWithProperties:self.adapter.properties imageLoader:imageLoader]; + } + } +} + +#pragma mark - MPNativeAdRendererImageHandlerDelegate + +- (BOOL)nativeAdViewInViewHierarchy +{ + return self.adViewInViewHierarchy; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/MPStaticNativeAdRendererSettings.h b/iphone/Maps/3party/MoPubSDK/Native Ads/MPStaticNativeAdRendererSettings.h new file mode 100644 index 00000000000..3ddc074ddbc --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/MPStaticNativeAdRendererSettings.h @@ -0,0 +1,30 @@ +// +// MPStaticNativeAdRendererSettings.h +// MoPubSDK +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import +#import "MPNativeAdRendererSettings.h" +#import "MPNativeAdRenderer.h" + +@interface MPStaticNativeAdRendererSettings : NSObject + +/** + * A rendering class that must be a UIView that implements the MPNativeAdRendering protocol. + * The ad will be rendered to a view of this class type. + */ +@property (nonatomic, assign) Class renderingViewClass; + +/** + * A block that returns the size of the view given a maximum width. This needs to be set when + * used in conjunction with ad placer classes so the ad placers can correctly size the cells + * that contain the ads. + * + * viewSizeHandler is not used for manual native ad integration. You must set the + * frame of your manually integrated native ad view. + */ +@property (nonatomic, readwrite, copy) MPNativeViewSizeHandler viewSizeHandler; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/MPStaticNativeAdRendererSettings.m b/iphone/Maps/3party/MoPubSDK/Native Ads/MPStaticNativeAdRendererSettings.m new file mode 100644 index 00000000000..264ca8ac5b8 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/MPStaticNativeAdRendererSettings.m @@ -0,0 +1,12 @@ +// +// MPStaticNativeAdRendererSettings.m +// MoPubSDK +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import "MPStaticNativeAdRendererSettings.h" + +@implementation MPStaticNativeAdRendererSettings + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/MPStreamAdPlacementData.h b/iphone/Maps/3party/MoPubSDK/Native Ads/MPStreamAdPlacementData.h new file mode 100644 index 00000000000..35769b68ed8 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/MPStreamAdPlacementData.h @@ -0,0 +1,35 @@ +// +// MPStreamAdPlacementData.h +// MoPub +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import +#import + +@class MPAdPositioning; +@class MPNativeAdData; + +@interface MPStreamAdPlacementData : NSObject + +- (instancetype)initWithPositioning:(MPAdPositioning *)positioning; +- (void)insertAdData:(MPNativeAdData *)data atIndexPath:(NSIndexPath *)adjustedIndexPath; +- (NSArray *)adjustedAdIndexPathsInAdjustedRange:(NSRange)range inSection:(NSInteger)section; +- (void)clearAdsInAdjustedRange:(NSRange)range inSection:(NSInteger)section; +- (NSIndexPath *)nextAdInsertionIndexPathForAdjustedIndexPath:(NSIndexPath *)adjustedIndexPath; +- (NSIndexPath *)previousAdInsertionIndexPathForAdjustedIndexPath:(NSIndexPath *)adjustedIndexPath; +- (BOOL)isAdAtAdjustedIndexPath:(NSIndexPath *)adjustedIndexPath; +- (NSArray *)adjustedIndexPathsWithAdsInSection:(NSUInteger)section; +- (MPNativeAdData *)adDataAtAdjustedIndexPath:(NSIndexPath *)adjustedIndexPath; +- (NSUInteger)adjustedNumberOfItems:(NSUInteger)numberOfItems inSection:(NSUInteger)section; +- (NSIndexPath *)adjustedIndexPathForOriginalIndexPath:(NSIndexPath *)indexPath; +- (NSIndexPath *)originalIndexPathForAdjustedIndexPath:(NSIndexPath *)indexPath; +- (void)insertSections:(NSIndexSet *)sections; +- (void)deleteSections:(NSIndexSet *)sections; +- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection; +- (void)insertItemsAtIndexPaths:(NSArray *)originalIndexPaths; +- (void)deleteItemsAtIndexPaths:(NSArray *)originalIndexPaths; +- (void)moveItemAtIndexPath:(NSIndexPath *)originalIndexPath toIndexPath:(NSIndexPath *)newIndexPath; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/MPStreamAdPlacementData.m b/iphone/Maps/3party/MoPubSDK/Native Ads/MPStreamAdPlacementData.m new file mode 100644 index 00000000000..b16b42cb01a --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/MPStreamAdPlacementData.m @@ -0,0 +1,514 @@ +// +// MPStreamAdPlacementData.m +// MoPub +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import "MPStreamAdPlacementData.h" +#import "MPAdPositioning.h" +#import "MPLogging.h" + +static const NSUInteger kMaximumNumberOfAdsPerStream = 255; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface MPStreamAdPlacementData () + +@property (nonatomic, strong) NSMutableDictionary *desiredOriginalPositions; +@property (nonatomic, strong) NSMutableDictionary *desiredInsertionPositions; +@property (nonatomic, strong) NSMutableDictionary *originalAdIndexPaths; +@property (nonatomic, strong) NSMutableDictionary *adjustedAdIndexPaths; +@property (nonatomic, strong) NSMutableDictionary *adDataObjects; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation MPStreamAdPlacementData + +- (id)initWithPositioning:(MPAdPositioning *)positioning +{ + self = [super init]; + if (self) { + [self initializeDesiredPositionsFromPositioning:positioning]; + self.originalAdIndexPaths = [NSMutableDictionary dictionary]; + self.adjustedAdIndexPaths = [NSMutableDictionary dictionary]; + self.adDataObjects = [NSMutableDictionary dictionary]; + } + return self; +} + + +- (NSMutableArray *)positioningArrayForSection:(NSUInteger)section inDictionary:(NSMutableDictionary *)dictionary +{ + NSMutableArray *array = [dictionary objectForKey:[NSNumber numberWithUnsignedInteger:section]]; + if (array) { + return array; + } else { + array = [NSMutableArray array]; + [dictionary setObject:array forKey:[NSNumber numberWithUnsignedInteger:section]]; + return array; + } +} + +- (void)initializeDesiredPositionsFromPositioning:(MPAdPositioning *)positioning +{ + NSArray *fixedPositions = [[positioning.fixedPositions array] sortedArrayUsingSelector:@selector(compare:)]; + + self.desiredOriginalPositions = [NSMutableDictionary dictionary]; + self.desiredInsertionPositions = [NSMutableDictionary dictionary]; + + [fixedPositions enumerateObjectsUsingBlock:^(NSIndexPath *position, NSUInteger idx, BOOL *stop) { + [self insertDesiredPositionsForIndexPath:position]; + }]; + + //Current behavior only inserts repeating ads following the last fixed position in the table, and they will only be inserted + //within the same section as that position. If no fixed positions exist, repeating ads will be placed only in the first section. + if (positioning.repeatingInterval > 1) { + NSInteger lastInsertionSection = [[fixedPositions lastObject] section]; + + NSMutableArray *desiredOriginalPositions = [self positioningArrayForSection:lastInsertionSection inDictionary:self.desiredOriginalPositions]; + + NSUInteger numberOfFixedAds = [desiredOriginalPositions count]; + NSUInteger numberOfRepeatingAds = kMaximumNumberOfAdsPerStream - numberOfFixedAds; + + NSInteger startingIndex = [fixedPositions lastObject] ? [(NSIndexPath *)[fixedPositions lastObject] row] : -1; + for (NSUInteger repeatingAdIndex = 1; repeatingAdIndex <= numberOfRepeatingAds; repeatingAdIndex++) { + NSInteger adIndexItem = startingIndex + positioning.repeatingInterval * repeatingAdIndex; + [self insertDesiredPositionsForIndexPath:[NSIndexPath indexPathForRow:adIndexItem inSection:lastInsertionSection]]; + } + } +} + +//assumes items are inserted sequentially, beginning to end +- (void)insertDesiredPositionsForIndexPath:(NSIndexPath *)indexPath +{ + NSMutableArray *desiredOriginalPositions = [self positioningArrayForSection:indexPath.section inDictionary:self.desiredOriginalPositions]; + NSIndexPath *insertionIndexPath = [NSIndexPath indexPathForRow:indexPath.row - [desiredOriginalPositions count] inSection:indexPath.section]; + [desiredOriginalPositions addObject:[insertionIndexPath copy]]; + + NSMutableArray *desiredInsertionPositions = [self positioningArrayForSection:indexPath.section inDictionary:self.desiredInsertionPositions]; + [desiredInsertionPositions addObject:[insertionIndexPath copy]]; +} + +- (NSUInteger)adjustedNumberOfItems:(NSUInteger)numberOfItems inSection:(NSUInteger)section +{ + if (numberOfItems <= 0) return 0; + + NSIndexPath *pathOfLastItem = [NSIndexPath indexPathForRow:numberOfItems - 1 inSection:section]; + NSMutableArray *originalAdIndexPaths = [self positioningArrayForSection:section inDictionary:self.originalAdIndexPaths]; + NSUInteger numberOfAdsBeforeLastItem = [self indexOfIndexPath:pathOfLastItem inSortedArray:originalAdIndexPaths options:NSBinarySearchingInsertionIndex | NSBinarySearchingLastEqual]; + + return numberOfItems + numberOfAdsBeforeLastItem; +} + +- (NSIndexPath *)adjustedIndexPathForOriginalIndexPath:(NSIndexPath *)indexPath +{ + if (!indexPath || indexPath.row == NSNotFound) { + return indexPath; + } + + NSMutableArray *originalAdIndexPaths = [self positioningArrayForSection:indexPath.section inDictionary:self.originalAdIndexPaths]; + NSUInteger numberOfAdsBeforeIndexPath = [self indexOfIndexPath:indexPath inSortedArray:originalAdIndexPaths options:NSBinarySearchingInsertionIndex | NSBinarySearchingLastEqual]; + + return [NSIndexPath indexPathForRow:indexPath.row + numberOfAdsBeforeIndexPath inSection:indexPath.section]; +} + +- (NSIndexPath *)originalIndexPathForAdjustedIndexPath:(NSIndexPath *)indexPath +{ + if (!indexPath || indexPath.row == NSNotFound) { + return indexPath; + } else if ([self isAdAtAdjustedIndexPath:indexPath]) { + return nil; + } else { + NSMutableArray *adjustedAdIndexPaths = [self positioningArrayForSection:indexPath.section inDictionary:self.adjustedAdIndexPaths]; + NSUInteger numberOfAdsBeforeIndexPath = [self indexOfIndexPath:indexPath inSortedArray:adjustedAdIndexPaths options:NSBinarySearchingInsertionIndex]; + return [NSIndexPath indexPathForRow:indexPath.row - numberOfAdsBeforeIndexPath inSection:indexPath.section]; + } +} + +- (NSIndexPath *)nextAdInsertionIndexPathForAdjustedIndexPath:(NSIndexPath *)adjustedIndexPath +{ + if (adjustedIndexPath.section > [self largestSectionIndexContainingAds]) { + return nil; + } + + NSMutableArray *desiredInsertionPositions = [self.desiredInsertionPositions objectForKey:[NSNumber numberWithUnsignedInteger:adjustedIndexPath.section]]; + + NSUInteger index = [self indexOfIndexPath:adjustedIndexPath inSortedArray:desiredInsertionPositions options:NSBinarySearchingInsertionIndex | NSBinarySearchingFirstEqual]; + + if (desiredInsertionPositions && (index < [desiredInsertionPositions count])) { + return [desiredInsertionPositions objectAtIndex:index]; + } else { + // Go to the next section. + return [self nextAdInsertionIndexPathForAdjustedIndexPath:[NSIndexPath indexPathForRow:0 inSection:adjustedIndexPath.section+1]]; + } +} + +- (NSIndexPath *)previousAdInsertionIndexPathForAdjustedIndexPath:(NSIndexPath *)adjustedIndexPath +{ + NSMutableArray *desiredInsertionPositions = [self positioningArrayForSection:adjustedIndexPath.section inDictionary:self.desiredInsertionPositions]; + NSUInteger index = [self indexOfIndexPath:adjustedIndexPath inSortedArray:desiredInsertionPositions options:NSBinarySearchingInsertionIndex | NSBinarySearchingFirstEqual]; + + if (index > 0) { + return desiredInsertionPositions[index - 1]; + } else { + return nil; + } +} + +- (void)insertAdData:(MPNativeAdData *)data atIndexPath:(NSIndexPath *)adjustedIndexPath +{ + NSMutableArray *desiredInsertionPositions = [self positioningArrayForSection:adjustedIndexPath.section inDictionary:self.desiredInsertionPositions]; + NSMutableArray *desiredOriginalPositions = [self positioningArrayForSection:adjustedIndexPath.section inDictionary:self.desiredOriginalPositions]; + NSMutableArray *adjustedAdIndexPaths = [self positioningArrayForSection:adjustedIndexPath.section inDictionary:self.adjustedAdIndexPaths]; + NSMutableArray *originalAdIndexPaths = [self positioningArrayForSection:adjustedIndexPath.section inDictionary:self.originalAdIndexPaths]; + NSMutableArray *adDataObjects = [self positioningArrayForSection:adjustedIndexPath.section inDictionary:self.adDataObjects]; + + NSUInteger indexInDesiredArrays = [self indexOfIndexPath:adjustedIndexPath inSortedArray:desiredInsertionPositions options:NSBinarySearchingFirstEqual]; + + if (indexInDesiredArrays == NSNotFound) { + MPLogWarn(@"Attempted to insert an ad at position %@, which is not in the desired array.", adjustedIndexPath); + return; + } + + NSIndexPath *originalPosition = desiredOriginalPositions[indexInDesiredArrays]; + NSIndexPath *insertionPosition = desiredInsertionPositions[indexInDesiredArrays]; + + NSUInteger insertionIndex = [self indexOfIndexPath:insertionPosition inSortedArray:adjustedAdIndexPaths options:NSBinarySearchingInsertionIndex | NSBinarySearchingFirstEqual]; + + [originalAdIndexPaths insertObject:originalPosition atIndex:insertionIndex]; + [adjustedAdIndexPaths insertObject:insertionPosition atIndex:insertionIndex]; + [adDataObjects insertObject:data atIndex:insertionIndex]; + + [desiredOriginalPositions removeObjectAtIndex:indexInDesiredArrays]; + [desiredInsertionPositions removeObjectAtIndex:indexInDesiredArrays]; + + for (NSUInteger i = insertionIndex + 1; i < [adjustedAdIndexPaths count]; i++) { + NSIndexPath *newIndexPath = adjustedAdIndexPaths[i]; + adjustedAdIndexPaths[i] = [NSIndexPath indexPathForRow:newIndexPath.row + 1 inSection:newIndexPath.section]; + } + + for (NSUInteger j = indexInDesiredArrays; j < [desiredInsertionPositions count]; j++) { + NSIndexPath *newInsertionPosition = desiredInsertionPositions[j]; + desiredInsertionPositions[j] = [NSIndexPath indexPathForRow:newInsertionPosition.row + 1 inSection:newInsertionPosition.section]; + } +} + +- (NSArray *)adjustedAdIndexPathsInAdjustedRange:(NSRange)range inSection:(NSInteger)section +{ + NSMutableArray *adjustedIndexPaths = [self positioningArrayForSection:section inDictionary:self.adjustedAdIndexPaths]; + + NSIndexSet *indexesOfObjectsInRange = [adjustedIndexPaths indexesOfObjectsPassingTest:^BOOL(NSIndexPath *adjustedIndexPath, NSUInteger idx, BOOL *stop) { + return NSLocationInRange(adjustedIndexPath.row, range); + }]; + + return [adjustedIndexPaths objectsAtIndexes:indexesOfObjectsInRange]; +} + +- (void)clearAdsInAdjustedRange:(NSRange)range inSection:(NSInteger)section +{ + NSMutableArray *adjustedAdIndexPaths = [self positioningArrayForSection:section inDictionary:self.adjustedAdIndexPaths]; + NSMutableArray *originalAdIndexPaths = [self positioningArrayForSection:section inDictionary:self.originalAdIndexPaths]; + NSMutableArray *adDataObjects = [self positioningArrayForSection:section inDictionary:self.adDataObjects]; + NSMutableArray *desiredInsertionPositions = [self positioningArrayForSection:section inDictionary:self.desiredInsertionPositions]; + NSMutableArray *desiredOriginalPositions = [self positioningArrayForSection:section inDictionary:self.desiredOriginalPositions]; + + NSIndexSet *indexesOfObjectsToRemove = [adjustedAdIndexPaths indexesOfObjectsPassingTest:^BOOL(NSIndexPath *adjustedIndexPath, NSUInteger idx, BOOL *stop) { + return NSLocationInRange(adjustedIndexPath.row, range); + }]; + + if ([indexesOfObjectsToRemove count]) { + [indexesOfObjectsToRemove enumerateIndexesWithOptions:NSEnumerationReverse usingBlock:^(NSUInteger idx, BOOL *stop) { + NSIndexPath *adjustedIndexPathToRemove = adjustedAdIndexPaths[idx]; + NSIndexPath *originalIndexPathToRemove = originalAdIndexPaths[idx]; + NSUInteger insertionIndex = [self indexOfIndexPath:originalIndexPathToRemove inSortedArray:desiredOriginalPositions options:NSBinarySearchingInsertionIndex | NSBinarySearchingFirstEqual]; + for (NSInteger i = insertionIndex; i < [desiredInsertionPositions count]; i++) { + NSIndexPath *nextIndexPath = desiredInsertionPositions[i]; + desiredInsertionPositions[i] = [NSIndexPath indexPathForRow:nextIndexPath.row - 1 inSection:nextIndexPath.section]; + } + [desiredOriginalPositions insertObject:originalIndexPathToRemove atIndex:insertionIndex]; + [desiredInsertionPositions insertObject:adjustedIndexPathToRemove atIndex:insertionIndex]; + + }]; + + [adjustedAdIndexPaths removeObjectsAtIndexes:indexesOfObjectsToRemove]; + [originalAdIndexPaths removeObjectsAtIndexes:indexesOfObjectsToRemove]; + [adDataObjects removeObjectsAtIndexes:indexesOfObjectsToRemove]; + } +} + +- (BOOL)isAdAtAdjustedIndexPath:(NSIndexPath *)adjustedIndexPath +{ + NSMutableArray *adjustedAdIndexPaths = [self positioningArrayForSection:adjustedIndexPath.section inDictionary:self.adjustedAdIndexPaths]; + NSUInteger indexOfIndexPath = [self indexOfIndexPath:adjustedIndexPath inSortedArray:adjustedAdIndexPaths options:NSBinarySearchingFirstEqual]; + + return indexOfIndexPath != NSNotFound; +} + +- (NSArray *)adjustedIndexPathsWithAdsInSection:(NSUInteger)section +{ + return [self positioningArrayForSection:section inDictionary:self.adjustedAdIndexPaths]; +} + +- (MPNativeAdData *)adDataAtAdjustedIndexPath:(NSIndexPath *)adjustedIndexPath +{ + NSMutableArray *adjustedAdIndexPaths = [self positioningArrayForSection:adjustedIndexPath.section inDictionary:self.adjustedAdIndexPaths]; + NSMutableArray *adDataObjects = [self positioningArrayForSection:adjustedIndexPath.section inDictionary:self.adDataObjects]; + + NSUInteger indexOfIndexPath = [self indexOfIndexPath:adjustedIndexPath inSortedArray:adjustedAdIndexPaths options:NSBinarySearchingFirstEqual]; + + if (indexOfIndexPath != NSNotFound) { + return adDataObjects[indexOfIndexPath]; + } else { + return nil; + } +} + +- (void)insertSections:(NSIndexSet *)sections +{ + [sections enumerateIndexesUsingBlock:^(NSUInteger insertionSection, BOOL *stop) { + // Explicitly casting indices to NSInteger because we're counting backwards. + NSInteger maxSection = [self largestSectionIndexContainingAds]; + NSInteger signedInsertionSection = insertionSection; + + // We need to shift all the data above the new section up by 1. + for (NSInteger i = maxSection; i >= signedInsertionSection; --i) { + if (self.desiredInsertionPositions[@(i)]) { + [self moveAllPositioningArraysInDictionariesAtSection:i toSection:i+1]; + } + } + }]; +} + +- (void)deleteSections:(NSIndexSet *)sections +{ + [sections enumerateIndexesWithOptions:NSEnumerationReverse usingBlock:^(NSUInteger deletionSection, BOOL *stop) { + NSUInteger maxSection = [self largestSectionIndexContainingAds]; + + [self clearPositioningArraysInDictionariesAtSection:deletionSection]; + + // We need to shift all the data above the deletionSection down by 1. + for (NSUInteger i = deletionSection; i < maxSection; ++i) { + if (self.desiredInsertionPositions[@(i+1)]) { + [self moveAllPositioningArraysInDictionariesAtSection:i+1 toSection:i]; + } + } + }]; +} + +- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection +{ + // Store the data at the section we're moving and retain it so it doesn't get deleted. + NSMutableArray *desiredInsertionPositions = [self positioningArrayForSection:section inDictionary:self.desiredInsertionPositions]; + NSMutableArray *desiredOriginalPositions = [self positioningArrayForSection:section inDictionary:self.desiredOriginalPositions]; + NSMutableArray *adjustedAdIndexPaths = [self positioningArrayForSection:section inDictionary:self.adjustedAdIndexPaths]; + NSMutableArray *originalAdIndexPaths = [self positioningArrayForSection:section inDictionary:self.originalAdIndexPaths]; + NSMutableArray *adDataObjects = [self positioningArrayForSection:section inDictionary:self.adDataObjects]; + + // Delete it from our dictionaries. + [self deleteSections:[NSIndexSet indexSetWithIndex:section]]; + + // Now insert an empty section at the new spot. + [self insertSections:[NSIndexSet indexSetWithIndex:newSection]]; + + // Fill in its data. + self.desiredInsertionPositions[@(newSection)] = desiredInsertionPositions; + self.desiredOriginalPositions[@(newSection)] = desiredOriginalPositions; + self.adjustedAdIndexPaths[@(newSection)] = adjustedAdIndexPaths; + self.originalAdIndexPaths[@(newSection)] = originalAdIndexPaths; + self.adDataObjects[@(newSection)] = adDataObjects; + + [self updateAllSectionsForPositioningArraysAtSection:newSection]; +} + +- (void)insertItemsAtIndexPaths:(NSArray *)originalIndexPaths +{ + originalIndexPaths = [originalIndexPaths sortedArrayUsingSelector:@selector(compare:)]; + + [originalIndexPaths enumerateObjectsUsingBlock:^(NSIndexPath *originalIndexPath, NSUInteger idx, BOOL *stop) { + NSMutableArray *desiredOriginalPositions = [self positioningArrayForSection:originalIndexPath.section inDictionary:self.desiredOriginalPositions]; + NSMutableArray *originalAdIndexPaths = [self positioningArrayForSection:originalIndexPath.section inDictionary:self.originalAdIndexPaths]; + + NSUInteger insertionIndex = [self indexOfIndexPath:originalIndexPath inSortedArray:desiredOriginalPositions options:NSBinarySearchingInsertionIndex | NSBinarySearchingFirstEqual]; + for (NSUInteger i = insertionIndex; i < [desiredOriginalPositions count]; i++) { + [self incrementDesiredIndexPathsAtIndex:i inSection:originalIndexPath.section]; + } + + NSUInteger originalInsertionIndex = [self indexOfIndexPath:originalIndexPath inSortedArray:originalAdIndexPaths options:NSBinarySearchingInsertionIndex | NSBinarySearchingFirstEqual]; + for (NSUInteger i = originalInsertionIndex; i < [originalAdIndexPaths count]; i++) { + [self incrementPlacedIndexPathsAtIndex:i inSection:originalIndexPath.section]; + } + }]; +} + +- (void)deleteItemsAtIndexPaths:(NSArray *)originalIndexPaths +{ + originalIndexPaths = [originalIndexPaths sortedArrayUsingSelector:@selector(compare:)]; + + __block NSUInteger currentNumberOfAdsInSection = 0; + __block NSInteger lastSection = [[originalIndexPaths firstObject] section]; + + [originalIndexPaths enumerateObjectsUsingBlock:^(NSIndexPath *originalIndexPath, NSUInteger idx, BOOL *stop) { + // Batch deletions are actually performed one at a time. This requires us to shift up the + // deletion index paths each time a deletion is performed. + // + // For example, batch-deleting the 2nd and 3rd items is not equivalent to incrementally + // deleting the 2nd item and then the 3rd item; the equivalent would be to delete the 2nd + // item each time. + if (originalIndexPath.section != lastSection) { + lastSection = originalIndexPath.section; + currentNumberOfAdsInSection = 0; + } + + NSMutableArray *desiredOriginalPositions = [self positioningArrayForSection:originalIndexPath.section inDictionary:self.desiredOriginalPositions]; + NSMutableArray *originalAdIndexPaths = [self positioningArrayForSection:originalIndexPath.section inDictionary:self.originalAdIndexPaths]; + + NSIndexPath *indexPathOfItemToDelete = [NSIndexPath indexPathForRow:originalIndexPath.row - currentNumberOfAdsInSection inSection:originalIndexPath.section]; + + NSUInteger searchIndexInDesired = [self indexOfIndexPath:indexPathOfItemToDelete inSortedArray:desiredOriginalPositions options:NSBinarySearchingInsertionIndex | NSBinarySearchingLastEqual]; + for (NSUInteger i = searchIndexInDesired; i < [desiredOriginalPositions count]; i++) { + [self decrementDesiredIndexPathsAtIndex:i inSection:originalIndexPath.section]; + } + + NSUInteger searchIndexInPlaced = [self indexOfIndexPath:indexPathOfItemToDelete inSortedArray:originalAdIndexPaths options:NSBinarySearchingInsertionIndex | NSBinarySearchingLastEqual]; + for (NSUInteger i = searchIndexInPlaced; i < [originalAdIndexPaths count]; i++) { + [self decrementPlacedIndexPathsAtIndex:i inSection:originalIndexPath.section]; + } + + currentNumberOfAdsInSection++; + }]; +} + +- (void)moveItemAtIndexPath:(NSIndexPath *)originalIndexPath toIndexPath:(NSIndexPath *)newIndexPath +{ + [self deleteItemsAtIndexPaths:@[originalIndexPath]]; + [self insertItemsAtIndexPaths:@[newIndexPath]]; +} + +#pragma mark - NSIndexPath array helpers + +- (NSUInteger)indexOfIndexPath:(NSIndexPath *)indexPath inSortedArray:(NSArray *)array options:(NSBinarySearchingOptions)options +{ + if (!indexPath || indexPath.row == NSNotFound) { + return NSNotFound; + } + return [array indexOfObject:indexPath inSortedRange:NSMakeRange(0, [array count]) options:options usingComparator:^NSComparisonResult(NSIndexPath *path1, NSIndexPath *path2) { + return [path1 compare:path2]; + }]; +} + +- (void)incrementDesiredIndexPathsAtIndex:(NSUInteger)index inSection:(NSUInteger)section +{ + NSMutableArray *desiredOriginalPositions = [self positioningArrayForSection:section inDictionary:self.desiredOriginalPositions]; + NSMutableArray *desiredInsertionPositions = [self positioningArrayForSection:section inDictionary:self.desiredInsertionPositions]; + + NSIndexPath *currentDesiredOriginalPosition = desiredOriginalPositions[index]; + NSIndexPath *newDesiredOriginalPosition = [NSIndexPath indexPathForRow:currentDesiredOriginalPosition.row + 1 inSection:currentDesiredOriginalPosition.section]; + desiredOriginalPositions[index] = newDesiredOriginalPosition; + + NSIndexPath *currentDesiredInsertionPosition = desiredInsertionPositions[index]; + NSIndexPath *newDesiredInsertionPosition = [NSIndexPath indexPathForRow:currentDesiredInsertionPosition.row + 1 inSection:currentDesiredInsertionPosition.section]; + desiredInsertionPositions[index] = newDesiredInsertionPosition; +} + +- (void)incrementPlacedIndexPathsAtIndex:(NSUInteger)index inSection:(NSUInteger)section +{ + NSMutableArray *originalAdIndexPaths = [self positioningArrayForSection:section inDictionary:self.originalAdIndexPaths]; + NSMutableArray *adjustedAdIndexPaths = [self positioningArrayForSection:section inDictionary:self.adjustedAdIndexPaths]; + + NSIndexPath *currentOriginalIndexPath = originalAdIndexPaths[index]; + NSIndexPath *newOriginalIndexPath = [NSIndexPath indexPathForRow:currentOriginalIndexPath.row + 1 inSection:currentOriginalIndexPath.section]; + originalAdIndexPaths[index] = newOriginalIndexPath; + + NSIndexPath *currentAdjustedIndexPath = adjustedAdIndexPaths[index]; + NSIndexPath *newAdjustedIndexPath = [NSIndexPath indexPathForRow:currentAdjustedIndexPath.row + 1 inSection:currentAdjustedIndexPath.section]; + adjustedAdIndexPaths[index] = newAdjustedIndexPath; +} + +- (void)decrementDesiredIndexPathsAtIndex:(NSUInteger)index inSection:(NSUInteger)section +{ + NSMutableArray *desiredOriginalPositions = [self positioningArrayForSection:section inDictionary:self.desiredOriginalPositions]; + NSMutableArray *desiredInsertionPositions = [self positioningArrayForSection:section inDictionary:self.desiredInsertionPositions]; + + NSIndexPath *currentDesiredOriginalPosition = desiredOriginalPositions[index]; + NSIndexPath *newDesiredOriginalPosition = [NSIndexPath indexPathForRow:currentDesiredOriginalPosition.row - 1 inSection:currentDesiredOriginalPosition.section]; + desiredOriginalPositions[index] = newDesiredOriginalPosition; + + NSIndexPath *currentDesiredInsertionPosition = desiredInsertionPositions[index]; + NSIndexPath *newDesiredInsertionPosition = [NSIndexPath indexPathForRow:currentDesiredInsertionPosition.row - 1 inSection:currentDesiredInsertionPosition.section]; + desiredInsertionPositions[index] = newDesiredInsertionPosition; +} + +- (void)decrementPlacedIndexPathsAtIndex:(NSUInteger)index inSection:(NSUInteger)section +{ + NSMutableArray *originalAdIndexPaths = [self positioningArrayForSection:section inDictionary:self.originalAdIndexPaths]; + NSMutableArray *adjustedAdIndexPaths = [self positioningArrayForSection:section inDictionary:self.adjustedAdIndexPaths]; + + NSIndexPath *currentOriginalIndexPath = originalAdIndexPaths[index]; + NSIndexPath *newOriginalIndexPath = [NSIndexPath indexPathForRow:currentOriginalIndexPath.row - 1 inSection:currentOriginalIndexPath.section]; + originalAdIndexPaths[index] = newOriginalIndexPath; + + NSIndexPath *currentAdjustedIndexPath = adjustedAdIndexPaths[index]; + NSIndexPath *newAdjustedIndexPath = [NSIndexPath indexPathForRow:currentAdjustedIndexPath.row - 1 inSection:currentAdjustedIndexPath.section]; + adjustedAdIndexPaths[index] = newAdjustedIndexPath; +} + +#pragma mark - Section modification helpers + +- (NSUInteger)largestSectionIndexContainingAds +{ + return [[[self.desiredInsertionPositions allKeys] valueForKeyPath:@"@max.unsignedIntValue"] unsignedIntegerValue]; +} + +// Does not update the index path sections. Call updateSectionForPositioningArray to update the sections in the index paths. +- (void)copyPositioningArrayInDictionary:(NSMutableDictionary *)dict atSection:(NSUInteger)atSection toSection:(NSUInteger)toSection +{ + if (dict[@(atSection)]) { + dict[@(toSection)] = dict[@(atSection)]; + } +} + +- (void)clearPositioningArraysInDictionariesAtSection:(NSUInteger)section +{ + [self.desiredInsertionPositions removeObjectForKey:@(section)]; + [self.desiredOriginalPositions removeObjectForKey:@(section)]; + [self.adjustedAdIndexPaths removeObjectForKey:@(section)]; + [self.originalAdIndexPaths removeObjectForKey:@(section)]; + [self.adDataObjects removeObjectForKey:@(section)]; +} + +// Moves the positioning arrays to the correct spots in the dictionaries and also updates the sections for all the index paths. +- (void)moveAllPositioningArraysInDictionariesAtSection:(NSUInteger)atSection toSection:(NSUInteger)toSection +{ + [self copyPositioningArrayInDictionary:self.desiredInsertionPositions atSection:atSection toSection:toSection]; + [self copyPositioningArrayInDictionary:self.desiredOriginalPositions atSection:atSection toSection:toSection]; + [self copyPositioningArrayInDictionary:self.adjustedAdIndexPaths atSection:atSection toSection:toSection]; + [self copyPositioningArrayInDictionary:self.originalAdIndexPaths atSection:atSection toSection:toSection]; + [self copyPositioningArrayInDictionary:self.adDataObjects atSection:atSection toSection:toSection]; + + [self updateAllSectionsForPositioningArraysAtSection:toSection]; + + [self clearPositioningArraysInDictionariesAtSection:atSection]; +} + +- (void)updateAllSectionsForPositioningArraysAtSection:(NSUInteger)section +{ + [self updateSectionForPositioningArray:self.desiredInsertionPositions[@(section)] toSection:section]; + [self updateSectionForPositioningArray:self.desiredOriginalPositions[@(section)] toSection:section]; + [self updateSectionForPositioningArray:self.adjustedAdIndexPaths[@(section)] toSection:section]; + [self updateSectionForPositioningArray:self.originalAdIndexPaths[@(section)] toSection:section]; +} + +- (void)updateSectionForPositioningArray:(NSMutableArray *)positioningArray toSection:(NSUInteger)section +{ + for (NSUInteger i = 0; i < positioningArray.count; ++i) { + NSIndexPath *indexPath = positioningArray[i]; + + NSUInteger indices[] = { section, [indexPath indexAtPosition:1] }; + positioningArray[i] = [NSIndexPath indexPathWithIndexes:indices length:2]; + } +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/MPStreamAdPlacer.h b/iphone/Maps/3party/MoPubSDK/Native Ads/MPStreamAdPlacer.h new file mode 100644 index 00000000000..e5f0ce823da --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/MPStreamAdPlacer.h @@ -0,0 +1,272 @@ +// +// MPStreamAdPlacer.h +// MoPub +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import +#import +#import "MPClientAdPositioning.h" + +@protocol MPStreamAdPlacerDelegate; +@protocol MPNativeAdRendering; +@class MPNativeAdRequestTargeting; +@class MPNativeAd; + +/** + * The `MPStreamAdPlacer` class allows you to retrieve native ads from the MoPub ad server and + * place them into any custom UI component that represents a stream of content. It does not actually + * present or insert any ads on its own; you must provide a delegate conforming to the + * `MPStreamAdPlacerDelegate` protocol to handle ad insertions. + * + * @warning **Note:** If you are inserting ads into a `UITableView` or `UICollectionView`, you + * should first consider whether the `UITableViewAdPlacer` or `UICollectionViewAdPlacer` classes are + * sufficient for your use case before choosing to use this class. + * + * @discussion Your app's first responsibility when creating a stream ad placer is to communicate + * the state of your stream. Specifically, you must provide it with the count of the + * original content items in your stream using -setItemCount:forSection:, so that it can determine + * where and how many ads should appear. Additionally, you must also make sure to notify the ad + * placer of any insertions, deletions, or rearrangement of content items or sections. + * + * Use the -loadAdsForAdUnitID: method to tell the stream ad placer to begin retrieving ads. In + * order to optimize performance, this call may not immediately result in the ad placer asking its + * delegate to insert any ads. Instead, the ad placer decides whether to insert ads by determining + * what content items are currently visible. This means that your delegate may be intermittently + * informed about new insertions, and is meant to minimize situations in which ads are requested for + * positions in the stream that have a low likelihood of visibility. + * + * ### Responding to Insertions and Rendering Ads + * + * Your delegate should respond to insertion callbacks by updating your stream's data source so + * that it knows to render an ad (rather than an original content item) at the given index path. + * Note that the implementation may vary depending on the design of your data source. + * + * Use -renderAdAtIndexPath:inView: to populate a view with the contents of an ad. + */ + +@interface MPStreamAdPlacer : NSObject + +/** + * An array of `NSIndexPath` objects representing the positions of items that are currently visible + * on the screen. + * + * The stream ad placer uses the contents of this array to determine where ads should be inserted. + * It calculates an on-screen range and uses a small look-ahead to place ads where the user is + * likely to view them. + */ +@property (nonatomic, strong) NSArray *visibleIndexPaths; + +@property (nonatomic, readonly) NSArray *rendererConfigurations; +@property (nonatomic, weak) UIViewController *viewController; +@property (nonatomic, weak) id delegate; +@property (nonatomic, readonly, copy) MPAdPositioning *adPositioning; + +/** + * Creates and returns a new ad placer that can display ads in a stream. + * + * @param controller The view controller which should be used to present modal content. + * @param positioning The positioning object that specifies where ads should be shown in the stream. + * @param rendererConfigurations An array of MPNativeAdRendererConfiguration objects that control how + * the native ad is rendered. You should pass in configurations that can render any ad type that + * may be displayed for the given ad unit. + * + */ ++ (instancetype)placerWithViewController:(UIViewController *)controller adPositioning:(MPAdPositioning *)positioning rendererConfigurations:(NSArray *)rendererConfigurations; + +/** + * Lets the ad placer know of how many items are in a section. This allows the ad placer + * to place ads more effectively around its visible range. + * + * @param count How many items are in the section. + * @param section The section that the ad placer is recording the count for. + */ +- (void)setItemCount:(NSUInteger)count forSection:(NSInteger)section; + +/** + * Uses the corresponding rendering class to render content in the view. + * + * @param indexPath The index path of the cell you want to render. + * @param view The view you want to render your contents into. + */ +- (void)renderAdAtIndexPath:(NSIndexPath *)indexPath inView:(UIView *)view; + +/** + * Get the size of the ad at the index path. + * + * @param indexPath Retrieve the size at indexPath. + * @param maxWidth The maximum acceptable width for the view. + * + * @return The size of the ad at indexPath. + */ +- (CGSize)sizeForAdAtIndexPath:(NSIndexPath *)indexPath withMaximumWidth:(CGFloat)maxWidth; + +/** + * Requests ads from the MoPub ad server using the specified ad unit ID. + * + * @param adUnitID A string representing a MoPub ad unit ID. + */ +- (void)loadAdsForAdUnitID:(NSString *)adUnitID; + +/** + * Requests ads from the MoPub ad server using the specified ad unit ID and targeting parameters. + * + * @param adUnitID A string representing a MoPub ad unit ID. + * @param targeting An object containing targeting information, such as geolocation data. + */ +- (void)loadAdsForAdUnitID:(NSString *)adUnitID targeting:(MPNativeAdRequestTargeting *)targeting; + +/** + * Returns whether an ad is ready to be displayed at the indexPath. + * + * @param indexPath The index path to examine for ad readiness. + */ +- (BOOL)isAdAtIndexPath:(NSIndexPath *)indexPath; + +/** + * Returns the number of items in the given section of the stream, including any ads that have been + * inserted. + * + * @param numberOfItems The number of content items. + * @param section The section the method will retrieve the adjusted number of items for. + */ +- (NSUInteger)adjustedNumberOfItems:(NSUInteger)numberOfItems inSection:(NSUInteger)section; + +/** + * Returns the index path representing the location of an item after accounting for ads that have + * been inserted into the stream. + * + * @param indexPath An index path object identifying the original location of a content item, before + * any ads have been inserted into the stream. + */ +- (NSIndexPath *)adjustedIndexPathForOriginalIndexPath:(NSIndexPath *)indexPath; + +/** + * Asks for the original position of a content item, given its position in the stream after ads + * have been inserted. + * + * If the specified index path does not identify a content item, but rather an ad, this method + * will return nil. + * + * @param indexPath An index path object identifying an item in the stream, after ads have been + * inserted. + */ +- (NSIndexPath *)originalIndexPathForAdjustedIndexPath:(NSIndexPath *)indexPath; + +/** + * Returns the index paths representing the locations of items after accounting for ads that have + * been inserted into the stream. + * + * @param indexPaths An array of index path objects each identifying the original location of a + * content item, before any ads have been inserted into the stream. + */ +- (NSArray *)adjustedIndexPathsForOriginalIndexPaths:(NSArray *)indexPaths; + +/** + * Retrieves the original positions of content items, given their positions in the stream after ads + * have been inserted. + * + * If a specified index path does not identify a content item, but rather an ad, it will not be + * included in the result. + * + * @param indexPaths An array of index path objects each identifying an item in the stream, after + * ads have been inserted. + */ +- (NSArray *)originalIndexPathsForAdjustedIndexPaths:(NSArray *)indexPaths; + +/** @name Notifying the Ad Placer of Content Updates */ + +/** + * Tells the ad placer that content items have been inserted at the specified index paths. + * + * This method allows the ad placer to adjust its ad positions correctly. + * + * @param originalIndexPaths An array of NSIndexPath objects that identify positions where content + * has been inserted. + */ +- (void)insertItemsAtIndexPaths:(NSArray *)originalIndexPaths; + +/** + * Tells the ad placer that content items have been deleted at the specified index paths. + * + * This method allows the ad placer to adjust its ad positions correctly, and remove from the + * stream if necessary + * + * @param originalIndexPaths An array of NSIndexPath objects that identify positions where content + * has been deleted. +*/ +- (void)deleteItemsAtIndexPaths:(NSArray *)originalIndexPaths; + +/** + * Tells the ad placer that a content item has moved from one index path to another. + * + * This method allows the ad placer to adjust its ad positions correctly. + * + * @param fromIndexPath The index path identifying the original location of the item. + * @param toIndexPath The destination index path for the item. + */ +- (void)moveItemAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath; + +/** + * Tells the ad placer that sections have been inserted at the specified indices. + * + * This method allows the ad placer to adjust its ad positions correctly. + * + * @param sections An NSIndexSet that identifies the positions where sections + * have been inserted. + */ +- (void)insertSections:(NSIndexSet *)sections; + +/** + * Tells the ad placer that sections have been deleted at the specified indices. + * + * This method allows the ad placer to adjust its ad positions correctly. + * + * @param sections An NSIndexSet that identifies the positions where sections + * have been deleted. + */ +- (void)deleteSections:(NSIndexSet *)sections; + +/** + * Tells the ad placer that a section has moved from one index to another. + * + * This method allows the ad placer to adjust its ad positions correctly. + * + * @param section The index identifying the original location of the section. + * @param newSection The destination index for the section. + */ +- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@protocol MPStreamAdPlacerDelegate + +@optional +- (void)adPlacer:(MPStreamAdPlacer *)adPlacer didLoadAdAtIndexPath:(NSIndexPath *)indexPath; +- (void)adPlacer:(MPStreamAdPlacer *)adPlacer didRemoveAdsAtIndexPaths:(NSArray *)indexPaths; + +/* + * This method is called when a native ad, placed by the stream ad placer, will present a modal view controller. + * + * @param placer The stream ad placer that contains the ad displaying the modal. + */ +- (void)nativeAdWillPresentModalForStreamAdPlacer:(MPStreamAdPlacer *)adPlacer; + +/* + * This method is called when a native ad, placed by the stream ad placer, did dismiss its modal view controller. + * + * @param placer The stream ad placer that contains the ad that dismissed the modal. + */ +- (void)nativeAdDidDismissModalForStreamAdPlacer:(MPStreamAdPlacer *)adPlacer; + +/* + * This method is called when a native ad, placed by the stream ad placer, will cause the app to background due to user interaction with the ad. + * + * @param placer The stream ad placer that contains the ad causing the app to background. + */ +- (void)nativeAdWillLeaveApplicationFromStreamAdPlacer:(MPStreamAdPlacer *)adPlacer; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/MPStreamAdPlacer.m b/iphone/Maps/3party/MoPubSDK/Native Ads/MPStreamAdPlacer.m new file mode 100644 index 00000000000..d68786fbb46 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/MPStreamAdPlacer.m @@ -0,0 +1,581 @@ +// +// MPStreamAdPlacer.m +// MoPub +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import "MPAdPositioning.h" +#import "MPInstanceProvider.h" +#import "MPLogging.h" +#import "MPNativeAd+Internal.h" +#import "MPNativeAdData.h" +#import "MPNativeAdDelegate.h" +#import "MPNativeAdRendererConfiguration.h" +#import "MPNativeAdRendererConstants.h" +#import "MPNativeAdRendering.h" +#import "MPNativeAdSource.h" +#import "MPNativePositionSource.h" +#import "MPNativeView.h" +#import "MPServerAdPositioning.h" +#import "MPStaticNativeAdRenderer.h" +#import "MPStreamAdPlacementData.h" +#import "MPStreamAdPlacer.h" + +static NSInteger const kAdInsertionLookAheadAmount = 3; +static const NSUInteger kIndexPathItemIndex = 1; + +@protocol MPNativeAdRenderer; + +@interface MPStreamAdPlacer () + +@property (nonatomic, strong) NSArray *rendererConfigurations; +@property (nonatomic, strong) MPNativeAdSource *adSource; +@property (nonatomic, strong) MPNativePositionSource *positioningSource; +@property (nonatomic, copy) MPAdPositioning *adPositioning; +@property (nonatomic, strong) MPStreamAdPlacementData *adPlacementData; +@property (nonatomic, copy) NSString *adUnitID; +@property (nonatomic, strong) NSMutableDictionary *sectionCounts; +@property (nonatomic, strong) NSIndexPath *topConsideredIndexPath; +@property (nonatomic, strong) NSIndexPath *bottomConsideredIndexPath; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation MPStreamAdPlacer + ++ (instancetype)placerWithViewController:(UIViewController *)controller adPositioning:(MPAdPositioning *)positioning rendererConfigurations:(NSArray *)rendererConfigurations +{ + MPStreamAdPlacer *placer = [[self alloc] initWithViewController:controller adPositioning:positioning rendererConfigurations:rendererConfigurations]; + return placer; +} + +- (instancetype)initWithViewController:(UIViewController *)controller adPositioning:(MPAdPositioning *)positioning rendererConfigurations:(NSArray *)rendererConfigurations +{ + NSAssert(controller != nil, @"A stream ad placer cannot be instantiated with a nil view controller."); + NSAssert(positioning != nil, @"A stream ad placer cannot be instantiated with a nil positioning object."); + + for (id rendererConfiguration in rendererConfigurations) { + NSAssert([rendererConfiguration isKindOfClass:[MPNativeAdRendererConfiguration class]], @"A stream ad placer must be instantiated with rendererConfigurations that are of type MPNativeAdRendererConfiguration."); + } + + self = [super init]; + if (self) { + _viewController = controller; + _adPositioning = [positioning copy]; + _adSource = [[MPInstanceProvider sharedProvider] buildNativeAdSourceWithDelegate:self]; + _adPlacementData = [[MPInstanceProvider sharedProvider] buildStreamAdPlacementDataWithPositioning:_adPositioning]; + _rendererConfigurations = rendererConfigurations; + _sectionCounts = [[NSMutableDictionary alloc] init]; + } + return self; +} + +- (void)dealloc +{ + [_positioningSource cancel]; +} + +- (void)setVisibleIndexPaths:(NSArray *)visibleIndexPaths +{ + if (visibleIndexPaths.count == 0) { + _visibleIndexPaths = nil; + self.topConsideredIndexPath = nil; + self.bottomConsideredIndexPath = nil; + return; + } + + _visibleIndexPaths = [visibleIndexPaths sortedArrayUsingSelector:@selector(compare:)]; + self.topConsideredIndexPath = self.visibleIndexPaths.firstObject; + self.bottomConsideredIndexPath = [self furthestValidIndexPathAfterIndexPath:self.visibleIndexPaths.lastObject withinDistance:visibleIndexPaths.count + kAdInsertionLookAheadAmount]; + + [self fillAdsInConsideredRange]; +} + +- (void)setItemCount:(NSUInteger)count forSection:(NSInteger)section +{ + self.sectionCounts[@(section)] = @(count); +} + +- (void)renderAdAtIndexPath:(NSIndexPath *)indexPath inView:(UIView *)view +{ + MPNativeAdData *adData = [self.adPlacementData adDataAtAdjustedIndexPath:indexPath]; + + if (!adData) { + MPLogError(@"-renderAdAtIndexPath: An ad does not exist at indexPath"); + return; + } + + // Remove any old native ad views from the view prior to adding the new ad view as a sub view. + for (UIView *subview in view.subviews) { + if ([subview isKindOfClass:[MPNativeView class]]) { + [subview removeFromSuperview]; + } + } + + [view addSubview:[adData.ad retrieveAdViewWithError:nil]]; + + CGSize adSize = [self sizeForAd:adData.ad withMaximumWidth:view.bounds.size.width andIndexPath:indexPath]; + [adData.ad updateAdViewSize:adSize]; +} + +- (CGSize)sizeForAdAtIndexPath:(NSIndexPath *)indexPath withMaximumWidth:(CGFloat)maxWidth +{ + MPNativeAdData *adData = [self.adPlacementData adDataAtAdjustedIndexPath:indexPath]; + + // Tell the ad that it should resize the native ad view. + CGSize adSize = [self sizeForAd:adData.ad withMaximumWidth:maxWidth andIndexPath:indexPath]; + [adData.ad updateAdViewSize:adSize]; + + return adSize; +} + +- (void)loadAdsForAdUnitID:(NSString *)adUnitID +{ + [self loadAdsForAdUnitID:adUnitID targeting:nil]; +} + +- (void)loadAdsForAdUnitID:(NSString *)adUnitID targeting:(MPNativeAdRequestTargeting *)targeting +{ + self.adUnitID = adUnitID; + + // Gather all the index paths with ads so we can notify the delegate that ads were removed. + NSMutableArray *adIndexPaths = [NSMutableArray array]; + for (NSNumber *section in self.sectionCounts) { + NSInteger intSection = [section unsignedIntegerValue]; + [adIndexPaths addObjectsFromArray:[self.adPlacementData adjustedIndexPathsWithAdsInSection:intSection]]; + } + + if (!adUnitID) { + // We need some placement data. Pass nil to it so it doesn't do any unnecessary work. + self.adPlacementData = [[MPInstanceProvider sharedProvider] buildStreamAdPlacementDataWithPositioning:nil]; + } else if ([self.adPositioning isKindOfClass:[MPClientAdPositioning class]]) { + // Reset to a placement data that has "desired" ads but not "placed" ones. + self.adPlacementData = [[MPInstanceProvider sharedProvider] buildStreamAdPlacementDataWithPositioning:self.adPositioning]; + } else if ([self.adPositioning isKindOfClass:[MPServerAdPositioning class]]) { + // Reset to a placement data that has no "desired" ads at all. + self.adPlacementData = [[MPInstanceProvider sharedProvider] buildStreamAdPlacementDataWithPositioning:nil]; + + // Get positioning information from the server. + self.positioningSource = [[MPInstanceProvider sharedProvider] buildNativePositioningSource]; + __typeof__(self) __weak weakSelf = self; + [self.positioningSource loadPositionsWithAdUnitIdentifier:self.adUnitID completionHandler:^(MPAdPositioning *positioning, NSError *error) { + __typeof__(self) strongSelf = weakSelf; + + if (!strongSelf) { + return; + } + + if (error) { + if ([error code] == MPNativePositionSourceEmptyResponse) { + MPLogError(@"ERROR: Ad placer cannot show any ads because ad positions have " + @"not been configured for your ad unit %@. You must assign positions " + @"by editing the ad unit's settings on the MoPub website.", + strongSelf.adUnitID); + } else { + MPLogError(@"ERROR: Ad placer failed to get positions from the ad server for " + @"ad unit ID %@. Error: %@", strongSelf.adUnitID, error); + } + } else { + strongSelf.adPlacementData = [[MPInstanceProvider sharedProvider] buildStreamAdPlacementDataWithPositioning:positioning]; + } + }]; + } + + if (adIndexPaths.count > 0) { + [self.delegate adPlacer:self didRemoveAdsAtIndexPaths:adIndexPaths]; + } + + if (!adUnitID) { + MPLogError(@"Ad placer cannot load ads with a nil ad unit ID."); + return; + } + + [self.adSource loadAdsWithAdUnitIdentifier:adUnitID rendererConfigurations:self.rendererConfigurations andTargeting:targeting]; +} + +- (BOOL)isAdAtIndexPath:(NSIndexPath *)indexPath +{ + return [self.adPlacementData isAdAtAdjustedIndexPath:indexPath]; +} + +- (NSUInteger)adjustedNumberOfItems:(NSUInteger)numberOfItems inSection:(NSUInteger)section +{ + return [self.adPlacementData adjustedNumberOfItems:numberOfItems inSection:section]; +} + +- (NSIndexPath *)adjustedIndexPathForOriginalIndexPath:(NSIndexPath *)indexPath +{ + return [self.adPlacementData adjustedIndexPathForOriginalIndexPath:indexPath]; +} + +- (NSIndexPath *)originalIndexPathForAdjustedIndexPath:(NSIndexPath *)indexPath +{ + return [self.adPlacementData originalIndexPathForAdjustedIndexPath:indexPath]; +} + +- (NSArray *)adjustedIndexPathsForOriginalIndexPaths:(NSArray *)indexPaths +{ + NSMutableArray *adjustedIndexPaths = [NSMutableArray array]; + for (NSIndexPath *indexPath in indexPaths) { + [adjustedIndexPaths addObject:[self adjustedIndexPathForOriginalIndexPath:indexPath]]; + } + return [adjustedIndexPaths copy]; +} + +- (NSArray *)originalIndexPathsForAdjustedIndexPaths:(NSArray *)indexPaths +{ + NSMutableArray *originalIndexPaths = [NSMutableArray array]; + for (NSIndexPath *indexPath in indexPaths) { + NSIndexPath *originalIndexPath = [self originalIndexPathForAdjustedIndexPath:indexPath]; + if (originalIndexPath) { + [originalIndexPaths addObject:originalIndexPath]; + } + } + return [originalIndexPaths copy]; +} + +- (void)insertItemsAtIndexPaths:(NSArray *)originalIndexPaths +{ + [self.adPlacementData insertItemsAtIndexPaths:originalIndexPaths]; + [originalIndexPaths enumerateObjectsUsingBlock:^(NSIndexPath *originalIndexPath, NSUInteger idx, BOOL *stop) { + NSInteger section = originalIndexPath.section; + [self setItemCount:[[self.sectionCounts objectForKey:@(section)] integerValue] + 1 forSection:section]; + }]; +} + +- (void)deleteItemsAtIndexPaths:(NSArray *)originalIndexPaths +{ + originalIndexPaths = [originalIndexPaths sortedArrayUsingSelector:@selector(compare:)]; + NSMutableSet *activeSections = [NSMutableSet setWithCapacity:[originalIndexPaths count]]; + [originalIndexPaths enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) { + [activeSections addObject:[NSNumber numberWithInteger:indexPath.section]]; + }]; + + NSMutableArray *removedIndexPaths = [NSMutableArray array]; + [activeSections enumerateObjectsUsingBlock:^(NSNumber *section, BOOL *stop) { + NSArray *originalIndexPathsInSection = [originalIndexPaths filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"section = %@", section]]; + NSRange deleteRange = [self rangeToDeleteInSection:section forOriginalContentIndexPaths:originalIndexPathsInSection]; + + NSArray *indexPathsToDelete = [self.adPlacementData adjustedAdIndexPathsInAdjustedRange:deleteRange inSection:[section integerValue]]; + [removedIndexPaths addObjectsFromArray:indexPathsToDelete]; + [self.adPlacementData clearAdsInAdjustedRange:deleteRange inSection:[section integerValue]]; + }]; + + [self.adPlacementData deleteItemsAtIndexPaths:originalIndexPaths]; + + [originalIndexPaths enumerateObjectsUsingBlock:^(NSIndexPath *originalIndexPath, NSUInteger idx, BOOL *stop) { + NSInteger section = originalIndexPath.section; + [self setItemCount:[[self.sectionCounts objectForKey:@(section)] integerValue] - 1 forSection:section]; + }]; + + if ([removedIndexPaths count]) { + [self.delegate adPlacer:self didRemoveAdsAtIndexPaths:removedIndexPaths]; + } +} + +- (void)moveItemAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath +{ + [self.adPlacementData moveItemAtIndexPath:fromIndexPath toIndexPath:toIndexPath]; +} + +- (void)insertSections:(NSIndexSet *)sections +{ + [self.adPlacementData insertSections:sections]; + [self insertItemCountSections:sections]; +} + +- (void)deleteSections:(NSIndexSet *)sections +{ + [self.adPlacementData deleteSections:sections]; + [self deleteItemCountSections:sections]; +} + +- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection +{ + [self.adPlacementData moveSection:section toSection:newSection]; + + NSUInteger originalSectionCount = [self.sectionCounts[@(section)] unsignedIntegerValue]; + + [self deleteItemCountSections:[NSIndexSet indexSetWithIndex:section]]; + [self insertItemCountSections:[NSIndexSet indexSetWithIndex:newSection]]; + + [self setItemCount:originalSectionCount forSection:newSection]; +} + +#pragma mark - Private + +- (void)insertItemCountSections:(NSIndexSet *)sections +{ + [sections enumerateIndexesUsingBlock:^(NSUInteger insertionSection, BOOL *stop) { + // Explicitly casting indices to NSInteger because we're counting backwards. + NSInteger maxSection = [[[self.sectionCounts allKeys] valueForKeyPath:@"@max.unsignedIntValue"] unsignedIntegerValue]; + NSInteger signedInsertionSection = insertionSection; + + // We need to shift all the data above the new section up by 1. This assumes each section has a count (as each section should). + for (NSInteger i = maxSection; i >= signedInsertionSection; --i) { + NSUInteger currentCount = [self.sectionCounts[@(i)] unsignedIntegerValue]; + [self setItemCount:currentCount forSection:i+1]; + } + + // Setting the count to 0 isn't exactly correct, but it will be updated correctly when numberOfItems/Rows is called onthe data source. + [self setItemCount:0 forSection:insertionSection]; + }]; +} + +- (void)deleteItemCountSections:(NSIndexSet *)sections +{ + [sections enumerateIndexesWithOptions:NSEnumerationReverse usingBlock:^(NSUInteger deletionSection, BOOL *stop) { + NSUInteger maxSection = [[[self.sectionCounts allKeys] valueForKeyPath:@"@max.unsignedIntValue"] unsignedIntegerValue]; + + // We need to shift all the data above the deletionSection down by 1. This assumes each section has a count (as each section should). + for (NSUInteger i = deletionSection; i <= maxSection; ++i) { + NSUInteger nextCount = [self.sectionCounts[@(i+1)] unsignedIntegerValue]; + [self setItemCount:nextCount forSection:i]; + } + + [self.sectionCounts removeObjectForKey:@(maxSection)]; + }]; +} + +/* + * Returns the range to consider removing cells from the datasource for a given section. + * + * We want to prevent a state where ads are present after the last remaining content item. + * In order to do this, we need to find the range between the last remaining content item + * (after deletion occurs) and last item being deleted. If the end of this range includes + * the (current) last remaining item, we should delete all ads within the range, since they + * would otherwise be "trailing" ads. + * + * The range returned from this method will not include any ads that appear before the + * last remaining content item, because all we care about is preventing trailing ads. + */ +- (NSRange)rangeToDeleteInSection:(NSNumber *)section forOriginalContentIndexPaths:(NSArray *)originalContentIndexPaths +{ + NSRange rangeToDelete = NSMakeRange(0, 0); + NSInteger sectionCount = [self.sectionCounts[section] integerValue]; + //In order to remove trailing ads, we need to find the first index path of the last contiguous block of content items + //That we're deleting. Using the item of this index path, we can create a range in which to remove ads from the datasource. + __block NSIndexPath *firstIndexPathOfLastContiguousContentItemBlock = [originalContentIndexPaths lastObject]; + + //determines if the last content item is being deleted. If not, no ads will be deleted. + if (sectionCount == firstIndexPathOfLastContiguousContentItemBlock.row + 1) { + + //Traverses (in reverse) the content index paths being deleted until it reaches the beginning of the section (all items being deleted), or a gap in that block. + [[[originalContentIndexPaths reverseObjectEnumerator] allObjects] enumerateObjectsUsingBlock:^(NSIndexPath *contentPath, NSUInteger idx, BOOL *stop) { + if (idx > 0 && contentPath.row == firstIndexPathOfLastContiguousContentItemBlock.row - 1) { + firstIndexPathOfLastContiguousContentItemBlock = contentPath; + } + }]; + + NSInteger sectionTotal = [self.adPlacementData adjustedNumberOfItems:sectionCount inSection:[section integerValue]]; + + if (firstIndexPathOfLastContiguousContentItemBlock.row == 0) { + rangeToDelete = NSMakeRange(0, sectionTotal); + } else { + //Last content item *not* being deleted - will be the new end of the section. + NSIndexPath *lastRemainingContentIndexPath = [NSIndexPath indexPathForRow:firstIndexPathOfLastContiguousContentItemBlock.row - 1 inSection:firstIndexPathOfLastContiguousContentItemBlock.section]; + NSIndexPath *adjustedLastContent = [self.adPlacementData adjustedIndexPathForOriginalIndexPath:lastRemainingContentIndexPath]; + rangeToDelete = NSMakeRange(adjustedLastContent.row, sectionTotal - adjustedLastContent.row); + } + } + return rangeToDelete; +} + +- (NSIndexPath *)furthestValidIndexPathAfterIndexPath:(NSIndexPath *)startingPath withinDistance:(NSUInteger)numberOfItems +{ + NSUInteger section = [startingPath indexAtPosition:0]; + NSInteger itemIndex = [startingPath indexAtPosition:1]; + + NSNumber *sectionCountNumber = self.sectionCounts[@(section)]; + NSUInteger sectionItemCount = [sectionCountNumber unsignedIntegerValue]; + NSUInteger itemsPassed = 0; + while (itemsPassed < numberOfItems) { + if (sectionItemCount > (itemIndex + 1)) { + ++itemIndex; + ++itemsPassed; + } else { + // Ignore 0 sized sections. + NSUInteger trySection = section; + do { + ++trySection; + sectionCountNumber = self.sectionCounts[@(trySection)]; + sectionItemCount = [sectionCountNumber unsignedIntegerValue]; + } while (sectionCountNumber && sectionItemCount == 0); + + // We can exit and use the last known valid index path if we can't get a section count number. + if (!sectionCountNumber) { + break; + } else { + // Otherwise we move onto the next non-zero section. + section = trySection; + ++itemsPassed; + itemIndex = 0; + } + } + } + + NSUInteger indices[] = {section, itemIndex}; + return [NSIndexPath indexPathWithIndexes:indices length:2]; +} + +- (NSIndexPath *)earliestValidIndexPathBeforeIndexPath:(NSIndexPath *)startingPath withinDistance:(NSUInteger)numberOfItems +{ + NSUInteger section = [startingPath indexAtPosition:0]; + NSInteger itemIndex = [startingPath indexAtPosition:1]; + + NSUInteger itemsPassed = 0; + while (itemsPassed < numberOfItems) { + if ((itemIndex - 1) >= 0) { + --itemIndex; + ++itemsPassed; + } else { + // Ignore 0 sized sections. + NSNumber *sectionCountNumber; + NSUInteger trySection = section; + NSUInteger trySectionCount; + + do { + --trySection; + sectionCountNumber = self.sectionCounts[@(trySection)]; + trySectionCount = [sectionCountNumber unsignedIntegerValue]; + } while (sectionCountNumber && trySectionCount == 0); + + // Exit and use the last known valid index path. + if (!sectionCountNumber) { + break; + } else { + // Otherwise we move backwards. + section = trySection; + itemIndex = trySectionCount - 1; + ++itemsPassed; + } + } + } + + NSUInteger indices[] = {section, itemIndex}; + return [NSIndexPath indexPathWithIndexes:indices length:2]; +} + +// Determines whether or not insertionPath is close enough to the visible cells to place an ad at insertionPath. +- (BOOL)shouldPlaceAdAtIndexPath:(NSIndexPath *)insertionPath +{ + if (!self.topConsideredIndexPath || !self.bottomConsideredIndexPath || !insertionPath) { + return NO; + } + + // We need to make sure the insertionPath is actually at a valid index in a section by confirming the index is less than the count in the section. + NSUInteger originalSectionCount = [self.sectionCounts[@(insertionPath.section)] unsignedIntegerValue]; + NSUInteger adjustedSectionCount = [self adjustedNumberOfItems:originalSectionCount inSection:insertionPath.section]; + + if ([insertionPath indexAtPosition:kIndexPathItemIndex] >= adjustedSectionCount) { + return NO; + } + + NSIndexPath *topAdjustedIndexPath = [self adjustedIndexPathForOriginalIndexPath:self.topConsideredIndexPath]; + NSIndexPath *bottomAdjustedIndexPath = [self adjustedIndexPathForOriginalIndexPath:self.bottomConsideredIndexPath]; + + return ([topAdjustedIndexPath compare:insertionPath] != NSOrderedDescending) && ([bottomAdjustedIndexPath compare:insertionPath] != NSOrderedAscending); +} + +- (MPNativeAdData *)retrieveAdDataForInsertionPath:(NSIndexPath *)insertionPath +{ + MPNativeAd *adObject = [self.adSource dequeueAdForAdUnitIdentifier:self.adUnitID]; + + if (!adObject) { + return nil; + } + + MPNativeAdData *adData = [[MPNativeAdData alloc] init]; + adData.adUnitID = self.adUnitID; + adData.ad = adObject; + + return adData; +} + +- (void)fillAdsInConsideredRange +{ + if (!self.topConsideredIndexPath || !self.bottomConsideredIndexPath) { + return; + } + + NSIndexPath *topAdjustedIndexPath = [self adjustedIndexPathForOriginalIndexPath:self.topConsideredIndexPath]; + NSIndexPath *insertionPath = [self.adPlacementData nextAdInsertionIndexPathForAdjustedIndexPath:topAdjustedIndexPath]; + + while ([self shouldPlaceAdAtIndexPath:insertionPath]) { + MPNativeAdData *adData = [self retrieveAdDataForInsertionPath:insertionPath]; + adData.ad.delegate = self; + + if (!adData) { + break; + } + + [self.adPlacementData insertAdData:adData atIndexPath:insertionPath]; + [self.delegate adPlacer:self didLoadAdAtIndexPath:insertionPath]; + + insertionPath = [self.adPlacementData nextAdInsertionIndexPathForAdjustedIndexPath:insertionPath]; + } +} + +#pragma mark - + +- (void)adSourceDidFinishRequest:(MPNativeAdSource *)source +{ + [self fillAdsInConsideredRange]; +} + +#pragma mark - + +- (UIViewController *)viewControllerForPresentingModalView +{ + return self.viewController; +} + +- (void)willPresentModalForNativeAd:(MPNativeAd *)nativeAd +{ + if ([self.delegate respondsToSelector:@selector(nativeAdWillPresentModalForStreamAdPlacer:)]) { + [self.delegate nativeAdWillPresentModalForStreamAdPlacer:self]; + } +} + +- (void)didDismissModalForNativeAd:(MPNativeAd *)nativeAd +{ + if ([self.delegate respondsToSelector:@selector(nativeAdDidDismissModalForStreamAdPlacer:)]) { + [self.delegate nativeAdDidDismissModalForStreamAdPlacer:self]; + } +} + +- (void)willLeaveApplicationFromNativeAd:(MPNativeAd *)nativeAd +{ + if ([self.delegate respondsToSelector:@selector(nativeAdWillLeaveApplicationFromStreamAdPlacer:)]) { + [self.delegate nativeAdWillLeaveApplicationFromStreamAdPlacer:self]; + } +} + +#pragma mark - Internal + +- (CGSize)sizeForAd:(MPNativeAd *)ad withMaximumWidth:(CGFloat)maxWidth andIndexPath:(NSIndexPath *)indexPath +{ + id renderer = ad.renderer; + + CGSize adSize; + + if ([renderer respondsToSelector:@selector(viewSizeHandler)] && renderer.viewSizeHandler) { + adSize = [renderer viewSizeHandler](maxWidth); + if (adSize.height == MPNativeViewDynamicDimension) { + UIView *adView = [ad retrieveAdViewForSizeCalculationWithError:nil]; + if (adView) { + CGSize hydratedAdViewSize = [adView sizeThatFits:CGSizeMake(adSize.width, CGFLOAT_MAX)]; + return hydratedAdViewSize; + } + } + return adSize; + } + + adSize = CGSizeMake(maxWidth, 44.0f); + MPLogWarn(@"WARNING: + (CGSize)viewSizeHandler is NOT implemented for native ad renderer %@ at index path %@. You MUST implement this method to ensure that ad placer native ad cells are correctly sized. Returning a default size of %@ for now.", NSStringFromClass([(id)renderer class]), indexPath, NSStringFromCGSize(adSize)); + + return adSize; +} +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/MPTableViewAdPlacer.h b/iphone/Maps/3party/MoPubSDK/Native Ads/MPTableViewAdPlacer.h new file mode 100644 index 00000000000..8207aa51574 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/MPTableViewAdPlacer.h @@ -0,0 +1,431 @@ +// +// MPTableViewAdPlacer.h +// MoPub +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import +#import +#import "MPClientAdPositioning.h" +#import "MPServerAdPositioning.h" + +@class MPNativeAdRequestTargeting; +@protocol MPTableViewAdPlacerDelegate; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * The `MPTableViewAdPlacer` class allows you to request native ads from the MoPub ad server and + * place them into a `UITableView` object. + * + * When an instance of this class is initialized with a table view, it wraps the table view's + * data source and delegate in order to insert ads and adjust the positions of your regular + * content cells. + */ + +@interface MPTableViewAdPlacer : NSObject + +@property (nonatomic, weak) id delegate; + +/** @name Initializing a Table View Ad Placer */ + +/** + * Creates and returns an ad placer that will insert ads into a table view at positions that can + * be configured dynamically on the MoPub website. + * + * When you make an ad request, the ad placer will ask the MoPub ad server for the positions where + * ads should be inserted into the table view. You can configure these positioning values by + * editing your ad unit's settings on the MoPub website. + * + * Using this method is equivalent to calling + * +placerWithTableView:viewController:adPositioning:rendererConfigurations: and passing in an + * `MPServerAdPositioning` object as the `positioning` parameter. + * + * @param tableView The table view in which to insert ads. + * @param controller The view controller which should be used to present modal content. + * @param rendererConfigurations An array of MPNativeAdRendererConfiguration objects that control how + * the native ad is rendered. You should pass in configurations that can render any ad type that + * may be displayed for the given ad unit. + * + * @return An `MPTableViewAdPlacer` object. + */ ++ (instancetype)placerWithTableView:(UITableView *)tableView viewController:(UIViewController *)controller rendererConfigurations:(NSArray *)rendererConfigurations; + +/** + * Creates and returns an ad placer that will insert ads into a table view at specified positions. + * + * When using this method, there are two options for controlling the positions where ads appear + * within the table view. + * + * First, you may pass an `MPServerAdPositioning` object as the `positioning` parameter, which tells + * the ad placer to obtain positioning information dynamically from the ad server, which you can + * configure on the MoPub website. In many cases, this is the preferred approach, since it allows + * you to modify the positions without rebuilding your application. Note that calling the + * convenience method +placerWithTableView:viewController:defaultAdRenderingClass: accomplishes + * this as well. + * + * Alternatively, if you wish to hard-code your positions, you may pass an `MPClientAdPositioning` + * object instead. + * + * @param tableView The table view in which to insert ads. + * @param controller The view controller which should be used to present modal content. + * @param positioning The positioning object that specifies where ads should be shown in the stream. + * @param rendererConfigurations An array of MPNativeAdRendererConfiguration objects that control how + * the native ad is rendered. You should pass in configurations that can render any ad type that + * may be displayed for the given ad unit. + * + * @return An `MPTableViewAdPlacer` object. + */ ++ (instancetype)placerWithTableView:(UITableView *)tableView viewController:(UIViewController *)controller adPositioning:(MPAdPositioning *)positioning rendererConfigurations:(NSArray *)rendererConfigurations; + +/** @name Requesting Ads */ + +/** + * Requests ads from the MoPub ad server using the specified ad unit ID. + * + * @param adUnitID A string representing a MoPub ad unit ID. + */ +- (void)loadAdsForAdUnitID:(NSString *)adUnitID; + +/** + * Requests ads from the MoPub ad server using the specified ad unit ID and targeting parameters. + * + * @param adUnitID A string representing a MoPub ad unit ID. + * @param targeting An object containing targeting information, such as geolocation data. + */ +- (void)loadAdsForAdUnitID:(NSString *)adUnitID targeting:(MPNativeAdRequestTargeting *)targeting; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * The MoPub SDK adds interfaces to the `UITableView` class to help your application with + * responsibilities related to `MPTableViewAdPlacer`. These APIs include methods to help notify the + * ad placer of all modifications to the original table view, as well as to simplify your + * application code such that it does not need to perform index path manipulations to account for + * the presence of ads. + * + * Since the ad placer replaces the original data source and delegate objects of your table view, + * the SDK also provides new methods for you to set these properties such that the ad placer remains + * aware of the changes. + */ + +@interface UITableView (MPTableViewAdPlacer) + +- (void)mp_setAdPlacer:(MPTableViewAdPlacer *)placer; + +/** @name Obtaining the Table View Ad Placer */ + +/** + * Returns the ad placer currently being used for this table view. + * + * @return An ad placer object or `nil` if no ad placer is being used. + */ +- (MPTableViewAdPlacer *)mp_adPlacer; + +/** @name Setting and Getting the Delegate and Data Source */ + +/** + * Sets the table view's data source. + * + * If your application needs to change a table view's data source after it has instantiated an ad + * placer using that table view, use this method rather than -[UITableView setDataSource:]. + * + * @param dataSource The new table view data source. + */ +- (void)mp_setDataSource:(id)dataSource; + +/** + * Returns the original data source of the table view. + * + * When you instantiate an ad placer using a table view, the ad placer replaces the table view's + * original data source object. If your application needs to access the original data source, use + * this method instead of -[UITableView dataSource]. + * + * @return The original table view data source. + */ +- (id)mp_dataSource; + +/** + * Sets the table view's delegate. + * + * If your application needs to change a table view's delegate after it has instantiated an ad + * placer using that table view, use this method rather than -[UITableView setDelegate:]. + * + * @param delegate The new table view delegate. + */ +- (void)mp_setDelegate:(id)delegate; + +/** + * Returns the original delegate of the table view. + * + * When you instantiate an ad placer using a table view, the ad placer replaces the table view's + * original delegate object. If your application needs to access the original delegate, use this + * method instead of -[UITableView delegate]. + * + * @return The original table view delegate. + */ +- (id)mp_delegate; + +/** @name Notifying the Table View Ad Placer of Content Changes */ + +/** + * Begin a series of method calls that insert, delete, or select rows and sections of the table + * view. + */ +- (void)mp_beginUpdates; + +/** + * Conclude a series of method calls that insert, delete, select, or reload rows and sections of + * the table view. + */ +- (void)mp_endUpdates; + +/** + * Reloads the rows and sections of the table view. + */ +- (void)mp_reloadData; + +/** + * Inserts rows in the receiver at the locations identified by an array of index paths, and informs + * the attached ad placer of the insertions. + * + * @param indexPaths An array of `NSIndexPath` objects that represent rows to insert into the + * table. + * @param animation A constant that either specifies the kind of animation to perform when + * inserting the cells or requests no animation. + */ +- (void)mp_insertRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation; + +/** + * Deletes rows in the receiver at the locations identified by an array of index paths, and informs + * the attached ad placer of the deletions. + * + * @param indexPaths An array of `NSIndexPath` objects identifying the rows to delete. + * @param animation A constant that either specifies the kind of animation to perform when + * deleting the cells or requests no animation. + */ +- (void)mp_deleteRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation; + +/** + * Reloads the specified rows using the given animation effect, and informs the attached ad placer + * that the row positions may have changed. + * + * @param indexPaths An array of `NSIndexPath` objects identifying the rows to reload. + * @param animation A constant that indicates how the reloading is to be animated. + */ +- (void)mp_reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation; + +/** + * Moves the row at a specified location to a destination location, taking into account ads + * inserted by the ad placer. + * + * @param indexPath An index path identifying the row to move. + * @param newIndexPath An index path identifying the row that is the destination of the row at + * *indexPath*. + */ +- (void)mp_moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath; + +/** + * Inserts one or more sections in the receiver, and informs the attached ad placer of the + * insertions. + * + * @param sections An index set that specifies the sections to insert in the receiving table view. + * If a section already exists at the specified index location, it is moved down one index + * location. + * @param animation A constant that indicates how the insertion is to be animated. + */ +- (void)mp_insertSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation; + +/** + * Deletes one or more sections in the receiver, and informs the attached ad placer of the + * deletions. + * + * @param sections An index set that specifies the sections to delete from the receiving table + * view. If a section exists after the specified index location, it is moved up one index location. + * @param animation A constant that either specifies the kind of animation to perform when deleting + * the section or requests no animation. + */ +- (void)mp_deleteSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation; + +/** + * Reloads the specified sections, and informs the attached ad placer that sections may have + * changed. + * + * @param sections An index set identifying the sections to reload. + * @param animation A constant that indicates how the reloading is to be animated. + */ +- (void)mp_reloadSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation; + +/** + * Moves a section to a new location in the table view, and informs the attached ad placer. + * + * @param section The index of the section to move. + * @param newSection The index in the table view that is the destination of the move for the + * section. The existing section at that location slides up or down to an adjoining index position + * to make room for it. + */ +- (void)mp_moveSection:(NSInteger)section toSection:(NSInteger)newSection; + +/** @name Methods Involving Index Paths */ + +/** + * Returns the table view cell at the specified index path. + * + * @param indexPath The index path locating the row in the table view. + * + * @return An object representing a cell of the table or `nil` if the cell is not visible or + * *indexPath* is out of range. + */ +- (UITableViewCell *)mp_cellForRowAtIndexPath:(NSIndexPath *)indexPath; + +/** + * Returns a reusable table-view cell object for the specified reuse identifier. + * + * @param identifier A string identifying the cell object to be reused. This parameter must not be + * `nil`. + * @param indexPath The index path specifying the location of the cell. The data source receives + * this information when asked for the cell and should just pass it along. + * + * @return A `UITableViewCell` object with the associated reuse identifier. + */ +- (id)mp_dequeueReusableCellWithIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath; + +/** + * Deselects a given row identified by index path, with an option to animate the deselection. + * + * @param indexPath An index path identifying a row in the receiver. + * @param animated YES if you want to animate the deselection and NO if the change should be + * immediate. + */ +- (void)mp_deselectRowAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated; + +/** + * Returns an index path representing the original row and section of a given table view cell, + * calculated before any ads were inserted. + * + * @param cell A cell object of the table view. + * + * @return An index path representing the row and section of the cell or `nil` if the index path + * is invalid or is a reference to a cell containing an ad. + */ +- (NSIndexPath *)mp_indexPathForCell:(UITableViewCell *)cell; + +/** + * Returns an index path representing the original row and section at the given point. + * + * @param point A point in the local coordinate system of the receiver (the table view's bounds). + * + * @return An index path representing the row and section associated with *point* or `nil` if the + * point is out of bounds of any row or is associated with a cell containing an ad. + */ +- (NSIndexPath *)mp_indexPathForRowAtPoint:(CGPoint)point; + +/** + * Returns the original index path for the selected row, as if no ads have been inserted. + * + * @return The original index path for the selected row. + */ +- (NSIndexPath *)mp_indexPathForSelectedRow; + +/** + * Returns an array of index paths each representing a non-ad row enclosed by a given rectangle, + * calculated before any ads were inserted. + * + * @param rect A rectangle defining an area of the table view in local coordinates. + * + * @return An array of `NSIndexPath` objects each representing a row and section index identifying + * a row within *rect*. Index paths representing ads are not returned. + */ +- (NSArray *)mp_indexPathsForRowsInRect:(CGRect)rect; + +/** + * Returns an array of the original index paths for the selected rows, as if no ads have been + * inserted. + * + * @return An array of the original index paths for the selected rows. + */ +- (NSArray *)mp_indexPathsForSelectedRows; + +/** + * Returns an array of index paths each identifying a visible non-ad row in the table view, + * calculated before any ads were inserted. + * + * @return An array of `NSIndexPath` objects each representing a visible non-ad row in the table + * view. Returns `nil` if no rows are visible. + */ +- (NSArray *)mp_indexPathsForVisibleRows; + +/** + * Returns the drawing area for a row identified by index path. + * + * @param indexPath An index path object that identifies a row of your original content. + * + * @return A rectangle defining the area in which the table view draws the row or `CGRectZero` if + * *indexPath* is invalid. + */ +- (CGRect)mp_rectForRowAtIndexPath:(NSIndexPath *)indexPath; + +/** + * Scrolls the table view so that the selected row nearest to a specified position in the table + * view is at that position. + * + * @param indexPath An index path that identifies a row in the table view by its row index and + * its section index. + * @param scrollPosition A constant that identifies a relative position in the receiving table view + * (top, middle, bottom) for row when scrolling concludes. + * @param animated YES if you want to animate the change in position, NO if it should be + * immediate. + */ +- (void)mp_scrollToRowAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(UITableViewScrollPosition)scrollPosition animated:(BOOL)animated; + +/** + * Selects a row in the receiver identified by index path, optionally scrolling the row to a + * location in the receiver. + * + * @param indexPath An index path identifying a row in the receiver. + * @param animated YES if you want to animate the selection and any change in position, NO if + * the change should be immediate. + * @param scrollPosition A constant that identifies a relative position in the receiving table view + * (top, middle, bottom) for the row when scrolling concludes. + */ +- (void)mp_selectRowAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated scrollPosition:(UITableViewScrollPosition)scrollPosition; + +/** + * Returns an array of the non-ad cells that are visible in the table view. + * + * @return An array containing `UITableViewCell` objects, each representing a visible, non-ad cell + * in the receiving table view. + */ +- (NSArray *)mp_visibleCells; + +@end + +@protocol MPTableViewAdPlacerDelegate + +@optional + +/* + * This method is called when a native ad, placed by the table view ad placer, will present a modal view controller. + * + * @param placer The table view ad placer that contains the ad displaying the modal. + */ +-(void)nativeAdWillPresentModalForTableViewAdPlacer:(MPTableViewAdPlacer *)placer; + +/* + * This method is called when a native ad, placed by the table view ad placer, did dismiss its modal view controller. + * + * @param placer The table view ad placer that contains the ad that dismissed the modal. + */ +-(void)nativeAdDidDismissModalForTableViewAdPlacer:(MPTableViewAdPlacer *)placer; + +/* + * This method is called when a native ad, placed by the table view ad placer, will cause the app to background due to user interaction with the ad. + * + * @param placer The table view ad placer that contains the ad causing the app to background. + */ +-(void)nativeAdWillLeaveApplicationFromTableViewAdPlacer:(MPTableViewAdPlacer *)placer; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Native Ads/MPTableViewAdPlacer.m b/iphone/Maps/3party/MoPubSDK/Native Ads/MPTableViewAdPlacer.m new file mode 100644 index 00000000000..e75183921c7 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Native Ads/MPTableViewAdPlacer.m @@ -0,0 +1,823 @@ +// +// MPTableViewAdPlacer.m +// MoPub +// +// Copyright (c) 2014 MoPub. All rights reserved. +// + +#import "MPTableViewAdPlacer.h" +#import "MPStreamAdPlacer.h" +#import "MPInstanceProvider.h" +#import "MPAdPlacerInvocation.h" +#import "MPTimer.h" +#import "MPNativeAdRendering.h" +#import "MPNativeAdUtils.h" +#import "MPGlobal.h" +#import "MPNativeAdRendererConfiguration.h" +#import "MPTableViewAdPlacerCell.h" +#import + +static NSString * const kTableViewAdPlacerReuseIdentifier = @"MPTableViewAdPlacerReuseIdentifier"; + +@interface MPTableViewAdPlacer () + +@property (nonatomic, strong) MPStreamAdPlacer *streamAdPlacer; +@property (nonatomic, strong) UITableView *tableView; +@property (nonatomic, weak) id originalDataSource; +@property (nonatomic, weak) id originalDelegate; +@property (nonatomic, strong) MPTimer *insertionTimer; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation MPTableViewAdPlacer + ++ (instancetype)placerWithTableView:(UITableView *)tableView viewController:(UIViewController *)controller rendererConfigurations:(NSArray *)rendererConfigurations +{ + return [[self class] placerWithTableView:tableView viewController:controller adPositioning:[MPServerAdPositioning positioning] rendererConfigurations:rendererConfigurations]; +} + ++ (instancetype)placerWithTableView:(UITableView *)tableView viewController:(UIViewController *)controller adPositioning:(MPAdPositioning *)positioning rendererConfigurations:(NSArray *)rendererConfigurations +{ + MPTableViewAdPlacer *tableViewAdPlacer = [[MPTableViewAdPlacer alloc] initWithTableView:tableView viewController:controller adPositioning:positioning rendererConfigurations:rendererConfigurations]; + return tableViewAdPlacer; +} + +- (instancetype)initWithTableView:(UITableView *)tableView viewController:(UIViewController *)controller adPositioning:(MPAdPositioning *)positioning rendererConfigurations:(NSArray *)rendererConfigurations +{ + for (id rendererConfiguration in rendererConfigurations) { + NSAssert([rendererConfiguration isKindOfClass:[MPNativeAdRendererConfiguration class]], @"A table view ad placer must be instantiated with rendererConfigurations that are of type MPNativeAdRendererConfiguration."); + } + + if (self = [super init]) { + _tableView = tableView; + _streamAdPlacer = [[MPInstanceProvider sharedProvider] buildStreamAdPlacerWithViewController:controller adPositioning:positioning rendererConfigurations:rendererConfigurations]; + _streamAdPlacer.delegate = self; + + _originalDataSource = tableView.dataSource; + _originalDelegate = tableView.delegate; + tableView.dataSource = self; + tableView.delegate = self; + + [self.tableView registerClass:[MPTableViewAdPlacerCell class] forCellReuseIdentifier:kTableViewAdPlacerReuseIdentifier]; + + [tableView mp_setAdPlacer:self]; + } + return self; +} + +- (void)dealloc +{ + [_insertionTimer invalidate]; +} + +#pragma mark - Public + +- (void)loadAdsForAdUnitID:(NSString *)adUnitID +{ + [self loadAdsForAdUnitID:adUnitID targeting:nil]; +} + +- (void)loadAdsForAdUnitID:(NSString *)adUnitID targeting:(MPNativeAdRequestTargeting *)targeting +{ + if (!self.insertionTimer) { + self.insertionTimer = [MPTimer timerWithTimeInterval:kUpdateVisibleCellsInterval target:self selector:@selector(updateVisibleCells) repeats:YES]; + self.insertionTimer.runLoopMode = NSRunLoopCommonModes; + [self.insertionTimer scheduleNow]; + } + [self.streamAdPlacer loadAdsForAdUnitID:adUnitID targeting:targeting]; +} + +#pragma mark - Ad Insertion + +- (void)updateVisibleCells +{ + NSArray *visiblePaths = self.tableView.mp_indexPathsForVisibleRows; + + if ([visiblePaths count]) { + [self.streamAdPlacer setVisibleIndexPaths:visiblePaths]; + } +} + +#pragma mark - + +- (void)adPlacer:(MPStreamAdPlacer *)adPlacer didLoadAdAtIndexPath:(NSIndexPath *)indexPath +{ + BOOL originalAnimationsEnabled = [UIView areAnimationsEnabled]; + //We only want to enable animations if the index path is before or within our visible cells + BOOL animationsEnabled = ([(NSIndexPath *)[self.tableView.indexPathsForVisibleRows lastObject] compare:indexPath] != NSOrderedAscending) && originalAnimationsEnabled; + + [UIView setAnimationsEnabled:animationsEnabled]; + [self.tableView mp_beginUpdates]; + [self.tableView insertRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationMiddle]; + [self.tableView mp_endUpdates]; + [UIView setAnimationsEnabled:originalAnimationsEnabled]; +} + +- (void)adPlacer:(MPStreamAdPlacer *)adPlacer didRemoveAdsAtIndexPaths:(NSArray *)indexPaths +{ + BOOL originalAnimationsEnabled = [UIView areAnimationsEnabled]; + [UIView setAnimationsEnabled:NO]; + [self.tableView mp_beginUpdates]; + [self.tableView deleteRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone]; + [self.tableView mp_endUpdates]; + [UIView setAnimationsEnabled:originalAnimationsEnabled]; +} + +- (void)nativeAdWillPresentModalForStreamAdPlacer:(MPStreamAdPlacer *)adPlacer +{ + if ([self.delegate respondsToSelector:@selector(nativeAdWillPresentModalForTableViewAdPlacer:)]) { + [self.delegate nativeAdWillPresentModalForTableViewAdPlacer:self]; + } +} + +- (void)nativeAdDidDismissModalForStreamAdPlacer:(MPStreamAdPlacer *)adPlacer +{ + if ([self.delegate respondsToSelector:@selector(nativeAdDidDismissModalForTableViewAdPlacer:)]) { + [self.delegate nativeAdDidDismissModalForTableViewAdPlacer:self]; + } +} + +- (void)nativeAdWillLeaveApplicationFromStreamAdPlacer:(MPStreamAdPlacer *)adPlacer +{ + if ([self.delegate respondsToSelector:@selector(nativeAdWillLeaveApplicationFromTableViewAdPlacer:)]) { + [self.delegate nativeAdWillLeaveApplicationFromTableViewAdPlacer:self]; + } +} + +#pragma mark - + +// Default is 1 if not implemented +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView +{ + if ([self.originalDataSource respondsToSelector:@selector(numberOfSectionsInTableView:)]) { + return [self.originalDataSource numberOfSectionsInTableView:tableView]; + } + else { + return 1; + } +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section +{ + NSUInteger numberOfItems = [self.originalDataSource tableView:tableView numberOfRowsInSection:section]; + [self.streamAdPlacer setItemCount:numberOfItems forSection:section]; + return [self.streamAdPlacer adjustedNumberOfItems:numberOfItems inSection:section]; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath +{ + if ([self.streamAdPlacer isAdAtIndexPath:indexPath]) { + MPTableViewAdPlacerCell *cell = (MPTableViewAdPlacerCell *)[tableView dequeueReusableCellWithIdentifier:kTableViewAdPlacerReuseIdentifier forIndexPath:indexPath]; + cell.clipsToBounds = YES; + + [self.streamAdPlacer renderAdAtIndexPath:indexPath inView:cell.contentView]; + return cell; + } + NSIndexPath *originalIndexPath = [self.streamAdPlacer originalIndexPathForAdjustedIndexPath:indexPath]; + return [self.originalDataSource tableView:tableView cellForRowAtIndexPath:originalIndexPath]; +} + +- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath +{ + if ([self.streamAdPlacer isAdAtIndexPath:indexPath]) { + return NO; + } + + id datasource = self.originalDataSource; + if ([datasource respondsToSelector:@selector(tableView:canEditRowAtIndexPath:)]) { + NSIndexPath *origPath = [self.streamAdPlacer originalIndexPathForAdjustedIndexPath:indexPath]; + return [datasource tableView:tableView canEditRowAtIndexPath:origPath]; + } + + // When the data source doesn't implement tableView:canEditRowAtIndexPath:, Apple assumes the cells are editable. So we return YES. + return YES; +} + +- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath +{ + NSInvocation *invocation = [MPAdPlacerInvocation invokeForTarget:self.originalDataSource with2ArgSelector:@selector(tableView:canMoveRowAtIndexPath:) firstArg:tableView secondArg:indexPath streamAdPlacer:self.streamAdPlacer]; + + return [MPAdPlacerInvocation boolResultForInvocation:invocation defaultValue:NO]; +} + +- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath +{ + [MPAdPlacerInvocation invokeForTarget:self.originalDataSource with3ArgIntSelector:@selector(tableView:commitEditingStyle:forRowAtIndexPath:) firstArg:tableView secondArg:editingStyle thirdArg:indexPath streamAdPlacer:self.streamAdPlacer]; +} + +- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath +{ + if ([self.streamAdPlacer isAdAtIndexPath:sourceIndexPath]) { + // Can't move an ad explicitly. + return; + } + + id dataSource = self.originalDataSource; + if ([dataSource respondsToSelector:@selector(tableView:moveRowAtIndexPath:toIndexPath:)]) { + NSIndexPath *origSource = [self.streamAdPlacer originalIndexPathForAdjustedIndexPath:sourceIndexPath]; + NSIndexPath *origDestination = [self.streamAdPlacer originalIndexPathForAdjustedIndexPath:destinationIndexPath]; + [dataSource tableView:tableView moveRowAtIndexPath:origSource toIndexPath:origDestination]; + } +} + +#pragma mark - + +// We don't override the following: +// +// -tableView:targetIndexPathForMoveFromRowAtIndexPath:toProposedIndexPath - No need to override because +// targeting is typically based on the adjusted paths. +// +// -tableView:accessoryTypeForRowWithIndexPath - Deprecated, and causes a runtime exception. + +- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath +{ + if ([self.streamAdPlacer isAdAtIndexPath:indexPath]) { + return [self.streamAdPlacer sizeForAdAtIndexPath:indexPath withMaximumWidth:CGRectGetWidth(self.tableView.bounds)].height; + } + + if ([self.originalDelegate respondsToSelector:@selector(tableView:heightForRowAtIndexPath:)]) { + NSIndexPath *originalIndexPath = [self.streamAdPlacer originalIndexPathForAdjustedIndexPath:indexPath]; + return [self.originalDelegate tableView:tableView heightForRowAtIndexPath:originalIndexPath]; + } + + return tableView.rowHeight; +} + +- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath +{ + [MPAdPlacerInvocation invokeForTarget:self.originalDelegate with3ArgSelector:@selector(tableView:willDisplayCell:forRowAtIndexPath:) firstArg:tableView secondArg:cell thirdArg:indexPath streamAdPlacer:self.streamAdPlacer]; +} + +- (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath +{ + [MPAdPlacerInvocation invokeForTarget:self.originalDelegate with3ArgSelector:@selector(tableView:didEndDisplayingCell:forRowAtIndexPath:) firstArg:tableView secondArg:cell thirdArg:indexPath streamAdPlacer:self.streamAdPlacer]; +} + +- (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath +{ + [MPAdPlacerInvocation invokeForTarget:self.originalDelegate with2ArgSelector:@selector(tableView:accessoryButtonTappedForRowWithIndexPath:) firstArg:tableView secondArg:indexPath streamAdPlacer:self.streamAdPlacer]; +} + +- (BOOL)tableView:(UITableView *)tableView shouldHighlightRowAtIndexPath:(NSIndexPath *)indexPath +{ + NSInvocation *invocation = [MPAdPlacerInvocation invokeForTarget:self.originalDelegate with2ArgSelector:@selector(tableView:shouldHighlightRowAtIndexPath:) firstArg:tableView secondArg:indexPath streamAdPlacer:self.streamAdPlacer]; + + return [MPAdPlacerInvocation boolResultForInvocation:invocation defaultValue:YES]; +} + +- (void)tableView:(UITableView *)tableView didHighlightRowAtIndexPath:(NSIndexPath *)indexPath +{ + [MPAdPlacerInvocation invokeForTarget:self.originalDelegate with2ArgSelector:@selector(tableView:didHighlightRowAtIndexPath:) firstArg:tableView secondArg:indexPath streamAdPlacer:self.streamAdPlacer]; +} + +- (void)tableView:(UITableView *)tableView didUnhighlightRowAtIndexPath:(NSIndexPath *)indexPath +{ + [MPAdPlacerInvocation invokeForTarget:self.originalDelegate with2ArgSelector:@selector(tableView:didUnhighlightRowAtIndexPath:) firstArg:tableView secondArg:indexPath streamAdPlacer:self.streamAdPlacer]; +} + +- (NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath +{ + if ([self.streamAdPlacer isAdAtIndexPath:indexPath]) { + return indexPath; + } + + id delegate = self.originalDelegate; + if ([delegate respondsToSelector:@selector(tableView:willSelectRowAtIndexPath:)]) { + NSIndexPath *origPath = [self.streamAdPlacer originalIndexPathForAdjustedIndexPath:indexPath]; + NSIndexPath *origResult = [delegate tableView:tableView willSelectRowAtIndexPath:origPath]; + return [self.streamAdPlacer adjustedIndexPathForOriginalIndexPath:origResult]; + } + + return indexPath; +} + +- (NSIndexPath *)tableView:(UITableView *)tableView willDeselectRowAtIndexPath:(NSIndexPath *)indexPath +{ + if ([self.streamAdPlacer isAdAtIndexPath:indexPath]) { + return indexPath; + } + + id delegate = self.originalDelegate; + if ([delegate respondsToSelector:@selector(tableView:willDeselectRowAtIndexPath:)]) { + NSIndexPath *origPath = [self.streamAdPlacer originalIndexPathForAdjustedIndexPath:indexPath]; + NSIndexPath *origResult = [delegate tableView:tableView willDeselectRowAtIndexPath:origPath]; + return [self.streamAdPlacer adjustedIndexPathForOriginalIndexPath:origResult]; + } + + return indexPath; +} + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath +{ + if ([self.streamAdPlacer isAdAtIndexPath:indexPath]) { + // The view inside the cell already has a gesture recognizer to handle the tap event. + [self.tableView deselectRowAtIndexPath:indexPath animated:NO]; + return; + } + + id delegate = self.originalDelegate; + if ([delegate respondsToSelector:@selector(tableView:didSelectRowAtIndexPath:)]) { + NSIndexPath *originalPath = [self.streamAdPlacer originalIndexPathForAdjustedIndexPath:indexPath]; + [delegate tableView:tableView didSelectRowAtIndexPath:originalPath]; + } +} + +- (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath +{ + [MPAdPlacerInvocation invokeForTarget:self.originalDelegate with2ArgSelector:@selector(tableView:didDeselectRowAtIndexPath:) firstArg:tableView secondArg:indexPath streamAdPlacer:self.streamAdPlacer]; +} + +- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath +{ + if ([self.streamAdPlacer isAdAtIndexPath:indexPath]) { + return UITableViewCellEditingStyleNone; + } + + id delegate = self.originalDelegate; + if ([delegate respondsToSelector:@selector(tableView:editingStyleForRowAtIndexPath:)]) { + NSIndexPath *origPath = [self.streamAdPlacer originalIndexPathForAdjustedIndexPath:indexPath]; + return [delegate tableView:tableView editingStyleForRowAtIndexPath:origPath]; + } + + // Apple returns UITableViewCellEditingStyleDelete by default when the cell is editable. So we'll do the same. + // We'll also return UITableViewCellEditingStyleNone if the cell isn't editable. + BOOL editable = [self tableView:tableView canEditRowAtIndexPath:indexPath]; + + if (editable) { + return UITableViewCellEditingStyleDelete; + } else { + return UITableViewCellEditingStyleNone; + } +} + +- (NSString *)tableView:(UITableView *)tableView titleForDeleteConfirmationButtonForRowAtIndexPath:(NSIndexPath *)indexPath +{ + NSInvocation *invocation = [MPAdPlacerInvocation invokeForTarget:self.originalDelegate with2ArgSelector:@selector(tableView:titleForDeleteConfirmationButtonForRowAtIndexPath:) firstArg:tableView secondArg:indexPath streamAdPlacer:self.streamAdPlacer]; + + return [MPAdPlacerInvocation resultForInvocation:invocation defaultValue:@"Delete"]; +} + +- (BOOL)tableView:(UITableView *)tableView shouldIndentWhileEditingRowAtIndexPath:(NSIndexPath *)indexPath +{ + NSInvocation *invocation = [MPAdPlacerInvocation invokeForTarget:self.originalDelegate with2ArgSelector:@selector(tableView:shouldIndentWhileEditingRowAtIndexPath:) firstArg:tableView secondArg:indexPath streamAdPlacer:self.streamAdPlacer]; + + return [MPAdPlacerInvocation boolResultForInvocation:invocation defaultValue:YES]; +} + +- (void)tableView:(UITableView *)tableView willBeginEditingRowAtIndexPath:(NSIndexPath *)indexPath +{ + [MPAdPlacerInvocation invokeForTarget:self.originalDelegate with2ArgSelector:@selector(tableView:willBeginEditingRowAtIndexPath:) firstArg:tableView secondArg:indexPath streamAdPlacer:self.streamAdPlacer]; +} + +- (void)tableView:(UITableView *)tableView didEndEditingRowAtIndexPath:(NSIndexPath *)indexPath +{ + [MPAdPlacerInvocation invokeForTarget:self.originalDelegate with2ArgSelector:@selector(tableView:didEndEditingRowAtIndexPath:) firstArg:tableView secondArg:indexPath streamAdPlacer:self.streamAdPlacer]; +} + +- (NSInteger)tableView:(UITableView *)tableView indentationLevelForRowAtIndexPath:(NSIndexPath *)indexPath +{ + NSInvocation *invocation = [MPAdPlacerInvocation invokeForTarget:self.originalDelegate with2ArgSelector:@selector(tableView:indentationLevelForRowAtIndexPath:) firstArg:tableView secondArg:indexPath streamAdPlacer:self.streamAdPlacer]; + + return [MPAdPlacerInvocation integerResultForInvocation:invocation + defaultValue:UITableViewCellEditingStyleNone]; +} + +- (BOOL)tableView:(UITableView *)tableView shouldShowMenuForRowAtIndexPath:(NSIndexPath *)indexPath +{ + NSInvocation *invocation = [MPAdPlacerInvocation invokeForTarget:self.originalDelegate with2ArgSelector:@selector(tableView:shouldShowMenuForRowAtIndexPath:) firstArg:tableView secondArg:indexPath streamAdPlacer:self.streamAdPlacer]; + + return [MPAdPlacerInvocation boolResultForInvocation:invocation defaultValue:NO]; +} + +- (BOOL)tableView:(UITableView *)tableView canPerformAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender +{ + if ([self.streamAdPlacer isAdAtIndexPath:indexPath]) { + // Can't copy or paste to an ad. + return NO; + } + + id delegate = self.originalDelegate; + if ([delegate respondsToSelector:@selector(tableView:canPerformAction:forRowAtIndexPath:withSender:)]) { + NSIndexPath *origPath = [self.streamAdPlacer originalIndexPathForAdjustedIndexPath:indexPath]; + return [delegate tableView:tableView canPerformAction:action forRowAtIndexPath:origPath withSender:sender]; + } + + return NO; +} + +- (void)tableView:(UITableView *)tableView performAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender { + + if ([self.streamAdPlacer isAdAtIndexPath:indexPath]) { + // Can't copy or paste to an ad. + return; + } + + id delegate = self.originalDelegate; + if ([delegate respondsToSelector:@selector(tableView:performAction:forRowAtIndexPath:withSender:)]) { + NSIndexPath *origPath = [self.streamAdPlacer originalIndexPathForAdjustedIndexPath:indexPath]; + [delegate tableView:tableView performAction:action forRowAtIndexPath:origPath withSender:sender]; + } +} + +#pragma mark - Method Forwarding + +- (BOOL)isKindOfClass:(Class)aClass { + return [super isKindOfClass:aClass] || + [self.originalDataSource isKindOfClass:aClass] || + [self.originalDelegate isKindOfClass:aClass]; +} + +- (BOOL)conformsToProtocol:(Protocol *)aProtocol +{ + return [super conformsToProtocol:aProtocol] || + [self.originalDelegate conformsToProtocol:aProtocol] || + [self.originalDataSource conformsToProtocol:aProtocol]; +} + +- (BOOL)respondsToSelector:(SEL)aSelector +{ + return [super respondsToSelector:aSelector] || + [self.originalDataSource respondsToSelector:aSelector] || + [self.originalDelegate respondsToSelector:aSelector]; +} + +- (id)forwardingTargetForSelector:(SEL)aSelector +{ + if ([self.originalDataSource respondsToSelector:aSelector]) { + return self.originalDataSource; + } else if ([self.originalDelegate respondsToSelector:aSelector]) { + return self.originalDelegate; + } else { + return [super forwardingTargetForSelector:aSelector]; + } +} + +@end + +#pragma mark - + +@implementation UITableView (MPTableViewAdPlacer) + +static char kAdPlacerKey; + +- (void)mp_setAdPlacer:(MPTableViewAdPlacer *)placer +{ + objc_setAssociatedObject(self, &kAdPlacerKey, placer, OBJC_ASSOCIATION_ASSIGN); +} + +- (MPTableViewAdPlacer *)mp_adPlacer +{ + return objc_getAssociatedObject(self, &kAdPlacerKey); +} + +- (void)mp_setDelegate:(id)delegate +{ + MPTableViewAdPlacer *adPlacer = [self mp_adPlacer]; + + if (adPlacer) { + adPlacer.originalDelegate = delegate; + } else { + self.delegate = delegate; + } +} + +- (id)mp_delegate +{ + MPTableViewAdPlacer *adPlacer = [self mp_adPlacer]; + + if (adPlacer) { + return adPlacer.originalDelegate; + } else { + return self.delegate; + } +} + +- (void)mp_setDataSource:(id)dataSource +{ + MPTableViewAdPlacer *adPlacer = [self mp_adPlacer]; + + if (adPlacer) { + adPlacer.originalDataSource = dataSource; + } else { + self.dataSource = dataSource; + } +} + +- (id)mp_dataSource +{ + MPTableViewAdPlacer *adPlacer = [self mp_adPlacer]; + + if (adPlacer) { + return adPlacer.originalDataSource; + } else { + return self.dataSource; + } +} + +- (void)mp_reloadData +{ + [self reloadData]; +} + +- (CGRect)mp_rectForRowAtIndexPath:(NSIndexPath *)indexPath +{ + MPTableViewAdPlacer *adPlacer = [self mp_adPlacer]; + NSIndexPath *adjustedIndexPath = indexPath; + + if (adPlacer) { + adjustedIndexPath = [adPlacer.streamAdPlacer adjustedIndexPathForOriginalIndexPath:adjustedIndexPath]; + } + + if (!indexPath || adjustedIndexPath) { + return [self rectForRowAtIndexPath:adjustedIndexPath]; + } else { + return CGRectZero; + } +} + +- (NSIndexPath *)mp_indexPathForRowAtPoint:(CGPoint)point +{ + MPTableViewAdPlacer *adPlacer = [self mp_adPlacer]; + NSIndexPath *adjustedIndexPath = [self indexPathForRowAtPoint:point]; + + if (adPlacer) { + adjustedIndexPath = [adPlacer.streamAdPlacer originalIndexPathForAdjustedIndexPath:adjustedIndexPath]; + } + + return adjustedIndexPath; +} + +- (NSIndexPath *)mp_indexPathForCell:(UITableViewCell *)cell +{ + MPTableViewAdPlacer *adPlacer = [self mp_adPlacer]; + NSIndexPath *adjustedIndexPath = [self indexPathForCell:cell]; + + if (adPlacer) { + adjustedIndexPath = [adPlacer.streamAdPlacer originalIndexPathForAdjustedIndexPath:adjustedIndexPath]; + } + + return adjustedIndexPath; +} + +- (NSArray *)mp_indexPathsForRowsInRect:(CGRect)rect +{ + MPTableViewAdPlacer *adPlacer = [self mp_adPlacer]; + NSArray *indexPaths = [self indexPathsForRowsInRect:rect]; + + if (adPlacer) { + indexPaths = [adPlacer.streamAdPlacer originalIndexPathsForAdjustedIndexPaths:indexPaths]; + } + + return indexPaths; +} + +- (UITableViewCell *)mp_cellForRowAtIndexPath:(NSIndexPath *)indexPath +{ + MPTableViewAdPlacer *adPlacer = [self mp_adPlacer]; + NSIndexPath *adjustedIndexPath = indexPath; + + if (adPlacer) { + adjustedIndexPath = [adPlacer.streamAdPlacer adjustedIndexPathForOriginalIndexPath:adjustedIndexPath]; + } + + return [self cellForRowAtIndexPath:adjustedIndexPath]; +} + +- (NSArray *)mp_visibleCells +{ + MPTableViewAdPlacer *adPlacer = [self mp_adPlacer]; + + if (adPlacer) { + NSArray *indexPaths = [self mp_indexPathsForVisibleRows]; + NSMutableArray *visibleCells = [NSMutableArray array]; + for (NSIndexPath *indexPath in indexPaths) { + [visibleCells addObject:[self mp_cellForRowAtIndexPath:indexPath]]; + } + return visibleCells; + } else { + return [self visibleCells]; + } +} + +- (NSArray *)mp_indexPathsForVisibleRows +{ + MPTableViewAdPlacer *adPlacer = [self mp_adPlacer]; + NSArray *adjustedIndexPaths = [self indexPathsForVisibleRows]; + + if (adPlacer) { + adjustedIndexPaths = [adPlacer.streamAdPlacer originalIndexPathsForAdjustedIndexPaths:adjustedIndexPaths]; + } + + return adjustedIndexPaths; +} + +- (void)mp_scrollToRowAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(UITableViewScrollPosition)scrollPosition animated:(BOOL)animated +{ + MPTableViewAdPlacer *adPlacer = [self mp_adPlacer]; + NSIndexPath *adjustedIndexPath = indexPath; + + if (adPlacer && indexPath.row != NSNotFound) { + adjustedIndexPath = [adPlacer.streamAdPlacer adjustedIndexPathForOriginalIndexPath:adjustedIndexPath]; + } + + [self scrollToRowAtIndexPath:adjustedIndexPath atScrollPosition:scrollPosition animated:animated]; +} + +- (void)mp_beginUpdates +{ + [self beginUpdates]; +} + +- (void)mp_endUpdates +{ + [self endUpdates]; +} + +- (void)mp_insertSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation +{ + MPTableViewAdPlacer *adPlacer = [self mp_adPlacer]; + + if (adPlacer) { + [adPlacer.streamAdPlacer insertSections:sections]; + } + + [self insertSections:sections withRowAnimation:animation]; +} + +- (void)mp_deleteSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation +{ + MPTableViewAdPlacer *adPlacer = [self mp_adPlacer]; + + if (adPlacer) { + [adPlacer.streamAdPlacer deleteSections:sections]; + } + + [self deleteSections:sections withRowAnimation:animation]; +} + +- (void)mp_reloadSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation +{ + [self reloadSections:sections withRowAnimation:animation]; +} + +- (void)mp_moveSection:(NSInteger)section toSection:(NSInteger)newSection +{ + MPTableViewAdPlacer *adPlacer = [self mp_adPlacer]; + + if (adPlacer) { + [adPlacer.streamAdPlacer moveSection:section toSection:newSection]; + } + + [self moveSection:section toSection:newSection]; +} + +- (void)mp_insertRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation +{ + MPTableViewAdPlacer *adPlacer = [self mp_adPlacer]; + NSArray *adjustedIndexPaths = indexPaths; + + if (adPlacer) { + [adPlacer.streamAdPlacer insertItemsAtIndexPaths:indexPaths]; + adjustedIndexPaths = [adPlacer.streamAdPlacer adjustedIndexPathsForOriginalIndexPaths:indexPaths]; + } + + // We perform the actual UI insertion AFTER updating the stream ad placer's + // data, because the insertion can trigger queries to the data source, which + // needs to reflect the post-insertion state. + [self insertRowsAtIndexPaths:adjustedIndexPaths withRowAnimation:animation]; +} + +- (void)mp_deleteRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation +{ + MPTableViewAdPlacer *adPlacer = [self mp_adPlacer]; + NSArray *adjustedIndexPaths = indexPaths; + + // We need to wrap the delete process in begin/end updates in case any ad + // cells are also deleted. MPStreamAdPlacer's deleteItemsAtIndexPaths: can + // call the delegate's didRemoveAdsAtIndexPaths, which will remove those + // ads from the tableview. + [self mp_beginUpdates]; + if (adPlacer) { + // We need to obtain the adjusted index paths to delete BEFORE we + // update the stream ad placer's data. + adjustedIndexPaths = [adPlacer.streamAdPlacer adjustedIndexPathsForOriginalIndexPaths:indexPaths]; + [adPlacer.streamAdPlacer deleteItemsAtIndexPaths:indexPaths]; + } + + // We perform the actual UI deletion AFTER updating the stream ad placer's + // data, because the deletion can trigger queries to the data source, which + // needs to reflect the post-deletion state. + [self deleteRowsAtIndexPaths:adjustedIndexPaths withRowAnimation:animation]; + [self mp_endUpdates]; +} + +- (void)mp_reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation +{ + MPTableViewAdPlacer *adPlacer = [self mp_adPlacer]; + NSArray *adjustedIndexPaths = indexPaths; + + if (adPlacer) { + adjustedIndexPaths = [adPlacer.streamAdPlacer adjustedIndexPathsForOriginalIndexPaths:indexPaths]; + } + + [self reloadRowsAtIndexPaths:adjustedIndexPaths withRowAnimation:animation]; +} + +- (void)mp_moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath +{ + MPTableViewAdPlacer *adPlacer = [self mp_adPlacer]; + NSIndexPath *adjustedFrom = indexPath; + NSIndexPath *adjustedTo = newIndexPath; + + if (adPlacer) { + // We need to obtain the adjusted index paths to move BEFORE we + // update the stream ad placer's data. + adjustedFrom = [adPlacer.streamAdPlacer adjustedIndexPathForOriginalIndexPath:indexPath]; + adjustedTo = [adPlacer.streamAdPlacer adjustedIndexPathForOriginalIndexPath:newIndexPath]; + + [adPlacer.streamAdPlacer moveItemAtIndexPath:indexPath toIndexPath:newIndexPath]; + } + + // We perform the actual UI operation AFTER updating the stream ad placer's + // data, because the operation can trigger queries to the data source, which + // needs to reflect the post-operation state. + [self moveRowAtIndexPath:adjustedFrom toIndexPath:adjustedTo]; +} + +- (NSIndexPath *)mp_indexPathForSelectedRow +{ + MPTableViewAdPlacer *adPlacer = [self mp_adPlacer]; + NSIndexPath *adjustedIndexPath = [self indexPathForSelectedRow]; + + if (adPlacer) { + adjustedIndexPath = [adPlacer.streamAdPlacer originalIndexPathForAdjustedIndexPath:adjustedIndexPath]; + } + + return adjustedIndexPath; +} + +- (NSArray *)mp_indexPathsForSelectedRows +{ + MPTableViewAdPlacer *adPlacer = [self mp_adPlacer]; + NSArray *adjustedIndexPaths = [self indexPathsForSelectedRows]; + + if (adPlacer) { + adjustedIndexPaths = [adPlacer.streamAdPlacer originalIndexPathsForAdjustedIndexPaths:adjustedIndexPaths]; + } + + return adjustedIndexPaths; +} + +- (void)mp_selectRowAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated scrollPosition:(UITableViewScrollPosition)scrollPosition +{ + MPTableViewAdPlacer *adPlacer = [self mp_adPlacer]; + NSIndexPath *adjustedIndexPath = indexPath; + + if (adPlacer) { + adjustedIndexPath = [adPlacer.streamAdPlacer adjustedIndexPathForOriginalIndexPath:indexPath]; + } + + if (!indexPath || adjustedIndexPath) { + [self selectRowAtIndexPath:adjustedIndexPath animated:animated scrollPosition:scrollPosition]; + } +} + +- (void)mp_deselectRowAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated +{ + MPTableViewAdPlacer *adPlacer = [self mp_adPlacer]; + NSIndexPath *adjustedIndexPath = indexPath; + + if (adPlacer) { + adjustedIndexPath = [adPlacer.streamAdPlacer adjustedIndexPathForOriginalIndexPath:indexPath]; + } + + if (!indexPath || adjustedIndexPath) { + [self deselectRowAtIndexPath:adjustedIndexPath animated:animated]; + } +} + +- (id)mp_dequeueReusableCellWithIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath +{ + MPTableViewAdPlacer *adPlacer = [self mp_adPlacer]; + NSIndexPath *adjustedIndexPath = indexPath; + + if (adPlacer) { + adjustedIndexPath = [adPlacer.streamAdPlacer adjustedIndexPathForOriginalIndexPath:indexPath]; + } + + if (!indexPath || adjustedIndexPath) { +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= MP_IOS_6_0 + if ([self respondsToSelector:@selector(dequeueReusableCellWithIdentifier:forIndexPath:)]) { + return [self dequeueReusableCellWithIdentifier:identifier forIndexPath:adjustedIndexPath]; + } else { + return [self dequeueReusableCellWithIdentifier:identifier]; + } +#endif + } else { + return nil; + } +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBAVPlayer.h b/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBAVPlayer.h new file mode 100644 index 00000000000..df00b29e4ee --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBAVPlayer.h @@ -0,0 +1,37 @@ +// +// MOPUBAVPlayer.h +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import + +@class MOPUBAVPlayer; + +@protocol MOPUBAVPlayerDelegate + +- (void)avPlayer:(MOPUBAVPlayer *)player didError:(NSError *)error withMessage:(NSString *)message; + +- (void)avPlayer:(MOPUBAVPlayer *)player playbackTimeDidProgress:(NSTimeInterval)currentPlaybackTime; + +- (void)avPlayerDidFinishPlayback:(MOPUBAVPlayer *)player; + +- (void)avPlayerDidRecoverFromStall:(MOPUBAVPlayer *)player; + +- (void)avPlayerDidStall:(MOPUBAVPlayer *)player; + +@end + + +@interface MOPUBAVPlayer : AVPlayer + +// Indicates the duration of the player item. +@property (nonatomic, readonly) NSTimeInterval currentItemDuration; + +// Returns the current time of the current player item. +@property (nonatomic, readonly) NSTimeInterval currentPlaybackTime; + +- (id)initWithDelegate:(id)delegate playerItem:(AVPlayerItem *)playerItem; + +- (void)dispose; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBAVPlayer.m b/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBAVPlayer.m new file mode 100644 index 00000000000..47bc00e84b8 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBAVPlayer.m @@ -0,0 +1,256 @@ +// +// MOPUBAVPlayer.m +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import "MPReachability.h" +#import "MOPUBAVPlayer.h" +#import "MPLogging.h" +#import "MPTimer.h" +#import "MPCoreInstanceProvider.h" + +static CGFloat const kAvPlayerTimerInterval = 0.1f; + +static NSString * const MPAVPlayerItemLoadErrorTemplate = @"Loading player item at %@ failed."; + +@interface MOPUBAVPlayer() + +@property (nonatomic, weak, readonly) id delegate; + +@property (nonatomic, copy) NSURL *mediaURL; +@property (nonatomic) MPTimer *playbackTimer; +@property (nonatomic) CMTime lastContinuousPlaybackCMTime; +@property (nonatomic) MPReachability *reachability; +@property (nonatomic) BOOL playbackDidStall; + +@end + +@implementation MOPUBAVPlayer + +- (id)initWithDelegate:(id)delegate playerItem:(AVPlayerItem *)playerItem +{ + if (playerItem && delegate) { + self = [super initWithPlayerItem:playerItem]; + if (self) { + _delegate = delegate; + + // AVPlayer KVO doesn't handle disconnect/reconnect case. + // Reachability is used to detect network drop and reconnect. + _reachability = [MPReachability reachabilityForInternetConnection]; + [_reachability startNotifier]; + [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(checkNetworkStatus:) name:kMPReachabilityChangedNotification object:nil]; + } + return self; + } else { + return nil; + } +} + +- (void)dealloc +{ + [self dispose]; +} + +#pragma mark - controls of AVPlayer + +- (void)play +{ + [super play]; + [self startTimeObserver]; + MPLogDebug(@"start playback"); +} + +- (void)pause +{ + [super pause]; + [self stopTimeObserver]; + MPLogDebug(@"playback paused"); +} + +- (void)setMuted:(BOOL)muted +{ + if ([[self superclass] instancesRespondToSelector:@selector(setMuted:)]) { + [super setMuted:muted]; + } else { + if (muted) { + [self setAudioVolume:0]; + } else { + [self setAudioVolume:1]; + } + } +} + +// iOS 6 doesn't have muted for avPlayerItem. Use volume to control mute/unmute +- (void)setAudioVolume:(float)volume +{ + NSArray *audioTracks = [self.currentItem.asset tracksWithMediaType:AVMediaTypeAudio]; + NSMutableArray *allAudioParams = [NSMutableArray array]; + for (AVAssetTrack *track in audioTracks) { + AVMutableAudioMixInputParameters *audioInputParams = [AVMutableAudioMixInputParameters audioMixInputParameters]; + [audioInputParams setVolume:volume atTime:kCMTimeZero]; + [audioInputParams setTrackID:[track trackID]]; + [allAudioParams addObject:audioInputParams]; + } + AVMutableAudioMix *audioMix = [AVMutableAudioMix audioMix]; + [audioMix setInputParameters:allAudioParams]; + [self.currentItem setAudioMix:audioMix]; +} + +#pragma mark - Timer + +- (void)startTimeObserver +{ + // Use custom timer to check for playback time changes and stall detection, since there are bugs + // in the AVPlayer time observing API that can cause crashes. Also, the AVPlayerItem stall notification + // does not always report accurately. + if (_playbackTimer == nil) { + _playbackTimer = [[MPCoreInstanceProvider sharedProvider] buildMPTimerWithTimeInterval:kAvPlayerTimerInterval target:self selector:@selector(timerTick) repeats:YES]; + // Add timer to main run loop with common modes to allow the timer to tick while user is scrolling. + _playbackTimer.runLoopMode = NSRunLoopCommonModes; + [_playbackTimer scheduleNow]; + _lastContinuousPlaybackCMTime = kCMTimeZero; + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playbackDidFinish) name:AVPlayerItemDidPlayToEndTimeNotification object:self.currentItem]; + } else { + [_playbackTimer resume]; + } +} + +- (void)timerTick +{ + if (!self.currentItem || self.currentItem.error != nil) { + [self stopTimeObserver]; + NSError *error = nil; + NSString *errorMessage = nil; + if (self.currentItem) { + error = self.currentItem.error; + errorMessage = self.currentItem.error.description ?: self.currentItem.errorLog.description; + } else { + errorMessage = [NSString stringWithFormat:MPAVPlayerItemLoadErrorTemplate, self.mediaURL]; + } + + if ([self.delegate respondsToSelector:@selector(avPlayer:didError:withMessage:)]) { + [self.delegate avPlayer:self didError:error withMessage:errorMessage]; + } + MPLogInfo(@"avplayer experienced error: %@", errorMessage); + } else { + CMTime currentCMTime = self.currentTime; + int32_t result = CMTimeCompare(currentCMTime, self.lastContinuousPlaybackCMTime); + // finished or stalled + if (result == 0) { + NSTimeInterval duration = self.currentItemDuration; + NSTimeInterval currentPlaybackTime = self.currentPlaybackTime; + if (!isnan(duration) && !isnan(currentPlaybackTime) && duration > 0 && currentPlaybackTime > 0) { + [self avPlayerDidStall]; + } + } else { + self.lastContinuousPlaybackCMTime = currentCMTime; + if (result > 0) { + NSTimeInterval currentPlaybackTime = self.currentPlaybackTime; + if (!isnan(currentPlaybackTime) && isfinite(currentPlaybackTime)) { + // There are bugs in AVPlayer that causes the currentTime to be negative + if (currentPlaybackTime < 0) { + currentPlaybackTime = 0; + } + [self avPlayer:self playbackTimeDidProgress:currentPlaybackTime]; + } + } + } + + } +} + +- (void)stopTimeObserver +{ + [_playbackTimer pause]; + MPLogDebug(@"AVPlayer timer stopped"); +} + +#pragma mark - disconnect/reconnect handling +- (void)checkNetworkStatus:(NSNotification *)notice +{ + MPNetworkStatus remoteHostStatus = [self.reachability currentReachabilityStatus]; + + if (remoteHostStatus == MPNotReachable) { + if (!self.rate) { + [self pause]; + if ([self.delegate respondsToSelector:@selector(avPlayerDidStall:)]) { + [self.delegate avPlayerDidStall:self]; + } + } + } else { + if (!self.rate) { + [self play]; + } + } +} + +#pragma mark - avPlayer state changes + +- (void)avPlayer:(MOPUBAVPlayer *)player playbackTimeDidProgress:(NSTimeInterval)currentPlaybackTime +{ + if (self.playbackDidStall) { + self.playbackDidStall = NO; + if ([self.delegate respondsToSelector:@selector(avPlayerDidRecoverFromStall:)]) { + [self.delegate avPlayerDidRecoverFromStall:self]; + } + } + + if ([self.delegate respondsToSelector:@selector(avPlayer:playbackTimeDidProgress:)]) { + [self.delegate avPlayer:self playbackTimeDidProgress:currentPlaybackTime]; + } +} + +- (void)avPlayerDidStall +{ + // Only call delegate methods once per stall cycle. + if (!self.playbackDidStall && [self.delegate respondsToSelector:@selector(avPlayerDidStall:)]) { + [self.delegate avPlayerDidStall:self]; + } + self.playbackDidStall = YES; +} + +- (void)playbackDidFinish +{ + // Make sure we stop time observing once we know we've done playing. + [self stopTimeObserver]; + if ([self.delegate respondsToSelector:@selector(avPlayerDidFinishPlayback:)]) { + [self.delegate avPlayerDidFinishPlayback:self]; + } + MPLogDebug(@"playback finished"); +} + +- (void)dispose +{ + [[NSNotificationCenter defaultCenter] removeObserver:self]; + + [self stopTimeObserver]; + [self.reachability stopNotifier]; + if (_playbackTimer) { + [_playbackTimer invalidate]; + _playbackTimer = nil; + } + + // Cancel preroll after time observer is removed, + // otherwise an NSInternalInconsistencyException may be thrown and crash on + // [AVCMNotificationDispatcher _copyAndRemoveListenerAndCallbackForWeakReferenceToListener:callback:name:object:], + // depends on timing. + [self cancelPendingPrerolls]; +} + + +#pragma mark - getter + +- (NSTimeInterval)currentItemDuration +{ + NSTimeInterval duration = CMTimeGetSeconds(self.currentItem.duration); + return (isfinite(duration)) ? duration : NAN; +} + +- (NSTimeInterval)currentPlaybackTime +{ + NSTimeInterval currentTime = CMTimeGetSeconds(self.currentTime); + return (isfinite(currentTime)) ? currentTime : NAN; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBAVPlayerView.h b/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBAVPlayerView.h new file mode 100644 index 00000000000..d87babb3ba3 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBAVPlayerView.h @@ -0,0 +1,15 @@ +// +// MPAVPlayerView.h +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import + +@class AVPlayer; + +@interface MOPUBAVPlayerView : UIView + +@property (nonatomic) AVPlayer *player; +@property (nonatomic) NSString *videoGravity; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBAVPlayerView.m b/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBAVPlayerView.m new file mode 100644 index 00000000000..2aa49937843 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBAVPlayerView.m @@ -0,0 +1,40 @@ +// +// MPAVPlayerView.m +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import +#import "MOPUBAVPlayerView.h" + +@implementation MOPUBAVPlayerView + ++ (Class)layerClass +{ + return [AVPlayerLayer class]; +} + +- (AVPlayer *)player +{ + AVPlayerLayer *playerLayer = (AVPlayerLayer *)self.layer; + return playerLayer.player; +} + +- (void)setPlayer:(AVPlayer *)player +{ + AVPlayerLayer *playerLayer = (AVPlayerLayer *)self.layer; + playerLayer.player = player; +} + +- (NSString *)videoGravity +{ + AVPlayerLayer *playerLayer = (AVPlayerLayer *)self.layer; + return playerLayer.videoGravity; +} + +- (void)setVideoGravity:(NSString *)videoGravity +{ + AVPlayerLayer *playerLayer = (AVPlayerLayer *)self.layer; + playerLayer.videoGravity = videoGravity; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBActivityIndicatorView.h b/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBActivityIndicatorView.h new file mode 100644 index 00000000000..5a6a5bda252 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBActivityIndicatorView.h @@ -0,0 +1,15 @@ +// +// MOPUBActivityIndicatorView.h +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import + +@interface MOPUBActivityIndicatorView : UIView + +- (void)startAnimating; +- (void)stopAnimating; +- (BOOL)isAnimating; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBActivityIndicatorView.m b/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBActivityIndicatorView.m new file mode 100644 index 00000000000..c6540e6fc4e --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBActivityIndicatorView.m @@ -0,0 +1,57 @@ +// +// MOPUBActivityIndicatorView.m +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import "MOPUBActivityIndicatorView.h" +#import "MPGlobal.h" +#import "UIColor+MPAdditions.h" + +static NSString * const kSpinnerBgColor = @"#000000"; +static CGFloat const kSpinnerAlpha = 0.5f; +static CGFloat const kSpinnerCornerRadius = 4.0f; + +@interface MOPUBActivityIndicatorView() + +@property (nonatomic) UIActivityIndicatorView *activityIndicator; +@property (nonatomic) UIView *bgView; + +@end + +@implementation MOPUBActivityIndicatorView + +- (instancetype)initWithFrame:(CGRect)frame +{ + if (self = [super initWithFrame:frame]) { + _bgView = [[UIView alloc] initWithFrame:frame]; + _bgView.backgroundColor = [UIColor mp_colorFromHexString:kSpinnerBgColor alpha:kSpinnerAlpha]; + _bgView.layer.cornerRadius = kSpinnerCornerRadius; + [self addSubview:_bgView]; + + _activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite]; + _activityIndicator.center = self.center; + _activityIndicator.frame = CGRectIntegral(_activityIndicator.frame); + [self addSubview:_activityIndicator]; + } + return self; +} + +- (void)startAnimating +{ + self.bgView.hidden = NO; + [self.activityIndicator startAnimating]; +} + +- (void)stopAnimating +{ + self.bgView.hidden = YES; + [self.activityIndicator stopAnimating]; +} + +- (BOOL)isAnimating +{ + return self.activityIndicator.isAnimating; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBFullscreenPlayerViewController.h b/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBFullscreenPlayerViewController.h new file mode 100644 index 00000000000..aa9ddfc2c86 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBFullscreenPlayerViewController.h @@ -0,0 +1,30 @@ +// +// MOPUBFullscreenPlayerViewController.h +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import + +@class MOPUBPlayerViewController; +@class MOPUBPlayerView; +@class MOPUBFullscreenPlayerViewController; + +@protocol MOPUBFullscreenPlayerViewControllerDelegate + +- (void)playerDidProgressToTime:(NSTimeInterval)playbackTime; +- (void)ctaTapped:(MOPUBFullscreenPlayerViewController *)viewController; +- (void)fullscreenPlayerWillLeaveApplication:(MOPUBFullscreenPlayerViewController *)viewController; + +@end + +typedef void (^MOPUBFullScreenPlayerViewControllerDismissBlock)(UIView *originalParentView); + +@interface MOPUBFullscreenPlayerViewController : UIViewController + +@property (nonatomic) MOPUBPlayerView *playerView; + +@property (nonatomic, weak) id delegate; + +- (instancetype)initWithVideoPlayer:(MOPUBPlayerViewController *)playerController dismissBlock:(MOPUBFullScreenPlayerViewControllerDismissBlock)dismiss; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBFullscreenPlayerViewController.m b/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBFullscreenPlayerViewController.m new file mode 100644 index 00000000000..500c111f25c --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBFullscreenPlayerViewController.m @@ -0,0 +1,363 @@ +// +// MOPUBFullscreenPlayerViewController.m +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import +#import "MOPUBFullscreenPlayerViewController.h" +#import "MOPUBPlayerView.h" +#import "MOPUBPlayerViewController.h" +#import "MPAdDestinationDisplayAgent.h" +#import "MPCoreInstanceProvider.h" +#import "MPGlobal.h" +#import "MPNativeAdConstants.h" +#import "MOPUBActivityIndicatorView.h" +#import "UIView+MPAdditions.h" +#import "UIButton+MPAdditions.h" +#import "UIColor+MPAdditions.h" + +static CGFloat const kDaaIconFullscreenLeftMargin = 16.0f; +static CGFloat const kDaaIconFullscreenTopMargin = 16.0f; +static CGFloat const kDaaIconSize = 16.0f; +static CGFloat const kCloseButtonRightMargin = 16.0f; +static CGFloat const kDefaultButtonTouchAreaInsets = 10.0f; + +static NSString * const kCloseButtonImage = @"MPCloseBtn.png"; +static NSString * const kCtaButtonTitleText = @"Learn More"; +static CGFloat const kCtaButtonTopMarginPortrait = 15.0f; +static CGFloat const kCtaButtonTrailingMarginLandscape = 15.0f; +static CGFloat const kCtaButtonBottomMarginLandscape = 15.0f; +static CGFloat const kCtaButtonBottomCornerRadius = 4.0f; +static CGFloat const kCtaButtonBottomBorderWidth = 0.5f; +static CGFloat const kCtaButtonBottomFontSize = 18.0f; +static CGFloat const kCtaButtonContentInsetsHorizontal = 35.0f; +static CGFloat const kCtaButtonContentInsetsVertical = 10.0f; +static CGFloat const kCtaButtonBackgroundAlpha = 0.2f; +static NSString * const kCtaButtonBackgroundColor = @"#000000"; + +static NSString * const kTopGradientColor = @"#000000"; +static NSString * const kBottomGradientColor= @"#000000"; +static CGFloat const kTopGradientAlpha = 0.4f; +static CGFloat const kBottomGradientAlpha = 0.0f; +static CGFloat const kGradientHeight = 42; + +static CGFloat const kStallSpinnerSize = 35.0f; + +@interface MOPUBFullscreenPlayerViewController () + +// UI components +@property (nonatomic) UIButton *daaButton; +@property (nonatomic) UIButton *closeButton; +@property (nonatomic) UIButton *ctaButton; +@property (nonatomic) MOPUBActivityIndicatorView *stallSpinner; +@property (nonatomic) UIActivityIndicatorView *playerNotReadySpinner; +@property (nonatomic) UIView *gradientView; +@property (nonatomic) CAGradientLayer *gradient; + +@property (nonatomic) MOPUBPlayerViewController *playerController; +@property (nonatomic) UIView *originalParentView; +@property (nonatomic) MPAdDestinationDisplayAgent *displayAgent; +@property (nonatomic, copy) MOPUBFullScreenPlayerViewControllerDismissBlock dismissBlock; + +@end + +@implementation MOPUBFullscreenPlayerViewController + +- (instancetype)initWithVideoPlayer:(MOPUBPlayerViewController *)playerController dismissBlock:(MOPUBFullScreenPlayerViewControllerDismissBlock)dismissBlock +{ + if (self = [super init]) { + _playerController = playerController; + _originalParentView = self.playerController.playerView.superview; + _playerView = self.playerController.playerView; + _playerController.delegate = self; + _dismissBlock = [dismissBlock copy]; + _displayAgent = [[MPCoreInstanceProvider sharedProvider] buildMPAdDestinationDisplayAgentWithDelegate:self]; + } + return self; +} + +- (void)viewDidLoad +{ + [super viewDidLoad]; + + [self setApplicationStatusBarHidden:YES]; + [self.playerController willEnterFullscreen]; + + self.view.backgroundColor = [UIColor blackColor]; + [self.view addSubview:self.playerView]; + + [self createAndAddGradientView]; + + self.daaButton = [UIButton buttonWithType:UIButtonTypeCustom]; + self.daaButton.frame = CGRectMake(0, 0, kDaaIconSize, kDaaIconSize); + [self.daaButton setImage:[UIImage imageNamed:MPResourcePathForResource(kDAAIconImageName)] forState:UIControlStateNormal]; + [self.daaButton addTarget:self action:@selector(daaButtonTapped) forControlEvents:UIControlEventTouchUpInside]; + self.daaButton.mp_TouchAreaInsets = UIEdgeInsetsMake(kDefaultButtonTouchAreaInsets, kDefaultButtonTouchAreaInsets, kDefaultButtonTouchAreaInsets, kDefaultButtonTouchAreaInsets); + [self.view addSubview:self.daaButton]; + + self.closeButton = [UIButton buttonWithType:UIButtonTypeCustom]; + [self.closeButton setImage:[UIImage imageNamed:MPResourcePathForResource(kCloseButtonImage)] forState:UIControlStateNormal]; + [self.closeButton addTarget:self action:@selector(closeButtonTapped) forControlEvents:UIControlEventTouchUpInside]; + self.closeButton.mp_TouchAreaInsets = UIEdgeInsetsMake(kDefaultButtonTouchAreaInsets, kDefaultButtonTouchAreaInsets, kDefaultButtonTouchAreaInsets, kDefaultButtonTouchAreaInsets); + [self.closeButton sizeToFit]; + [self.view addSubview:self.closeButton]; + + self.ctaButton = [UIButton buttonWithType:UIButtonTypeCustom]; + [self.ctaButton setTitle:kCtaButtonTitleText forState:UIControlStateNormal]; + [self.ctaButton setBackgroundColor:[UIColor mp_colorFromHexString:kCtaButtonBackgroundColor alpha:kCtaButtonBackgroundAlpha]]; + [self.ctaButton addTarget:self action:@selector(ctaButtonTapped) forControlEvents:UIControlEventTouchUpInside]; + self.ctaButton.layer.cornerRadius = kCtaButtonBottomCornerRadius; + self.ctaButton.titleLabel.font = [UIFont fontWithName:@"HelveticaNeue" size:kCtaButtonBottomFontSize]; + [self.ctaButton setContentEdgeInsets:UIEdgeInsetsMake(kCtaButtonContentInsetsVertical, kCtaButtonContentInsetsHorizontal, kCtaButtonContentInsetsVertical, kCtaButtonContentInsetsHorizontal)]; + [[self.ctaButton layer] setBorderWidth:kCtaButtonBottomBorderWidth]; + [[self.ctaButton layer] setBorderColor:[UIColor whiteColor].CGColor]; + [self.ctaButton sizeToFit]; + [self.view addSubview:self.ctaButton]; + + if (!self.playerController.isReadyToPlay) { + [self createPlayerNotReadySpinner]; + } + + // Once the video enters fullscreen mode, we should resume the playback if it is paused. + if (self.playerController.paused) { + [self.playerController resume]; + } +} + +- (void)createAndAddGradientView +{ + // Create the gradient + self.gradientView = [UIView new]; + self.gradientView.userInteractionEnabled = NO; + UIColor *topColor = [UIColor mp_colorFromHexString:kTopGradientColor alpha:kTopGradientAlpha]; + UIColor *bottomColor= [UIColor mp_colorFromHexString:kBottomGradientColor alpha:kBottomGradientAlpha]; + self.gradient = [CAGradientLayer layer]; + self.gradient.colors = [NSArray arrayWithObjects: (id)topColor.CGColor, (id)bottomColor.CGColor, nil]; + CGSize screenSize = MPScreenBounds().size; + self.gradient.frame = CGRectMake(0, 0, screenSize.width, kGradientHeight); + + //Add gradient to view + [self.gradientView.layer insertSublayer:self.gradient atIndex:0]; + [self.view addSubview:self.gradientView]; +} + +- (void)showStallSpinner +{ + if (!self.stallSpinner) { + self.stallSpinner = [[MOPUBActivityIndicatorView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, kStallSpinnerSize, kStallSpinnerSize)]; + [self.view addSubview:self.stallSpinner]; + } +} + +- (void)hideStallSpinner +{ + if (self.stallSpinner) { + [self.stallSpinner stopAnimating]; + [self.stallSpinner removeFromSuperview]; + } +} + +- (void)createPlayerNotReadySpinner +{ + if (!self.playerNotReadySpinner) { + self.playerNotReadySpinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite]; + [self.view addSubview:self.playerNotReadySpinner]; + [self.playerNotReadySpinner startAnimating]; + } +} + +- (void)removePlayerNotReadySpinner +{ + [self.playerNotReadySpinner stopAnimating]; + [self.playerNotReadySpinner removeFromSuperview]; + self.playerNotReadySpinner = nil; +} + +#pragma mark - Layout UI components + +- (void)viewWillLayoutSubviews +{ + [super viewWillLayoutSubviews]; + + [self layoutPlayerView]; + [self layoutDaaButton]; + [self layoutCloseButton]; + [self layoutCtaButton]; + [self layoutStallSpinner]; + [self layoutPlayerNotReadySpinner]; + [self layoutGradientView]; +} + +- (void)layoutPlayerView +{ + CGSize screenSize = MPScreenBounds().size; + self.playerView.videoGravity = AVLayerVideoGravityResizeAspectFill; + if (UIInterfaceOrientationIsLandscape(MPInterfaceOrientation())) { + self.playerView.frame = CGRectMake(0, 0, screenSize.width, screenSize.height); + } else { + self.playerView.mp_width = screenSize.width; + self.playerView.mp_height = self.playerView.mp_width/self.playerController.videoAspectRatio; + self.playerView.center = self.view.center; + self.playerView.frame = CGRectIntegral(self.playerView.frame); + } +} + +- (void)layoutDaaButton +{ + self.daaButton.mp_x = kDaaIconFullscreenLeftMargin; + self.daaButton.mp_y = kDaaIconFullscreenTopMargin; +} + +- (void)layoutCloseButton +{ + CGSize screenSize = MPScreenBounds().size; + self.closeButton.mp_x = screenSize.width - kCloseButtonRightMargin - self.closeButton.mp_width; + CGFloat daaCenterY = self.daaButton.frame.origin.y + self.daaButton.mp_height/2.0f; + self.closeButton.mp_y = daaCenterY - self.closeButton.mp_height/2.0f; + self.closeButton.frame = CGRectIntegral(self.closeButton.frame); +} + +- (void)layoutCtaButton +{ + UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation; + if (UIInterfaceOrientationIsLandscape(orientation)) { + self.ctaButton.mp_x = CGRectGetMaxX(self.playerView.frame) - kCtaButtonTrailingMarginLandscape - CGRectGetWidth(self.ctaButton.bounds); + self.ctaButton.mp_y = CGRectGetMaxY(self.playerView.frame) - kCtaButtonBottomMarginLandscape - CGRectGetHeight(self.ctaButton.bounds); + } else { + self.ctaButton.center = self.view.center; + self.ctaButton.mp_y = CGRectGetMaxY(self.playerView.frame) + kCtaButtonTopMarginPortrait; + self.ctaButton.frame = CGRectIntegral(self.ctaButton.frame); + } +} + +- (void)layoutStallSpinner +{ + if (self.stallSpinner) { + CGSize screenSize = MPScreenBounds().size; + self.stallSpinner.center = CGPointMake(screenSize.width/2.0f, screenSize.height/2.0f); + self.stallSpinner.frame = CGRectIntegral(self.stallSpinner.frame); + } +} + +- (void)layoutPlayerNotReadySpinner +{ + if (self.playerNotReadySpinner) { + CGSize screenSize = MPScreenBounds().size; + self.playerNotReadySpinner.center = CGPointMake(screenSize.width/2.0f, screenSize.height/2.0f); + self.playerNotReadySpinner.frame = CGRectIntegral(self.playerNotReadySpinner.frame); + } +} + +- (void)layoutGradientView +{ + if (UIInterfaceOrientationIsLandscape(MPInterfaceOrientation())) { + self.gradientView.hidden = NO; + } else { + self.gradientView.hidden = YES; + } + CGSize screenSize = MPScreenBounds().size; + self.gradient.frame = CGRectMake(0, 0, screenSize.width, kGradientHeight); + self.gradientView.frame = CGRectMake(0, 0, screenSize.width, kGradientHeight); +} + +#pragma mark - button tap + +- (void)closeButtonTapped +{ + [self dismissViewControllerAnimated:NO completion:^{ + if (self.dismissBlock) { + self.dismissBlock(self.originalParentView); + } + }]; +} + +- (void)ctaButtonTapped +{ + if ([self.delegate respondsToSelector:@selector(ctaTapped:)]) { + [self.delegate ctaTapped:self]; + } + [self.displayAgent displayDestinationForURL:self.playerController.defaultActionURL]; +} + +- (void)daaButtonTapped +{ + [self.displayAgent displayDestinationForURL:[NSURL URLWithString:kDAAIconTapDestinationURL]]; +} + +#pragma mark - MOPUBPlayerViewControllerDelegate + +- (void)playerPlaybackDidStart:(MOPUBPlayerViewController *)player +{ + [self removePlayerNotReadySpinner]; +} + +- (void)playerViewController:(MOPUBPlayerViewController *)playerViewController willShowReplayView:(MOPUBPlayerView *)view +{ + [self.view bringSubviewToFront:self.daaButton]; +} + +- (void)playerViewController:(MOPUBPlayerViewController *)playerViewController didStall:(MOPUBAVPlayer *)player +{ + if (self.stallSpinner) { + if (!self.stallSpinner.superview) { + [self.view addSubview:self.stallSpinner]; + } + if (!self.stallSpinner.isAnimating) { + [self.stallSpinner startAnimating]; + } + } else { + [self showStallSpinner]; + [self.stallSpinner startAnimating]; + } +} + +- (void)playerViewController:(MOPUBPlayerViewController *)playerViewController didRecoverFromStall:(MOPUBAVPlayer *)player +{ + [self hideStallSpinner]; +} + +- (void)playerDidProgressToTime:(NSTimeInterval)playbackTime +{ + if ([self.delegate respondsToSelector:@selector(playerDidProgressToTime:)]) { + [self.delegate playerDidProgressToTime:playbackTime]; + } +} + +#pragma mark - + +- (UIViewController *)viewControllerForPresentingModalView +{ + return self; +} + +- (void)displayAgentWillPresentModal +{ + [self.playerController pause]; +} + +- (void)displayAgentWillLeaveApplication +{ + [self.playerController pause]; + [self.delegate fullscreenPlayerWillLeaveApplication:self]; +} + +- (void)displayAgentDidDismissModal +{ + [self.playerController resume]; +} + +#pragma mark - Hidding status bar (pre-iOS 7) + +- (void)setApplicationStatusBarHidden:(BOOL)hidden +{ + [[UIApplication sharedApplication] mp_preIOS7setApplicationStatusBarHidden:hidden]; +} + +#pragma mark - Hidding status bar (iOS 7 and above) + +- (BOOL)prefersStatusBarHidden +{ + return YES; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBNativeVideoAdAdapter.h b/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBNativeVideoAdAdapter.h new file mode 100644 index 00000000000..a4b62d8875e --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBNativeVideoAdAdapter.h @@ -0,0 +1,23 @@ +// +// MOPUBNativeVideoAdAdapter.h +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import "MPNativeAdAdapter.h" + +@class MPAdConfiguration; + +@interface MOPUBNativeVideoAdAdapter : NSObject + +@property (nonatomic, weak) id delegate; +@property (nonatomic, readonly) NSArray *impressionTrackerURLs; +@property (nonatomic, readonly) NSArray *clickTrackerURLs; +@property (nonatomic) MPAdConfiguration *adConfiguration; + +- (instancetype)initWithAdProperties:(NSMutableDictionary *)properties; + +- (void)handleVideoViewImpression; +- (void)handleVideoViewClick; +- (void)handleVideoHasProgressedToTime:(NSTimeInterval)playbackTime; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBNativeVideoAdAdapter.m b/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBNativeVideoAdAdapter.m new file mode 100644 index 00000000000..65f38f0fa96 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBNativeVideoAdAdapter.m @@ -0,0 +1,188 @@ +// +// MOPUBNativeVideoAdAdapter.m +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import "MOPUBNativeVideoAdAdapter.h" +#import "MPNativeAdError.h" +#import "MPAdDestinationDisplayAgent.h" +#import "MPCoreInstanceProvider.h" +#import "MPNativeAdConstants.h" +#import "MPLogging.h" +#import "MPStaticNativeAdImpressionTimer.h" +#import "MOPUBNativeVideoAdConfigValues.h" + +@interface MOPUBNativeVideoAdAdapter() + +@property (nonatomic) MPStaticNativeAdImpressionTimer *staticImpressionTimer; +@property (nonatomic, readonly) MPAdDestinationDisplayAgent *destinationDisplayAgent; + +@end + +@implementation MOPUBNativeVideoAdAdapter + +@synthesize properties = _properties; +@synthesize defaultActionURL = _defaultActionURL; + +- (instancetype)initWithAdProperties:(NSMutableDictionary *)properties +{ + if (self = [super init]) { + + // Let's make sure the data types of all the provided native ad properties are strings before creating the adapter. + + NSArray *keysToCheck = @[kAdIconImageKey, kAdMainImageKey, kAdTextKey, kAdTitleKey, kAdCTATextKey, kVASTVideoKey]; + + for (NSString *key in keysToCheck) { + id value = properties[key]; + if (value != nil && ![value isKindOfClass:[NSString class]]) { + return nil; + } + } + + BOOL valid = YES; + NSArray *impressionTrackers = [properties objectForKey:kImpressionTrackerURLsKey]; + if (![impressionTrackers isKindOfClass:[NSArray class]] || [impressionTrackers count] < 1) { + valid = NO; + } else { + _impressionTrackerURLs = MPConvertStringArrayToURLArray(impressionTrackers); + } + + NSObject *clickTracker = [properties objectForKey:kClickTrackerURLKey]; + + // The click tracker could either be a single URL or an array of URLS. + if ([clickTracker isKindOfClass:[NSArray class]]) { + _clickTrackerURLs = MPConvertStringArrayToURLArray((NSArray *)clickTracker); + } else if ([clickTracker isKindOfClass:[NSString class]]) { + NSURL *url = [NSURL URLWithString:(NSString *)clickTracker]; + if (url) { + _clickTrackerURLs = @[ url ]; + } else { + valid = NO; + } + } else { + valid = NO; + } + + _defaultActionURL = [NSURL URLWithString:[properties objectForKey:kDefaultActionURLKey]]; + + [properties removeObjectsForKeys:[NSArray arrayWithObjects:kImpressionTrackerURLsKey, kClickTrackerURLKey, kDefaultActionURLKey, nil]]; + _properties = properties; + + if (!valid) { + return nil; + } + + // Add the DAA icon settings to our properties dictionary. + [properties setObject:MPResourcePathForResource(kDAAIconImageName) forKey:kAdDAAIconImageKey]; + + _destinationDisplayAgent = [[MPCoreInstanceProvider sharedProvider] buildMPAdDestinationDisplayAgentWithDelegate:self]; + + _staticImpressionTimer = nil; + } + + return self; +} + +- (void)dealloc +{ + [self removeStaticImpressionTimer]; + [_destinationDisplayAgent cancel]; + [_destinationDisplayAgent setDelegate:nil]; + _delegate = nil; +} + +#pragma mark - Private + +- (void)removeStaticImpressionTimer +{ + _staticImpressionTimer.delegate = nil; + _staticImpressionTimer = nil; +} + +#pragma mark - + +- (void)willAttachToView:(UIView *)view +{ + [self removeStaticImpressionTimer]; + + // Set up a static impression timer that will fire the mopub impression if the video fails to play prior to meeting the video impression tracking requirements. + MOPUBNativeVideoAdConfigValues *nativeVideoAdConfig = [self.properties objectForKey:kNativeVideoAdConfigKey]; + + // impressionMinVisiblePercent is an integer (a value of 50 means 50%) while the impression timer takes in a float (.50 means 50%) so we have to multiply it by .01f. + self.staticImpressionTimer = [[MPStaticNativeAdImpressionTimer alloc] initWithRequiredSecondsForImpression:nativeVideoAdConfig.impressionVisible requiredViewVisibilityPercentage:nativeVideoAdConfig.impressionMinVisiblePercent*0.01f]; + self.staticImpressionTimer.delegate = self; + + [self.staticImpressionTimer startTrackingView:view]; +} + +- (void)displayContentForURL:(NSURL *)URL rootViewController:(UIViewController *)controller +{ + if (!controller) { + return; + } + + if (!URL || ![URL isKindOfClass:[NSURL class]] || ![URL.absoluteString length]) { + return; + } + + [self.destinationDisplayAgent displayDestinationForURL:URL]; +} + +#pragma mark - DAA Icon + +- (void)displayContentForDAAIconTap +{ + [self.destinationDisplayAgent displayDestinationForURL:[NSURL URLWithString:kDAAIconTapDestinationURL]]; +} + +#pragma mark - Impression and click tracking. Renderer calls those two methods + +- (void)handleVideoViewImpression +{ + [self.delegate nativeAdWillLogImpression:self]; +} + +- (void)handleVideoViewClick +{ + [self.delegate nativeAdDidClick:self]; +} + +- (void)handleVideoHasProgressedToTime:(NSTimeInterval)playbackTime +{ + // If the video makes progress, don't allow static impression tracking. + self.staticImpressionTimer.delegate = nil; + self.staticImpressionTimer = nil; +} + +#pragma mark - + +- (void)trackImpression +{ + // We'll fire a static impression if the video hasn't started playing by the time the static impression timer has met its requirements. + [self.delegate nativeAdWillLogImpression:self]; +} + +#pragma mark - + +- (UIViewController *)viewControllerForPresentingModalView +{ + return [self.delegate viewControllerForPresentingModalView]; +} + +- (void)displayAgentWillPresentModal +{ + [self.delegate nativeAdWillPresentModalForAdapter:self]; +} + +- (void)displayAgentWillLeaveApplication +{ + [self.delegate nativeAdWillLeaveApplicationFromAdapter:self]; +} + +- (void)displayAgentDidDismissModal +{ + [self.delegate nativeAdDidDismissModalForAdapter:self]; +} + +// -adConfiguration delegate method is automatically implemented via the adConfiguration property declaration. +@end diff --git a/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBNativeVideoAdConfigValues.h b/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBNativeVideoAdConfigValues.h new file mode 100644 index 00000000000..b50d6427a20 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBNativeVideoAdConfigValues.h @@ -0,0 +1,26 @@ +// +// MOPUBNativeVideoAdConfigValues.h +// MoPubSDK +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import + +@interface MOPUBNativeVideoAdConfigValues : NSObject + +@property (nonatomic, readonly) NSInteger playVisiblePercent; +@property (nonatomic, readonly) NSInteger pauseVisiblePercent; +@property (nonatomic, readonly) NSInteger impressionMinVisiblePercent; +@property (nonatomic, readonly) NSTimeInterval impressionVisible; +@property (nonatomic, readonly) NSTimeInterval maxBufferingTime; +@property (nonatomic, readonly) NSDictionary *trackers; + +- (instancetype)initWithPlayVisiblePercent:(NSInteger)playVisiblePercent + pauseVisiblePercent:(NSInteger)pauseVisiblePercent + impressionMinVisiblePercent:(NSInteger)impressionMinVisiblePercent + impressionVisible:(NSTimeInterval)impressionVisible + maxBufferingTime:(NSTimeInterval)maxBufferingTime trackers:(NSDictionary *)trackers; +- (BOOL)isValid; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBNativeVideoAdConfigValues.m b/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBNativeVideoAdConfigValues.m new file mode 100644 index 00000000000..8b471f2510c --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBNativeVideoAdConfigValues.m @@ -0,0 +1,49 @@ +// +// MOPUBNativeVideoAdConfigValues.m +// MoPubSDK +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import "MOPUBNativeVideoAdConfigValues.h" + +@implementation MOPUBNativeVideoAdConfigValues + +- (instancetype)initWithPlayVisiblePercent:(NSInteger)playVisiblePercent + pauseVisiblePercent:(NSInteger)pauseVisiblePercent + impressionMinVisiblePercent:(NSInteger)impressionMinVisiblePercent + impressionVisible:(NSTimeInterval)impressionVisible + maxBufferingTime:(NSTimeInterval)maxBufferingTime trackers:(NSDictionary *)trackers +{ + self = [super init]; + if (self) { + _playVisiblePercent = playVisiblePercent; + _pauseVisiblePercent = pauseVisiblePercent; + _impressionMinVisiblePercent = impressionMinVisiblePercent; + _impressionVisible = impressionVisible; + _maxBufferingTime = maxBufferingTime; + _trackers = trackers; + } + return self; +} + +- (BOOL)isValid +{ + return ([self isValidPercentage:self.playVisiblePercent] && + [self isValidPercentage:self.pauseVisiblePercent] && + [self isValidPercentage:self.impressionMinVisiblePercent] && + [self isValidTimeInterval:self.impressionVisible] && + [self isValidTimeInterval:self.maxBufferingTime]); +} + +- (BOOL)isValidPercentage:(NSInteger)percentage +{ + return (percentage >= 0 && percentage <= 100); +} + +- (BOOL)isValidTimeInterval:(NSTimeInterval)timeInterval +{ + return timeInterval > 0; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBNativeVideoCustomEvent.h b/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBNativeVideoCustomEvent.h new file mode 100644 index 00000000000..e24df7e59a4 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBNativeVideoCustomEvent.h @@ -0,0 +1,12 @@ +// +// MOPUBNativeVideoCustomEvent.h +// MoPubSDK +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import "MPNativeCustomEvent.h" + +@interface MOPUBNativeVideoCustomEvent : MPNativeCustomEvent + +@end diff --git a/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBNativeVideoCustomEvent.m b/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBNativeVideoCustomEvent.m new file mode 100644 index 00000000000..3d30aa8643a --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBNativeVideoCustomEvent.m @@ -0,0 +1,76 @@ +// +// MOPUBNativeVideoCustomEvent.m +// MoPubSDK +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import "MPNativeAd.h" +#import "MPNativeAdError.h" +#import "MPNativeAdUtils.h" +#import "MPNativeAdConstants.h" +#import "MOPUBNativeVideoAdAdapter.h" +#import "MOPUBNativeVideoAdConfigValues.h" +#import "MOPUBNativeVideoCustomEvent.h" +#import "MPLogging.h" +#import "MPVideoConfig.h" +#import "MPVASTManager.h" +#import "MPNativeAd+Internal.h" + +@implementation MOPUBNativeVideoCustomEvent + +- (void)handleSuccessfulVastParsing:(MPVASTResponse *)mpVastResponse info:(NSDictionary *)info +{ + NSMutableDictionary *infoMutableCopy = [info mutableCopy]; + [infoMutableCopy setObject:[[MPVideoConfig alloc] initWithVASTResponse:mpVastResponse additionalTrackers:((MOPUBNativeVideoAdConfigValues *)info[kNativeVideoAdConfigKey]).trackers] forKey:kVideoConfigKey]; + MOPUBNativeVideoAdAdapter *adAdapter = [[MOPUBNativeVideoAdAdapter alloc] initWithAdProperties:infoMutableCopy]; + if (adAdapter.properties) { + MPNativeAd *interfaceAd = [[MPNativeAd alloc] initWithAdAdapter:adAdapter]; + [interfaceAd.impressionTrackerURLs addObjectsFromArray:adAdapter.impressionTrackerURLs]; + [interfaceAd.clickTrackerURLs addObjectsFromArray:adAdapter.clickTrackerURLs]; + // Get the image urls so we can download them prior to returning the ad. + NSMutableArray *imageURLs = [NSMutableArray array]; + for (NSString *key in [info allKeys]) { + if ([[key lowercaseString] hasSuffix:@"image"] && [[info objectForKey:key] isKindOfClass:[NSString class]]) { + if (![MPNativeAdUtils addURLString:[info objectForKey:key] toURLArray:imageURLs]) { + [self.delegate nativeCustomEvent:self didFailToLoadAdWithError:MPNativeAdNSErrorForInvalidImageURL()]; + } + } + } + + [super precacheImagesWithURLs:imageURLs completionBlock:^(NSArray *errors) { + if (errors) { + MPLogDebug(@"%@", errors); + [self.delegate nativeCustomEvent:self didFailToLoadAdWithError:MPNativeAdNSErrorForImageDownloadFailure()]; + } else { + [self.delegate nativeCustomEvent:self didLoadAd:interfaceAd]; + } + }]; + } else { + [self.delegate nativeCustomEvent:self didFailToLoadAdWithError:MPNativeAdNSErrorForInvalidAdServerResponse(nil)]; + } +} + +- (void)requestAdWithCustomEventInfo:(NSDictionary *)info +{ + MOPUBNativeVideoAdConfigValues *nativeVideoAdConfigValues = [info objectForKey:kNativeVideoAdConfigKey]; + if (nativeVideoAdConfigValues && [nativeVideoAdConfigValues isValid]) { + NSString *vastString = [info objectForKey:kVASTVideoKey]; + if (vastString) { + [MPVASTManager fetchVASTWithData:[vastString dataUsingEncoding:NSUTF8StringEncoding] + completion: ^(MPVASTResponse *mpVastResponse, NSError *error) { + if (error) { + [self.delegate nativeCustomEvent:self didFailToLoadAdWithError:MPNativeAdNSErrorForVASTParsingFailure()]; + } else { + [self handleSuccessfulVastParsing:mpVastResponse info:info]; + } + }]; + } else { + [self.delegate nativeCustomEvent:self didFailToLoadAdWithError:MPNativeAdNSErrorForVASTParsingFailure()]; + } + } else { + [self.delegate nativeCustomEvent:self didFailToLoadAdWithError:MPNativeAdNSErrorForVideoConfigInvalid()]; + } +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBNativeVideoImpressionAgent.h b/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBNativeVideoImpressionAgent.h new file mode 100644 index 00000000000..c8c9d595893 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBNativeVideoImpressionAgent.h @@ -0,0 +1,16 @@ +// +// MOPUBNativeVideoImpressionAgent.h +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import +#import + +@interface MOPUBNativeVideoImpressionAgent : NSObject + +- (instancetype)initWithVideoView:(UIView *)videoView requiredVisibilityPercentage:(CGFloat)visiblePercentage requiredPlaybackDuration:(NSTimeInterval)playbackDuration; + +- (BOOL)shouldTrackImpressionWithCurrentPlaybackTime:(NSTimeInterval)currentPlaybackTime; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBNativeVideoImpressionAgent.m b/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBNativeVideoImpressionAgent.m new file mode 100644 index 00000000000..67a0f1f73d3 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBNativeVideoImpressionAgent.m @@ -0,0 +1,62 @@ +// +// MOPUBNativeVideoImpressionAgent.m +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import "MOPUBNativeVideoImpressionAgent.h" +#import "MPGlobal.h" + +static const NSTimeInterval kInvalidTimestamp = -1; + +@interface MOPUBNativeVideoImpressionAgent () + +@property (nonatomic) CGFloat requiredVisiblePercentage; +@property (nonatomic) NSTimeInterval requiredPlaybackDuration; +@property (nonatomic) NSTimeInterval visibilitySatisfiedPlaybackTime; +@property (nonatomic, weak) UIView *measuredVideoView; +@property (nonatomic) BOOL requirementsSatisfied; + +@end + +@implementation MOPUBNativeVideoImpressionAgent + +- (instancetype)initWithVideoView:(UIView *)videoView requiredVisibilityPercentage:(CGFloat)visiblePercentage requiredPlaybackDuration:(NSTimeInterval)playbackDuration +{ + self = [super init]; + + if (self) { + _measuredVideoView = videoView; + _requiredVisiblePercentage = visiblePercentage; + _requiredPlaybackDuration = playbackDuration; + _visibilitySatisfiedPlaybackTime = kInvalidTimestamp; + _requirementsSatisfied = NO; + } + + return self; +} + +- (BOOL)shouldTrackImpressionWithCurrentPlaybackTime:(NSTimeInterval)currentPlaybackTime +{ + // this class's work is done once the requirements are met + if (!self.requirementsSatisfied) { + if (MPViewIntersectsParentWindowWithPercent(self.measuredVideoView, self.requiredVisiblePercentage)) { + // if this is the first time we satisfied the visibility requirement or + // if the user replays the video and we haven't satisfied the impression yet, set satisfied playback timestamp + if (self.visibilitySatisfiedPlaybackTime == kInvalidTimestamp || self.visibilitySatisfiedPlaybackTime > currentPlaybackTime) { + self.visibilitySatisfiedPlaybackTime = currentPlaybackTime; + } + + // we consider the requirements met if the visibility requirements are met for requiredPlaybackDuration seconds of actual continuous playback + if (currentPlaybackTime - self.visibilitySatisfiedPlaybackTime >= self.requiredPlaybackDuration) { + self.requirementsSatisfied = YES; + } + } else { + self.visibilitySatisfiedPlaybackTime = kInvalidTimestamp; + } + } + + return self.requirementsSatisfied; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBPlayerManager.h b/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBPlayerManager.h new file mode 100644 index 00000000000..ffb5122005b --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBPlayerManager.h @@ -0,0 +1,20 @@ +// +// MOPUBPlayerManager.h +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import + +@class MOPUBPlayerViewController; +@class MOPUBNativeVideoAdConfigValues; +@class MPVideoConfig; +@class MPAdConfigurationLogEventProperties; + +@interface MOPUBPlayerManager : NSObject + ++ (MOPUBPlayerManager *)sharedInstance; +- (void)disposePlayerViewController; + +- (MOPUBPlayerViewController *)playerViewControllerWithVideoConfig:(MPVideoConfig *)videoConfig nativeVideoAdConfig:(MOPUBNativeVideoAdConfigValues *)nativeVideoAdConfig logEventProperties:(MPAdConfigurationLogEventProperties *)logEventProperties; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBPlayerManager.m b/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBPlayerManager.m new file mode 100644 index 00000000000..195a790075d --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBPlayerManager.m @@ -0,0 +1,47 @@ +// +// MOPUBPlayerManager.m +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import "MOPUBPlayerManager.h" +#import "MOPUBPlayerViewController.h" +#import "MOPUBNativeVideoAdRenderer.h" +#import "MPVideoConfig.h" +#import "MOPUBNativeVideoAdConfigValues.h" + +@interface MOPUBPlayerManager() + +@property (nonatomic) MOPUBPlayerViewController *currentPlayerViewController; + +@end + +@implementation MOPUBPlayerManager + ++ (MOPUBPlayerManager *)sharedInstance +{ + static MOPUBPlayerManager *sharedInstance; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sharedInstance = [[MOPUBPlayerManager alloc] init]; + }); + return sharedInstance; +} + +- (void)disposePlayerViewController +{ + [self.currentPlayerViewController dispose]; + self.currentPlayerViewController = nil; +} + +- (MOPUBPlayerViewController *)playerViewControllerWithVideoConfig:(MPVideoConfig *)videoConfig nativeVideoAdConfig:(MOPUBNativeVideoAdConfigValues *)nativeVideoAdConfig logEventProperties:(MPAdConfigurationLogEventProperties *)logEventProperties +{ + // make sure only one instance of avPlayer at a time + if (self.currentPlayerViewController) { + [self disposePlayerViewController]; + } + + self.currentPlayerViewController = [[MOPUBPlayerViewController alloc] initWithVideoConfig:videoConfig nativeVideoAdConfig:nativeVideoAdConfig logEventProperties:logEventProperties]; + return self.currentPlayerViewController; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBPlayerView.h b/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBPlayerView.h new file mode 100644 index 00000000000..db32cf4b5eb --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBPlayerView.h @@ -0,0 +1,38 @@ +// +// MPPlayerView.h +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import +#import "MOPUBAVPlayer.h" + +@class MOPUBPlayerView; + +typedef NS_ENUM(NSUInteger, MOPUBPlayerDisplayMode) { + MOPUBPlayerDisplayModeInline = 0, + MOPUBPlayerDisplayModeFullscreen +}; + +@protocol MOPUBPlayerViewDelegate + +- (void)playerViewDidTapReplayButton:(MOPUBPlayerView *)view; +- (void)playerViewWillShowReplayView:(MOPUBPlayerView *)view; +- (void)playerViewWillEnterFullscreen:(MOPUBPlayerView *)view; + +@end + +@interface MOPUBPlayerView : UIControl + +@property (nonatomic) MOPUBAVPlayer *avPlayer; +@property (nonatomic) MOPUBPlayerDisplayMode displayMode; +@property (nonatomic, copy) NSString *videoGravity; + +- (instancetype)initWithFrame:(CGRect)frame delegate:(id)delegate; + +- (void)createPlayerView; +- (void)playbackTimeDidProgress; +- (void)playbackDidFinish; +- (void)setProgressBarVisible:(BOOL)visible; +- (void)handleVideoInitFailure; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBPlayerView.m b/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBPlayerView.m new file mode 100644 index 00000000000..622e69bea9d --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBPlayerView.m @@ -0,0 +1,227 @@ +// +// MPPlayerView.m +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import "MPGlobal.h" +#import "MPLogging.h" +#import "MOPUBAVPlayerView.h" +#import "MOPUBAVPlayer.h" +#import "MOPUBAVPlayerView.h" +#import "MOPUBPlayerView.h" +#import "MOPUBPlayerViewController.h" +#import "MOPUBReplayView.h" +#import "UIView+MPAdditions.h" +#import "UIColor+MPAdditions.h" + +static NSString * const kProgressBarFillColor = @"#FFCC4D"; +static CGFloat const kVideoProgressBarHeight = 4.0f; + +// gradient +static NSString * const kTopGradientColor = @"#000000"; +static NSString * const kBottomGradientColor = @"#000000"; +static CGFloat const kTopGradientAlpha = 0.0f; +static CGFloat const kBottomGradientAlpha = 0.4f; +static CGFloat const kGradientViewHeight = 25.0f; + +@interface MOPUBPlayerView() + +// UI elements +@property (nonatomic) MOPUBAVPlayerView *avView; +@property (nonatomic) MOPUBReplayView *replayView; +@property (nonatomic) UIButton *replayVideoButton; +@property (nonatomic) UIView *progressBarBackground; +@property (nonatomic) UIView *progressBar; +@property (nonatomic) UIView *gradientView; +@property (nonatomic) CAGradientLayer *gradient; + +@property (nonatomic) UITapGestureRecognizer *tapGestureRecognizer; +@property (nonatomic, weak) id delegate; + +@end + +@implementation MOPUBPlayerView + +- (instancetype)initWithFrame:(CGRect)frame delegate:(id)delegate +{ + self = [super initWithFrame:frame]; + if (self) { + _delegate = delegate; + + _tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(avPlayerTapped)]; + [self addGestureRecognizer:_tapGestureRecognizer]; + } + return self; +} + +- (void)dealloc +{ + [self.tapGestureRecognizer removeTarget:self action:@selector(avPlayerTapped)]; +} + +- (void)createPlayerView +{ + self.clipsToBounds = YES; + if (!self.gradientView && self.displayMode == MOPUBPlayerDisplayModeInline) { + // Create the gradient + self.gradientView = [UIView new]; + UIColor *topColor = [UIColor mp_colorFromHexString:kTopGradientColor alpha:kTopGradientAlpha]; + UIColor *bottomColor = [UIColor mp_colorFromHexString:kBottomGradientColor alpha:kBottomGradientAlpha]; + self.gradient = [CAGradientLayer layer]; + self.gradient.colors = [NSArray arrayWithObjects: (id)topColor.CGColor, (id)bottomColor.CGColor, nil]; + self.gradient.frame = CGRectMake(0, 0, CGRectGetWidth(self.bounds), kGradientViewHeight); + + //Add gradient to view + [self.gradientView.layer insertSublayer:self.gradient atIndex:0]; + [self addSubview:self.gradientView]; + } + + if (!self.progressBar) { + self.progressBar = [[UIView alloc] init]; + self.progressBarBackground = [[UIView alloc] init]; + [self addSubview:self.progressBarBackground]; + + self.progressBarBackground.backgroundColor = [UIColor colorWithRed:.5f green:.5f blue:.5f alpha:.5f]; + self.progressBar.backgroundColor = [UIColor mp_colorFromHexString:kProgressBarFillColor alpha:1.0f]; + [self addSubview:self.progressBar]; + } +} + +#pragma mark - set avPlayer + +- (void)setAvPlayer:(MOPUBAVPlayer *)player +{ + if (!player) { + MPLogError(@"Cannot set avPlayer to nil"); + return; + } + if (_avPlayer == player) { + return; + } + _avPlayer = player; + [_avView removeFromSuperview]; + _avView = [[MOPUBAVPlayerView alloc] initWithFrame:CGRectZero]; + _avView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; + self.videoGravity = AVLayerVideoGravityResizeAspectFill; + self.avView.player = self.avPlayer; + self.avView.frame = (CGRect){CGPointZero, self.bounds.size}; + [self insertSubview:_avView atIndex:0]; +} + +- (void)setVideoGravity:(NSString *)videoGravity +{ + ((AVPlayerLayer *)_avView.layer).videoGravity = videoGravity; +} + +// make the player view not clickable when initializing video failed. +- (void)handleVideoInitFailure +{ + [self removeGestureRecognizer:self.tapGestureRecognizer]; +} + +#pragma mark - Synchronize UI Elements + +- (void)playbackTimeDidProgress +{ + [self layoutProgressBar]; +} + +- (void)playbackDidFinish +{ + if (!self.replayView) { + self.replayView = [[MOPUBReplayView alloc] initWithFrame:self.avView.bounds displayMode:self.displayMode]; + __weak typeof(self) weakSelf = self; + self.replayView.actionBlock = ^(MOPUBReplayView *view) { + __strong typeof(self) strongSelf = weakSelf; + if ([strongSelf.delegate respondsToSelector:@selector(playerViewDidTapReplayButton:)]) { + [strongSelf.delegate playerViewDidTapReplayButton:strongSelf]; + } + [strongSelf.replayView removeFromSuperview]; + strongSelf.replayView = nil; + }; + [self addSubview:self.replayView]; + + if ([self.delegate respondsToSelector:@selector(playerViewWillShowReplayView:)]) { + [self.delegate playerViewWillShowReplayView:self]; + } + } +} + +- (void)setProgressBarVisible:(BOOL)visible +{ + self.progressBarBackground.hidden = !visible; + self.progressBar.hidden = !visible; +} + +#pragma mark - Touch event + +- (void)avPlayerTapped +{ + // Only trigger tap event in infeed mode + if (self.displayMode == MOPUBPlayerDisplayModeInline) { + self.displayMode = MOPUBPlayerDisplayModeFullscreen; + if ([self.delegate respondsToSelector:@selector(playerViewWillEnterFullscreen:)]) { + [self.delegate playerViewWillEnterFullscreen:self]; + } + [self setNeedsLayout]; + [self layoutIfNeeded]; + } +} + +#pragma mark - layout views + +- (void)layoutSubviews +{ + [super layoutSubviews]; + + [self layoutProgressBar]; + [self layoutGradientview]; + [self layoutReplayView]; +} + +- (void)layoutProgressBar +{ + if (self.avPlayer && !isnan(self.avPlayer.currentItemDuration)) { + CGFloat vcWidth = CGRectGetWidth(self.bounds); + CGFloat currentProgress = self.avPlayer.currentPlaybackTime/self.avPlayer.currentItemDuration; + if (currentProgress < 0) { + currentProgress = 0; + MPLogError(@"Progress shouldn't be < 0"); + } + if (currentProgress > 1) { + currentProgress = 1; + MPLogError(@"Progress shouldn't be > 1"); + } + + self.progressBar.frame = CGRectMake(0, CGRectGetMaxY(self.avView.frame)- kVideoProgressBarHeight, vcWidth * currentProgress, kVideoProgressBarHeight); + self.progressBarBackground.frame = CGRectMake(0, CGRectGetMaxY(self.avView.frame) - kVideoProgressBarHeight, vcWidth, kVideoProgressBarHeight); + } +} + + +- (void)layoutGradientview +{ + if (self.displayMode == MOPUBPlayerDisplayModeInline) { + self.gradientView.hidden = NO; + self.gradient.frame = CGRectMake(0, 0, CGRectGetWidth(self.bounds), kGradientViewHeight); + self.gradientView.frame = CGRectMake(0, CGRectGetMaxY(self.avView.frame) - kGradientViewHeight, CGRectGetWidth(self.bounds), kGradientViewHeight); + } else { + self.gradientView.hidden = YES; + } +} + +- (void)layoutReplayView +{ + if (self.replayView) { + CGSize screenSize = MPScreenBounds().size; + if (UIInterfaceOrientationIsLandscape([[UIApplication sharedApplication] statusBarOrientation]) && self.displayMode == MOPUBPlayerDisplayModeFullscreen) { + self.replayView.frame = CGRectMake(0, 0, screenSize.width, screenSize.height); + } else { + self.replayView.frame = self.avView.frame; + } + [self.replayView setNeedsLayout]; + [self.replayView layoutIfNeeded]; + } +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBPlayerViewController.h b/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBPlayerViewController.h new file mode 100644 index 00000000000..34c7f56496b --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBPlayerViewController.h @@ -0,0 +1,77 @@ +// +// MOPUBPlayerViewController.h +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import +#import "MOPUBPlayerView.h" + +@class AVPlayerItem; +@class MOPUBAVPlayer; +@class MOPUBPlayerViewController; +@class MOPUBNativeVideoAdConfigValues; +@class MPAdConfigurationLogEventProperties; +@class MPVASTTracking; +@class MPVideoConfig; + +@protocol MOPUBPlayerViewControllerDelegate + +@optional + +- (void)willEnterFullscreen:(MOPUBPlayerViewController *)viewController; +- (void)playerPlaybackWillStart:(MOPUBPlayerViewController *)player; +- (void)playerPlaybackDidStart:(MOPUBPlayerViewController *)player; +- (void)playerDidProgressToTime:(NSTimeInterval)playbackTime; +- (void)playerViewController:(MOPUBPlayerViewController *)playerViewController didTapReplayButton:(MOPUBPlayerView *)view; +- (void)playerViewController:(MOPUBPlayerViewController *)playerViewController willShowReplayView:(MOPUBPlayerView *)view; +- (void)playerViewController:(MOPUBPlayerViewController *)playerViewController didStall:(MOPUBAVPlayer *)player; +- (void)playerViewController:(MOPUBPlayerViewController *)playerViewController didRecoverFromStall:(MOPUBAVPlayer *)player; + +- (UIViewController *)viewControllerForPresentingModalView; + +@end + +@interface MOPUBPlayerViewController : UIViewController + +@property (nonatomic, readonly) NSURL *mediaURL; + +@property (nonatomic, readonly) MOPUBPlayerView *playerView; +@property (nonatomic, readonly) AVPlayerItem *playerItem; +@property (nonatomic, readonly) MOPUBAVPlayer *avPlayer; +@property (nonatomic) MPVASTTracking *vastTracking; +@property (nonatomic, readonly) CGFloat videoAspectRatio; +@property (nonatomic, readonly) MOPUBNativeVideoAdConfigValues *nativeVideoAdConfig; + +#pragma mark - Configurations/States +@property (nonatomic) MOPUBPlayerDisplayMode displayMode; +@property (nonatomic) BOOL muted; +@property (nonatomic) BOOL startedLoading; +@property (nonatomic) BOOL playing; +@property (nonatomic) BOOL paused; +@property (nonatomic) BOOL isReadyToPlay; +@property (nonatomic) BOOL disposed; + +#pragma - Call to action click tracking url +@property (nonatomic) NSURL *defaultActionURL; + +@property (nonatomic, weak) id delegate; + +#pragma mark - Initializer +- (instancetype)initWithVideoConfig:(MPVideoConfig *)videoConfig nativeVideoAdConfig:(MOPUBNativeVideoAdConfigValues *)nativeVideoAdConfig logEventProperties:(MPAdConfigurationLogEventProperties *)logEventProperties; +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; + +- (void)loadAndPlayVideo; +- (void)seekToTime:(NSTimeInterval)time; +- (void)pause; +- (void)resume; +- (void)dispose; + +- (BOOL)shouldStartNewPlayer; +- (BOOL)shouldResumePlayer; +- (BOOL)shouldPausePlayer; + +- (void)willEnterFullscreen; +- (void)willExitFullscreen; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBPlayerViewController.m b/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBPlayerViewController.m new file mode 100644 index 00000000000..2daea1fb88a --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBPlayerViewController.m @@ -0,0 +1,634 @@ +// +// MOPUBPlayerViewController.m +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import +#import "MOPUBAVPlayer.h" +#import "MOPUBFullscreenPlayerViewController.h" +#import "MOPUBPlayerViewController.h" +#import "MOPUBActivityIndicatorView.h" +#import "MPAdDestinationDisplayAgent.h" +#import "MPCoreInstanceProvider.h" +#import "MPGlobal.h" +#import "MPLogging.h" +#import "MPLogEvent+NativeVideo.h" +#import "MPLogEventRecorder.h" +#import "MOPUBNativeVideoAdConfigValues.h" +#import "MPVastTracking.h" +#import "MPVideoConfig.h" +#import "UIView+MPAdditions.h" +#import "MOPUBNativeVideoAdConfigValues.h" +#import "MPVideoConfig.h" +#import "UIButton+MPAdditions.h" + +#define kDefaultVideoAspectRatio 16.0f/9.0f + +static NSString * const kMutedButtonImage = @"MPMutedBtn.png"; +static NSString * const kUnmutedButtonImage = @"MPUnmutedBtn.png"; + +static NSString * const kTracksKey = @"tracks"; +static NSString * const kPlayableKey = @"playable"; + +// playerItem keys +static NSString * const kStatusKey = @"status"; +static NSString * const kCurrentItemKey = @"currentItem"; +static NSString * const kLoadedTimeRangesKey = @"loadedTimeRanges"; +static void *AudioControllerBufferingObservationContext = &AudioControllerBufferingObservationContext; + +// UI specifications +static CGFloat const kMuteIconInlineModeLeftMargin = 6.0f; +static CGFloat const kMuteIconInlineModeBottomMargin = 5.0f; +static CGFloat const kMuteIconInlineModeTouchAreaInsets = 25.0f; + +static CGFloat const kLoadingIndicatorTopMargin = 8.0f; +static CGFloat const kLoadingIndicatorRightMargin = 8.0f; + +// force resume playback in 3 seconds. player might get stuck due to stalled item +static CGFloat const kDelayPlayInSeconds = 3.0f; + +// We compare the buffered time to the length of the video to determine when it has been +// fully buffered. To account for rounding errors, allow a small error when making this +// calculation. +static const double kVideoFinishedBufferingAllowedError = 0.1; + +@interface MOPUBPlayerViewController() + +@property (nonatomic) UIButton *muteButton; +@property (nonatomic) UIActivityIndicatorView *loadingIndicator; +@property (nonatomic) MPAdDestinationDisplayAgent *displayAgent; + +// KVO might be triggerd multipe times. This property is used to make sure the view will only be created once. +@property (nonatomic) BOOL alreadyInitialized; +@property (nonatomic) MPAdConfigurationLogEventProperties *logEventProperties; +@property (nonatomic) BOOL downloadFinishedEventFired; +@property (nonatomic) BOOL alreadyCreatedPlayerView; +@property (nonatomic) BOOL finishedPlaying; + +@end + +@implementation MOPUBPlayerViewController + +- (instancetype)initWithVideoConfig:(MPVideoConfig *)videoConfig nativeVideoAdConfig:(MOPUBNativeVideoAdConfigValues *)nativeVideoAdConfig logEventProperties:(MPAdConfigurationLogEventProperties *)logEventProperties +{ + if (self = [super init]) { + _mediaURL = videoConfig.mediaURL; + _playerView = [[MOPUBPlayerView alloc] initWithFrame:CGRectZero delegate:self]; + self.displayMode = MOPUBPlayerDisplayModeInline; + + NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; + [notificationCenter addObserver:self selector:@selector(applicationDidEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil]; + [notificationCenter addObserver:self selector:@selector(applicationDidEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil]; + _nativeVideoAdConfig = nativeVideoAdConfig; + _logEventProperties = logEventProperties; + + // default aspect ratio is 16:9 + _videoAspectRatio = kDefaultVideoAspectRatio; + } + return self; +} + +#pragma mark - UIViewController + +- (void)viewDidLoad +{ + [super viewDidLoad]; + + [self.view addSubview:self.playerView]; + [self startLoadingIndicator]; +} + +- (void)viewWillLayoutSubviews +{ + [super viewWillLayoutSubviews]; + + // Bring mute button to front. This is necessary because the video view might be detached + // and re-attached during fullscreen to in-feed transition + [self.view bringSubviewToFront:self.muteButton]; + // Set playerView's frame so it will work for rotation + self.playerView.frame = self.view.bounds; + + [self layoutLoadingIndicator]; +} + +- (void)layoutLoadingIndicator +{ + if (_loadingIndicator) { + _loadingIndicator.mp_x = CGRectGetWidth(self.view.bounds) - kLoadingIndicatorRightMargin - CGRectGetWidth(_loadingIndicator.bounds); + _loadingIndicator.mp_y = kLoadingIndicatorTopMargin; + } +} + +#pragma mark - dealloc or dispose the controller + +- (void)dealloc +{ + [[NSNotificationCenter defaultCenter] removeObserver:self]; + + if (self.avPlayer) { + [self.avPlayer removeObserver:self forKeyPath:kStatusKey]; + } + + if (self.playerItem) { + [self.playerItem removeObserver:self forKeyPath:kStatusKey]; + [self.playerItem removeObserver:self forKeyPath:kLoadedTimeRangesKey]; + } + + MPLogDebug(@"playerViewController dealloc called"); +} + +- (void)dispose +{ + [self.view removeFromSuperview]; + [self.avPlayer dispose]; + self.avPlayer = nil; + self.disposed = YES; +} + +#pragma mark - load asset, set up aVplayer and avPlayer view + +- (void)handleVideoInitError +{ + MPAddLogEvent([[MPLogEvent alloc] initWithLogEventProperties:self.logEventProperties nativeVideoEventType:MPNativeVideoEventTypeErrorFailedToPlay]); + [self.vastTracking handleVideoEvent:MPVideoEventTypeError videoTimeOffset:self.avPlayer.currentPlaybackTime]; + [self stopLoadingIndicator]; + [self.playerView handleVideoInitFailure]; +} + +- (void)loadAndPlayVideo +{ + self.startedLoading = YES; + + AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:self.mediaURL options:nil]; + + if (asset == nil) { + MPLogError(@"failed to initialize video asset for URL %@", self.mediaURL); + [self handleVideoInitError]; + + return; + } + + MPAddLogEvent([[MPLogEvent alloc ] initWithLogEventProperties:self.logEventProperties nativeVideoEventType:MPNativeVideoEventTypeDownloadStart]); + + NSArray *requestedKeys = @[kTracksKey, kPlayableKey]; + [asset loadValuesAsynchronouslyForKeys:requestedKeys completionHandler:^{ + dispatch_async(dispatch_get_main_queue(), ^{ + if (!self.disposed) { + [self prepareToPlayAsset:asset withKeys:requestedKeys]; + } + }); + }]; +} + +- (void)setVideoAspectRatioWithAsset:(AVURLAsset *)asset +{ + if (asset && [asset tracksWithMediaType:AVMediaTypeVideo].count > 0) { + AVAssetTrack *videoTrack = [asset tracksWithMediaType:AVMediaTypeVideo][0]; + CGSize naturalSize = CGSizeApplyAffineTransform(videoTrack.naturalSize, videoTrack.preferredTransform); + naturalSize = CGSizeMake(fabs(naturalSize.width), fabs(naturalSize.height)); + + // make sure the natural size is at least 1pt (not 0) check + if (naturalSize.height > 0 && naturalSize.width > 0) { + _videoAspectRatio = naturalSize.width / naturalSize.height; + } + } +} + +- (void)prepareToPlayAsset:(AVURLAsset *)asset withKeys:(NSArray *)requestedKeys +{ + NSError *error = nil; + + if (!asset.playable) { + MPLogError(@"asset is not playable"); + [self handleVideoInitError]; + + return; + } + + AVKeyValueStatus status = [asset statusOfValueForKey:kTracksKey error:&error]; + if (status == AVKeyValueStatusFailed) { + MPLogError(@"AVKeyValueStatusFailed"); + [self handleVideoInitError]; + + return; + } else if (status == AVKeyValueStatusLoaded) { + [self setVideoAspectRatioWithAsset:asset]; + + self.playerItem = [AVPlayerItem playerItemWithAsset:asset]; + self.avPlayer = [[MOPUBAVPlayer alloc] initWithDelegate:self playerItem:self.playerItem]; + self.avPlayer.muted = YES; + + [self.playerView setAvPlayer:self.avPlayer]; + } +} + +#pragma mark - video ready to play +- (void)initOnVideoReady +{ + [self startPlayer]; + MPAddLogEvent([[MPLogEvent alloc] initWithLogEventProperties:self.logEventProperties nativeVideoEventType:MPNativeVideoEventTypeVideoReady]); +} + +- (void)createView +{ + [self.playerView createPlayerView]; + [self createMuteButton]; +} + +- (void)createMuteButton +{ + if (!self.muteButton) { + self.muteButton = [UIButton buttonWithType:UIButtonTypeCustom]; + [self.muteButton setImage:[UIImage imageNamed:MPResourcePathForResource(kMutedButtonImage)] forState:UIControlStateNormal]; + [self.muteButton setImage:[UIImage imageNamed:MPResourcePathForResource(kUnmutedButtonImage)] forState:UIControlStateSelected]; + [self.muteButton addTarget:self action:@selector(muteButtonTapped) forControlEvents:UIControlEventTouchUpInside]; + self.muteButton.mp_TouchAreaInsets = UIEdgeInsetsMake(kMuteIconInlineModeTouchAreaInsets, kMuteIconInlineModeTouchAreaInsets, kMuteIconInlineModeTouchAreaInsets, kMuteIconInlineModeTouchAreaInsets); + [self.muteButton sizeToFit]; + [self.view addSubview:self.muteButton]; + self.muteButton.frame = CGRectMake(kMuteIconInlineModeLeftMargin, CGRectGetMaxY(self.view.bounds) - kMuteIconInlineModeBottomMargin - CGRectGetHeight(self.muteButton.bounds), CGRectGetWidth(self.muteButton.bounds), CGRectGetHeight(self.muteButton.bounds)); + } +} + +- (void)startPlayer +{ + [self.avPlayer play]; + self.playing = YES; + self.isReadyToPlay = YES; + if ([self.delegate respondsToSelector:@selector(playerPlaybackDidStart:)]) { + [self.delegate playerPlaybackDidStart:self]; + } +} + +#pragma mark - displayAgent + +- (MPAdDestinationDisplayAgent *)displayAgent +{ + if (!_displayAgent) { + _displayAgent = [[MPCoreInstanceProvider sharedProvider] buildMPAdDestinationDisplayAgentWithDelegate:self]; + } + return _displayAgent; +} + + +#pragma mark - setter for player related objects + +- (void)setPlayerItem:(AVPlayerItem *)playerItem +{ + if (_playerItem) { + [_playerItem removeObserver:self forKeyPath:kStatusKey]; + [_playerItem removeObserver:self forKeyPath:kLoadedTimeRangesKey]; + } + _playerItem = playerItem; + if (!playerItem) { + return; + } + + [_playerItem addObserver:self forKeyPath:kStatusKey options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:nil]; + [_playerItem addObserver:self + forKeyPath:kLoadedTimeRangesKey + options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew + context:AudioControllerBufferingObservationContext]; +} + +- (void)setAvPlayer:(MOPUBAVPlayer *)avPlayer +{ + if (_avPlayer) { + [_avPlayer removeObserver:self forKeyPath:kStatusKey]; + } + _avPlayer = avPlayer; + if (_avPlayer) { + NSKeyValueObservingOptions options = (NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew); + [_avPlayer addObserver:self forKeyPath:kStatusKey options:options context:nil]; + } +} + +- (void)setMuted:(BOOL)muted +{ + _muted = muted; + [self.muteButton setSelected:!muted]; + self.avPlayer.muted = muted; +} + +#pragma mark - displayMode + +- (MOPUBPlayerDisplayMode)displayMode +{ + return self.playerView.displayMode; +} + +- (void)setDisplayMode:(MOPUBPlayerDisplayMode)displayMode +{ + self.playerView.displayMode = displayMode; + if (displayMode == MOPUBPlayerDisplayModeInline) { + self.muted = YES; + } else { + self.muted = NO; + } +} + +#pragma mark - acvivityIndicator +- (UIActivityIndicatorView *)loadingIndicator +{ + if (!_loadingIndicator) { + _loadingIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite]; + _loadingIndicator.hidesWhenStopped = YES; + _loadingIndicator.color = [UIColor whiteColor]; + [self.view addSubview:_loadingIndicator]; + } + return _loadingIndicator; +} + +- (void)startLoadingIndicator +{ + [self.loadingIndicator.superview bringSubviewToFront:_loadingIndicator]; + [self.loadingIndicator startAnimating]; +} + +- (void)stopLoadingIndicator +{ + if (_loadingIndicator && _loadingIndicator.isAnimating) { + [_loadingIndicator stopAnimating]; + } +} + +- (void)removeLoadingIndicator +{ + if (_loadingIndicator) { + [_loadingIndicator stopAnimating]; + [_loadingIndicator removeFromSuperview]; + _loadingIndicator = nil; + } +} + + +#pragma mark - Tap actions + +- (void)muteButtonTapped +{ + self.muteButton.selected = !self.muteButton.selected; + self.muted = !self.muteButton.selected; + + MPVideoEventType eventType = self.muted ? MPVideoEventTypeMuted : MPVideoEventTypeUnmuted; + [self.vastTracking handleVideoEvent:eventType videoTimeOffset:self.avPlayer.currentPlaybackTime]; +} + +# pragma mark - KVO + +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context +{ + if (object == self.avPlayer) { + if (self.avPlayer.status == AVPlayerItemStatusFailed) { + if (self.isReadyToPlay) { + MPAddLogEvent([[MPLogEvent alloc] initWithLogEventProperties:self.logEventProperties nativeVideoEventType:MPNativeVideoEventTypeErrorDuringPlayback]); + } else { + MPAddLogEvent([[MPLogEvent alloc] initWithLogEventProperties:self.logEventProperties nativeVideoEventType:MPNativeVideoEventTypeErrorFailedToPlay]); + } + MPLogError(@"avPlayer status failed"); + [self.vastTracking handleVideoEvent:MPVideoEventTypeError videoTimeOffset:self.avPlayer.currentPlaybackTime]; + } + } else if (object == self.playerItem) { + if (context == AudioControllerBufferingObservationContext) { + NSArray *timeRangeArray = [self.playerItem loadedTimeRanges]; + if (timeRangeArray && timeRangeArray.count > 0) { + CMTimeRange aTimeRange = [[timeRangeArray objectAtIndex:0] CMTimeRangeValue]; + double startTime = CMTimeGetSeconds(aTimeRange.start); + double loadedDuration = CMTimeGetSeconds(aTimeRange.duration); + double videoDuration = CMTimeGetSeconds(self.playerItem.duration); + if ((startTime + loadedDuration + kVideoFinishedBufferingAllowedError) >= videoDuration && !self.downloadFinishedEventFired) { + self.downloadFinishedEventFired = YES; + MPAddLogEvent([[MPLogEvent alloc ] initWithLogEventProperties:self.logEventProperties nativeVideoEventType:MPNativeVideoEventTypeDownloadFinished]); + } + } + } + if ([keyPath isEqualToString:kStatusKey]) { + switch (self.playerItem.status) { + case AVPlayerItemStatusReadyToPlay: + self.vastTracking.videoDuration = CMTimeGetSeconds(self.playerItem.duration); + if (!self.alreadyInitialized) { + self.alreadyInitialized = YES; + [self initOnVideoReady]; + } + break; + case AVPlayerItemStatusFailed: + { + MPLogError(@"avPlayerItem status failed"); + [self.vastTracking handleVideoEvent:MPVideoEventTypeError videoTimeOffset:self.avPlayer.currentPlaybackTime]; + break; + } + default: + break; + } + } + } else { + [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; + } +} + +#pragma mark - player controls + +- (void)pause +{ + self.paused = YES; + self.playing = NO; + [self.avPlayer pause]; + [self.vastTracking handleVideoEvent:MPVideoEventTypePause videoTimeOffset:self.avPlayer.currentPlaybackTime]; +} + +- (void)resume +{ + self.paused = NO; + self.playing = YES; + [self.avPlayer play]; + [self.vastTracking handleVideoEvent:MPVideoEventTypeResume videoTimeOffset:self.avPlayer.currentPlaybackTime]; +} + +- (void)seekToTime:(NSTimeInterval)time +{ + [self.avPlayer seekToTime:CMTimeMakeWithSeconds(time, NSEC_PER_SEC) toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero]; +} + +#pragma mark - auto play helper method +- (BOOL)shouldStartNewPlayer +{ + UIApplicationState state = [[UIApplication sharedApplication] applicationState]; + if (!self.startedLoading && !self.playing && !self.paused && state == UIApplicationStateActive) { + return YES; + } + return NO; +} + +- (BOOL)shouldResumePlayer +{ + UIApplicationState state = [[UIApplication sharedApplication] applicationState]; + if (self.startedLoading && self.paused == YES && self.displayMode == MOPUBPlayerDisplayModeInline + && state == UIApplicationStateActive) { + return YES; + } + return NO; +} + +- (BOOL)shouldPausePlayer +{ + if (self.playing && self.displayMode == MOPUBPlayerDisplayModeInline) { + return YES; + } + return NO; +} + +#pragma mark - enter fullscreen or exit fullscreen + +- (void)willEnterFullscreen +{ + self.displayMode = MOPUBPlayerDisplayModeFullscreen; + [self.vastTracking handleVideoEvent:MPVideoEventTypeFullScreen videoTimeOffset:self.avPlayer.currentPlaybackTime]; + [self.vastTracking handleVideoEvent:MPVideoEventTypeExpand videoTimeOffset:self.avPlayer.currentPlaybackTime]; +} + +- (void)willExitFullscreen +{ + self.displayMode = MOPUBPlayerDisplayModeInline; + [self.vastTracking handleVideoEvent:MPVideoEventTypeExitFullScreen videoTimeOffset:self.avPlayer.currentPlaybackTime]; + [self.vastTracking handleVideoEvent:MPVideoEventTypeCollapse videoTimeOffset:self.avPlayer.currentPlaybackTime]; +} + +#pragma mark - MOPUBAVPlayerDelegate + +- (void)avPlayer:(MOPUBAVPlayer *)player playbackTimeDidProgress:(NSTimeInterval)currentPlaybackTime +{ + // stop the loading indicator if it exists and is animating. + [self stopLoadingIndicator]; + + // When the KVO sends AVPlayerItemStatusReadyToPlay, there could still be a delay for the video really starts playing. + // If we create the mute button and progress bar immediately after AVPlayerItemStatusReadyToPlay signal, we might + // end up with showing them before the video is visible. To prevent that, we create mute button and progress bar here. + // There will be 0.1s delay after the video starts playing, but it's a much better user experience. + + if (!self.alreadyCreatedPlayerView) { + [self createView]; + self.alreadyCreatedPlayerView = YES; + } + + [self.playerView playbackTimeDidProgress]; + + if ([self.delegate respondsToSelector:@selector(playerDidProgressToTime:)]) { + [self.delegate playerDidProgressToTime:currentPlaybackTime]; + } +} + +- (void)avPlayer:(MOPUBAVPlayer *)player didError:(NSError *)error withMessage:(NSString *)message +{ + [self.avPlayer pause]; + MPAddLogEvent([[MPLogEvent alloc] initWithLogEventProperties:self.logEventProperties nativeVideoEventType:MPNativeVideoEventTypeErrorDuringPlayback]); + [self.vastTracking handleVideoEvent:MPVideoEventTypeError videoTimeOffset:self.avPlayer.currentPlaybackTime]; +} + +- (void)avPlayerDidFinishPlayback:(MOPUBAVPlayer *)player +{ + self.finishedPlaying = YES; + [self removeLoadingIndicator]; + [self.avPlayer pause]; + // update view + [self.playerView playbackDidFinish]; + [self.vastTracking handleVideoEvent:MPVideoEventTypeCompleted videoTimeOffset:self.avPlayer.currentPlaybackTime]; +} + +- (void)avPlayerDidRecoverFromStall:(MOPUBAVPlayer *)player +{ + if (self.displayMode == MOPUBPlayerDisplayModeInline) { + [self removeLoadingIndicator]; + } else { + if ([self.delegate respondsToSelector:@selector(playerViewController:didRecoverFromStall:)]) { + [self.delegate playerViewController:self didRecoverFromStall:player]; + } + } + + [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(resume) object:nil]; +} + +- (void)avPlayerDidStall:(MOPUBAVPlayer *)player +{ + MPAddLogEvent([[MPLogEvent alloc] initWithLogEventProperties:self.logEventProperties nativeVideoEventType:MPNativeVideoEventTypeBuffering]); + if (self.displayMode == MOPUBPlayerDisplayModeInline) { + [self startLoadingIndicator]; + } else { + if ([self.delegate respondsToSelector:@selector(playerViewController:didStall:)]) { + [self.delegate playerViewController:self didStall:self.avPlayer]; + } + } + + // Try to resume the video play after 3 seconds. The perform selector request is cancelled when + // didRecoverFromStall signal is received. This way, we won't queue up the requests. + [self performSelector:@selector(resume) withObject:nil afterDelay:kDelayPlayInSeconds]; +} + +#pragma mark - MOPUBPlayerViewDelegate +- (void)playerViewDidTapReplayButton:(MOPUBPlayerView *)view +{ + self.muteButton.hidden = NO; + self.finishedPlaying = NO; + [self.playerView setProgressBarVisible:YES]; + [self seekToTime:0]; + [self.avPlayer play]; +} + +- (void)playerViewWillShowReplayView:(MOPUBPlayerView *)view +{ + self.muteButton.hidden = YES; + [self.playerView setProgressBarVisible:NO]; + if (self.displayMode == MOPUBPlayerDisplayModeFullscreen) { + if ([self.delegate respondsToSelector:@selector(playerViewController:didTapReplayButton:)]) { + [self.delegate playerViewController:self willShowReplayView:self.playerView]; + } + } +} + +- (void)playerViewWillEnterFullscreen:(MOPUBPlayerView *)view +{ + if ([self.delegate respondsToSelector:@selector(willEnterFullscreen:)]) { + [self.delegate willEnterFullscreen:self]; + } +} + +#pragma mark - Application state monitoring + +- (void)applicationDidEnterBackground:(NSNotification *)notification +{ + if (self.avPlayer && self.avPlayer.rate > 0) { + [self pause]; + } +} + +- (void)applicationDidEnterForeground:(NSNotification *)notification +{ + // Resume video playback only if the visible area is larger than or equal to the autoplay threshold. + + BOOL playVisible = MPViewIntersectsParentWindowWithPercent(self.playerView, self.nativeVideoAdConfig.playVisiblePercent/100.0f); + if (self.avPlayer && self.isReadyToPlay && !self.finishedPlaying && playVisible) { + [self resume]; + } +} + +#pragma mark - + +- (UIViewController *)viewControllerForPresentingModalView +{ + return [self.delegate viewControllerForPresentingModalView]; +} + +- (void)displayAgentWillPresentModal +{ + [self pause]; +} + +- (void)displayAgentWillLeaveApplication +{ + [self pause]; +} + +- (void)displayAgentDidDismissModal +{ + [self resume]; +} + +@end + diff --git a/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBReplayView.h b/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBReplayView.h new file mode 100644 index 00000000000..3e421007d1e --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBReplayView.h @@ -0,0 +1,19 @@ +// +// MPBReplayView.h +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import +#import "MOPUBPlayerView.h" + +@class MOPUBReplayView; + +typedef void (^MPBReplayActionBlock)(MOPUBReplayView *replayView); + +@interface MOPUBReplayView : UIView + +@property (nonatomic, copy) MPBReplayActionBlock actionBlock; + +- (instancetype)initWithFrame:(CGRect)frame displayMode:(MOPUBPlayerDisplayMode)displayMode; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBReplayView.m b/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBReplayView.m new file mode 100644 index 00000000000..e55c70f5060 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/NativeVideo/Internal/MOPUBReplayView.m @@ -0,0 +1,56 @@ +// +// MPBReplayView.m +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import "MOPUBReplayView.h" +#import "MPGlobal.h" +#import "UIView+MPAdditions.h" +#import "UIColor+MPAdditions.h" + +static NSString * const kPlayButtonImage = @"MPPlayBtn.png"; +static NSString * const kOverlayBgColor = @"#000000"; +static CGFloat const kOverlayAlpha = 0.5f; + +@interface MOPUBReplayView() + +@property (nonatomic) UIView *overlayView; +@property (nonatomic) UIButton *replayVideoButton; + +@end + +@implementation MOPUBReplayView + +- (instancetype)initWithFrame:(CGRect)frame displayMode:(MOPUBPlayerDisplayMode)displayMode +{ + if (self = [super initWithFrame:frame]) { + // only apply the overlay for fullscreen mode + if (displayMode == MOPUBPlayerDisplayModeFullscreen) { + _overlayView = [UIView new]; + _overlayView.backgroundColor = [UIColor mp_colorFromHexString:kOverlayBgColor alpha:kOverlayAlpha]; + [self addSubview:_overlayView]; + } + + _replayVideoButton = [UIButton buttonWithType:UIButtonTypeCustom]; + [_replayVideoButton setImage:[UIImage imageNamed:MPResourcePathForResource(kPlayButtonImage)] forState:UIControlStateNormal]; + [_replayVideoButton addTarget:self action:@selector(replayButtonTapped) forControlEvents:UIControlEventTouchUpInside]; + [_replayVideoButton sizeToFit]; + [self addSubview:_replayVideoButton]; + } + return self; +} + +- (void)layoutSubviews +{ + [super layoutSubviews]; + self.overlayView.frame = self.bounds; + self.replayVideoButton.center = self.center; + self.replayVideoButton.frame = CGRectIntegral(self.replayVideoButton.frame); +} + +- (void)replayButtonTapped +{ + self.actionBlock(self); +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/NativeVideo/MOPUBNativeVideoAdRenderer.h b/iphone/Maps/3party/MoPubSDK/NativeVideo/MOPUBNativeVideoAdRenderer.h new file mode 100644 index 00000000000..ecf7d971a64 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/NativeVideo/MOPUBNativeVideoAdRenderer.h @@ -0,0 +1,18 @@ +// +// MOPUBNativeVideoAdRenderer.h +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import +#import "MPNativeAdRenderer.h" + +@class MPNativeAdRendererConfiguration; +@class MPStaticNativeAdRendererSettings; + +@interface MOPUBNativeVideoAdRenderer : NSObject + +@property (nonatomic, readonly) MPNativeViewSizeHandler viewSizeHandler; + ++ (MPNativeAdRendererConfiguration *)rendererConfigurationWithRendererSettings:(id)rendererSettings; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/NativeVideo/MOPUBNativeVideoAdRenderer.m b/iphone/Maps/3party/MoPubSDK/NativeVideo/MOPUBNativeVideoAdRenderer.m new file mode 100644 index 00000000000..ac142ba959f --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/NativeVideo/MOPUBNativeVideoAdRenderer.m @@ -0,0 +1,355 @@ +// +// MOPUBNativeVideoAdRenderer.m +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import "MOPUBNativeVideoAdRenderer.h" +#import "MPNativeAdRendererConfiguration.h" +#import "MPNativeAdRenderer.h" +#import "MPNativeAdRendering.h" +#import "MPNativeAdAdapter.h" +#import "MPNativeAdConstants.h" +#import "MPNativeAdError.h" +#import "MPNativeAdRendererImageHandler.h" +#import "MPTimer.h" +#import "MPGlobal.h" +#import "MPLogging.h" +#import "MOPUBNativeVideoAdRendererSettings.h" +#import "MOPUBFullscreenPlayerViewController.h" +#import "MOPUBPlayerManager.h" +#import "MOPUBNativeVideoAdAdapter.h" +#import "MPVASTTracking.h" +#import "MPVideoConfig.h" +#import "MOPUBNativeVideoAdConfigValues.h" +#import "MOPUBNativeVideoImpressionAgent.h" +#import "MOPUBPlayerViewController.h" +#import "MPNativeAdRenderingImageLoader.h" + +static const CGFloat kAutoPlayTimerInterval = 0.25f; + +@interface MOPUBNativeVideoAdRenderer () + +@property (nonatomic) UIView *adView; +@property (nonatomic) MOPUBNativeVideoAdAdapter *adapter; +@property (nonatomic) BOOL adViewInViewHierarchy; +@property (nonatomic) Class renderingViewClass; +@property (nonatomic) MPNativeAdRendererImageHandler *rendererImageHandler; + +@property (nonatomic, weak) MOPUBPlayerViewController *videoController; +@property (nonatomic) MPTimer *autoPlayTimer; +@property (nonatomic) MPVideoConfig *videoConfig; +@property (nonatomic) MPVASTTracking *vastTracking; +@property (nonatomic) MOPUBNativeVideoAdConfigValues *nativeVideoAdConfig; +@property (nonatomic) MOPUBNativeVideoImpressionAgent *trackingAgent; +@property (nonatomic) BOOL trackingImpressionFired; + +@end + +@implementation MOPUBNativeVideoAdRenderer + ++ (MPNativeAdRendererConfiguration *)rendererConfigurationWithRendererSettings:(id)rendererSettings +{ + MPNativeAdRendererConfiguration *config = [[MPNativeAdRendererConfiguration alloc] init]; + config.rendererClass = [self class]; + config.rendererSettings = rendererSettings; + config.supportedCustomEvents = @[@"MOPUBNativeVideoCustomEvent"]; + + return config; +} + +- (instancetype)initWithRendererSettings:(id)rendererSettings +{ + if (self = [super init]) { + MOPUBNativeVideoAdRendererSettings *settings = (MOPUBNativeVideoAdRendererSettings *)rendererSettings; + _renderingViewClass = settings.renderingViewClass; + _viewSizeHandler = [settings.viewSizeHandler copy]; + _rendererImageHandler = [MPNativeAdRendererImageHandler new]; + _rendererImageHandler.delegate = self; + } + + return self; +} + +- (void)dealloc +{ + [_autoPlayTimer invalidate]; + _autoPlayTimer = nil; + + // free the video memory + [[MOPUBPlayerManager sharedInstance] disposePlayerViewController]; +} + +- (UIView *)retrieveViewWithAdapter:(MOPUBNativeVideoAdAdapter *)adapter error:(NSError **)error +{ + if (!adapter) { + if (error) { + *error = MPNativeAdNSErrorForRenderValueTypeError(); + } + + return nil; + } + + self.adapter = adapter; + + [self initAdView]; + [self setupVideoView]; + + self.adView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; + + // We only load text here. We delay loading of images until the view is added to the view hierarchy + // so we don't unnecessarily load images from the cache if the user is scrolling fast. So we will + // just store the image URLs for now. + if ([self.adView respondsToSelector:@selector(nativeMainTextLabel)]) { + self.adView.nativeMainTextLabel.text = [adapter.properties objectForKey:kAdTextKey]; + } + + if ([self.adView respondsToSelector:@selector(nativeTitleTextLabel)]) { + self.adView.nativeTitleTextLabel.text = [adapter.properties objectForKey:kAdTitleKey]; + } + + if ([self.adView respondsToSelector:@selector(nativeCallToActionTextLabel)] && self.adView.nativeCallToActionTextLabel) { + self.adView.nativeCallToActionTextLabel.text = [adapter.properties objectForKey:kAdCTATextKey]; + } + + if ([self.adView respondsToSelector:@selector(nativePrivacyInformationIconImageView)]) { + // MoPub ads pass the privacy information icon key through the properties dictionary. + NSString *daaIconImageLoc = [adapter.properties objectForKey:kAdDAAIconImageKey]; + if (daaIconImageLoc) { + UIImageView *imageView = self.adView.nativePrivacyInformationIconImageView; + imageView.hidden = NO; + + UIImage *daaIconImage = [UIImage imageNamed:daaIconImageLoc]; + imageView.image = daaIconImage; + + // Attach a gesture recognizer to handle loading the daa icon URL. + UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(DAAIconTapped)]; + imageView.userInteractionEnabled = YES; + [imageView addGestureRecognizer:tapRecognizer]; + } else if ([adapter respondsToSelector:@selector(privacyInformationIconView)]) { + // The ad network may provide its own view for its privacy information icon. We assume the ad handles the tap on the icon as well. + UIView *privacyIconAdView = [adapter privacyInformationIconView]; + privacyIconAdView.frame = self.adView.nativePrivacyInformationIconImageView.bounds; + privacyIconAdView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; + self.adView.nativePrivacyInformationIconImageView.userInteractionEnabled = YES; + [self.adView.nativePrivacyInformationIconImageView addSubview:privacyIconAdView]; + self.adView.nativePrivacyInformationIconImageView.hidden = NO; + } else { + self.adView.nativePrivacyInformationIconImageView.userInteractionEnabled = NO; + self.adView.nativePrivacyInformationIconImageView.hidden = YES; + } + } + + if ([self shouldLoadMediaView]) { + UIView *mediaView = [self.adapter mainMediaView]; + UIView *mainImageView = [self.adView nativeMainImageView]; + + mediaView.frame = mainImageView.bounds; + mediaView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; + mediaView.userInteractionEnabled = YES; + mainImageView.userInteractionEnabled = YES; + + [mainImageView addSubview:mediaView]; + } + + // See if the ad contains a star rating and notify the view if it does. + if ([self.adView respondsToSelector:@selector(layoutStarRating:)]) { + NSNumber *starRatingNum = [adapter.properties objectForKey:kAdStarRatingKey]; + + if ([starRatingNum isKindOfClass:[NSNumber class]] && starRatingNum.floatValue >= kStarRatingMinValue && starRatingNum.floatValue <= kStarRatingMaxValue) { + [self.adView layoutStarRating:starRatingNum]; + } + } + + return self.adView; +} + +- (BOOL)shouldLoadMediaView +{ + return [self.adapter respondsToSelector:@selector(mainMediaView)] + && [self.adapter mainMediaView] + && [self.adView respondsToSelector:@selector(nativeMainImageView)]; +} + +- (void)DAAIconTapped +{ + if ([self.adapter respondsToSelector:@selector(displayContentForDAAIconTap)]) { + [self.adapter displayContentForDAAIconTap]; + } +} + +- (void)adViewWillMoveToSuperview:(UIView *)superview +{ + self.adViewInViewHierarchy = (superview != nil); + + if (superview) { + // We'll start asychronously loading the native ad images now. + if ([self.adapter.properties objectForKey:kAdIconImageKey] && [self.adView respondsToSelector:@selector(nativeIconImageView)]) { + [self.rendererImageHandler loadImageForURL:[NSURL URLWithString:[self.adapter.properties objectForKey:kAdIconImageKey]] intoImageView:self.adView.nativeIconImageView]; + } + + // Only handle the loading of the main image if the adapter doesn't already have a view for it. + if (!([self.adapter respondsToSelector:@selector(mainMediaView)] && [self.adapter mainMediaView])) { + if ([self.adapter.properties objectForKey:kAdMainImageKey] && [self.adView respondsToSelector:@selector(nativeMainImageView)]) { + [self.rendererImageHandler loadImageForURL:[NSURL URLWithString:[self.adapter.properties objectForKey:kAdMainImageKey]] intoImageView:self.adView.nativeMainImageView]; + } + } + + // Layout custom assets here as the custom assets may contain images that need to be loaded. + if ([self.adView respondsToSelector:@selector(layoutCustomAssetsWithProperties:imageLoader:)]) { + // Create a simplified image loader for the ad view to use. + MPNativeAdRenderingImageLoader *imageLoader = [[MPNativeAdRenderingImageLoader alloc] initWithImageHandler:self.rendererImageHandler]; + [self.adView layoutCustomAssetsWithProperties:self.adapter.properties imageLoader:imageLoader]; + } + } +} + +- (void)tick:(MPTimer *)timer +{ + if (self.videoController) { + BOOL playVisible = MPViewIntersectsParentWindowWithPercent(self.videoController.playerView, self.nativeVideoAdConfig.playVisiblePercent/100.0f); + if (playVisible) { + // start new + if ([self.videoController shouldStartNewPlayer]) { + [self.videoController loadAndPlayVideo]; + } + + // resume play + if ([self.videoController shouldResumePlayer]) { + [self.videoController resume]; + } + } + + // pause video + BOOL pauseVisible = !MPViewIntersectsParentWindowWithPercent(self.videoController.playerView, self.nativeVideoAdConfig.pauseVisiblePercent/100.0f); + if (pauseVisible) { + if ([self.videoController shouldPausePlayer]) { + [self.videoController pause]; + } + } + } +} + +#pragma mark - MOPUBPlayerViewControllerDelegate + +- (void)willEnterFullscreen:(MOPUBPlayerViewController *)viewController +{ + [self enterFullscreen:[[self.adapter delegate] viewControllerForPresentingModalView]]; +} + +- (void)playerDidProgressToTime:(NSTimeInterval)playbackTime +{ + [self.adapter handleVideoHasProgressedToTime:playbackTime]; + + // Only the first impression is tracked. + if (!self.trackingImpressionFired && [self.trackingAgent shouldTrackImpressionWithCurrentPlaybackTime:playbackTime]) { + self.trackingImpressionFired = YES; + + // Fire MoPub impression tracking + [self.adapter handleVideoViewImpression]; + // Fire VAST Impression Tracking + [self.vastTracking handleVideoEvent:MPVideoEventTypeImpression + videoTimeOffset:playbackTime]; + } + [self.vastTracking handleVideoEvent:MPVideoEventTypeTimeUpdate videoTimeOffset:playbackTime]; +} + +- (void)ctaTapped:(MOPUBFullscreenPlayerViewController *)viewController +{ + // MoPub video CTA button clicked. Only the first click is tracked. The check is handled in MPNativeAd. + [self.adapter handleVideoViewClick]; + [self.vastTracking handleVideoEvent:MPVideoEventTypeClick + videoTimeOffset:self.videoController.avPlayer.currentPlaybackTime]; +} + +- (void)fullscreenPlayerWillLeaveApplication:(MOPUBFullscreenPlayerViewController *)viewController +{ + if ([self.adapter.delegate respondsToSelector:@selector(nativeAdWillLeaveApplicationFromAdapter:)]) { + [self.adapter.delegate nativeAdWillLeaveApplicationFromAdapter:self.adapter]; + } +} + +// being called from MPNativeAd +- (void)nativeAdTapped +{ + [self.vastTracking handleVideoEvent:MPVideoEventTypeClick + videoTimeOffset:self.videoController.avPlayer.currentPlaybackTime]; +} + +#pragma mark - MPNativeAdRendererImageHandlerDelegate + +- (BOOL)nativeAdViewInViewHierarchy +{ + return self.adViewInViewHierarchy; +} + +#pragma mark - Internal +- (void)enterFullscreen:(UIViewController *)fromViewController +{ + MOPUBFullscreenPlayerViewController *vc = [[MOPUBFullscreenPlayerViewController alloc] initWithVideoPlayer:self.videoController dismissBlock:^(UIView *originalParentView) { + self.videoController.view.frame = originalParentView.bounds; + self.videoController.delegate = self; + [self.videoController willExitFullscreen]; + if ([self.adapter.delegate respondsToSelector:@selector(nativeAdDidDismissModalForAdapter:)]) { + [self.adapter.delegate nativeAdDidDismissModalForAdapter:self.adapter]; + } + [originalParentView addSubview:self.videoController.playerView]; + }]; + vc.delegate = self; + if ([self.adapter.delegate respondsToSelector:@selector(nativeAdWillPresentModalForAdapter:)]) { + [self.adapter.delegate nativeAdWillPresentModalForAdapter:self.adapter]; + } + [fromViewController presentViewController:vc animated:NO completion:nil]; +} + +- (void)initAdView +{ + if (!self.videoController) { + if ([self.renderingViewClass respondsToSelector:@selector(nibForAd)]) { + self.adView = (UIView *)[[[self.renderingViewClass nibForAd] instantiateWithOwner:nil options:nil] firstObject]; + } else { + self.adView = [[self.renderingViewClass alloc] init]; + } + } +} + +- (void)setupVideoView +{ + // If a video controller is nil or it's already been disposed, create/recreate the videoController + if ([self.adView respondsToSelector:(@selector(nativeVideoView))]) { + BOOL createdNewVideoController = NO; + self.videoConfig = [self.adapter.properties objectForKey:kVideoConfigKey]; + self.nativeVideoAdConfig = [self.adapter.properties objectForKey:kNativeVideoAdConfigKey]; + + if (!self.videoController || self.videoController.disposed) { + createdNewVideoController = YES; + self.videoController = [[MOPUBPlayerManager sharedInstance] playerViewControllerWithVideoConfig:self.videoConfig + nativeVideoAdConfig:self.nativeVideoAdConfig + logEventProperties:[self.adapter.properties valueForKey:kLogEventRequestPropertiesKey]]; + self.videoController.defaultActionURL = self.adapter.defaultActionURL; + self.videoController.displayMode = MOPUBPlayerDisplayModeInline; + self.videoController.delegate = self; + self.videoController.view.frame = self.adView.nativeVideoView.bounds; + [self.adView.nativeVideoView addSubview:self.videoController.view]; + [self.adView bringSubviewToFront:self.adView.nativeVideoView]; + + if (!self.autoPlayTimer) { + self.autoPlayTimer = [MPTimer timerWithTimeInterval:kAutoPlayTimerInterval target:self selector:@selector(tick:) repeats:YES]; + self.autoPlayTimer.runLoopMode = NSRunLoopCommonModes; + [self.autoPlayTimer scheduleNow]; + } + + self.trackingAgent = [[MOPUBNativeVideoImpressionAgent alloc] initWithVideoView:self.videoController.playerView requiredVisibilityPercentage:self.nativeVideoAdConfig.impressionMinVisiblePercent/100.0f requiredPlaybackDuration:self.nativeVideoAdConfig.impressionVisible]; + } + // Lazy load vast tracking. It must be created after we know the video controller has been initialized. + // If we created a new video controller, we must ensure the vast tracking has the new view. + if (!self.vastTracking) { + self.vastTracking = [[MPVASTTracking alloc] initWithMPVideoConfig:self.videoConfig videoView:self.videoController.playerView]; + } else if (createdNewVideoController) { + [self.vastTracking handleNewVideoView:self.videoController.playerView]; + } + // Always set the videoControllers vast tracking object to be the current renderer's + self.videoController.vastTracking = self.vastTracking; + } +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/NativeVideo/MOPUBNativeVideoAdRendererSettings.h b/iphone/Maps/3party/MoPubSDK/NativeVideo/MOPUBNativeVideoAdRendererSettings.h new file mode 100644 index 00000000000..7f4c915daf4 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/NativeVideo/MOPUBNativeVideoAdRendererSettings.h @@ -0,0 +1,28 @@ +// +// MOPUBNativeVideoAdRendererSettings.h +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import +#import "MPNativeAdRendererSettings.h" +#import "MPNativeAdRenderer.h" + +@interface MOPUBNativeVideoAdRendererSettings : NSObject + +/** + * A rendering class that must be a UIView that implements the MPNativeAdRendering protocol. + * The ad will be rendered to a view of this class type. + */ +@property (nonatomic, assign) Class renderingViewClass; + +/** + * A block that returns the size of the view given a maximum width. This needs to be set when + * used in conjunction with ad placer classes so the ad placers can correctly size the cells + * that contain the ads. + * + * viewSizeHandler is not used for manual native ad integration. You must set the + * frame of your manually integrated native ad view. + */ +@property (nonatomic, readwrite, copy) MPNativeViewSizeHandler viewSizeHandler; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/NativeVideo/MOPUBNativeVideoAdRendererSettings.m b/iphone/Maps/3party/MoPubSDK/NativeVideo/MOPUBNativeVideoAdRendererSettings.m new file mode 100644 index 00000000000..cd339fc2858 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/NativeVideo/MOPUBNativeVideoAdRendererSettings.m @@ -0,0 +1,10 @@ +// +// MOPUBNativeVideoAdRendererSettings.m +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import "MOPUBNativeVideoAdRendererSettings.h" + +@implementation MOPUBNativeVideoAdRendererSettings + +@end diff --git a/iphone/Maps/3party/MoPubSDK/Resources/MPCloseBtn.png b/iphone/Maps/3party/MoPubSDK/Resources/MPCloseBtn.png new file mode 100644 index 00000000000..c96fa808651 Binary files /dev/null and b/iphone/Maps/3party/MoPubSDK/Resources/MPCloseBtn.png differ diff --git a/iphone/Maps/3party/MoPubSDK/Resources/MPCloseBtn@2x.png b/iphone/Maps/3party/MoPubSDK/Resources/MPCloseBtn@2x.png new file mode 100644 index 00000000000..b87f96b2299 Binary files /dev/null and b/iphone/Maps/3party/MoPubSDK/Resources/MPCloseBtn@2x.png differ diff --git a/iphone/Maps/3party/MoPubSDK/Resources/MPCloseBtn@3x.png b/iphone/Maps/3party/MoPubSDK/Resources/MPCloseBtn@3x.png new file mode 100644 index 00000000000..42e927b2af3 Binary files /dev/null and b/iphone/Maps/3party/MoPubSDK/Resources/MPCloseBtn@3x.png differ diff --git a/iphone/Maps/3party/MoPubSDK/Resources/MPCloseButtonX.png b/iphone/Maps/3party/MoPubSDK/Resources/MPCloseButtonX.png new file mode 100644 index 00000000000..ca4fa298141 Binary files /dev/null and b/iphone/Maps/3party/MoPubSDK/Resources/MPCloseButtonX.png differ diff --git a/iphone/Maps/3party/MoPubSDK/Resources/MPCloseButtonX@2x.png b/iphone/Maps/3party/MoPubSDK/Resources/MPCloseButtonX@2x.png new file mode 100644 index 00000000000..85e39a31b69 Binary files /dev/null and b/iphone/Maps/3party/MoPubSDK/Resources/MPCloseButtonX@2x.png differ diff --git a/iphone/Maps/3party/MoPubSDK/Resources/MPCloseButtonX@3x.png b/iphone/Maps/3party/MoPubSDK/Resources/MPCloseButtonX@3x.png new file mode 100644 index 00000000000..c6e5df439c9 Binary files /dev/null and b/iphone/Maps/3party/MoPubSDK/Resources/MPCloseButtonX@3x.png differ diff --git a/iphone/Maps/3party/MoPubSDK/Resources/MPCountdownTimer.html b/iphone/Maps/3party/MoPubSDK/Resources/MPCountdownTimer.html new file mode 100644 index 00000000000..70a496a2665 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Resources/MPCountdownTimer.html @@ -0,0 +1,35 @@ + + + + + + + + + + + +
+
+ +
+
+ + diff --git a/iphone/Maps/3party/MoPubSDK/Resources/MPDAAIcon.png b/iphone/Maps/3party/MoPubSDK/Resources/MPDAAIcon.png new file mode 100644 index 00000000000..3443a86174d Binary files /dev/null and b/iphone/Maps/3party/MoPubSDK/Resources/MPDAAIcon.png differ diff --git a/iphone/Maps/3party/MoPubSDK/Resources/MPDAAIcon@2x.png b/iphone/Maps/3party/MoPubSDK/Resources/MPDAAIcon@2x.png new file mode 100644 index 00000000000..e09e34bef93 Binary files /dev/null and b/iphone/Maps/3party/MoPubSDK/Resources/MPDAAIcon@2x.png differ diff --git a/iphone/Maps/3party/MoPubSDK/Resources/MPDAAIcon@3x.png b/iphone/Maps/3party/MoPubSDK/Resources/MPDAAIcon@3x.png new file mode 100644 index 00000000000..22a01abd051 Binary files /dev/null and b/iphone/Maps/3party/MoPubSDK/Resources/MPDAAIcon@3x.png differ diff --git a/iphone/Maps/3party/MoPubSDK/Resources/MPMutedBtn.png b/iphone/Maps/3party/MoPubSDK/Resources/MPMutedBtn.png new file mode 100644 index 00000000000..fe63b365e21 Binary files /dev/null and b/iphone/Maps/3party/MoPubSDK/Resources/MPMutedBtn.png differ diff --git a/iphone/Maps/3party/MoPubSDK/Resources/MPMutedBtn@2x.png b/iphone/Maps/3party/MoPubSDK/Resources/MPMutedBtn@2x.png new file mode 100644 index 00000000000..d6443f02c83 Binary files /dev/null and b/iphone/Maps/3party/MoPubSDK/Resources/MPMutedBtn@2x.png differ diff --git a/iphone/Maps/3party/MoPubSDK/Resources/MPMutedBtn@3x.png b/iphone/Maps/3party/MoPubSDK/Resources/MPMutedBtn@3x.png new file mode 100644 index 00000000000..8ccd290a075 Binary files /dev/null and b/iphone/Maps/3party/MoPubSDK/Resources/MPMutedBtn@3x.png differ diff --git a/iphone/Maps/3party/MoPubSDK/Resources/MPPlayBtn.png b/iphone/Maps/3party/MoPubSDK/Resources/MPPlayBtn.png new file mode 100644 index 00000000000..ddaca971ce9 Binary files /dev/null and b/iphone/Maps/3party/MoPubSDK/Resources/MPPlayBtn.png differ diff --git a/iphone/Maps/3party/MoPubSDK/Resources/MPPlayBtn@2x.png b/iphone/Maps/3party/MoPubSDK/Resources/MPPlayBtn@2x.png new file mode 100644 index 00000000000..51c911e22cd Binary files /dev/null and b/iphone/Maps/3party/MoPubSDK/Resources/MPPlayBtn@2x.png differ diff --git a/iphone/Maps/3party/MoPubSDK/Resources/MPPlayBtn@3x.png b/iphone/Maps/3party/MoPubSDK/Resources/MPPlayBtn@3x.png new file mode 100644 index 00000000000..1affb6ec8d5 Binary files /dev/null and b/iphone/Maps/3party/MoPubSDK/Resources/MPPlayBtn@3x.png differ diff --git a/iphone/Maps/3party/MoPubSDK/Resources/MPUnmutedBtn.png b/iphone/Maps/3party/MoPubSDK/Resources/MPUnmutedBtn.png new file mode 100644 index 00000000000..a7d52e3b073 Binary files /dev/null and b/iphone/Maps/3party/MoPubSDK/Resources/MPUnmutedBtn.png differ diff --git a/iphone/Maps/3party/MoPubSDK/Resources/MPUnmutedBtn@2x.png b/iphone/Maps/3party/MoPubSDK/Resources/MPUnmutedBtn@2x.png new file mode 100644 index 00000000000..1860e979220 Binary files /dev/null and b/iphone/Maps/3party/MoPubSDK/Resources/MPUnmutedBtn@2x.png differ diff --git a/iphone/Maps/3party/MoPubSDK/Resources/MPUnmutedBtn@3x.png b/iphone/Maps/3party/MoPubSDK/Resources/MPUnmutedBtn@3x.png new file mode 100644 index 00000000000..6ec3ce68859 Binary files /dev/null and b/iphone/Maps/3party/MoPubSDK/Resources/MPUnmutedBtn@3x.png differ diff --git a/iphone/Maps/3party/MoPubSDK/Resources/MRAID.bundle/mraid.js b/iphone/Maps/3party/MoPubSDK/Resources/MRAID.bundle/mraid.js new file mode 100644 index 00000000000..5b0d84b9b23 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/Resources/MRAID.bundle/mraid.js @@ -0,0 +1,916 @@ +/* +Do not modify this version of the file. It will be copied over when any of the project's targets are built. +If you wish to modify mraid.js, modify the version located at mopub-sdk-common/mraid/mraid.js. +*/ +(function() { + var isIOS = (/iphone|ipad|ipod/i).test(window.navigator.userAgent.toLowerCase()); + if (isIOS) { + console = {}; + console.log = function(log) { + var iframe = document.createElement('iframe'); + iframe.setAttribute('src', 'ios-log: ' + log); + document.documentElement.appendChild(iframe); + iframe.parentNode.removeChild(iframe); + iframe = null; + }; + console.debug = console.info = console.warn = console.error = console.log; + } +}()); + + +(function() { + var mraid = window.mraid = {}; + + ////////////////////////////////////////////////////////////////////////////////////////////////// + + // Bridge interface to SDK + + var bridge = window.mraidbridge = { + nativeSDKFiredReady: false, + nativeCallQueue: [], + nativeCallInFlight: false, + lastSizeChangeProperties: null + }; + + bridge.fireChangeEvent = function(properties) { + for (var p in properties) { + if (properties.hasOwnProperty(p)) { + // Change handlers defined by MRAID below + var handler = changeHandlers[p]; + handler(properties[p]); + } + } + }; + + bridge.nativeCallComplete = function(command) { + if (this.nativeCallQueue.length === 0) { + this.nativeCallInFlight = false; + return; + } + + var nextCall = this.nativeCallQueue.pop(); + window.location = nextCall; + }; + + bridge.executeNativeCall = function(args) { + var command = args.shift(); + + if (!this.nativeSDKFiredReady) { + console.log('rejecting ' + command + ' because mraid is not ready'); + bridge.notifyErrorEvent('mraid is not ready', command); + return; + } + + var call = 'mraid://' + command; + + var key, value; + var isFirstArgument = true; + + for (var i = 0; i < args.length; i += 2) { + key = args[i]; + value = args[i + 1]; + + if (value === null) continue; + + if (isFirstArgument) { + call += '?'; + isFirstArgument = false; + } else { + call += '&'; + } + + call += encodeURIComponent(key) + '=' + encodeURIComponent(value); + } + + if (this.nativeCallInFlight) { + this.nativeCallQueue.push(call); + } else { + this.nativeCallInFlight = true; + window.location = call; + } + }; + + + bridge.setCurrentPosition = function(x, y, width, height) { + currentPosition = { + x: x, + y: y, + width: width, + height: height + }; + broadcastEvent(EVENTS.INFO, 'Set current position to ' + stringify(currentPosition)); + }; + + bridge.setDefaultPosition = function(x, y, width, height) { + defaultPosition = { + x: x, + y: y, + width: width, + height: height + }; + broadcastEvent(EVENTS.INFO, 'Set default position to ' + stringify(defaultPosition)); + }; + + bridge.setMaxSize = function(width, height) { + maxSize = { + width: width, + height: height + }; + + expandProperties.width = width; + expandProperties.height = height; + + broadcastEvent(EVENTS.INFO, 'Set max size to ' + stringify(maxSize)); + }; + + bridge.setPlacementType = function(_placementType) { + placementType = _placementType; + broadcastEvent(EVENTS.INFO, 'Set placement type to ' + stringify(placementType)); + }; + + bridge.setScreenSize = function(width, height) { + screenSize = { + width: width, + height: height + }; + broadcastEvent(EVENTS.INFO, 'Set screen size to ' + stringify(screenSize)); + }; + + bridge.setState = function(_state) { + state = _state; + broadcastEvent(EVENTS.INFO, 'Set state to ' + stringify(state)); + broadcastEvent(EVENTS.STATECHANGE, state); + }; + + bridge.setIsViewable = function(_isViewable) { + isViewable = _isViewable; + broadcastEvent(EVENTS.INFO, 'Set isViewable to ' + stringify(isViewable)); + broadcastEvent(EVENTS.VIEWABLECHANGE, isViewable); + }; + + bridge.setSupports = function(sms, tel, calendar, storePicture, inlineVideo) { + supportProperties = { + sms: sms, + tel: tel, + calendar: calendar, + storePicture: storePicture, + inlineVideo: inlineVideo + }; + }; + + bridge.notifyReadyEvent = function() { + this.nativeSDKFiredReady = true; + broadcastEvent(EVENTS.READY); + }; + + bridge.notifyErrorEvent = function(message, action) { + broadcastEvent(EVENTS.ERROR, message, action); + }; + + // Temporary aliases while we migrate to the new API + bridge.fireReadyEvent = bridge.notifyReadyEvent; + bridge.fireErrorEvent = bridge.notifyErrorEvent; + + bridge.notifySizeChangeEvent = function(width, height) { + if (this.lastSizeChangeProperties && + width == this.lastSizeChangeProperties.width && height == this.lastSizeChangeProperties.height) { + return; + } + + this.lastSizeChangeProperties = { + width: width, + height: height + }; + broadcastEvent(EVENTS.SIZECHANGE, width, height); + }; + + bridge.notifyStateChangeEvent = function() { + if (state === STATES.LOADING) { + broadcastEvent(EVENTS.INFO, 'Native SDK initialized.'); + } + + broadcastEvent(EVENTS.INFO, 'Set state to ' + stringify(state)); + broadcastEvent(EVENTS.STATECHANGE, state); + }; + + bridge.notifyViewableChangeEvent = function() { + broadcastEvent(EVENTS.INFO, 'Set isViewable to ' + stringify(isViewable)); + broadcastEvent(EVENTS.VIEWABLECHANGE, isViewable); + }; + + + // Constants. //////////////////////////////////////////////////////////////////////////////////// + + var VERSION = mraid.VERSION = '2.0'; + + var STATES = mraid.STATES = { + LOADING: 'loading', + DEFAULT: 'default', + EXPANDED: 'expanded', + HIDDEN: 'hidden', + RESIZED: 'resized' + }; + + var EVENTS = mraid.EVENTS = { + ERROR: 'error', + INFO: 'info', + READY: 'ready', + STATECHANGE: 'stateChange', + VIEWABLECHANGE: 'viewableChange', + SIZECHANGE: 'sizeChange' + }; + + var PLACEMENT_TYPES = mraid.PLACEMENT_TYPES = { + UNKNOWN: 'unknown', + INLINE: 'inline', + INTERSTITIAL: 'interstitial' + }; + + // External MRAID state: may be directly or indirectly modified by the ad JS. //////////////////// + + // Properties which define the behavior of an expandable ad. + var expandProperties = { + width: false, + height: false, + useCustomClose: false, + isModal: true + }; + + var resizeProperties = { + width: false, + height: false, + offsetX: false, + offsetY: false, + customClosePosition: 'top-right', + allowOffscreen: true + }; + + var orientationProperties = { + allowOrientationChange: true, + forceOrientation: "none" + }; + + var supportProperties = { + sms: false, + tel: false, + calendar: false, + storePicture: false, + inlineVideo: false + }; + + // default is undefined so that notifySizeChangeEvent can track changes + var lastSizeChangeProperties; + + var maxSize = {}; + + var currentPosition = {}; + + var defaultPosition = {}; + + var screenSize = {}; + + var hasSetCustomClose = false; + + var listeners = {}; + + // Internal MRAID state. Modified by the native SDK. ///////////////////////////////////////////// + + var state = STATES.LOADING; + + var isViewable = false; + + var placementType = PLACEMENT_TYPES.UNKNOWN; + + var hostSDKVersion = { + 'major': 0, + 'minor': 0, + 'patch': 0 + }; + + ////////////////////////////////////////////////////////////////////////////////////////////////// + + var EventListeners = function(event) { + this.event = event; + this.count = 0; + var listeners = {}; + + this.add = function(func) { + var id = String(func); + if (!listeners[id]) { + listeners[id] = func; + this.count++; + } + }; + + this.remove = function(func) { + var id = String(func); + if (listeners[id]) { + listeners[id] = null; + delete listeners[id]; + this.count--; + return true; + } else { + return false; + } + }; + + this.removeAll = function() { + for (var id in listeners) { + if (listeners.hasOwnProperty(id)) this.remove(listeners[id]); + } + }; + + this.broadcast = function(args) { + for (var id in listeners) { + if (listeners.hasOwnProperty(id)) listeners[id].apply(mraid, args); + } + }; + + this.toString = function() { + var out = [event, ':']; + for (var id in listeners) { + if (listeners.hasOwnProperty(id)) out.push('|', id, '|'); + } + return out.join(''); + }; + }; + + var broadcastEvent = function() { + var args = new Array(arguments.length); + var l = arguments.length; + for (var i = 0; i < l; i++) args[i] = arguments[i]; + var event = args.shift(); + if (listeners[event]) listeners[event].broadcast(args); + }; + + var contains = function(value, array) { + for (var i in array) { + if (array[i] === value) return true; + } + return false; + }; + + var clone = function(obj) { + if (obj === null) return null; + var f = function() {}; + f.prototype = obj; + return new f(); + }; + + var stringify = function(obj) { + if (typeof obj === 'object') { + var out = []; + if (obj.push) { + // Array. + for (var p in obj) out.push(obj[p]); + return '[' + out.join(',') + ']'; + } else { + // Other object. + for (var p in obj) out.push("'" + p + "': " + obj[p]); + return '{' + out.join(',') + '}'; + } + } else return String(obj); + }; + + var trim = function(str) { + return str.replace(/^\s+|\s+$/g, ''); + }; + + // Functions that will be invoked by the native SDK whenever a "change" event occurs. + var changeHandlers = { + state: function(val) { + if (state === STATES.LOADING) { + broadcastEvent(EVENTS.INFO, 'Native SDK initialized.'); + } + state = val; + broadcastEvent(EVENTS.INFO, 'Set state to ' + stringify(val)); + broadcastEvent(EVENTS.STATECHANGE, state); + }, + + viewable: function(val) { + isViewable = val; + broadcastEvent(EVENTS.INFO, 'Set isViewable to ' + stringify(val)); + broadcastEvent(EVENTS.VIEWABLECHANGE, isViewable); + }, + + placementType: function(val) { + broadcastEvent(EVENTS.INFO, 'Set placementType to ' + stringify(val)); + placementType = val; + }, + + sizeChange: function(val) { + broadcastEvent(EVENTS.INFO, 'Set screenSize to ' + stringify(val)); + for (var key in val) { + if (val.hasOwnProperty(key)) screenSize[key] = val[key]; + } + }, + + supports: function(val) { + broadcastEvent(EVENTS.INFO, 'Set supports to ' + stringify(val)); + supportProperties = val; + }, + + hostSDKVersion: function(val) { + // val is expected to be formatted like 'X.Y.Z[-+]identifier'. + var versions = val.split('.').map(function(version) { + return parseInt(version, 10); + }).filter(function(version) { + return version >= 0; + }); + + if (versions.length >= 3) { + hostSDKVersion['major'] = parseInt(versions[0], 10); + hostSDKVersion['minor'] = parseInt(versions[1], 10); + hostSDKVersion['patch'] = parseInt(versions[2], 10); + broadcastEvent(EVENTS.INFO, 'Set hostSDKVersion to ' + stringify(hostSDKVersion)); + } + } + }; + + var validate = function(obj, validators, action, merge) { + if (!merge) { + // Check to see if any required properties are missing. + if (obj === null) { + broadcastEvent(EVENTS.ERROR, 'Required object not provided.', action); + return false; + } else { + for (var i in validators) { + if (validators.hasOwnProperty(i) && obj[i] === undefined) { + broadcastEvent(EVENTS.ERROR, 'Object is missing required property: ' + i, action); + return false; + } + } + } + } + + for (var prop in obj) { + var validator = validators[prop]; + var value = obj[prop]; + if (validator && !validator(value)) { + // Failed validation. + broadcastEvent(EVENTS.ERROR, 'Value of property ' + prop + ' is invalid: ' + value, action); + return false; + } + } + return true; + }; + + var expandPropertyValidators = { + useCustomClose: function(v) { return (typeof v === 'boolean'); }, + }; + + ////////////////////////////////////////////////////////////////////////////////////////////////// + + mraid.addEventListener = function(event, listener) { + if (!event || !listener) { + broadcastEvent(EVENTS.ERROR, 'Both event and listener are required.', 'addEventListener'); + } else if (!contains(event, EVENTS)) { + broadcastEvent(EVENTS.ERROR, 'Unknown MRAID event: ' + event, 'addEventListener'); + } else { + if (!listeners[event]) { + listeners[event] = new EventListeners(event); + } + listeners[event].add(listener); + } + }; + + mraid.close = function() { + if (state === STATES.HIDDEN) { + broadcastEvent(EVENTS.ERROR, 'Ad cannot be closed when it is already hidden.', + 'close'); + } else bridge.executeNativeCall(['close']); + }; + + mraid.expand = function(URL) { + if (!(this.getState() === STATES.DEFAULT || this.getState() === STATES.RESIZED)) { + broadcastEvent(EVENTS.ERROR, 'Ad can only be expanded from the default or resized state.', 'expand'); + } else { + var args = ['expand', + 'shouldUseCustomClose', expandProperties.useCustomClose + ]; + + if (URL) { + args = args.concat(['url', URL]); + } + + bridge.executeNativeCall(args); + } + }; + + mraid.getExpandProperties = function() { + var properties = { + width: expandProperties.width, + height: expandProperties.height, + useCustomClose: expandProperties.useCustomClose, + isModal: expandProperties.isModal + }; + return properties; + }; + + + mraid.getCurrentPosition = function() { + return { + x: currentPosition.x, + y: currentPosition.y, + width: currentPosition.width, + height: currentPosition.height + }; + }; + + mraid.getDefaultPosition = function() { + return { + x: defaultPosition.x, + y: defaultPosition.y, + width: defaultPosition.width, + height: defaultPosition.height + }; + }; + + mraid.getMaxSize = function() { + return { + width: maxSize.width, + height: maxSize.height + }; + }; + + mraid.getPlacementType = function() { + return placementType; + }; + + mraid.getScreenSize = function() { + return { + width: screenSize.width, + height: screenSize.height + }; + }; + + mraid.getState = function() { + return state; + }; + + mraid.isViewable = function() { + return isViewable; + }; + + mraid.getVersion = function() { + return mraid.VERSION; + }; + + mraid.open = function(URL) { + if (!URL) broadcastEvent(EVENTS.ERROR, 'URL is required.', 'open'); + else bridge.executeNativeCall(['open', 'url', URL]); + }; + + mraid.removeEventListener = function(event, listener) { + if (!event) { + broadcastEvent(EVENTS.ERROR, 'Event is required.', 'removeEventListener'); + return; + } + + if (listener) { + // If we have a valid event, we'll try to remove the listener from it. + var success = false; + if (listeners[event]) { + success = listeners[event].remove(listener); + } + + // If we didn't have a valid event or couldn't remove the listener from the event, broadcast an error and return early. + if (!success) { + broadcastEvent(EVENTS.ERROR, 'Listener not currently registered for event.', 'removeEventListener'); + return; + } + + } else if (!listener && listeners[event]) { + listeners[event].removeAll(); + } + + if (listeners[event] && listeners[event].count === 0) { + listeners[event] = null; + delete listeners[event]; + } + }; + + mraid.setExpandProperties = function(properties) { + if (validate(properties, expandPropertyValidators, 'setExpandProperties', true)) { + if (properties.hasOwnProperty('useCustomClose')) { + expandProperties.useCustomClose = properties.useCustomClose; + } + } + }; + + mraid.useCustomClose = function(shouldUseCustomClose) { + expandProperties.useCustomClose = shouldUseCustomClose; + hasSetCustomClose = true; + bridge.executeNativeCall(['usecustomclose', 'shouldUseCustomClose', shouldUseCustomClose]); + }; + + // MRAID 2.0 APIs //////////////////////////////////////////////////////////////////////////////// + + mraid.createCalendarEvent = function(parameters) { + CalendarEventParser.initialize(parameters); + if (CalendarEventParser.parse()) { + bridge.executeNativeCall(CalendarEventParser.arguments); + } else { + broadcastEvent(EVENTS.ERROR, CalendarEventParser.errors[0], 'createCalendarEvent'); + } + }; + + mraid.supports = function(feature) { + return supportProperties[feature]; + }; + + mraid.playVideo = function(uri) { + if (!mraid.isViewable()) { + broadcastEvent(EVENTS.ERROR, 'playVideo cannot be called until the ad is viewable', 'playVideo'); + return; + } + + if (!uri) { + broadcastEvent(EVENTS.ERROR, 'playVideo must be called with a valid URI', 'playVideo'); + } else { + bridge.executeNativeCall(['playVideo', 'uri', uri]); + } + }; + + mraid.storePicture = function(uri) { + if (!mraid.isViewable()) { + broadcastEvent(EVENTS.ERROR, 'storePicture cannot be called until the ad is viewable', 'storePicture'); + return; + } + + if (!uri) { + broadcastEvent(EVENTS.ERROR, 'storePicture must be called with a valid URI', 'storePicture'); + } else { + bridge.executeNativeCall(['storePicture', 'uri', uri]); + } + }; + + + var resizePropertyValidators = { + width: function(v) { + return !isNaN(v) && v > 0; + }, + height: function(v) { + return !isNaN(v) && v > 0; + }, + offsetX: function(v) { + return !isNaN(v); + }, + offsetY: function(v) { + return !isNaN(v); + }, + customClosePosition: function(v) { + return (typeof v === 'string' && + ['top-right', 'bottom-right', 'top-left', 'bottom-left', 'center', 'top-center', 'bottom-center'].indexOf(v) > -1); + }, + allowOffscreen: function(v) { + return (typeof v === 'boolean'); + } + }; + + mraid.setOrientationProperties = function(properties) { + + if (properties.hasOwnProperty('allowOrientationChange')) { + orientationProperties.allowOrientationChange = properties.allowOrientationChange; + } + + if (properties.hasOwnProperty('forceOrientation')) { + orientationProperties.forceOrientation = properties.forceOrientation; + } + + var args = ['setOrientationProperties', + 'allowOrientationChange', orientationProperties.allowOrientationChange, + 'forceOrientation', orientationProperties.forceOrientation + ]; + bridge.executeNativeCall(args); + }; + + mraid.getOrientationProperties = function() { + return { + allowOrientationChange: orientationProperties.allowOrientationChange, + forceOrientation: orientationProperties.forceOrientation + }; + }; + + mraid.resize = function() { + if (!(this.getState() === STATES.DEFAULT || this.getState() === STATES.RESIZED)) { + broadcastEvent(EVENTS.ERROR, 'Ad can only be resized from the default or resized state.', 'resize'); + } else if (!resizeProperties.width || !resizeProperties.height) { + broadcastEvent(EVENTS.ERROR, 'Must set resize properties before calling resize()', 'resize'); + } else { + var args = ['resize', + 'width', resizeProperties.width, + 'height', resizeProperties.height, + 'offsetX', resizeProperties.offsetX || 0, + 'offsetY', resizeProperties.offsetY || 0, + 'customClosePosition', resizeProperties.customClosePosition, + 'allowOffscreen', !!resizeProperties.allowOffscreen + ]; + + bridge.executeNativeCall(args); + } + }; + + mraid.getResizeProperties = function() { + var properties = { + width: resizeProperties.width, + height: resizeProperties.height, + offsetX: resizeProperties.offsetX, + offsetY: resizeProperties.offsetY, + customClosePosition: resizeProperties.customClosePosition, + allowOffscreen: resizeProperties.allowOffscreen + }; + return properties; + }; + + mraid.setResizeProperties = function(properties) { + if (validate(properties, resizePropertyValidators, 'setResizeProperties', true)) { + + var desiredProperties = ['width', 'height', 'offsetX', 'offsetY', 'customClosePosition', 'allowOffscreen']; + + var length = desiredProperties.length; + + for (var i = 0; i < length; i++) { + var propname = desiredProperties[i]; + if (properties.hasOwnProperty(propname)) { + resizeProperties[propname] = properties[propname]; + } + } + } + }; + + // Determining SDK version /////////////////////////////////////////////////////////////////////// + + mraid.getHostSDKVersion = function() { + return hostSDKVersion; + } + + // Calendar helpers ////////////////////////////////////////////////////////////////////////////// + + var CalendarEventParser = { + initialize: function(parameters) { + this.parameters = parameters; + this.errors = []; + this.arguments = ['createCalendarEvent']; + }, + + parse: function() { + if (!this.parameters) { + this.errors.push('The object passed to createCalendarEvent cannot be null.'); + } else { + this.parseDescription(); + this.parseLocation(); + this.parseSummary(); + this.parseStartAndEndDates(); + this.parseReminder(); + this.parseRecurrence(); + this.parseTransparency(); + } + + var errorCount = this.errors.length; + if (errorCount) { + this.arguments.length = 0; + } + + return (errorCount === 0); + }, + + parseDescription: function() { + this._processStringValue('description'); + }, + + parseLocation: function() { + this._processStringValue('location'); + }, + + parseSummary: function() { + this._processStringValue('summary'); + }, + + parseStartAndEndDates: function() { + this._processDateValue('start'); + this._processDateValue('end'); + }, + + parseReminder: function() { + var reminder = this._getParameter('reminder'); + if (!reminder) { + return; + } + + if (reminder < 0) { + this.arguments.push('relativeReminder'); + this.arguments.push(parseInt(reminder) / 1000); + } else { + this.arguments.push('absoluteReminder'); + this.arguments.push(reminder); + } + }, + + parseRecurrence: function() { + var recurrenceDict = this._getParameter('recurrence'); + if (!recurrenceDict) { + return; + } + + this.parseRecurrenceInterval(recurrenceDict); + this.parseRecurrenceFrequency(recurrenceDict); + this.parseRecurrenceEndDate(recurrenceDict); + this.parseRecurrenceArrayValue(recurrenceDict, 'daysInWeek'); + this.parseRecurrenceArrayValue(recurrenceDict, 'daysInMonth'); + this.parseRecurrenceArrayValue(recurrenceDict, 'daysInYear'); + this.parseRecurrenceArrayValue(recurrenceDict, 'monthsInYear'); + }, + + parseTransparency: function() { + var validValues = ['opaque', 'transparent']; + + if (this.parameters.hasOwnProperty('transparency')) { + var transparency = this.parameters.transparency; + if (contains(transparency, validValues)) { + this.arguments.push('transparency'); + this.arguments.push(transparency); + } else { + this.errors.push('transparency must be opaque or transparent'); + } + } + }, + + parseRecurrenceArrayValue: function(recurrenceDict, kind) { + if (recurrenceDict.hasOwnProperty(kind)) { + var array = recurrenceDict[kind]; + if (!array || !(array instanceof Array)) { + this.errors.push(kind + ' must be an array.'); + } else { + var arrayStr = array.join(','); + this.arguments.push(kind); + this.arguments.push(arrayStr); + } + } + }, + + parseRecurrenceInterval: function(recurrenceDict) { + if (recurrenceDict.hasOwnProperty('interval')) { + var interval = recurrenceDict.interval; + if (!interval) { + this.errors.push('Recurrence interval cannot be null.'); + } else { + this.arguments.push('interval'); + this.arguments.push(interval); + } + } else { + // If a recurrence rule was specified without an interval, use a default value of 1. + this.arguments.push('interval'); + this.arguments.push(1); + } + }, + + parseRecurrenceFrequency: function(recurrenceDict) { + if (recurrenceDict.hasOwnProperty('frequency')) { + var frequency = recurrenceDict.frequency; + var validFrequencies = ['daily', 'weekly', 'monthly', 'yearly']; + if (contains(frequency, validFrequencies)) { + this.arguments.push('frequency'); + this.arguments.push(frequency); + } else { + this.errors.push('Recurrence frequency must be one of: "daily", "weekly", "monthly", "yearly".'); + } + } + }, + + parseRecurrenceEndDate: function(recurrenceDict) { + var expires = recurrenceDict.expires; + + if (!expires) { + return; + } + + this.arguments.push('expires'); + this.arguments.push(expires); + }, + + _getParameter: function(key) { + if (this.parameters.hasOwnProperty(key)) { + return this.parameters[key]; + } + + return null; + }, + + _processStringValue: function(kind) { + if (this.parameters.hasOwnProperty(kind)) { + var value = this.parameters[kind]; + this.arguments.push(kind); + this.arguments.push(value); + } + }, + + _processDateValue: function(kind) { + if (this.parameters.hasOwnProperty(kind)) { + var dateString = this._getParameter(kind); + this.arguments.push(kind); + this.arguments.push(dateString); + } + } + }; +}()); \ No newline at end of file diff --git a/iphone/Maps/3party/MoPubSDK/RewardedVideo/Internal/MPMoPubRewardedPlayableCustomEvent.h b/iphone/Maps/3party/MoPubSDK/RewardedVideo/Internal/MPMoPubRewardedPlayableCustomEvent.h new file mode 100644 index 00000000000..015a6e30bea --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/RewardedVideo/Internal/MPMoPubRewardedPlayableCustomEvent.h @@ -0,0 +1,15 @@ +// +// MPMoPubRewardedPlayableCustomEvent.h +// MoPubSDK +// +// Copyright © 2016 MoPub. All rights reserved. +// + +#import "MPRewardedVideoCustomEvent.h" +#import "MPPrivateRewardedVideoCustomEventDelegate.h" + +@interface MPMoPubRewardedPlayableCustomEvent : MPRewardedVideoCustomEvent +@property (nonatomic, readonly) NSTimeInterval countdownDuration; + +@property (nonatomic, weak) id delegate; +@end diff --git a/iphone/Maps/3party/MoPubSDK/RewardedVideo/Internal/MPMoPubRewardedPlayableCustomEvent.m b/iphone/Maps/3party/MoPubSDK/RewardedVideo/Internal/MPMoPubRewardedPlayableCustomEvent.m new file mode 100644 index 00000000000..365ce24ec8d --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/RewardedVideo/Internal/MPMoPubRewardedPlayableCustomEvent.m @@ -0,0 +1,173 @@ +// +// MPMoPubRewardedPlayableCustomEvent.m +// MoPubSDK +// +// Copyright © 2016 MoPub. All rights reserved. +// + +#import "MPMoPubRewardedPlayableCustomEvent.h" +#import "MPMRAIDInterstitialViewController.h" +#import "MPAdConfiguration.h" +#import "MPInstanceProvider.h" +#import "MPLogging.h" +#import "MPRewardedVideoError.h" +#import "MPCountdownTimerView.h" + +const NSTimeInterval kDefaultCountdownTimerIntervalInSeconds = 30; + +@interface MPMoPubRewardedPlayableCustomEvent() +@property (nonatomic, assign) BOOL adAvailable; +@property (nonatomic, strong) MPMRAIDInterstitialViewController *interstitial; +@property (nonatomic, strong) MPCountdownTimerView *timerView; +@property (nonatomic, assign) BOOL userRewarded; +@end + +@implementation MPMoPubRewardedPlayableCustomEvent + +- (void)dealloc { + [_timerView stopAndSignalCompletion:NO]; +} + +// Lazy initialization property for the MRAID interstitial. +- (MPMRAIDInterstitialViewController *)interstitial { + if (_interstitial == nil) { + _interstitial = [[MPMRAIDInterstitialViewController alloc] initWithAdConfiguration:self.delegate.configuration]; + } + + return _interstitial; +} + +// Retrieves a valid countdown duration to use for the timer. In the event that `rewardedPlayableDuration` +// from `MPAdConfiguration` is less than zero, the default value `kDefaultCountdownTimerIntervalInSeconds` +// will be used instead. +- (NSTimeInterval)countdownDuration { + NSTimeInterval duration = self.delegate.configuration.rewardedPlayableDuration; + if (duration <= 0) { + duration = kDefaultCountdownTimerIntervalInSeconds; + } + + return duration; +} + +// Shows the native close button and deallocates the countdown timer since it will no +// longer be used. +- (void)showCloseButton { + [self.interstitial setCloseButtonStyle:MPInterstitialCloseButtonStyleAlwaysVisible]; + [self.timerView removeFromSuperview]; + self.timerView = nil; +} + +// Only reward the user once; either by countdown timer elapsing or rewarding on click +// (if configured). +- (void)rewardUserWithConfiguration:(MPAdConfiguration *)configuration timerHasElapsed:(BOOL)hasElasped { + if (!self.userRewarded && (hasElasped || configuration.rewardedPlayableShouldRewardOnClick)) { + MPLogInfo(@"MoPub rewarded playable user rewarded."); + + [self.delegate rewardedVideoShouldRewardUserForCustomEvent:self reward:configuration.selectedReward]; + self.userRewarded = YES; + } +} + +#pragma mark - MPRewardedVideoCustomEvent + +@dynamic delegate; + +- (void)requestRewardedVideoWithCustomEventInfo:(NSDictionary *)info { + MPLogInfo(@"Loading MoPub rewarded playable"); + self.interstitial.delegate = self; + + [self.interstitial setCloseButtonStyle:MPInterstitialCloseButtonStyleAlwaysHidden]; + [self.interstitial startLoading]; +} + +- (BOOL)hasAdAvailable { + return self.adAvailable; +} + +- (void)handleAdPlayedForCustomEventNetwork { + // no-op +} + +- (void)handleCustomEventInvalidated { + // no-op +} + +- (void)presentRewardedVideoFromViewController:(UIViewController *)viewController { + if (self.hasAdAvailable) { + // Add the countdown timer to the interstitial and start the timer. + self.timerView = [[MPCountdownTimerView alloc] initWithFrame:viewController.view.bounds duration:self.countdownDuration]; + [self.interstitial.view addSubview:self.timerView]; + + typeof(self) __weak weakSelf = self; + [self.timerView startWithTimerCompletion:^(BOOL hasElapsed) { + [weakSelf rewardUserWithConfiguration:self.configuration timerHasElapsed:hasElapsed]; + [weakSelf showCloseButton]; + }]; + + [self.interstitial presentInterstitialFromViewController:viewController]; + } + else { + MPLogInfo(@"Failed to show MoPub rewarded playable"); + NSError *error = [NSError errorWithDomain:MoPubRewardedVideoAdsSDKDomain code:MPRewardedVideoAdErrorNoAdsAvailable userInfo:nil]; + [self.delegate rewardedVideoDidFailToPlayForCustomEvent:self error:error]; + [self showCloseButton]; + } +} + +#pragma mark - MPInterstitialViewControllerDelegate + +- (void)interstitialDidLoadAd:(MPInterstitialViewController *)interstitial { + MPLogInfo(@"MoPub rewarded playable did load"); + self.adAvailable = YES; + [self.delegate rewardedVideoDidLoadAdForCustomEvent:self]; +} + +- (void)interstitialDidAppear:(MPInterstitialViewController *)interstitial { + MPLogInfo(@"MoPub rewarded playable did appear"); + [self.delegate rewardedVideoDidAppearForCustomEvent:self]; +} + +- (void)interstitialWillAppear:(MPInterstitialViewController *)interstitial { + MPLogInfo(@"MoPub rewarded playable will appear"); + [self.delegate rewardedVideoWillAppearForCustomEvent:self]; +} + +- (void)interstitialDidFailToLoadAd:(MPInterstitialViewController *)interstitial { + MPLogInfo(@"MoPub rewarded playable failed to load"); + self.adAvailable = NO; + [self.delegate rewardedVideoDidFailToLoadAdForCustomEvent:self error:nil]; +} + +- (void)interstitialWillDisappear:(MPInterstitialViewController *)interstitial { + [self.delegate rewardedVideoWillDisappearForCustomEvent:self]; +} + +- (void)interstitialDidDisappear:(MPInterstitialViewController *)interstitial { + self.adAvailable = NO; + [self.timerView stopAndSignalCompletion:NO]; + [self.delegate rewardedVideoDidDisappearForCustomEvent:self]; + + // Get rid of the interstitial view controller when done with it so we don't hold on longer than needed + self.interstitial = nil; +} + +- (void)interstitialDidReceiveTapEvent:(MPInterstitialViewController *)interstitial { + [self rewardUserWithConfiguration:self.configuration timerHasElapsed:NO]; + [self.delegate rewardedVideoDidReceiveTapEventForCustomEvent:self]; +} + +- (void)interstitialWillLeaveApplication:(MPInterstitialViewController *)interstitial { + [self.delegate rewardedVideoWillLeaveApplicationForCustomEvent:self]; +} + +#pragma mark - MPPrivateRewardedVideoCustomEventDelegate + +- (NSString *)adUnitId { + return [self.delegate adUnitId]; +} + +- (MPAdConfiguration *)configuration { + return [self.delegate configuration]; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/RewardedVideo/Internal/MPMoPubRewardedVideoCustomEvent.h b/iphone/Maps/3party/MoPubSDK/RewardedVideo/Internal/MPMoPubRewardedVideoCustomEvent.h new file mode 100644 index 00000000000..5fc7bf8d48e --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/RewardedVideo/Internal/MPMoPubRewardedVideoCustomEvent.h @@ -0,0 +1,15 @@ +// +// MPMoPubRewardedVideoCustomEvent.h +// MoPubSDK + +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import "MPRewardedVideoCustomEvent.h" +#import "MPPrivateRewardedVideoCustomEventDelegate.h" + +@interface MPMoPubRewardedVideoCustomEvent : MPRewardedVideoCustomEvent + +@property (nonatomic, weak) id delegate; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/RewardedVideo/Internal/MPMoPubRewardedVideoCustomEvent.m b/iphone/Maps/3party/MoPubSDK/RewardedVideo/Internal/MPMoPubRewardedVideoCustomEvent.m new file mode 100644 index 00000000000..01fae42b6fe --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/RewardedVideo/Internal/MPMoPubRewardedVideoCustomEvent.m @@ -0,0 +1,134 @@ +// +// MPMoPubRewardedVideoCustomEvent.m +// MoPubSDK +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import "MPMoPubRewardedVideoCustomEvent.h" +#import "MPMRAIDInterstitialViewController.h" +#import "MPInstanceProvider.h" +#import "MPLogging.h" +#import "MPRewardedVideoReward.h" +#import "MPAdConfiguration.h" +#import "MPRewardedVideoAdapter.h" +#import "MPRewardedVideoReward.h" +#import "MPRewardedVideoError.h" + +@interface MPMoPubRewardedVideoCustomEvent() + +@property (nonatomic) MPMRAIDInterstitialViewController *interstitial; +@property (nonatomic) BOOL adAvailable; + +@end + +@implementation MPMoPubRewardedVideoCustomEvent + +@dynamic delegate; + +- (void)requestRewardedVideoWithCustomEventInfo:(NSDictionary *)info +{ + MPLogInfo(@"Loading MoPub rewarded video"); + self.interstitial = [[MPInstanceProvider sharedProvider] buildMPMRAIDInterstitialViewControllerWithDelegate:self + configuration:[self.delegate configuration]]; + + [self.interstitial setCloseButtonStyle:MPInterstitialCloseButtonStyleAlwaysHidden]; + [self.interstitial startLoading]; +} + +- (BOOL)hasAdAvailable +{ + return self.adAvailable; +} + +- (void)handleAdPlayedForCustomEventNetwork +{ + // no-op +} + +- (void)handleCustomEventInvalidated +{ + // no-op +} + +- (void)presentRewardedVideoFromViewController:(UIViewController *)viewController +{ + if ([self hasAdAvailable]) { + [self.interstitial presentInterstitialFromViewController:viewController]; + } else { + MPLogInfo(@"Failed to show MoPub rewarded video"); + NSError *error = [NSError errorWithDomain:MoPubRewardedVideoAdsSDKDomain code:MPRewardedVideoAdErrorNoAdsAvailable userInfo:nil]; + [self.delegate rewardedVideoDidFailToPlayForCustomEvent:self error:error]; + } +} + +#pragma mark - MPMRAIDInterstitialViewControllerDelegate + +- (void)interstitialDidLoadAd:(MPInterstitialViewController *)interstitial +{ + MPLogInfo(@"MoPub rewarded video did load"); + self.adAvailable = YES; + [self.delegate rewardedVideoDidLoadAdForCustomEvent:self]; +} + +- (void)interstitialDidAppear:(MPInterstitialViewController *)interstitial +{ + MPLogInfo(@"MoPub rewarded video did appear"); + [self.delegate rewardedVideoDidAppearForCustomEvent:self]; +} + +- (void)interstitialWillAppear:(MPInterstitialViewController *)interstitial +{ + MPLogInfo(@"MoPub rewarded video will appear"); + [self.delegate rewardedVideoWillAppearForCustomEvent:self]; +} + +- (void)interstitialDidFailToLoadAd:(MPInterstitialViewController *)interstitial +{ + MPLogInfo(@"MoPub rewarded video failed to load"); + self.adAvailable = NO; + [self.delegate rewardedVideoDidFailToLoadAdForCustomEvent:self error:nil]; +} + +- (void)interstitialWillDisappear:(MPInterstitialViewController *)interstitial +{ + [self.delegate rewardedVideoWillDisappearForCustomEvent:self]; +} + +- (void)interstitialDidDisappear:(MPInterstitialViewController *)interstitial +{ + self.adAvailable = NO; + [self.delegate rewardedVideoDidDisappearForCustomEvent:self]; + + // Get rid of the interstitial view controller when done with it so we don't hold on longer than needed + self.interstitial = nil; +} + +- (void)interstitialDidReceiveTapEvent:(MPInterstitialViewController *)interstitial +{ + [self.delegate rewardedVideoDidReceiveTapEventForCustomEvent:self]; +} + +- (void)interstitialWillLeaveApplication:(MPInterstitialViewController *)interstitial +{ + [self.delegate rewardedVideoWillLeaveApplicationForCustomEvent:self]; +} + +- (void)interstitialRewardedVideoEnded +{ + MPLogInfo(@"MoPub rewarded video finished playing."); + [self.delegate rewardedVideoShouldRewardUserForCustomEvent:self reward:[self configuration].selectedReward]; +} + +#pragma mark - MPPrivateRewardedVideoCustomEventDelegate +- (NSString *)adUnitId +{ + return [self.delegate adUnitId]; +} + +- (MPAdConfiguration *)configuration +{ + return [self.delegate configuration]; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/RewardedVideo/Internal/MPPrivateRewardedVideoCustomEventDelegate.h b/iphone/Maps/3party/MoPubSDK/RewardedVideo/Internal/MPPrivateRewardedVideoCustomEventDelegate.h new file mode 100644 index 00000000000..acb689084d7 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/RewardedVideo/Internal/MPPrivateRewardedVideoCustomEventDelegate.h @@ -0,0 +1,18 @@ +// +// MPPrivateRewardedVideoCustomEventDelegate.h +// MoPub +// +// Copyright © 2016 MoPub. All rights reserved. +// + +#import "MPRewardedVideoCustomEvent.h" + +@class MPAdConfiguration; +@class CLLocation; + +@protocol MPPrivateRewardedVideoCustomEventDelegate + +- (NSString *)adUnitId; +- (MPAdConfiguration *)configuration; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/RewardedVideo/Internal/MPRewardedVideo+Internal.h b/iphone/Maps/3party/MoPubSDK/RewardedVideo/Internal/MPRewardedVideo+Internal.h new file mode 100644 index 00000000000..2e80354637c --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/RewardedVideo/Internal/MPRewardedVideo+Internal.h @@ -0,0 +1,14 @@ +// +// MPRewardedVideo+Internal.h +// MoPubSDK +// Copyright © 2016 MoPub. All rights reserved. +// + +#import "MPRewardedVideo.h" + +@interface MPRewardedVideo (Internal) + ++ (MPRewardedVideo *)sharedInstance; +- (void)startRewardedVideoConnectionWithUrl:(NSURL *)url; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdManager.h b/iphone/Maps/3party/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdManager.h new file mode 100644 index 00000000000..ee9c2734ad2 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdManager.h @@ -0,0 +1,104 @@ +// +// MPRewardedVideoAdManager.h +// MoPubSDK +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import +#import + +@class MPRewardedVideoReward; +@class CLLocation; +@protocol MPRewardedVideoAdManagerDelegate; + +/** + * `MPRewardedVideoAdManager` represents a rewarded video for a single ad unit ID. This is the object that + * `MPRewardedVideo` uses to load and present the ad. + */ +@interface MPRewardedVideoAdManager : NSObject + +@property (nonatomic, weak) id delegate; +@property (nonatomic, readonly) NSString *adUnitID; +@property (nonatomic, strong) NSArray *mediationSettings; +@property (nonatomic, copy) NSString *customerId; + +/** + * An array of rewards that are available for the rewarded ad that can be selected when presenting the ad. + */ +@property (nonatomic, readonly) NSArray *availableRewards; + +/** + * The currently selected reward that will be awarded to the user upon completion of the ad. By default, + * this corresponds to the first reward in `availableRewards`. + */ +@property (nonatomic, readonly) MPRewardedVideoReward *selectedReward; + +- (instancetype)initWithAdUnitID:(NSString *)adUnitID delegate:(id)delegate; + +/** + * Returns the custom event class type. + */ +- (Class)customEventClass; + +/** + * Loads a rewarded video ad with the ad manager's ad unit ID. + * + * @param keywords A string representing a set of keywords that should be passed to the MoPub ad server to receive + * more relevant advertising. + * + * @param location Latitude/Longitude that are passed to the MoPub ad server + * If this method is called when an ad is already available and we haven't already played a video for the last time we loaded an ad, + * the object will simply notify the delegate that an ad loaded. + * + * @param customerId The user's id within the app. + * + * However, if an ad has been played for the last time a load was issued and load is called again, the method will request a new ad. + */ +- (void)loadRewardedVideoAdWithKeywords:(NSString *)keywords location:(CLLocation *)location customerId:(NSString *)customerId; + +/** + * Tells the caller whether the underlying ad network currently has an ad available for presentation. + */ +- (BOOL)hasAdAvailable; + +/** + * Plays a rewarded video ad, choosing the first reward from `availableRewards` to award the user. + * + * @param viewController Presents the rewarded video ad from viewController. + */ +- (void)presentRewardedVideoAdFromViewController:(UIViewController *)viewController __deprecated_msg("use presentRewardedVideoAdFromViewController:withReward: instead."); + +/** + * Plays a rewarded video ad. + * + * @param viewController Presents the rewarded video ad from viewController. + * @param reward A reward chosen from `availableRewards` to award the user upon completion. + * This value should not be `nil`. If the reward that is passed in did not come from `availableRewards`, + * this method will not present the rewarded ad and invoke `rewardedVideoDidFailToPlayForAdManager:error:`. + */ +- (void)presentRewardedVideoAdFromViewController:(UIViewController *)viewController withReward:(MPRewardedVideoReward *)reward; + +/** + * This method is called when another ad unit has played a rewarded video from the same network this ad manager's custom event + * represents. + */ +- (void)handleAdPlayedForCustomEventNetwork; + +@end + +@protocol MPRewardedVideoAdManagerDelegate + +- (void)rewardedVideoDidLoadForAdManager:(MPRewardedVideoAdManager *)manager; +- (void)rewardedVideoDidFailToLoadForAdManager:(MPRewardedVideoAdManager *)manager error:(NSError *)error; +- (void)rewardedVideoDidExpireForAdManager:(MPRewardedVideoAdManager *)manager; +- (void)rewardedVideoDidFailToPlayForAdManager:(MPRewardedVideoAdManager *)manager error:(NSError *)error; +- (void)rewardedVideoWillAppearForAdManager:(MPRewardedVideoAdManager *)manager; +- (void)rewardedVideoDidAppearForAdManager:(MPRewardedVideoAdManager *)manager; +- (void)rewardedVideoWillDisappearForAdManager:(MPRewardedVideoAdManager *)manager; +- (void)rewardedVideoDidDisappearForAdManager:(MPRewardedVideoAdManager *)manager; +- (void)rewardedVideoDidReceiveTapEventForAdManager:(MPRewardedVideoAdManager *)manager; +- (void)rewardedVideoWillLeaveApplicationForAdManager:(MPRewardedVideoAdManager *)manager; +- (void)rewardedVideoShouldRewardUserForAdManager:(MPRewardedVideoAdManager *)manager reward:(MPRewardedVideoReward *)reward; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdManager.m b/iphone/Maps/3party/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdManager.m new file mode 100644 index 00000000000..8951f8870f9 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdManager.m @@ -0,0 +1,292 @@ +// +// MPRewardedVideoAdManager.m +// MoPubSDK +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import "MPRewardedVideoAdManager.h" + +#import "MPAdServerCommunicator.h" +#import "MPAdServerURLBuilder.h" +#import "MPRewardedVideoAdapter.h" +#import "MPInstanceProvider.h" +#import "MPCoreInstanceProvider.h" +#import "MPRewardedVideoError.h" +#import "MPLogging.h" +#import "MoPub.h" + +@interface MPRewardedVideoAdManager () + +@property (nonatomic, strong) MPRewardedVideoAdapter *adapter; +@property (nonatomic, strong) MPAdServerCommunicator *communicator; +@property (nonatomic, strong) MPAdConfiguration *configuration; +@property (nonatomic, assign) BOOL loading; +@property (nonatomic, assign) BOOL playedAd; +@property (nonatomic, assign) BOOL ready; + +@end + +@implementation MPRewardedVideoAdManager + +- (instancetype)initWithAdUnitID:(NSString *)adUnitID delegate:(id)delegate +{ + if (self = [super init]) { + _adUnitID = [adUnitID copy]; + _communicator = [[MPCoreInstanceProvider sharedProvider] buildMPAdServerCommunicatorWithDelegate:self]; + _delegate = delegate; + } + + return self; +} + +- (void)dealloc +{ + [_communicator cancel]; +} + +- (NSArray *)availableRewards +{ + return self.configuration.availableRewards; +} + +- (MPRewardedVideoReward *)selectedReward +{ + return self.configuration.selectedReward; +} + +- (Class)customEventClass +{ + return self.configuration.customEventClass; +} + +- (BOOL)hasAdAvailable +{ + // If we've already played an ad, return NO since we allow one play per load. + if (self.playedAd) { + return NO; + } + return [self.adapter hasAdAvailable]; +} + +- (void)loadRewardedVideoAdWithKeywords:(NSString *)keywords location:(CLLocation *)location customerId:(NSString *)customerId +{ + // We will just tell the delegate that we have loaded an ad if we already have one ready. However, if we have already + // played a video for this ad manager, we will go ahead and request another ad from the server so we aren't potentially + // stuck playing ads from the same network for a prolonged period of time which could be unoptimal with respect to the waterfall. + if (self.ready && !self.playedAd) { + // If we already have an ad, do not set the customerId. We'll leave the customerId as the old one since the ad we currently have + // may be tied to an older customerId. + [self.delegate rewardedVideoDidLoadForAdManager:self]; + } else { + // This has multiple behaviors. For ads that require us to set the customID: (outside of load), this will overwrite the ad's previously + // set customerId. Other ads require customerId on presentation in which we will use this new id coming in when presenting the ad. + self.customerId = customerId; + [self loadAdWithURL:[MPAdServerURLBuilder URLWithAdUnitID:self.adUnitID + keywords:keywords + location:location + testing:NO]]; + } +} + +- (void)presentRewardedVideoAdFromViewController:(UIViewController *)viewController +{ + [self presentRewardedVideoAdFromViewController:viewController withReward:nil]; +} + +- (void)presentRewardedVideoAdFromViewController:(UIViewController *)viewController withReward:(MPRewardedVideoReward *)reward +{ + // If we've already played an ad, don't allow playing of another since we allow one play per load. + if (self.playedAd) { + NSError *error = [NSError errorWithDomain:MoPubRewardedVideoAdsSDKDomain code:MPRewardedVideoAdErrorAdAlreadyPlayed userInfo:nil]; + [self.delegate rewardedVideoDidFailToPlayForAdManager:self error:error]; + return; + } + + // No reward is specified + if (reward == nil) { + // Only a single currency; It should automatically select the only currency available. + if (self.availableRewards.count == 1) { + MPRewardedVideoReward * defaultReward = self.availableRewards[0]; + self.configuration.selectedReward = defaultReward; + } + // Unspecified rewards in a multicurrency situation are not allowed. + else { + NSError *error = [NSError errorWithDomain:MoPubRewardedVideoAdsSDKDomain code:MPRewardedVideoAdErrorNoRewardSelected userInfo:nil]; + [self.delegate rewardedVideoDidFailToPlayForAdManager:self error:error]; + return; + } + } + // Reward is specified + else { + // Verify that the reward exists in the list of available rewards. If it doesn't, fail to play the ad. + if (![self.availableRewards containsObject:reward]) { + NSError *error = [NSError errorWithDomain:MoPubRewardedVideoAdsSDKDomain code:MPRewardedVideoAdErrorInvalidReward userInfo:nil]; + [self.delegate rewardedVideoDidFailToPlayForAdManager:self error:error]; + return; + } + // Reward passes validation, set it as selected. + else { + self.configuration.selectedReward = reward; + } + } + + [self.adapter presentRewardedVideoFromViewController:viewController]; +} + +- (void)handleAdPlayedForCustomEventNetwork +{ + // We only need to notify the backing ad network if the ad is marked ready for display. + if (self.ready) { + [self.adapter handleAdPlayedForCustomEventNetwork]; + } +} + +#pragma mark - Private + +- (void)loadAdWithURL:(NSURL *)URL +{ + self.playedAd = NO; + + if (self.loading) { + MPLogWarn(@"Rewarded video manager is already loading an ad. " + @"Wait for previous load to finish."); + return; + } + + MPLogInfo(@"Rewarded video manager is loading ad with MoPub server URL: %@", URL); + + self.loading = YES; + [self.communicator loadURL:URL]; +} + +#pragma mark - MPAdServerCommunicatorDelegate + +- (void)communicatorDidReceiveAdConfiguration:(MPAdConfiguration *)configuration +{ + self.configuration = configuration; + + MPLogInfo(@"Rewarded video ad is fetching ad network type: %@", self.configuration.networkType); + + if (self.configuration.adUnitWarmingUp) { + MPLogInfo(kMPWarmingUpErrorLogFormatWithAdUnitID, self.adUnitID); + self.loading = NO; + NSError *error = [NSError errorWithDomain:MoPubRewardedVideoAdsSDKDomain code:MPRewardedVideoAdErrorAdUnitWarmingUp userInfo:nil]; + [self.delegate rewardedVideoDidFailToLoadForAdManager:self error:error]; + return; + } + + if ([self.configuration.networkType isEqualToString:kAdTypeClear]) { + MPLogInfo(kMPClearErrorLogFormatWithAdUnitID, self.adUnitID); + self.loading = NO; + NSError *error = [NSError errorWithDomain:MoPubRewardedVideoAdsSDKDomain code:MPRewardedVideoAdErrorNoAdsAvailable userInfo:nil]; + [self.delegate rewardedVideoDidFailToLoadForAdManager:self error:error]; + return; + } + + MPRewardedVideoAdapter *adapter = [[MPInstanceProvider sharedProvider] buildRewardedVideoAdapterWithDelegate:self]; + + if (!adapter) { + NSError *error = [NSError errorWithDomain:MoPubRewardedVideoAdsSDKDomain code:MPRewardedVideoAdErrorUnknown userInfo:nil]; + [self rewardedVideoDidFailToLoadForAdapter:nil error:error]; + return; + } + + self.adapter = adapter; + [self.adapter getAdWithConfiguration:configuration]; +} + +- (void)communicatorDidFailWithError:(NSError *)error +{ + self.ready = NO; + self.loading = NO; + + [self.delegate rewardedVideoDidFailToLoadForAdManager:self error:error]; +} + +#pragma mark - MPRewardedVideoAdapterDelegate + +- (id)instanceMediationSettingsForClass:(Class)aClass +{ + for (id settings in self.mediationSettings) { + if ([settings isKindOfClass:aClass]) { + return settings; + } + } + + return nil; +} + +- (void)rewardedVideoDidLoadForAdapter:(MPRewardedVideoAdapter *)adapter +{ + self.ready = YES; + self.loading = NO; + [self.delegate rewardedVideoDidLoadForAdManager:self]; +} + +- (void)rewardedVideoDidFailToLoadForAdapter:(MPRewardedVideoAdapter *)adapter error:(NSError *)error +{ + self.ready = NO; + self.loading = NO; + [self loadAdWithURL:self.configuration.failoverURL]; +} + +- (void)rewardedVideoDidExpireForAdapter:(MPRewardedVideoAdapter *)adapter +{ + self.ready = NO; + [self.delegate rewardedVideoDidExpireForAdManager:self]; +} + +- (void)rewardedVideoDidFailToPlayForAdapter:(MPRewardedVideoAdapter *)adapter error:(NSError *)error +{ + [self.delegate rewardedVideoDidFailToPlayForAdManager:self error:error]; +} + +- (void)rewardedVideoWillAppearForAdapter:(MPRewardedVideoAdapter *)adapter +{ + [self.delegate rewardedVideoWillAppearForAdManager:self]; +} + +- (void)rewardedVideoDidAppearForAdapter:(MPRewardedVideoAdapter *)adapter +{ + [self.delegate rewardedVideoDidAppearForAdManager:self]; +} + +- (void)rewardedVideoWillDisappearForAdapter:(MPRewardedVideoAdapter *)adapter +{ + [self.delegate rewardedVideoWillDisappearForAdManager:self]; +} + +- (void)rewardedVideoDidDisappearForAdapter:(MPRewardedVideoAdapter *)adapter +{ + self.ready = NO; + self.playedAd = YES; + [self.delegate rewardedVideoDidDisappearForAdManager:self]; +} + +- (void)rewardedVideoDidReceiveTapEventForAdapter:(MPRewardedVideoAdapter *)adapter +{ + [self.delegate rewardedVideoDidReceiveTapEventForAdManager:self]; +} + +- (void)rewardedVideoWillLeaveApplicationForAdapter:(MPRewardedVideoAdapter *)adapter +{ + [self.delegate rewardedVideoWillLeaveApplicationForAdManager:self]; +} + +- (void)rewardedVideoShouldRewardUserForAdapter:(MPRewardedVideoAdapter *)adapter reward:(MPRewardedVideoReward *)reward +{ + [self.delegate rewardedVideoShouldRewardUserForAdManager:self reward:reward]; +} + +- (NSString *)rewardedVideoAdUnitId +{ + return self.adUnitID; +} + +- (NSString *)rewardedVideoCustomerId +{ + return self.customerId; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdapter.h b/iphone/Maps/3party/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdapter.h new file mode 100644 index 00000000000..3a81d62be69 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdapter.h @@ -0,0 +1,78 @@ +// +// MPRewardedVideoAdapter.h +// MoPubSDK +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import +#import +#import "MPPrivateRewardedVideoCustomEventDelegate.h" + +@class MPAdConfiguration; +@class MPRewardedVideoReward; + +@protocol MPRewardedVideoAdapterDelegate; +@protocol MPMediationSettingsProtocol; + +/** + * `MPRewardedVideoAdapter` directly communicates with the appropriate custom event to + * load and show a rewarded video. It is also the class that handles impression + * and click tracking. Finally, the class will report a failure to load an ad if the ad + * takes too long to load. + */ +@interface MPRewardedVideoAdapter : NSObject + +@property (nonatomic, weak) id delegate; + +- (instancetype)initWithDelegate:(id)delegate; + +/** + * Called to retrieve an ad once we get a response from the server. + * + * @param configuration Contains the details about the ad we are loading. + */ +- (void)getAdWithConfiguration:(MPAdConfiguration *)configuration; + +/** + * Tells the caller whether the underlying ad network currently has an ad available for presentation. + */ +- (BOOL)hasAdAvailable; + +/** + * Plays a rewarded video ad. + * + * @param viewController Presents the rewarded video ad from viewController. + */ +- (void)presentRewardedVideoFromViewController:(UIViewController *)viewController; + +/** + * This method is called when another ad unit has played a rewarded video from the same network this adapter's custom event + * represents. + */ +- (void)handleAdPlayedForCustomEventNetwork; + +@end + +@protocol MPRewardedVideoAdapterDelegate + +- (id)instanceMediationSettingsForClass:(Class)aClass; + +- (void)rewardedVideoDidLoadForAdapter:(MPRewardedVideoAdapter *)adapter; +- (void)rewardedVideoDidFailToLoadForAdapter:(MPRewardedVideoAdapter *)adapter error:(NSError *)error; +- (void)rewardedVideoDidExpireForAdapter:(MPRewardedVideoAdapter *)adapter; +- (void)rewardedVideoDidFailToPlayForAdapter:(MPRewardedVideoAdapter *)adapter error:(NSError *)error; +- (void)rewardedVideoWillAppearForAdapter:(MPRewardedVideoAdapter *)adapter; +- (void)rewardedVideoDidAppearForAdapter:(MPRewardedVideoAdapter *)adapter; +- (void)rewardedVideoWillDisappearForAdapter:(MPRewardedVideoAdapter *)adapter; +- (void)rewardedVideoDidDisappearForAdapter:(MPRewardedVideoAdapter *)adapter; +- (void)rewardedVideoDidReceiveTapEventForAdapter:(MPRewardedVideoAdapter *)adapter; +- (void)rewardedVideoWillLeaveApplicationForAdapter:(MPRewardedVideoAdapter *)adapter; +- (void)rewardedVideoShouldRewardUserForAdapter:(MPRewardedVideoAdapter *)adapter reward:(MPRewardedVideoReward *)reward; + +@optional +- (NSString *)rewardedVideoAdUnitId; +- (NSString *)rewardedVideoCustomerId; +- (MPAdConfiguration *)configuration; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdapter.m b/iphone/Maps/3party/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdapter.m new file mode 100644 index 00000000000..73410a606ed --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdapter.m @@ -0,0 +1,284 @@ +// +// MPRewardedVideoAdapter.m +// MoPubSDK +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import "MPRewardedVideoAdapter.h" + +#import "MPAdConfiguration.h" +#import "MPAnalyticsTracker.h" +#import "MPCoreInstanceProvider.h" +#import "MPRewardedVideoError.h" +#import "MPRewardedVideoCustomEvent.h" +#import "MPInstanceProvider.h" +#import "MPLogging.h" +#import "MPTimer.h" +#import "MPRewardedVideoReward.h" +#import "MPRewardedVideo+Internal.h" + +static const NSString *kRewardedVideoApiVersion = @"1"; + +@interface MPRewardedVideoAdapter () + +@property (nonatomic, strong) MPRewardedVideoCustomEvent *rewardedVideoCustomEvent; +@property (nonatomic, strong) MPAdConfiguration *configuration; +@property (nonatomic, strong) MPTimer *timeoutTimer; +@property (nonatomic, assign) BOOL hasTrackedImpression; +@property (nonatomic, assign) BOOL hasTrackedClick; +// Once an ad successfully loads, we want to block sending more successful load events. +@property (nonatomic, assign) BOOL hasSuccessfullyLoaded; +// Since we only notify the application of one success per load, we also only notify the application of one expiration per success. +@property (nonatomic, assign) BOOL hasExpired; + +@end + +@implementation MPRewardedVideoAdapter + +- (instancetype)initWithDelegate:(id)delegate +{ + if (self = [super init]) { + _delegate = delegate; + } + + return self; +} + +- (void)dealloc +{ + // The rewarded video system now no longer holds references to the custom event. The custom event may have a system + // that holds extra references to the custom event. Let's tell the custom event that we no longer need it. + [_rewardedVideoCustomEvent handleCustomEventInvalidated]; + + [_timeoutTimer invalidate]; + + // Make sure the custom event isn't released synchronously as objects owned by the custom event + // may do additional work after a callback that results in dealloc being called + [[MPCoreInstanceProvider sharedProvider] keepObjectAliveForCurrentRunLoopIteration:_rewardedVideoCustomEvent]; +} + +- (void)getAdWithConfiguration:(MPAdConfiguration *)configuration +{ + MPLogInfo(@"Looking for custom event class named %@.", configuration.customEventClass); + + self.configuration = configuration; + + self.rewardedVideoCustomEvent = [[MPInstanceProvider sharedProvider] buildRewardedVideoCustomEventFromCustomClass:configuration.customEventClass delegate:self]; + + if (self.rewardedVideoCustomEvent) { + [self startTimeoutTimer]; + [self.rewardedVideoCustomEvent requestRewardedVideoWithCustomEventInfo:configuration.customEventClassData]; + } else { + NSError *error = [NSError errorWithDomain:MoPubRewardedVideoAdsSDKDomain code:MPRewardedVideoAdErrorInvalidCustomEvent userInfo:nil]; + [self.delegate rewardedVideoDidFailToLoadForAdapter:self error:error]; + } +} + +- (BOOL)hasAdAvailable +{ + return [self.rewardedVideoCustomEvent hasAdAvailable]; +} + +- (void)presentRewardedVideoFromViewController:(UIViewController *)viewController +{ + [self.rewardedVideoCustomEvent presentRewardedVideoFromViewController:viewController]; +} + +- (void)handleAdPlayedForCustomEventNetwork +{ + [self.rewardedVideoCustomEvent handleAdPlayedForCustomEventNetwork]; +} + +#pragma mark - Private + +- (void)startTimeoutTimer +{ + NSTimeInterval timeInterval = (self.configuration && self.configuration.adTimeoutInterval >= 0) ? + self.configuration.adTimeoutInterval : REWARDED_VIDEO_TIMEOUT_INTERVAL; + + if (timeInterval > 0) { + self.timeoutTimer = [[MPCoreInstanceProvider sharedProvider] buildMPTimerWithTimeInterval:timeInterval + target:self + selector:@selector(timeout) + repeats:NO]; + + [self.timeoutTimer scheduleNow]; + } +} + +- (void)timeout +{ + NSError *error = [NSError errorWithDomain:MoPubRewardedVideoAdsSDKDomain code:MPRewardedVideoAdErrorTimeout userInfo:nil]; + [self.delegate rewardedVideoDidFailToLoadForAdapter:self error:error]; +} + +- (void)didStopLoading +{ + [self.timeoutTimer invalidate]; +} + +- (NSURL *)rewardedVideoCompletionUrlByAppendingClientParams +{ + NSString *finalCompletionUrlString = self.configuration.rewardedVideoCompletionUrl; + if ([self.delegate respondsToSelector:@selector(rewardedVideoCustomerId)] && [self.delegate rewardedVideoCustomerId].length > 0) { + // self.configuration.rewardedVideoCompletionUrl is already url encoded. Only the customer_id added by the client needs url encoding. + NSString *urlEncodedCustomerId = [[self.delegate rewardedVideoCustomerId] mp_URLEncodedString]; + finalCompletionUrlString = [NSString stringWithFormat:@"%@&customer_id=%@", finalCompletionUrlString, urlEncodedCustomerId]; + } + finalCompletionUrlString = [NSString stringWithFormat:@"%@&nv=%@&v=%@", finalCompletionUrlString, [MP_SDK_VERSION mp_URLEncodedString], kRewardedVideoApiVersion]; + + if (self.configuration.selectedReward) { + finalCompletionUrlString = [NSString stringWithFormat:@"%@&rcn=%@&rca=%i", finalCompletionUrlString, [self.configuration.selectedReward.currencyType mp_URLEncodedString], [self.configuration.selectedReward.amount intValue]]; + } + + return [NSURL URLWithString:finalCompletionUrlString]; +} + +#pragma mark - Metrics + +- (void)trackImpression +{ + [[[MPCoreInstanceProvider sharedProvider] sharedMPAnalyticsTracker] trackImpressionForConfiguration:self.configuration]; +} + +- (void)trackClick +{ + [[[MPCoreInstanceProvider sharedProvider] sharedMPAnalyticsTracker] trackClickForConfiguration:self.configuration]; +} + +#pragma mark - MPRewardedVideoCustomEventDelegate + +- (id)instanceMediationSettingsForClass:(Class)aClass +{ + return [self.delegate instanceMediationSettingsForClass:aClass]; +} + +- (void)rewardedVideoDidLoadAdForCustomEvent:(MPRewardedVideoCustomEvent *)customEvent +{ + // Don't report multiple successful loads. Backing ad networks may replenish their caches triggering multiple successful load + // callbacks. + if (self.hasSuccessfullyLoaded) { + return; + } + + self.hasSuccessfullyLoaded = YES; + [self didStopLoading]; + [self.delegate rewardedVideoDidLoadForAdapter:self]; +} + +- (void)rewardedVideoDidFailToLoadAdForCustomEvent:(MPRewardedVideoCustomEvent *)customEvent error:(NSError *)error +{ + // Detach the custom event from the adapter. An ad *may* end up, after some time, loading successfully + // from the underlying network, but we don't want to bubble up the event to the application since we + // are reporting a timeout here. + [self.rewardedVideoCustomEvent handleCustomEventInvalidated]; + self.rewardedVideoCustomEvent = nil; + + [self didStopLoading]; + [self.delegate rewardedVideoDidFailToLoadForAdapter:self error:error]; +} + +- (void)rewardedVideoDidExpireForCustomEvent:(MPRewardedVideoCustomEvent *)customEvent +{ + // Only allow one expire per custom event to match up with one successful load callback per custom event. + if (self.hasExpired) { + return; + } + + self.hasExpired = YES; + [self.delegate rewardedVideoDidExpireForAdapter:self]; +} + +- (void)rewardedVideoDidFailToPlayForCustomEvent:(MPRewardedVideoCustomEvent *)customEvent error:(NSError *)error +{ + [self.delegate rewardedVideoDidFailToPlayForAdapter:self error:error]; +} + +- (void)rewardedVideoWillAppearForCustomEvent:(MPRewardedVideoCustomEvent *)customEvent +{ + [self.delegate rewardedVideoWillAppearForAdapter:self]; +} + +- (void)rewardedVideoDidAppearForCustomEvent:(MPRewardedVideoCustomEvent *)customEvent +{ + if ([self.rewardedVideoCustomEvent enableAutomaticImpressionAndClickTracking] && !self.hasTrackedImpression) { + self.hasTrackedImpression = YES; + [self trackImpression]; + } + + [self.delegate rewardedVideoDidAppearForAdapter:self]; +} + +- (void)rewardedVideoWillDisappearForCustomEvent:(MPRewardedVideoCustomEvent *)customEvent +{ + [self.delegate rewardedVideoWillDisappearForAdapter:self]; +} + +- (void)rewardedVideoDidDisappearForCustomEvent:(MPRewardedVideoCustomEvent *)customEvent +{ + [self.delegate rewardedVideoDidDisappearForAdapter:self]; +} + +- (void)rewardedVideoWillLeaveApplicationForCustomEvent:(MPRewardedVideoCustomEvent *)customEvent +{ + [self.delegate rewardedVideoWillLeaveApplicationForAdapter:self]; +} + +- (void)rewardedVideoDidReceiveTapEventForCustomEvent:(MPRewardedVideoCustomEvent *)customEvent +{ + if ([self.rewardedVideoCustomEvent enableAutomaticImpressionAndClickTracking] && !self.hasTrackedClick) { + self.hasTrackedClick = YES; + [self trackClick]; + } + + [self.delegate rewardedVideoDidReceiveTapEventForAdapter:self]; +} + +- (void)rewardedVideoShouldRewardUserForCustomEvent:(MPRewardedVideoCustomEvent *)customEvent reward:(MPRewardedVideoReward *)reward +{ + if (self.configuration && self.configuration.rewardedVideoCompletionUrl) { + // server to server callback + [[MPRewardedVideo sharedInstance] startRewardedVideoConnectionWithUrl:[self rewardedVideoCompletionUrlByAppendingClientParams]]; + } else { + // server to server not enabled. It uses client side rewarding. + if (self.configuration) { + MPRewardedVideoReward *mopubConfiguredReward = self.configuration.selectedReward; + // If reward is set in adConfig, use reward that's set in adConfig. + // Currency type has to be defined in mopubConfiguredReward in order to use mopubConfiguredReward. + if (mopubConfiguredReward && mopubConfiguredReward.currencyType != kMPRewardedVideoRewardCurrencyTypeUnspecified){ + reward = mopubConfiguredReward; + } + } + + if (reward) { + [self.delegate rewardedVideoShouldRewardUserForAdapter:self reward:reward]; + } + } +} + +- (NSString *)customerIdForRewardedVideoCustomEvent:(MPRewardedVideoCustomEvent *)customEvent +{ + if ([self.delegate respondsToSelector:@selector(rewardedVideoCustomerId)]) { + return [self.delegate rewardedVideoCustomerId]; + } + + return nil; +} + +#pragma mark - MPPrivateRewardedVideoCustomEventDelegate + +- (NSString *)adUnitId +{ + if ([self.delegate respondsToSelector:@selector(rewardedVideoAdUnitId)]) { + return [self.delegate rewardedVideoAdUnitId]; + } + return nil; +} + +- (MPAdConfiguration *)configuration +{ + return _configuration; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoConnection.h b/iphone/Maps/3party/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoConnection.h new file mode 100644 index 00000000000..d573b3c9ee5 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoConnection.h @@ -0,0 +1,20 @@ +// +// MPRewardedVideoConnection.h +// MoPubSDK +// Copyright © 2016 MoPub. All rights reserved. +// + +@class MPRewardedVideoConnection; + +@protocol MPRewardedVideoConnectionDelegate + +- (void)rewardedVideoConnectionCompleted:(MPRewardedVideoConnection *)connection url:(NSURL *)url; + +@end + +@interface MPRewardedVideoConnection : NSObject + +- (instancetype)initWithUrl:(NSURL *)url delegate:(id)delegate; +- (void)sendRewardedVideoCompletionRequest; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoConnection.m b/iphone/Maps/3party/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoConnection.m new file mode 100644 index 00000000000..28db5763986 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoConnection.m @@ -0,0 +1,95 @@ +// +// MPRewardedVideoConnection.m +// MoPubSDK + +// Copyright © 2016 MoPub. All rights reserved. +// + +#import +#import "MPRewardedVideoConnection.h" + +static const NSTimeInterval kMaximumRequestRetryInterval = 900.0; // 15 mins +static const NSTimeInterval kMinimumRequestRetryInterval = 5.0; +static const NSTimeInterval kMaximumBackoffTime = 60.0; +static const CGFloat kRetryIntervalBackoffMultiplier = 2.0; + +@interface MPRewardedVideoConnection() + +@property (nonatomic) NSURLConnection *connection; +@property (nonatomic) NSURL *url; +@property (nonatomic) NSUInteger retryCount; +@property (nonatomic) NSTimeInterval accumulatedRetryInterval; +@property (nonatomic, weak) id delegate; + +@end + +@implementation MPRewardedVideoConnection + +- (instancetype)initWithUrl:(NSURL *)url delegate:(id)delegate +{ + if (self = [super init]) { + _url = url; + _delegate = delegate; + } + return self; +} + +- (void)sendRewardedVideoCompletionRequest +{ + NSURLRequest *request = [NSURLRequest requestWithURL:self.url]; + [self.connection cancel]; + self.connection = [NSURLConnection connectionWithRequest:request delegate:self]; +} + +- (void)retryRewardedVideoCompletionRequest +{ + NSTimeInterval retryInterval = [self backoffTime:self.retryCount]; + + self.accumulatedRetryInterval += retryInterval; + + if (self.accumulatedRetryInterval < kMaximumRequestRetryInterval) { + [self performSelector:@selector(sendRewardedVideoCompletionRequest) withObject:nil afterDelay:retryInterval]; + } else { + [self.delegate rewardedVideoConnectionCompleted:self url:self.url]; + [self.connection cancel]; + } + self.retryCount++; +} + +- (NSTimeInterval)backoffTime:(NSUInteger)retryCount +{ + NSTimeInterval interval = pow(kRetryIntervalBackoffMultiplier, retryCount) * kMinimumRequestRetryInterval; + + // If interval > kMaximumBackoffTime, we'll retry every kMaximumBackoffTime seconds to ensure retry happens + // often enough. + if (interval > kMaximumBackoffTime) { + interval = kMaximumBackoffTime; + } + return interval; +} + +#pragma mark - + +- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error +{ + if (error.code == NSURLErrorTimedOut || + error.code == NSURLErrorNetworkConnectionLost || + error.code == NSURLErrorNotConnectedToInternet) { + [self retryRewardedVideoCompletionRequest]; + } else { + [self.delegate rewardedVideoConnectionCompleted:self url:self.url]; + } +} + +- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response +{ + NSInteger statusCode = [(NSHTTPURLResponse *)response statusCode]; + // only retry on 5xx + if (statusCode >= 500 && statusCode <= 599) { + [self retryRewardedVideoCompletionRequest]; + } else { + [self.delegate rewardedVideoConnectionCompleted:self url:self.url]; + } +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/RewardedVideo/MPMediationSettingsProtocol.h b/iphone/Maps/3party/MoPubSDK/RewardedVideo/MPMediationSettingsProtocol.h new file mode 100644 index 00000000000..180b77e6100 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/RewardedVideo/MPMediationSettingsProtocol.h @@ -0,0 +1,35 @@ +// +// MPMediationSettingsProtocol.h +// MoPubSDK +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import + +/** + * The MoPub SDK has a concept of mediation settings that allow you to define objects + * that allow the application to configure specific settings for your custom event's ad + * network. + * + * Your object can be a global mediation setting that contains settings you deem to be constant + * across all of your ad network's ads. Ideally this is where you will place settings necessary for + * your ad network's intialization as well. The global medation setting object should be ready for + * your custom event by the time you load the ad from your network. Inside your custom event, you can retrieve + * the global mediation settings by calling `[-globalMediationSettingsForClass:]([MoPub -globalMediationSettingsForClass:])` + * passing in the class type of your global based mediation settings object. + * + * You can also define instance based mediation settings. The application may or may not define + * a mediation settings object per ad unit ID in their application. This allows ads in different locations + * to behave differently. The instance based mediation settings object should be available to your custom event + * by the time you load the ad from your network. Inside your custom event, you can retrieve the instance based + * mediation settings by calling `[-instanceMediationSettingsForClass:]([MPRewardedVideoCustomEventDelegate -instanceMediationSettingsForClass:])` + * passing in the class type of your instance based mediation settings object. + * + * **Important**: Your custom event must not assume it will receive a global or any instance based mediation settings + * as the application may choose not to supply any. Your custom event must have a default implementation in the event + * the application doesn't wish to provide any specific settings. + */ +@protocol MPMediationSettingsProtocol + +@end diff --git a/iphone/Maps/3party/MoPubSDK/RewardedVideo/MPRewardedVideo.h b/iphone/Maps/3party/MoPubSDK/RewardedVideo/MPRewardedVideo.h new file mode 100644 index 00000000000..289a638d4b1 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/RewardedVideo/MPRewardedVideo.h @@ -0,0 +1,204 @@ +// +// MPRewardedVideo.h +// MoPubSDK +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import +#import + +@class MPRewardedVideoReward; +@class CLLocation; +@protocol MPRewardedVideoDelegate; + +/** + * `MPRewardedVideo` allows you to load and play rewarded video ads. All ad events are + * reported, with an ad unit ID, to the delegate allowing the application to respond to the events + * for the corresponding ad. + * + * **Important**: You must call `[initializeRewardedVideoWithGlobalMediationSettings:delegate:][MoPub initializeRewardedVideoWithGlobalMediationSettings:delegate:]` + * to initialize the rewarded video system. + */ +@interface MPRewardedVideo : NSObject + +/** + * Loads a rewarded video ad for the given ad unit ID. + * + * The mediation settings array should contain ad network specific objects for networks that may be loaded for the given ad unit ID. + * You should set the properties on these objects to determine how the underlying ad network should behave. You only need to supply + * objects for the networks you wish to configure. If you do not want your network to behave differently from its default behavior, do + * not pass in an mediation settings object for that network. + * + * @param adUnitID The ad unit ID that ads should be loaded from. + * @param mediationSettings An array of mediation settings objects that map to networks that may show ads for the ad unit ID. This array + * should only contain objects for networks you wish to configure. This can be nil. + */ ++ (void)loadRewardedVideoAdWithAdUnitID:(NSString *)adUnitID withMediationSettings:(NSArray *)mediationSettings; + +/** + * Loads a rewarded video ad for the given ad unit ID. + * + * The mediation settings array should contain ad network specific objects for networks that may be loaded for the given ad unit ID. + * You should set the properties on these objects to determine how the underlying ad network should behave. You only need to supply + * objects for the networks you wish to configure. If you do not want your network to behave differently from its default behavior, do + * not pass in an mediation settings object for that network. + * + * @param adUnitID The ad unit ID that ads should be loaded from. + * @param keywords A string representing a set of keywords that should be passed to the MoPub ad server to receive + * more relevant advertising. + * @param location Latitude/Longitude that are passed to the MoPub ad server + * @param mediationSettings An array of mediation settings objects that map to networks that may show ads for the ad unit ID. This array + * should only contain objects for networks you wish to configure. This can be nil. + */ ++ (void)loadRewardedVideoAdWithAdUnitID:(NSString *)adUnitID keywords:(NSString *)keywords location:(CLLocation *)location mediationSettings:(NSArray *)mediationSettings; + +/** + * Loads a rewarded video ad for the given ad unit ID. + * + * The mediation settings array should contain ad network specific objects for networks that may be loaded for the given ad unit ID. + * You should set the properties on these objects to determine how the underlying ad network should behave. You only need to supply + * objects for the networks you wish to configure. If you do not want your network to behave differently from its default behavior, do + * not pass in an mediation settings object for that network. + * + * @param adUnitID The ad unit ID that ads should be loaded from. + * @param keywords A string representing a set of keywords that should be passed to the MoPub ad server to receive + * more relevant advertising. + * @param location Latitude/Longitude that are passed to the MoPub ad server + * @param customerId This is the ID given to the user by the publisher to identify them in their app + * @param mediationSettings An array of mediation settings objects that map to networks that may show ads for the ad unit ID. This array + * should only contain objects for networks you wish to configure. This can be nil. + */ ++ (void)loadRewardedVideoAdWithAdUnitID:(NSString *)adUnitID keywords:(NSString *)keywords location:(CLLocation *)location customerId:(NSString *)customerId mediationSettings:(NSArray *)mediationSettings; + +/** + * Returns whether or not an ad is available for the given ad unit ID. + * + * @param adUnitID The ad unit ID associated with the ad you want to retrieve the availability for. + */ ++ (BOOL)hasAdAvailableForAdUnitID:(NSString *)adUnitID; + +/** + * Returns an array of rewards that are available for the given ad unit ID. + */ ++ (NSArray *)availableRewardsForAdUnitID:(NSString *)adUnitID; + +/** + * The currently selected reward that will be awarded to the user upon completion of the ad. By default, + * this corresponds to the first reward in `availableRewardsForAdUnitID:`. + */ ++ (MPRewardedVideoReward *)selectedRewardForAdUnitID:(NSString *)adUnitID; + +/** + * Plays a rewarded video ad. + * + * @param adUnitID The ad unit ID associated with the video ad you wish to play. + * @param viewController The view controller that will present the rewarded video ad. + * @param reward A reward selected from `availableRewardsForAdUnitID:` to award the user upon successful completion of the ad. + * This value should not be `nil`. + * + * @warning **Important**: You should not attempt to play the rewarded video unless `+hasAdAvailableForAdUnitID:` indicates that an + * ad is available for playing or you have received the `[-rewardedVideoAdDidLoadForAdUnitID:]([MPRewardedVideoDelegate rewardedVideoAdDidLoadForAdUnitID:])` + * message. + */ ++ (void)presentRewardedVideoAdForAdUnitID:(NSString *)adUnitID fromViewController:(UIViewController *)viewController withReward:(MPRewardedVideoReward *)reward; + +/** + * Plays a rewarded video ad, automatically selecting the first available reward in `availableRewardsForAdUnitID:`. + * + * @param adUnitID The ad unit ID associated with the video ad you wish to play. + * @param viewController The view controller that will present the rewarded video ad. + * + * @warning **Important**: You should not attempt to play the rewarded video unless `+hasAdAvailableForAdUnitID:` indicates that an + * ad is available for playing or you have received the `[-rewardedVideoAdDidLoadForAdUnitID:]([MPRewardedVideoDelegate rewardedVideoAdDidLoadForAdUnitID:])` + * message. + */ ++ (void)presentRewardedVideoAdForAdUnitID:(NSString *)adUnitID fromViewController:(UIViewController *)viewController __deprecated_msg("use presentRewardedVideoAdForAdUnitID:fromViewController:withReward: instead."); + +@end + +@protocol MPRewardedVideoDelegate + +@optional + +/** + * This method is called after an ad loads successfully. + * + * @param adUnitID The ad unit ID of the ad associated with the event. + */ +- (void)rewardedVideoAdDidLoadForAdUnitID:(NSString *)adUnitID; + +/** + * This method is called after an ad fails to load. + * + * @param adUnitID The ad unit ID of the ad associated with the event. + * @param error An error indicating why the ad failed to load. + */ +- (void)rewardedVideoAdDidFailToLoadForAdUnitID:(NSString *)adUnitID error:(NSError *)error; + +/** + * This method is called when a previously loaded rewarded video is no longer eligible for presentation. + * + * @param adUnitID The ad unit ID of the ad associated with the event. + */ +- (void)rewardedVideoAdDidExpireForAdUnitID:(NSString *)adUnitID; + +/** + * This method is called when an attempt to play a rewarded video fails. + * + * @param adUnitID The ad unit ID of the ad associated with the event. + * @param error An error describing why the video couldn't play. + */ +- (void)rewardedVideoAdDidFailToPlayForAdUnitID:(NSString *)adUnitID error:(NSError *)error; + +/** + * This method is called when a rewarded video ad is about to appear. + * + * @param adUnitID The ad unit ID of the ad associated with the event. + */ +- (void)rewardedVideoAdWillAppearForAdUnitID:(NSString *)adUnitID; + +/** + * This method is called when a rewarded video ad has appeared. + * + * @param adUnitID The ad unit ID of the ad associated with the event. + */ +- (void)rewardedVideoAdDidAppearForAdUnitID:(NSString *)adUnitID; + +/** + * This method is called when a rewarded video ad will be dismissed. + * + * @param adUnitID The ad unit ID of the ad associated with the event. + */ +- (void)rewardedVideoAdWillDisappearForAdUnitID:(NSString *)adUnitID; + +/** + * This method is called when a rewarded video ad has been dismissed. + * + * @param adUnitID The ad unit ID of the ad associated with the event. + */ +- (void)rewardedVideoAdDidDisappearForAdUnitID:(NSString *)adUnitID; + +/** + * This method is called when the user taps on the ad. + * + * @param adUnitID The ad unit ID of the ad associated with the event. + */ +- (void)rewardedVideoAdDidReceiveTapEventForAdUnitID:(NSString *)adUnitID; + +/** + * This method is called when a rewarded video ad will cause the user to leave the application. + * + * @param adUnitID The ad unit ID of the ad associated with the event. + */ +- (void)rewardedVideoAdWillLeaveApplicationForAdUnitID:(NSString *)adUnitID; + +/** + * This method is called when the user should be rewarded for watching a rewarded video ad. + * + * @param adUnitID The ad unit ID of the ad associated with the event. + * @param reward The object that contains all the information regarding how much you should reward the user. + */ +- (void)rewardedVideoAdShouldRewardForAdUnitID:(NSString *)adUnitID reward:(MPRewardedVideoReward *)reward; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/RewardedVideo/MPRewardedVideo.m b/iphone/Maps/3party/MoPubSDK/RewardedVideo/MPRewardedVideo.m new file mode 100644 index 00000000000..d922fa8c381 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/RewardedVideo/MPRewardedVideo.m @@ -0,0 +1,261 @@ +// +// MPRewardedVideo.m +// MoPubSDK +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import "MPRewardedVideo.h" +#import "MPLogging.h" +#import "MPRewardedVideoAdManager.h" +#import "MPInstanceProvider.h" +#import "MPRewardedVideoError.h" +#import "MPRewardedVideoConnection.h" +#import "MPRewardedVideo+Internal.h" + +static MPRewardedVideo *gSharedInstance = nil; + +@interface MPRewardedVideo () + +@property (nonatomic, strong) NSMutableDictionary *rewardedVideoAdManagers; +@property (nonatomic, weak) id delegate; +@property (nonatomic) NSMutableArray *rewardedVideoConnections; + ++ (MPRewardedVideo *)sharedInstance; + +@end + +@implementation MPRewardedVideo + +- (instancetype)init +{ + if (self = [super init]) { + _rewardedVideoAdManagers = [[NSMutableDictionary alloc] init]; + _rewardedVideoConnections = [NSMutableArray new]; + } + + return self; +} + ++ (void)loadRewardedVideoAdWithAdUnitID:(NSString *)adUnitID withMediationSettings:(NSArray *)mediationSettings +{ + [MPRewardedVideo loadRewardedVideoAdWithAdUnitID:adUnitID keywords:nil location:nil mediationSettings:mediationSettings]; +} + ++ (void)loadRewardedVideoAdWithAdUnitID:(NSString *)adUnitID keywords:(NSString *)keywords location:(CLLocation *)location mediationSettings:(NSArray *)mediationSettings +{ + [self loadRewardedVideoAdWithAdUnitID:adUnitID keywords:keywords location:location customerId:nil mediationSettings:mediationSettings]; +} + ++ (void)loadRewardedVideoAdWithAdUnitID:(NSString *)adUnitID keywords:(NSString *)keywords location:(CLLocation *)location customerId:(NSString *)customerId mediationSettings:(NSArray *)mediationSettings +{ + MPRewardedVideo *sharedInstance = [[self class] sharedInstance]; + + if (![adUnitID length]) { + NSError *error = [NSError errorWithDomain:MoPubRewardedVideoAdsSDKDomain code:MPRewardedVideoAdErrorInvalidAdUnitID userInfo:nil]; + [sharedInstance.delegate rewardedVideoAdDidFailToLoadForAdUnitID:adUnitID error:error]; + return; + } + + MPRewardedVideoAdManager *adManager = sharedInstance.rewardedVideoAdManagers[adUnitID]; + + if (!adManager) { + adManager = [[MPInstanceProvider sharedProvider] buildRewardedVideoAdManagerWithAdUnitID:adUnitID delegate:sharedInstance]; + sharedInstance.rewardedVideoAdManagers[adUnitID] = adManager; + } + + adManager.mediationSettings = mediationSettings; + + [adManager loadRewardedVideoAdWithKeywords:keywords location:location customerId:customerId]; +} + ++ (BOOL)hasAdAvailableForAdUnitID:(NSString *)adUnitID +{ + MPRewardedVideo *sharedInstance = [[self class] sharedInstance]; + MPRewardedVideoAdManager *adManager = sharedInstance.rewardedVideoAdManagers[adUnitID]; + + return [adManager hasAdAvailable]; +} + ++ (NSArray *)availableRewardsForAdUnitID:(NSString *)adUnitID +{ + MPRewardedVideo *sharedInstance = [[self class] sharedInstance]; + MPRewardedVideoAdManager *adManager = sharedInstance.rewardedVideoAdManagers[adUnitID]; + + return adManager.availableRewards; +} + ++ (MPRewardedVideoReward *)selectedRewardForAdUnitID:(NSString *)adUnitID +{ + MPRewardedVideo *sharedInstance = [[self class] sharedInstance]; + MPRewardedVideoAdManager *adManager = sharedInstance.rewardedVideoAdManagers[adUnitID]; + + return adManager.selectedReward; +} + ++ (void)presentRewardedVideoAdForAdUnitID:(NSString *)adUnitID fromViewController:(UIViewController *)viewController withReward:(MPRewardedVideoReward *)reward +{ + MPRewardedVideo *sharedInstance = [[self class] sharedInstance]; + MPRewardedVideoAdManager *adManager = sharedInstance.rewardedVideoAdManagers[adUnitID]; + + if (!adManager) { + MPLogWarn(@"The rewarded video could not be shown: " + @"no ads have been loaded for adUnitID: %@", adUnitID); + + return; + } + + if (!viewController) { + MPLogWarn(@"The rewarded video could not be shown: " + @"a nil view controller was passed to -presentRewardedVideoAdForAdUnitID:fromViewController:."); + + return; + } + + if (![viewController.view.window isKeyWindow]) { + MPLogWarn(@"Attempting to present a rewarded video ad in non-key window. The ad may not render properly."); + } + + [adManager presentRewardedVideoAdFromViewController:viewController withReward:reward]; +} + ++ (void)presentRewardedVideoAdForAdUnitID:(NSString *)adUnitID fromViewController:(UIViewController *)viewController +{ + [MPRewardedVideo presentRewardedVideoAdForAdUnitID:adUnitID fromViewController:viewController withReward:nil]; +} + +#pragma mark - Private + ++ (MPRewardedVideo *)sharedInstance +{ + static dispatch_once_t once; + + dispatch_once(&once, ^{ + gSharedInstance = [[self alloc] init]; + }); + + return gSharedInstance; +} + +// This is private as we require the developer to initialize rewarded video through the MoPub object. ++ (void)initializeWithDelegate:(id)delegate +{ + MPRewardedVideo *sharedInstance = [[self class] sharedInstance]; + + // Do not allow calls to initialize twice. + if (sharedInstance.delegate) { + MPLogWarn(@"Attempting to initialize MPRewardedVideo when it has already been initialized."); + } else { + sharedInstance.delegate = delegate; + } +} + +#pragma mark - MPRewardedVideoAdManagerDelegate + +- (void)rewardedVideoDidLoadForAdManager:(MPRewardedVideoAdManager *)manager +{ + if ([self.delegate respondsToSelector:@selector(rewardedVideoAdDidLoadForAdUnitID:)]) { + [self.delegate rewardedVideoAdDidLoadForAdUnitID:manager.adUnitID]; + } +} + +- (void)rewardedVideoDidFailToLoadForAdManager:(MPRewardedVideoAdManager *)manager error:(NSError *)error +{ + if ([self.delegate respondsToSelector:@selector(rewardedVideoAdDidFailToLoadForAdUnitID:error:)]) { + [self.delegate rewardedVideoAdDidFailToLoadForAdUnitID:manager.adUnitID error:error]; + } +} + +- (void)rewardedVideoDidExpireForAdManager:(MPRewardedVideoAdManager *)manager +{ + if ([self.delegate respondsToSelector:@selector(rewardedVideoAdDidExpireForAdUnitID:)]) { + [self.delegate rewardedVideoAdDidExpireForAdUnitID:manager.adUnitID]; + } +} + +- (void)rewardedVideoDidFailToPlayForAdManager:(MPRewardedVideoAdManager *)manager error:(NSError *)error +{ + if ([self.delegate respondsToSelector:@selector(rewardedVideoAdDidFailToPlayForAdUnitID:error:)]) { + [self.delegate rewardedVideoAdDidFailToPlayForAdUnitID:manager.adUnitID error:error]; + } +} + +- (void)rewardedVideoWillAppearForAdManager:(MPRewardedVideoAdManager *)manager +{ + if ([self.delegate respondsToSelector:@selector(rewardedVideoAdWillAppearForAdUnitID:)]) { + [self.delegate rewardedVideoAdWillAppearForAdUnitID:manager.adUnitID]; + } +} + +- (void)rewardedVideoDidAppearForAdManager:(MPRewardedVideoAdManager *)manager +{ + if ([self.delegate respondsToSelector:@selector(rewardedVideoAdDidAppearForAdUnitID:)]) { + [self.delegate rewardedVideoAdDidAppearForAdUnitID:manager.adUnitID]; + } +} + +- (void)rewardedVideoWillDisappearForAdManager:(MPRewardedVideoAdManager *)manager +{ + if ([self.delegate respondsToSelector:@selector(rewardedVideoAdWillDisappearForAdUnitID:)]) { + [self.delegate rewardedVideoAdWillDisappearForAdUnitID:manager.adUnitID]; + } +} + +- (void)rewardedVideoDidDisappearForAdManager:(MPRewardedVideoAdManager *)manager +{ + if ([self.delegate respondsToSelector:@selector(rewardedVideoAdDidDisappearForAdUnitID:)]) { + [self.delegate rewardedVideoAdDidDisappearForAdUnitID:manager.adUnitID]; + } + + // Since multiple ad units may be attached to the same network, we should notify the custom events (which should then notify the application) + // that their ads may not be available anymore since another ad unit might have "played" their ad. We go through and notify all ad managers + // that are of the type of ad that is playing now. + Class customEventClass = manager.customEventClass; + + for (id key in self.rewardedVideoAdManagers) { + MPRewardedVideoAdManager *adManager = self.rewardedVideoAdManagers[key]; + + if (adManager != manager && adManager.customEventClass == customEventClass) { + [adManager handleAdPlayedForCustomEventNetwork]; + } + } +} + +- (void)rewardedVideoDidReceiveTapEventForAdManager:(MPRewardedVideoAdManager *)manager +{ + if ([self.delegate respondsToSelector:@selector(rewardedVideoAdDidReceiveTapEventForAdUnitID:)]) { + [self.delegate rewardedVideoAdDidReceiveTapEventForAdUnitID:manager.adUnitID]; + } +} + +- (void)rewardedVideoWillLeaveApplicationForAdManager:(MPRewardedVideoAdManager *)manager +{ + if ([self.delegate respondsToSelector:@selector(rewardedVideoAdWillLeaveApplicationForAdUnitID:)]) { + [self.delegate rewardedVideoAdWillLeaveApplicationForAdUnitID:manager.adUnitID]; + } +} + +- (void)rewardedVideoShouldRewardUserForAdManager:(MPRewardedVideoAdManager *)manager reward:(MPRewardedVideoReward *)reward +{ + if ([self.delegate respondsToSelector:@selector(rewardedVideoAdShouldRewardForAdUnitID:reward:)]) { + [self.delegate rewardedVideoAdShouldRewardForAdUnitID:manager.adUnitID reward:reward]; + } +} + +#pragma mark - rewarded video server to server callback + +- (void)startRewardedVideoConnectionWithUrl:(NSURL *)url +{ + MPRewardedVideoConnection *connection = [[MPRewardedVideoConnection alloc] initWithUrl:url delegate:self]; + [self.rewardedVideoConnections addObject:connection]; + [connection sendRewardedVideoCompletionRequest]; +} + +#pragma mark - MPRewardedVideoConnectionDelegate + +- (void)rewardedVideoConnectionCompleted:(MPRewardedVideoConnection *)connection url:(NSURL *)url +{ + [self.rewardedVideoConnections removeObject:connection]; +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/RewardedVideo/MPRewardedVideoCustomEvent.h b/iphone/Maps/3party/MoPubSDK/RewardedVideo/MPRewardedVideoCustomEvent.h new file mode 100644 index 00000000000..a0fc2b3ddfb --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/RewardedVideo/MPRewardedVideoCustomEvent.h @@ -0,0 +1,301 @@ +// +// MPRewardedVideoCustomEvent.h +// MoPubSDK +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import +#import + +@protocol MPRewardedVideoCustomEventDelegate; +@protocol MPMediationSettingsProtocol; + +@class MPRewardedVideoReward; + +/** + * The MoPub iOS SDK mediates third party Ad Networks using custom events. The custom events are + * responsible for instantiating and manipulating objects in the third party SDK and translating + * and communicating events from those objects back to the MoPub SDK by notifying a delegate. + * + * `MPRewardedVideoCustomEvent` is a base class for custom events that support full-screen rewarded video ads. + * By implementing subclasses of `MPRewardedVideoCustomEvent` you can enable the MoPub SDK to + * natively support a wide variety of third-party ad networks. + * + * At runtime, the MoPub SDK will find and instantiate an `MPRewardedVideoCustomEvent` subclass as needed and + * invoke its `-requestRewardedVideoWithCustomEventInfo:` method. + */ + +@interface MPRewardedVideoCustomEvent : NSObject + +@property (nonatomic, weak) id delegate; + +/** @name Requesting and Displaying a Rewarded Video Ad */ + +/** + * Called when the MoPub SDK requires a new rewarded video ad. + * + * When the MoPub SDK receives a response indicating it should load a custom event, it will send + * this message to your custom event class. Your implementation of this method should load an + * rewarded video ad from a third-party ad network. It must also notify the + * `MPRewardedVideoCustomEventDelegate` of certain lifecycle events. + * + * The default implementation of this method does nothing. Subclasses must override this method and implement code to load a rewarded video here. + * + * **Important**: The application may provide a mediation settings object containing properties that you should use to configure how you use + * the ad network's APIs. Call `[-mediationSettingsForClass:]([MPRewardedVideoCustomEventDelegate mediationSettingsForClass:])` + * specifying a specific class that your custom event uses to retrieve the mediation settings object if it exists. You define + * the mediation settings class and the properties it supports for your custom event. + * + * @param info A dictionary containing additional custom data associated with a given custom event + * request. This data is configurable on the MoPub website, and may be used to pass dynamic information, such as publisher IDs. + */ +- (void)requestRewardedVideoWithCustomEventInfo:(NSDictionary *)info; + +/** + * Called when the MoPubSDK wants to know if an ad is currently available for the ad network. + * + * This call is typically invoked when the application wants to check whether an ad unit has an ad ready to display. + * + * Subclasses must override this method and implement coheck whether or not a rewarded vidoe ad is available for presentation. + * + */ +- (BOOL)hasAdAvailable; + +/** + * Called when the rewarded video should be displayed. + * + * This message is sent sometime after a rewarded video has been successfully loaded, as a result + * of your code calling `-[MPRewardedVideo presentRewardedVideoAdForAdUnitID:fromViewController:]`. Your implementation + * of this method should present the rewarded video ad from the specified view controller. + * + * The default implementation of this method does nothing. Subclasses must override this method and implement code to display a rewarded video here. + * + * If you decide to [opt out of automatic impression tracking](enableAutomaticImpressionAndClickTracking), you should place your + * manual calls to [-trackImpression]([MPRewardedVideoCustomEventDelegate trackImpression]) in this method to ensure correct metrics. + * + * @param viewController The controller to use to present the rewarded video modally. + */ +- (void)presentRewardedVideoFromViewController:(UIViewController *)viewController; + +/** @name Impression and Click Tracking */ + +/** + * Override to opt out of automatic impression and click tracking. + * + * By default, the MPRewardedVideoCustomEventDelegate will automatically record impressions and clicks in + * response to the appropriate callbacks. You may override this behavior by implementing this method + * to return `NO`. + * + * @warning **Important**: If you do this, you are responsible for calling the `[-trackImpression]([MPRewardedVideoCustomEventDelegate trackImpression])` and + * `[-trackClick]([MPRewardedVideoCustomEventDelegate trackClick])` methods on the custom event delegate. Additionally, you should make sure that these + * methods are only called **once** per ad. + */ +- (BOOL)enableAutomaticImpressionAndClickTracking; + +/** + * Override this method to handle when an ad was played for this custom event's network, but under a different ad unit ID. + * + * Due to the way ad mediation works, two ad units may load the same ad network for displaying ads. When one ad unit plays + * an ad, the other ad unit may need to update its state and notify the application an ad may no longer be available as it + * may have already played. If an ad becomes unavailable for this custom event, call + * `[-rewardedVideoDidExpireForCustomEvent:]([MPRewardedVideoCustomEventDelegate rewardedVideoDidExpireForCustomEvent:])` + * to notify the application that an ad is no longer available. + * + * This method will only be called if your custom event has reported that an ad had successfully loaded. The default implementation of this method does nothing. + * Subclasses must override this method and implement code to handle when the custom event is no longer needed by the rewarded video system. + */ +- (void)handleAdPlayedForCustomEventNetwork; + +/** + * Override this method to handle when the custom event is no longer needed by the rewarded video system. + * + * This method is called once the rewarded video system no longer references your custom event. This method + * is provided as you may have a centralized object holding onto this custom event. If that is the case and your + * centralized object no longer needs the custom event, then you should remove the custom event from the centralized + * object in this method causing the custom event to deallocate. See `MPAdColonyRewardedVideoCustomEvent` for an + * example of how and why this method is used. + * + * Implementation of this method is not necessary if you do not hold any extra references to it. `-dealloc` will still + * be called. However, it is expected you will need to override this method to prevent memory leaks. It is safe to override with nothing + * if you believe you will not leak memory. + */ +- (void)handleCustomEventInvalidated; + +@end + +@protocol MPRewardedVideoCustomEventDelegate + +/** @name Rewarded Video Ad Mediation Settings */ + +/** + * Call this method to retrieve a mediation settings object (if one is provided by the application) for this instance + * of your ad. + * + * @param aClass The specific mediation settings class your custom event uses to configure itself for its ad network. + */ +- (id)instanceMediationSettingsForClass:(Class)aClass; + +/** @name Rewarded Video Ad Event Callbacks - Fetching Ads */ + +/** + * Call this method immediately after an ad loads succesfully. + * + * @param customEvent You should pass `self` to allow the MoPub SDK to associate this event with the correct + * instance of your custom event. + * + * @warning **Important**: Your custom event subclass **must** call this method when it successfully loads an ad. + * Failure to do so will disrupt the mediation waterfall and cause future ad requests to stall. + */ +- (void)rewardedVideoDidLoadAdForCustomEvent:(MPRewardedVideoCustomEvent *)customEvent; + +/** + * Call this method immediately after an ad fails to load. + * + * @param customEvent You should pass `self` to allow the MoPub SDK to associate this event with the correct + * instance of your custom event. + * + * @param error (*optional*) You may pass an error describing the failure. + * + * @warning **Important**: Your custom event subclass **must** call this method when it fails to load an ad. + * Failure to do so will disrupt the mediation waterfall and cause future ad requests to stall. + */ +- (void)rewardedVideoDidFailToLoadAdForCustomEvent:(MPRewardedVideoCustomEvent *)customEvent error:(NSError *)error; + +/** + * Call this method if a previously loaded rewarded video should no longer be eligible for presentation. + * + * Some third-party networks will mark rewarded videos as expired (indicating they should not be + * presented) *after* they have loaded. You may use this method to inform the MoPub SDK that a + * previously loaded rewarded video has expired and that a new rewarded video should be obtained. + * + * @param customEvent You should pass `self` to allow the MoPub SDK to associate this event with the correct + * instance of your custom event. + */ +- (void)rewardedVideoDidExpireForCustomEvent:(MPRewardedVideoCustomEvent *)customEvent; + +/** + * Call this method when the application has attempted to play a rewarded video and it cannot be played. + * + * A common usage of this delegate method is when the application tries to play an ad and an ad is not available for play. + * + * @param customEvent You should pass `self` to allow the MoPub SDK to associate this event with the correct + * instance of your custom event. + * + * @param error The error describing why the video couldn't play. + */ +- (void)rewardedVideoDidFailToPlayForCustomEvent:(MPRewardedVideoCustomEvent *)customEvent error:(NSError *)error; + +/** + * Call this method when an ad is about to appear. + * + * @param customEvent You should pass `self` to allow the MoPub SDK to associate this event with the correct + * instance of your custom event. + * + * @warning **Important**: Your custom event subclass **must** call this method when it is about to present the rewarded video. + * Failure to do so will disrupt the mediation waterfall and cause future ad requests to stall. + */ +- (void)rewardedVideoWillAppearForCustomEvent:(MPRewardedVideoCustomEvent *)customEvent; + +/** + * Call this method when an ad has finished appearing. + * + * @param customEvent You should pass `self` to allow the MoPub SDK to associate this event with the correct + * instance of your custom event. + * + * @warning **Important**: Your custom event subclass **must** call this method when it is finished presenting the rewarded video. + * Failure to do so will disrupt the mediation waterfall and cause future ad requests to stall. + * + * **Note**: If it is not possible to know when the rewarded video *finished* appearing, you should call + * this immediately after calling `-rewardedVideoWillAppearForCustomEvent:`. + */ +- (void)rewardedVideoDidAppearForCustomEvent:(MPRewardedVideoCustomEvent *)customEvent; + +/** + * Call this method when an ad is about to disappear. + * + * @param customEvent You should pass `self` to allow the MoPub SDK to associate this event with the correct + * instance of your custom event. + * + * @warning **Important**: Your custom event subclass **must** call this method when it is about to dismiss the rewarded video. + * Failure to do so will disrupt the mediation waterfall and cause future ad requests to stall. + */ +- (void)rewardedVideoWillDisappearForCustomEvent:(MPRewardedVideoCustomEvent *)customEvent; + +/** + * Call this method when an ad has finished disappearing. + * + * @param customEvent You should pass `self` to allow the MoPub SDK to associate this event with the correct + * instance of your custom event. + * + * @warning **Important**: Your custom event subclass **must** call this method when it is finished with dismissing the rewarded video. + * Failure to do so will disrupt the mediation waterfall and cause future ad requests to stall. + * + * **Note**: if it is not possible to know when the rewarded video *finished* dismissing, you should call + * this immediately after calling `-rewardedVideoWillDisappearForCustomEvent:`. + */ +- (void)rewardedVideoDidDisappearForCustomEvent:(MPRewardedVideoCustomEvent *)customEvent; + +/** + * Call this method when the rewarded video ad will cause the user to leave the application. + * + * For example, the user may have tapped on the video which redirects the user to the App Store or Safari. + * + * @param customEvent You should pass `self` to allow the MoPub SDK to associate this event with the correct + * instance of your custom event. + */ +- (void)rewardedVideoWillLeaveApplicationForCustomEvent:(MPRewardedVideoCustomEvent *)customEvent; + +/** + * Call this method when the user taps on the rewarded video ad. + * + * This method is optional. When automatic click and impression tracking are enabled (the default) + * this method will track a click (the click is guaranteed to only be tracked once per ad). + * + * **Note**: some third-party networks provide a "will leave application" callback instead of/in + * addition to a "user did click" callback. You should call this method in response to either of + * those callbacks (since leaving the application is generally an indicator of a user tap). + * + * @param customEvent You should pass `self` to allow the MoPub SDK to associate this event with the correct + * instance of your custom event. + */ +- (void)rewardedVideoDidReceiveTapEventForCustomEvent:(MPRewardedVideoCustomEvent *)customEvent; + +/** + * Call this method when the user should be rewarded for watching the rewarded video. + * + * @param customEvent You should pass `self` to allow the MoPub SDK to associate this event with the correct + * instance of your custom event. + * + * @param reward The reward object that contains the currency type as well as the amount that should be rewarded to + * the user. If the concept of currency type doesn't exist for your ad network, set the reward's currency type as + * kMPRewardedVideoRewardCurrencyTypeUnspecified. + */ +- (void)rewardedVideoShouldRewardUserForCustomEvent:(MPRewardedVideoCustomEvent *)customEvent reward:(MPRewardedVideoReward *)reward; + +/** + * Call this method to get the customer ID associated with this custom event. + * + * @return The user's customer ID. + */ +- (NSString *)customerIdForRewardedVideoCustomEvent:(MPRewardedVideoCustomEvent *)customEvent; + +/** @name Impression and Click Tracking */ + +/** + * Call this method to track an impression. + * + * @warning **Important**: You should **only** call this method if you have [opted out of automatic click and impression tracking]([MPRewardedVideoCustomEvent enableAutomaticImpressionAndClickTracking]). + * By default the MoPub SDK automatically tracks impressions. + */ +- (void)trackImpression; + +/** + * Call this method to track a click. + * + * @warning **Important**: You should **only** call this method if you have [opted out of automatic click and impression tracking]([MPRewardedVideoCustomEvent enableAutomaticImpressionAndClickTracking]). + * By default the MoPub SDK automatically tracks clicks. + */ +- (void)trackClick; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/RewardedVideo/MPRewardedVideoCustomEvent.m b/iphone/Maps/3party/MoPubSDK/RewardedVideo/MPRewardedVideoCustomEvent.m new file mode 100644 index 00000000000..2ed84596e0c --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/RewardedVideo/MPRewardedVideoCustomEvent.m @@ -0,0 +1,52 @@ +// +// MPRewardedVideoCustomEvent.m +// MoPubSDK +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import "MPRewardedVideoCustomEvent.h" +#import + +@implementation MPRewardedVideoCustomEvent + +- (void)requestRewardedVideoWithCustomEventInfo:(NSDictionary *)info +{ + // The default implementation of this method does nothing. Subclasses must override this method + // and implement code to load a rewarded video here. +} + +- (BOOL)hasAdAvailable +{ + // Subclasses must override this method and implement coheck whether or not a rewarded vidoe ad + // is available for presentation. + + return NO; +} + +- (void)presentRewardedVideoFromViewController:(UIViewController *)viewController +{ + // The default implementation of this method does nothing. Subclasses must override this method + // and implement code to display a rewarded video here. +} + +- (BOOL)enableAutomaticImpressionAndClickTracking +{ + // Subclasses may override this method to return NO to perform impression and click tracking + // manually. + return YES; +} + +- (void)handleAdPlayedForCustomEventNetwork +{ + // The default implementation of this method does nothing. Subclasses must override this method + // and implement code to handle when another ad unit plays an ad for the same ad network this custom event is representing. +} + +- (void)handleCustomEventInvalidated +{ + // The default implementation of this method does nothing. Subclasses must override this method + // and implement code to handle when the custom event is no longer needed by the rewarded video system. +} + +@end diff --git a/iphone/Maps/3party/MoPubSDK/RewardedVideo/MPRewardedVideoError.h b/iphone/Maps/3party/MoPubSDK/RewardedVideo/MPRewardedVideoError.h new file mode 100644 index 00000000000..9d9e8e9cc19 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/RewardedVideo/MPRewardedVideoError.h @@ -0,0 +1,24 @@ +// +// MPRewardedVideoError.h +// MoPubSDK +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import + +typedef enum { + MPRewardedVideoAdErrorUnknown = -1, + + MPRewardedVideoAdErrorTimeout = -1000, + MPRewardedVideoAdErrorAdUnitWarmingUp = -1001, + MPRewardedVideoAdErrorNoAdsAvailable = -1100, + MPRewardedVideoAdErrorInvalidCustomEvent = -1200, + MPRewardedVideoAdErrorMismatchingAdTypes = -1300, + MPRewardedVideoAdErrorAdAlreadyPlayed = -1400, + MPRewardedVideoAdErrorInvalidAdUnitID = -1500, + MPRewardedVideoAdErrorInvalidReward = -1600, + MPRewardedVideoAdErrorNoRewardSelected = -1601, +} MPRewardedVideoErrorCode; + +extern NSString * const MoPubRewardedVideoAdsSDKDomain; diff --git a/iphone/Maps/3party/MoPubSDK/RewardedVideo/MPRewardedVideoError.m b/iphone/Maps/3party/MoPubSDK/RewardedVideo/MPRewardedVideoError.m new file mode 100644 index 00000000000..b02d26cea94 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/RewardedVideo/MPRewardedVideoError.m @@ -0,0 +1,10 @@ +// +// MPRewardedVideoError.m +// MoPubSDK +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import "MPRewardedVideoError.h" + +NSString * const MoPubRewardedVideoAdsSDKDomain = @"MoPubRewardedVideoAdsSDKDomain"; \ No newline at end of file diff --git a/iphone/Maps/3party/MoPubSDK/RewardedVideo/MPRewardedVideoReward.h b/iphone/Maps/3party/MoPubSDK/RewardedVideo/MPRewardedVideoReward.h new file mode 100644 index 00000000000..5738b0c3cf7 --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/RewardedVideo/MPRewardedVideoReward.h @@ -0,0 +1,59 @@ +// +// MPRewardedVideoReward.h +// MoPubSDK +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import + +/** + * A constant that indicates that no currency type was specified with the reward. + */ +extern NSString *const kMPRewardedVideoRewardCurrencyTypeUnspecified; + +/** + * A constant that indicates that no currency amount was specified with the reward. + */ +extern NSInteger const kMPRewardedVideoRewardCurrencyAmountUnspecified; + + +/** + * `MPRewardedVideoReward` contains all the information needed to reward the user for watching + * a rewarded video ad. The class provides a currency amount and currency type. + */ + +@interface MPRewardedVideoReward : NSObject + +/** + * The type of currency that should be rewarded to the user. + * + * An undefined currency type should be specified as `kMPRewardedVideoRewardCurrencyTypeUnspecified`. + */ +@property (nonatomic, readonly) NSString *currencyType; + +/** + * The amount of currency to reward to the user. + * + * An undefined currency amount should be specified as `kMPRewardedVideoRewardCurrencyAmountUnspecified` + * wrapped as an NSNumber. + */ +@property (nonatomic, readonly) NSNumber *amount; + +/** + * Initializes the object with an undefined currency type (`kMPRewardedVideoRewardCurrencyTypeUnspecified`) and + * the amount passed in. + * + * @param amount The amount of currency the user is receiving. + */ +- (instancetype)initWithCurrencyAmount:(NSNumber *)amount; + +/** + * Initializes the object's properties with the currencyType and amount. + * + * @param currencyType The type of currency the user is receiving. + * @param amount The amount of currency the user is receiving. + */ +- (instancetype)initWithCurrencyType:(NSString *)currencyType amount:(NSNumber *)amount; + +@end diff --git a/iphone/Maps/3party/MoPubSDK/RewardedVideo/MPRewardedVideoReward.m b/iphone/Maps/3party/MoPubSDK/RewardedVideo/MPRewardedVideoReward.m new file mode 100644 index 00000000000..8f711673a4f --- /dev/null +++ b/iphone/Maps/3party/MoPubSDK/RewardedVideo/MPRewardedVideoReward.m @@ -0,0 +1,30 @@ +// +// MPRewardedVideoReward.m +// MoPubSDK +// +// Copyright (c) 2015 MoPub. All rights reserved. +// + +#import "MPRewardedVideoReward.h" + +NSString *const kMPRewardedVideoRewardCurrencyTypeUnspecified = @"MPMoPubRewardedVideoRewardCurrencyTypeUnspecified"; +NSInteger const kMPRewardedVideoRewardCurrencyAmountUnspecified = 0; + +@implementation MPRewardedVideoReward + +- (instancetype)initWithCurrencyType:(NSString *)currencyType amount:(NSNumber *)amount +{ + if (self = [super init]) { + _currencyType = [NSString stringWithUTF8String:[currencyType cStringUsingEncoding:NSISOLatin1StringEncoding]]; + _amount = amount; + } + + return self; +} + +- (instancetype)initWithCurrencyAmount:(NSNumber *)amount +{ + return [self initWithCurrencyType:kMPRewardedVideoRewardCurrencyTypeUnspecified amount:amount]; +} + +@end diff --git a/iphone/Maps/Maps.xcodeproj/project.pbxproj b/iphone/Maps/Maps.xcodeproj/project.pbxproj index b75087ccaa2..acc3ae643a8 100644 --- a/iphone/Maps/Maps.xcodeproj/project.pbxproj +++ b/iphone/Maps/Maps.xcodeproj/project.pbxproj @@ -156,9 +156,6 @@ 3432E1781E49B3A2008477E9 /* Bolts.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3432E1771E49B3A2008477E9 /* Bolts.framework */; }; 3432E1791E49B3A2008477E9 /* Bolts.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3432E1771E49B3A2008477E9 /* Bolts.framework */; }; 3432E17A1E49B3A2008477E9 /* Bolts.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3432E1771E49B3A2008477E9 /* Bolts.framework */; }; - 3432E17C1E49B484008477E9 /* FBAudienceNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3432E17B1E49B484008477E9 /* FBAudienceNetwork.framework */; }; - 3432E17D1E49B484008477E9 /* FBAudienceNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3432E17B1E49B484008477E9 /* FBAudienceNetwork.framework */; }; - 3432E17E1E49B484008477E9 /* FBAudienceNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3432E17B1E49B484008477E9 /* FBAudienceNetwork.framework */; }; 343E75971E5B1EE20041226A /* MWMCollectionViewController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 343E75961E5B1EE20041226A /* MWMCollectionViewController.mm */; }; 343E75981E5B1EE20041226A /* MWMCollectionViewController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 343E75961E5B1EE20041226A /* MWMCollectionViewController.mm */; }; 343E75991E5B1EE20041226A /* MWMCollectionViewController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 343E75961E5B1EE20041226A /* MWMCollectionViewController.mm */; }; @@ -488,6 +485,9 @@ 34ED298B1E3BB9B40054D003 /* RoutePoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34ED29891E3BB9B40054D003 /* RoutePoint.swift */; }; 34ED298C1E3BB9B40054D003 /* RoutePoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34ED29891E3BB9B40054D003 /* RoutePoint.swift */; }; 34EF94291C05A6F30050B714 /* MWMSegue.mm in Sources */ = {isa = PBXBuildFile; fileRef = F607C18D1C047FDC00B53A87 /* MWMSegue.mm */; }; + 34F4098B1E9E221700E57AC0 /* FBAudienceNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 34F4098A1E9E221700E57AC0 /* FBAudienceNetwork.framework */; }; + 34F4098C1E9E221700E57AC0 /* FBAudienceNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 34F4098A1E9E221700E57AC0 /* FBAudienceNetwork.framework */; }; + 34F4098D1E9E221700E57AC0 /* FBAudienceNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 34F4098A1E9E221700E57AC0 /* FBAudienceNetwork.framework */; }; 34F5E0D31E3F254800B1C415 /* UIView+Hierarchy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34F5E0D21E3F254800B1C415 /* UIView+Hierarchy.swift */; }; 34F5E0D41E3F254800B1C415 /* UIView+Hierarchy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34F5E0D21E3F254800B1C415 /* UIView+Hierarchy.swift */; }; 34F5E0D51E3F254800B1C415 /* UIView+Hierarchy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34F5E0D21E3F254800B1C415 /* UIView+Hierarchy.swift */; }; @@ -1597,7 +1597,6 @@ 342EE40F1C43DAA7009F6A49 /* MWMAuthorizationWebViewLoginViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MWMAuthorizationWebViewLoginViewController.h; sourceTree = ""; }; 342EE4101C43DAA7009F6A49 /* MWMAuthorizationWebViewLoginViewController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MWMAuthorizationWebViewLoginViewController.mm; sourceTree = ""; }; 3432E1771E49B3A2008477E9 /* Bolts.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Bolts.framework; sourceTree = ""; }; - 3432E17B1E49B484008477E9 /* FBAudienceNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = FBAudienceNetwork.framework; sourceTree = ""; }; 343E75951E5B1EE20041226A /* MWMCollectionViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MWMCollectionViewController.h; sourceTree = ""; }; 343E75961E5B1EE20041226A /* MWMCollectionViewController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MWMCollectionViewController.mm; sourceTree = ""; }; 3446C6761DDCA9A200146687 /* libtraffic.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libtraffic.a; path = "../../../omim-build/xcode/Debug/libtraffic.a"; sourceTree = ""; }; @@ -1796,6 +1795,7 @@ 34D8087C1E79360D002F0584 /* AlamofireImage.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AlamofireImage.framework; path = Carthage/Build/iOS/AlamofireImage.framework; sourceTree = ""; }; 34D808851E793F91002F0584 /* Pushwoosh.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Pushwoosh.framework; path = Carthage/Build/iOS/Pushwoosh.framework; sourceTree = ""; }; 34ED29891E3BB9B40054D003 /* RoutePoint.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoutePoint.swift; sourceTree = ""; }; + 34F4098A1E9E221700E57AC0 /* FBAudienceNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = FBAudienceNetwork.framework; path = MoPubSDK/AdNetworkSupport/Facebook/SDK/FBAudienceNetwork.framework; sourceTree = ""; }; 34F5E0D21E3F254800B1C415 /* UIView+Hierarchy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView+Hierarchy.swift"; sourceTree = ""; }; 34F5E0D61E3F334700B1C415 /* Types.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Types.swift; sourceTree = ""; }; 34F5E0DA1E3F3ED300B1C415 /* MWMRoutePoint.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MWMRoutePoint.h; sourceTree = ""; }; @@ -2342,7 +2342,6 @@ 34D808801E79361E002F0584 /* Alamofire.framework in Frameworks */, 340474F91E08199D00C92850 /* FBSDKLoginKit.framework in Frameworks */, 67B78B491E422E2E0018E590 /* iAd.framework in Frameworks */, - 3432E17C1E49B484008477E9 /* FBAudienceNetwork.framework in Frameworks */, 34D808871E793FA3002F0584 /* Pushwoosh.framework in Frameworks */, 3404750B1E08199E00C92850 /* MyTargetSDK.framework in Frameworks */, 340474F61E08199D00C92850 /* FBSDKCoreKit.framework in Frameworks */, @@ -2354,6 +2353,7 @@ 67B78B4A1E422E2E0018E590 /* MessageUI.framework in Frameworks */, 3404750E1E08199E00C92850 /* MyTrackerSDK.framework in Frameworks */, 67B78B481E422E2E0018E590 /* MobileCoreServices.framework in Frameworks */, + 34F4098B1E9E221700E57AC0 /* FBAudienceNetwork.framework in Frameworks */, 67B78B561E4233480018E590 /* AdSupport.framework in Frameworks */, 3432E1781E49B3A2008477E9 /* Bolts.framework in Frameworks */, 340474F31E08199D00C92850 /* Fabric.framework in Frameworks */, @@ -2371,6 +2371,7 @@ 671E78D31E6A423300B2859B /* librouting_common.a in Frameworks */, 34D808861E793F91002F0584 /* Pushwoosh.framework in Frameworks */, 34D8087B1E793606002F0584 /* Alamofire.framework in Frameworks */, + 34F4098C1E9E221700E57AC0 /* FBAudienceNetwork.framework in Frameworks */, 34D8087D1E79360D002F0584 /* AlamofireImage.framework in Frameworks */, 67B78B551E42333C0018E590 /* AdSupport.framework in Frameworks */, 67B78B471E422E0A0018E590 /* MobileCoreServices.framework in Frameworks */, @@ -2408,7 +2409,6 @@ 6741AACF1BF356BA002C974C /* libosrm.a in Frameworks */, 6741AAD01BF356BA002C974C /* libplatform.a in Frameworks */, 3404750F1E08199E00C92850 /* MyTrackerSDK.framework in Frameworks */, - 3432E17D1E49B484008477E9 /* FBAudienceNetwork.framework in Frameworks */, 6741AAD11BF356BA002C974C /* libprotobuf.a in Frameworks */, 6741AAD31BF356BA002C974C /* librouting.a in Frameworks */, 674A7E281C0DA573003D48E1 /* libstb_image.a in Frameworks */, @@ -2436,7 +2436,6 @@ 340474FB1E08199D00C92850 /* FBSDKLoginKit.framework in Frameworks */, 67B78B4F1E422E300018E590 /* iAd.framework in Frameworks */, 344D63191E795A3C006F17CB /* SystemConfiguration.framework in Frameworks */, - 3432E17E1E49B484008477E9 /* FBAudienceNetwork.framework in Frameworks */, 34D808881E793FA4002F0584 /* Pushwoosh.framework in Frameworks */, 3404750D1E08199E00C92850 /* MyTargetSDK.framework in Frameworks */, 340474F81E08199D00C92850 /* FBSDKCoreKit.framework in Frameworks */, @@ -2448,6 +2447,7 @@ 67B78B501E422E300018E590 /* MessageUI.framework in Frameworks */, 340475101E08199E00C92850 /* MyTrackerSDK.framework in Frameworks */, 67B78B4E1E422E300018E590 /* MobileCoreServices.framework in Frameworks */, + 34F4098D1E9E221700E57AC0 /* FBAudienceNetwork.framework in Frameworks */, 67B78B571E42334A0018E590 /* AdSupport.framework in Frameworks */, 3432E17A1E49B3A2008477E9 /* Bolts.framework in Frameworks */, 340474F51E08199D00C92850 /* Fabric.framework in Frameworks */, @@ -2606,7 +2606,7 @@ 3432E1771E49B3A2008477E9 /* Bolts.framework */, 340474DC1E08199D00C92850 /* Crashlytics.framework */, 340474DD1E08199D00C92850 /* Fabric.framework */, - 3432E17B1E49B484008477E9 /* FBAudienceNetwork.framework */, + 34F4098A1E9E221700E57AC0 /* FBAudienceNetwork.framework */, 340474DE1E08199D00C92850 /* FBSDKCoreKit.framework */, 340474DF1E08199D00C92850 /* FBSDKLoginKit.framework */, 340474E01E08199D00C92850 /* FBSDKShareKit.framework */, @@ -5889,6 +5889,10 @@ buildSettings = { CLANG_ENABLE_MODULES = YES; ENABLE_BITCODE = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK", + ); LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_NAME = "xc dbg"; }; @@ -5899,6 +5903,10 @@ buildSettings = { CLANG_ENABLE_MODULES = YES; ENABLE_BITCODE = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK", + ); LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_NAME = "xc rel"; }; @@ -5909,6 +5917,10 @@ buildSettings = { CLANG_ENABLE_MODULES = YES; ENABLE_BITCODE = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK", + ); LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_NAME = "xc dbg"; VALID_ARCHS = "arm64 armv7 armv7s i386 x86_64"; @@ -5920,6 +5932,10 @@ buildSettings = { CLANG_ENABLE_MODULES = YES; ENABLE_BITCODE = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK", + ); LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_NAME = "xc beta"; }; @@ -5930,6 +5946,10 @@ buildSettings = { CLANG_ENABLE_MODULES = YES; ENABLE_BITCODE = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK", + ); LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_NAME = "xc rel"; VALID_ARCHS = "arm64 armv7 armv7s i386 x86_64"; @@ -5941,6 +5961,10 @@ buildSettings = { CLANG_ENABLE_MODULES = YES; ENABLE_BITCODE = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK", + ); LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_NAME = "xc pf"; }; @@ -5950,6 +5974,10 @@ isa = XCBuildConfiguration; buildSettings = { BUILD_DIR = "$(BUILD_ROOT)"; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK", + ); LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", @@ -5996,6 +6024,10 @@ isa = XCBuildConfiguration; buildSettings = { BUILD_DIR = "$(BUILD_ROOT)"; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK", + ); LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", @@ -6042,6 +6074,10 @@ isa = XCBuildConfiguration; buildSettings = { BUILD_DIR = "$(BUILD_ROOT)"; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK", + ); LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", @@ -6088,6 +6124,10 @@ isa = XCBuildConfiguration; buildSettings = { BUILD_DIR = "$(BUILD_ROOT)"; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK", + ); LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", @@ -6134,6 +6174,10 @@ isa = XCBuildConfiguration; buildSettings = { BUILD_DIR = "$(BUILD_ROOT)"; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK", + ); LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", @@ -6180,6 +6224,10 @@ isa = XCBuildConfiguration; buildSettings = { BUILD_DIR = "$(BUILD_ROOT)"; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK", + ); LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", @@ -6281,6 +6329,10 @@ buildSettings = { BUILD_DIR = "$(BUILD_ROOT)"; CLANG_ENABLE_MODULES = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK", + ); LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", @@ -6380,6 +6432,10 @@ buildSettings = { BUILD_DIR = "$(BUILD_ROOT)"; CLANG_ENABLE_MODULES = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK", + ); LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", @@ -6482,6 +6538,10 @@ buildSettings = { BUILD_DIR = "$(BUILD_ROOT)"; CLANG_ENABLE_MODULES = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK", + ); LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", @@ -6584,6 +6644,10 @@ buildSettings = { BUILD_DIR = "$(BUILD_ROOT)"; CLANG_ENABLE_MODULES = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK", + ); LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", @@ -6686,6 +6750,10 @@ buildSettings = { BUILD_DIR = "$(BUILD_ROOT)"; CLANG_ENABLE_MODULES = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK", + ); LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", @@ -6789,6 +6857,10 @@ buildSettings = { BUILD_DIR = "$(BUILD_ROOT)"; CLANG_ENABLE_MODULES = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/3party/MoPubSDK/AdNetworkSupport/Facebook/SDK", + ); LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LIBRARY_SEARCH_PATHS = ( "$(inherited)",