diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 000000000..f4b958fdc --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,26 @@ +One line summary of the issue here + +- [ ] I am submitting a bug report for existing functionality +- [ ] I visited https://developers.mopub.com/ and found no answer +- [ ] I checked https://twittercommunity.com/c/advertiser-api/mopub to make sure that this issue has not already been filed +- [ ] I checked to make sure that this issue has not already been filed + +#### MoPub SDK Version: + +#### Device model and OS Version: + +#### Xcode Version: + +#### Ad Unit IDs used in reproducing the issue: + +#### Steps to reproduce the behavior: +Please list all relevant steps to reproduce the observed behavior. + +#### Expected behavior: +As concisely as possible, describe the expected behavior. + +#### Observed behavior: +As concisely as possible, describe the observed behavior. + +#### Evidence: +Device log files, Network log file, etc. \ No newline at end of file diff --git a/.gitignore b/.gitignore index 78acf828f..d69328830 100644 --- a/.gitignore +++ b/.gitignore @@ -49,6 +49,9 @@ MoPubSwiftSampleApp/ /Pods AdNetworkSupport/InMobi/ Jenkinsfile +MoPubSampleApp/ +Gemfile +Gemfile.lock # Canary app # Should not publicly distribute the interal folder (containing the internal testing files) diff --git a/CHANGELOG.md b/CHANGELOG.md index b4e9cc432..8fbacc673 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,88 @@ +## Version 5.9.0 (September 16, 2019) +- **Features** + - Add iOS 13 support to both SDK and MoPub Sample app. + - Totally remove `UIWebView` implementation and comments in MoPub SDK and MoPub Sample app. + - Add multi-window support for MoPub Sample app in iPadOS 13. New window can be opened by Drag & Dropping an ad cell in the ad list. + - Remove support for `tel` and `sms` functions for MRAID ads. + - Add Dark Mode support for MoPub Sample app in iOS 13. + - Remove the Objective C sample app project. + - Adopt `XCFramework` and the new Xcode build system with fastlane script updates, and thus require Xcode 11 to build instead of Xcode 9. + - Remove deprecated VAST extension `MoPubViewabilityTracker`. + - Replace deprecated `MPMoviePlayerViewController` with `AVPlayerViewController`. This affects MRAID videos. + - Replace deprecated `UIAlertView` with `UIAlertViewController`. + +- **Bug Fixes** + - Update `MPRealTimeTimer` so that it can properly handle foreground notifications that aren't balanced with backgrounding notifications. + - Fix an assertion crash in GDPR Sync that only happens in debug builds. + - Present `SKStoreProductViewController` only in portrait mode, so that we can prevent a `SKStoreProductViewController` crash in landscape mode (as designed by Apple). + - Fix an infinite load ad bug that happens when the ad URL to retry is the same as the failed ad URL. + - Fix a bug where location information is not sent to Ad Server when location permission has been allowed, the app can collect PII, and no app-specified location is set. + + ## Version 5.8.0 (July 22, 2019) +- **Features** + - Minimum version of the MoPub SDK bumped to iOS 9. + - StoreKit Improvement: New Apple URL schemes for apps.apple.com, books.apple.com, and music.apple.com are now parsed for `SKStoreProductViewController`. + - StoreKit Improvement: Affiliate token and campagin token are now parsed for `SKStoreProductViewController`. + - Existing banner constants are deprecated in favor of new, configurable height-based constants. To use these, `MPAdView`'s frame must be set before an ad load is attempted. + - Updated `MPAdView`'s `initWithAdUnitId:size:`, `loadAd`, and `adViewDidLoadAd:` APIs by providing overloads `initWithAdUnitId:`, `loadAdWithMaxAdSize:`, and `adViewDidLoadAd:adSize:` which move the requested ad size to load time instead of at initialization time. + - `SFSafariViewController` is now exclusively used for in-app clickthrough destinations. + - Disallow the sending of empty ad unit IDs for consent. + +- **Bug Fixes** + - iOS 13 fixes: Explicitly set `modalPresentationStyle` for all modals in the MoPubSDK to `UIModalPresentationFullSCreen` since iOS 13 beta 1 changed the default modal presentation behavior. + - Fixed occasional crash due with `MPTimer` by ensuring it is always run on the main runloop. + - Fixed bug where banner and medium rectangle auto refresh timer was being fired even if the refresh interval was zero. + - Fixed bug where updated ad targeting parameters were not sent when banners were auto refreshing. + - Fixed a bug where the `UIButton+MPAdditions` category was impacting all `UIButton`s in the app. MoPub-specific `UIButton` customization is now contained in a subclass. + +## Version 5.7.1 (June 3, 2019) +- **Features** + - Impression Level Revenue Data can now be received via a notification + +- **Bug Fixes** + - Fixed occasional crash due to multithreading bug + +## Version 5.7.0 (May 20, 2019) +- **Features** + - Impression Level Revenue Data: A data object that includes revenue information associated with each impression + - Verizon Ads SDK now supported as a mediated network + +- **Bug Fixes** + - Fixed bug where native video fires an impression when main image asset is missing + - Fixed MRAID off-screen compliance for resized ads on tablets + - Fixed crash in Canary App when tapping on the `+` on iPad + - Replaced deprecated usage of `openURL:` with `openURL:options:completionHandler:` for iOS10+ + - Fixed bug where click trackers can fire more than once on HTML banners and HTML interstitials + - Fixed bug in Canary App where ad units that were read using the QR code reader were not being saved + - Fixed bug where GDPR consent dialog was allowed to be presented twice in a row + +## Version 5.6.0 (March 18, 2019) +- **Features** + - Added `+` button to the Canary sample app allowing manual entry of custom ad units + +- **Bug Fixes** + - MRAID orientation, expansion, and resizing edge case bug fixes + - MRAID expansion will no longer trigger a click tracking event + - MRAID logging no longer spams the device console + - Fixed position bug of the Rewarded Video countdown timer when rotating the device after the ad loads + +## Version 5.5.0 (January 28, 2019) +- **Features** + - Advanced Bidding automatically initializes + - GDPR legitimate interest API now available; publishers may opt into allowing supported networks to collect user information on the basis of legitimate interest. + - We now distribute separate frameworks for simulator, device, and universal architectures + +- **Bug Fixes** + - Fixed rewarded video state occasionally not being reset correctly upon load failure + - Tweaked MRAID `ready` event timing so that it's in-spec + - Canary test app improvements and bug fixes + +## Version 5.4.1 (November 28, 2018) +- **Bug Fixes** + - Changed the MoPubSampleApp+Framework target to MoPubSampleApp in the Objective-C Sample App. + - Fixed crash when `MPTableViewAdPlacer` makes multiple ad requests within a short amount of time. + - Fixed bug with the internal state of rewarded video when the video fails to play. + ## Version 5.4.0 (October 3, 2018) - **Features** - SDK distribution as a dynamic framework is now available. @@ -7,7 +92,7 @@ - HTTP error codes now include the localized error description. - Added missing mraid.js file protections when showing MRAID ads. - Fixed native video crash. - - Fixed native ad timeout timer invalidation. + - Fixed native ad timeout timer invalidation. ## Version 5.3.0 (August 15, 2018) - **Features** diff --git a/Canary/Canary.xcodeproj/project.pbxproj b/Canary/Canary.xcodeproj/project.pbxproj index 921cc7316..2f61f6ee9 100644 --- a/Canary/Canary.xcodeproj/project.pbxproj +++ b/Canary/Canary.xcodeproj/project.pbxproj @@ -7,8 +7,18 @@ objects = { /* Begin PBXBuildFile section */ + 1DE64A0B3BDD8FCD47D78D70 /* Pods_CanaryUnitTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6C40A5887B79C7007EC58F2F /* Pods_CanaryUnitTests.framework */; }; + 2A1F52F422160A0300933277 /* ManualEntryInterfaceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A1F52F322160A0300933277 /* ManualEntryInterfaceViewController.swift */; }; + 2A1F52F62216230300933277 /* UIAlertController+Picker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A1F52F52216230300933277 /* UIAlertController+Picker.swift */; }; + 2A1F52F7221628A000933277 /* ManualEntryInterfaceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A1F52F322160A0300933277 /* ManualEntryInterfaceViewController.swift */; }; + 2A1F52F8221628AA00933277 /* UIAlertController+Picker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A1F52F52216230300933277 /* UIAlertController+Picker.swift */; }; + 2A35FAA921B5DA1C00DC8805 /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2A35FAA821B5DA1C00DC8805 /* AVFoundation.framework */; }; + 2A35FAAB21B5DA6200DC8805 /* QRCodeCameraInterfaceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A35FAAA21B5DA6200DC8805 /* QRCodeCameraInterfaceViewController.swift */; }; + 2A35FAAC21B5DA6200DC8805 /* QRCodeCameraInterfaceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A35FAAA21B5DA6200DC8805 /* QRCodeCameraInterfaceViewController.swift */; }; + 2A49E2002298766C0049B61B /* TestingMenuDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A49E1FF2298766C0049B61B /* TestingMenuDataSource.swift */; }; + 2A49E20222987B530049B61B /* SafeAreaTestViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A49E20122987B530049B61B /* SafeAreaTestViewController.swift */; }; 2A4D35E3211D074800BE9377 /* APIEndpointMenuDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A4D35E1211D074800BE9377 /* APIEndpointMenuDataSource.swift */; }; - 3C669C6604F60567A169BAC2 /* Pods_Canary__Internal_.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DD29E9B2187F9B97C60CAB75 /* Pods_Canary__Internal_.framework */; }; + 5A5CDC8C0D154CB3FBD61596 /* Pods_AppStore_Application.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7E7C4E30CE724CAC318447FB /* Pods_AppStore_Application.framework */; }; B2564EE320AB5039000B9F7A /* SampleAds.plist in Resources */ = {isa = PBXBuildFile; fileRef = BCF7095E1FBCCF50009A3981 /* SampleAds.plist */; }; B275DB2D200FD64000F053F8 /* SavedAdsDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = B275DB2C200FD64000F053F8 /* SavedAdsDataSource.swift */; }; B275DB2E200FD64000F053F8 /* SavedAdsDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = B275DB2C200FD64000F053F8 /* SavedAdsDataSource.swift */; }; @@ -16,8 +26,8 @@ B2D3EEB6200DAF94009FEBC9 /* SavedAdsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2D3EEB4200DAF94009FEBC9 /* SavedAdsManager.swift */; }; BC003AFD20DC42FE00CD1FEB /* LogingLevelMenuDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC003AFC20DC42FE00CD1FEB /* LogingLevelMenuDataSource.swift */; }; BC003AFE20DC42FE00CD1FEB /* LogingLevelMenuDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC003AFC20DC42FE00CD1FEB /* LogingLevelMenuDataSource.swift */; }; - BC00C5B8208FCF46008E0AEB /* AppDelegate+Consent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC00C5B7208FCF46008E0AEB /* AppDelegate+Consent.swift */; }; - BC00C5B9208FCF46008E0AEB /* AppDelegate+Consent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC00C5B7208FCF46008E0AEB /* AppDelegate+Consent.swift */; }; + BC014493220B6E2F0090F497 /* CanaryScreenshots.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC014492220B6E2F0090F497 /* CanaryScreenshots.swift */; }; + BC01449B220B6EBF0090F497 /* SnapshotHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC01449A220B6EBF0090F497 /* SnapshotHelper.swift */; }; BC01F6502004191400D6898B /* InterstitialAdViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC01F64F2004191400D6898B /* InterstitialAdViewController.swift */; }; BC01F6512004191400D6898B /* InterstitialAdViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC01F64F2004191400D6898B /* InterstitialAdViewController.swift */; }; BC01F65320042BD600D6898B /* RewardedAdViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC01F65220042BD600D6898B /* RewardedAdViewController.swift */; }; @@ -30,7 +40,7 @@ BC04945420F91BFC00CFD9C2 /* NetworkAdsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC04944C20F91BFC00CFD9C2 /* NetworkAdsViewController.swift */; }; BC04945A20F91C9E00CFD9C2 /* StoryboardInstantiable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC04945920F91C9E00CFD9C2 /* StoryboardInstantiable.swift */; }; BC04945B20F91C9E00CFD9C2 /* StoryboardInstantiable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC04945920F91C9E00CFD9C2 /* StoryboardInstantiable.swift */; }; - BC04945E20F9266A00CFD9C2 /* Internal.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC04945C20F9266A00CFD9C2 /* Internal.swift */; }; + BC04945E20F9266A00CFD9C2 /* InternalState.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC04945C20F9266A00CFD9C2 /* InternalState.swift */; }; BC16904D201F8F8E000EA77F /* AdFormat.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC16904C201F8F8E000EA77F /* AdFormat.swift */; }; BC16904E201F8F8E000EA77F /* AdFormat.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC16904C201F8F8E000EA77F /* AdFormat.swift */; }; BC243C3D20A9F5B800DE6EA9 /* MenuViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC243C3C20A9F5B800DE6EA9 /* MenuViewController.swift */; }; @@ -47,13 +57,11 @@ BC33E0391FBB627B0060ECBE /* SampleAdsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC33E0381FBB627B0060ECBE /* SampleAdsViewController.swift */; }; BC33E03B1FBB627B0060ECBE /* SavedAdsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC33E03A1FBB627B0060ECBE /* SavedAdsViewController.swift */; }; BC33E03E1FBB627B0060ECBE /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BC33E03C1FBB627B0060ECBE /* Main.storyboard */; }; - BC33E0401FBB627B0060ECBE /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BC33E03F1FBB627B0060ECBE /* Assets.xcassets */; }; BC33E0431FBB627B0060ECBE /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BC33E0411FBB627B0060ECBE /* LaunchScreen.storyboard */; }; BC33E04C1FBB63260060ECBE /* SavedAdsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC33E03A1FBB627B0060ECBE /* SavedAdsViewController.swift */; }; BC33E04D1FBB63260060ECBE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC33E0361FBB627B0060ECBE /* AppDelegate.swift */; }; BC33E04E1FBB63260060ECBE /* SampleAdsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC33E0381FBB627B0060ECBE /* SampleAdsViewController.swift */; }; BC33E0511FBB63260060ECBE /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BC33E0411FBB627B0060ECBE /* LaunchScreen.storyboard */; }; - BC33E0521FBB63260060ECBE /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BC33E03F1FBB627B0060ECBE /* Assets.xcassets */; }; BC33E0531FBB63260060ECBE /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BC33E03C1FBB627B0060ECBE /* Main.storyboard */; }; BC3B0C8920058290002D28B1 /* NativeAdViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC3B0C8820058290002D28B1 /* NativeAdViewController.swift */; }; BC3B0C8A20058290002D28B1 /* NativeAdViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC3B0C8820058290002D28B1 /* NativeAdViewController.swift */; }; @@ -69,8 +77,6 @@ BC3B0C992007DBAA002D28B1 /* TweetCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC3B0C972007DBAA002D28B1 /* TweetCollectionViewCell.swift */; }; BC3B0C9B2007DBC8002D28B1 /* TweetCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = BC3B0C9A2007DBC8002D28B1 /* TweetCollectionViewCell.xib */; }; BC3B0C9C2007DBC8002D28B1 /* TweetCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = BC3B0C9A2007DBC8002D28B1 /* TweetCollectionViewCell.xib */; }; - BC467DB1206465CB0083ECC4 /* AppDelegate+AdvancedBidders.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC467DB0206465CB0083ECC4 /* AppDelegate+AdvancedBidders.swift */; }; - BC467DB2206465CB0083ECC4 /* AppDelegate+AdvancedBidders.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC467DB0206465CB0083ECC4 /* AppDelegate+AdvancedBidders.swift */; }; BC4CE299213604DA00CA0220 /* NativeAdTableViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = BC4CE298213604DA00CA0220 /* NativeAdTableViewController.xib */; }; BC4CE29A213604DA00CA0220 /* NativeAdTableViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = BC4CE298213604DA00CA0220 /* NativeAdTableViewController.xib */; }; BC4CE29C2136054500CA0220 /* NativeAdTableDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC4CE29B2136054500CA0220 /* NativeAdTableDataSource.swift */; }; @@ -83,10 +89,14 @@ BC4CE2A62137343C00CA0220 /* NativeAdCollectionDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC4CE2A42137343C00CA0220 /* NativeAdCollectionDataSource.swift */; }; BC4E235F20A5FE2E000BA519 /* ContainerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC4E235E20A5FE2E000BA519 /* ContainerViewController.swift */; }; BC4E236020A5FE2E000BA519 /* ContainerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC4E235E20A5FE2E000BA519 /* ContainerViewController.swift */; }; + BC4FE3032208C57000B5D240 /* AdapterVersionsMenuDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC4FE3022208C57000B5D240 /* AdapterVersionsMenuDataSource.swift */; }; + BC4FE3042208C57000B5D240 /* AdapterVersionsMenuDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC4FE3022208C57000B5D240 /* AdapterVersionsMenuDataSource.swift */; }; + BC4FE3072208E8E500B5D240 /* CollapsibleAdapterInfoTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC4FE3062208E8E500B5D240 /* CollapsibleAdapterInfoTableViewCell.swift */; }; + BC4FE3082208E8E500B5D240 /* CollapsibleAdapterInfoTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC4FE3062208E8E500B5D240 /* CollapsibleAdapterInfoTableViewCell.swift */; }; + BC4FE30A2208E90800B5D240 /* CollapsibleAdapterInfoTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = BC4FE3092208E90800B5D240 /* CollapsibleAdapterInfoTableViewCell.xib */; }; + BC4FE30B2208E90800B5D240 /* CollapsibleAdapterInfoTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = BC4FE3092208E90800B5D240 /* CollapsibleAdapterInfoTableViewCell.xib */; }; BC525EC8212F15A5007B1761 /* MediumRectangleAdViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC525EC7212F15A5007B1761 /* MediumRectangleAdViewController.swift */; }; BC525EC9212F15A5007B1761 /* MediumRectangleAdViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC525EC7212F15A5007B1761 /* MediumRectangleAdViewController.swift */; }; - BC525ED1212F229A007B1761 /* LeaderboardAdViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC525ED0212F229A007B1761 /* LeaderboardAdViewController.swift */; }; - BC525ED2212F229A007B1761 /* LeaderboardAdViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC525ED0212F229A007B1761 /* LeaderboardAdViewController.swift */; }; BC525ED4212F2D71007B1761 /* InterstitialAdDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC525ED3212F2D71007B1761 /* InterstitialAdDataSource.swift */; }; BC525ED5212F2D71007B1761 /* InterstitialAdDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC525ED3212F2D71007B1761 /* InterstitialAdDataSource.swift */; }; BC525ED7212F50E3007B1761 /* RewardedAdDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC525ED6212F50E3007B1761 /* RewardedAdDataSource.swift */; }; @@ -99,13 +109,23 @@ BC525EEA2135BA4E007B1761 /* PreferredWidthLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC525EE82135BA4E007B1761 /* PreferredWidthLabel.swift */; }; BC59F25D20F524E40051DA00 /* AdUnitDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC59F25C20F524E30051DA00 /* AdUnitDataSource.swift */; }; BC59F25E20F524E40051DA00 /* AdUnitDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC59F25C20F524E30051DA00 /* AdUnitDataSource.swift */; }; + BC5F735D221F4C1D00EED041 /* FilteredAdUnitDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC5F735C221F4C1D00EED041 /* FilteredAdUnitDataSource.swift */; }; + BC5F735E221F4C1D00EED041 /* FilteredAdUnitDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC5F735C221F4C1D00EED041 /* FilteredAdUnitDataSource.swift */; }; + BC5F73602220AF6800EED041 /* FilterableAdUnitTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC5F735F2220AF6800EED041 /* FilterableAdUnitTableViewController.swift */; }; + BC5F73612220AF6800EED041 /* FilterableAdUnitTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC5F735F2220AF6800EED041 /* FilterableAdUnitTableViewController.swift */; }; BC63B9221FBD14A00033ACD6 /* AdUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC63B9211FBD14A00033ACD6 /* AdUnit.swift */; }; BC63B9231FBD14A00033ACD6 /* AdUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC63B9211FBD14A00033ACD6 /* AdUnit.swift */; }; BC647D9A20F967C800FAA12C /* BaseSplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC647D9920F967C800FAA12C /* BaseSplitViewController.swift */; }; BC647D9B20F967C800FAA12C /* BaseSplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC647D9920F967C800FAA12C /* BaseSplitViewController.swift */; }; + BC713BAE21700BBC003655B2 /* ReleaseTestingSplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC713BAD21700BBC003655B2 /* ReleaseTestingSplitViewController.swift */; }; + BC713BB121700C9B003655B2 /* ReleaseTestingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC713BB021700C9B003655B2 /* ReleaseTestingViewController.swift */; }; + BC7892E9220B9C22007A6218 /* AccessibilityIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC7892E8220B9C22007A6218 /* AccessibilityIdentifier.swift */; }; + BC7892EA220B9C22007A6218 /* AccessibilityIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC7892E8220B9C22007A6218 /* AccessibilityIdentifier.swift */; }; + BC7892EB220CB896007A6218 /* AccessibilityIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC7892E8220B9C22007A6218 /* AccessibilityIdentifier.swift */; }; + BC7892ED220CBAAD007A6218 /* XCTestCase+Waiting.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC7892EC220CBAAD007A6218 /* XCTestCase+Waiting.swift */; }; + BC7892EF220CC0FB007A6218 /* Screenshot.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC7892EE220CC0FB007A6218 /* Screenshot.swift */; }; BC95D23C2097B9180030C230 /* MainTabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC95D23B2097B9180030C230 /* MainTabBarController.swift */; }; BC95D23D2097B9180030C230 /* MainTabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC95D23B2097B9180030C230 /* MainTabBarController.swift */; }; - BC96A33A20AE306F00AC3266 /* PrivacyMenuDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC96A33920AE306F00AC3266 /* PrivacyMenuDataSource.swift */; }; BC96A33B20AE306F00AC3266 /* PrivacyMenuDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC96A33920AE306F00AC3266 /* PrivacyMenuDataSource.swift */; }; BC96A33D20AE32DB00AC3266 /* BasicMenuTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC96A33C20AE32DB00AC3266 /* BasicMenuTableViewCell.swift */; }; BC96A33E20AE32DB00AC3266 /* BasicMenuTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC96A33C20AE32DB00AC3266 /* BasicMenuTableViewCell.swift */; }; @@ -135,11 +155,11 @@ BCB63FE020AA426300C22C7F /* MenuDisplayable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCB63FDE20AA426300C22C7F /* MenuDisplayable.swift */; }; BCB63FE220AA442B00C22C7F /* MenuDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCB63FE120AA442B00C22C7F /* MenuDataSource.swift */; }; BCB63FE320AA442B00C22C7F /* MenuDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCB63FE120AA442B00C22C7F /* MenuDataSource.swift */; }; + BCB8CFB321C06230007F7F6C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BC33E03F1FBB627B0060ECBE /* Assets.xcassets */; }; + BCB8CFB421C06237007F7F6C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BC33E03F1FBB627B0060ECBE /* Assets.xcassets */; }; BCD0506C2003F1FF00FFC36D /* UIView+Nib.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCD0506B2003F1FF00FFC36D /* UIView+Nib.swift */; }; BCD0506D2003F20400FFC36D /* UIView+Nib.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCD0506B2003F1FF00FFC36D /* UIView+Nib.swift */; }; - BCE7078020B3392100DA4BCB /* PrivacyInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCE7077F20B3392100DA4BCB /* PrivacyInfoViewController.swift */; }; BCE7078120B3392100DA4BCB /* PrivacyInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCE7077F20B3392100DA4BCB /* PrivacyInfoViewController.swift */; }; - BCE7078320B3424E00DA4BCB /* PrivacyInfoDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCE7078220B3424E00DA4BCB /* PrivacyInfoDataSource.swift */; }; BCE7078420B3424E00DA4BCB /* PrivacyInfoDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCE7078220B3424E00DA4BCB /* PrivacyInfoDataSource.swift */; }; BCE7078620B34C3400DA4BCB /* MPBool+Description.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCE7078520B34C3400DA4BCB /* MPBool+Description.swift */; }; BCE7078720B34C3400DA4BCB /* MPBool+Description.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCE7078520B34C3400DA4BCB /* MPBool+Description.swift */; }; @@ -149,6 +169,7 @@ BCEB5DA72008270300FE5165 /* CGFloat+Random.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCEB5DA52008270300FE5165 /* CGFloat+Random.swift */; }; BCEB5DA92008279900FE5165 /* UIColor+Random.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCEB5DA82008279900FE5165 /* UIColor+Random.swift */; }; BCEB5DAA2008279900FE5165 /* UIColor+Random.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCEB5DA82008279900FE5165 /* UIColor+Random.swift */; }; + BCF6F7E421752DDF00FDE594 /* ReleaseTestingAds.plist in Resources */ = {isa = PBXBuildFile; fileRef = BCF6F7E321752DDF00FDE594 /* ReleaseTestingAds.plist */; }; BCF7095F1FBCCF50009A3981 /* SampleAds.plist in Resources */ = {isa = PBXBuildFile; fileRef = BCF7095E1FBCCF50009A3981 /* SampleAds.plist */; }; BCFE5B371FE4469200D760E9 /* AdUnitTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCFE5B361FE4469200D760E9 /* AdUnitTableViewCell.swift */; }; BCFE5B381FE4469200D760E9 /* AdUnitTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCFE5B361FE4469200D760E9 /* AdUnitTableViewCell.swift */; }; @@ -156,9 +177,82 @@ BCFE5B3C1FE446D600D760E9 /* AdUnitTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = BCFE5B3A1FE446D600D760E9 /* AdUnitTableViewCell.xib */; }; BCFE5B431FE84B5200D760E9 /* BannerAdViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCFE5B421FE84B5200D760E9 /* BannerAdViewController.swift */; }; BCFE5B441FE84B5200D760E9 /* BannerAdViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCFE5B421FE84B5200D760E9 /* BannerAdViewController.swift */; }; - D1EA998EABF442DAD42BE461 /* Pods_Canary.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E125D60FEE1A91995D3FDF82 /* Pods_Canary.framework */; }; + CE237F3A216C04A800A8134A /* MoPubBaseTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE237F2D216C028B00A8134A /* MoPubBaseTestCase.swift */; }; + CE237F3B216C04B300A8134A /* AdDetailPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE237F33216C02BF00A8134A /* AdDetailPage.swift */; }; + CE237F3C216C04B300A8134A /* AdListPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE237F32216C02BF00A8134A /* AdListPage.swift */; }; + CE237F3D216C04B800A8134A /* BannerAdLabels.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE237F38216C02CE00A8134A /* BannerAdLabels.swift */; }; + CE237F3E216C04BC00A8134A /* BasePage.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE237F36216C02C700A8134A /* BasePage.swift */; }; + CE464D3121AE3A1900A13424 /* NativeCollectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE464D2F21AE39F300A13424 /* NativeCollectionTests.swift */; }; + CE4E2E2621AF2F6A00368EFD /* NativeTableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE4E2E2421AF2F6500368EFD /* NativeTableTests.swift */; }; + CE6BBCB8219A369E0055F3B0 /* NativeVideoAdTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE6BBCB6219A23580055F3B0 /* NativeVideoAdTests.swift */; }; + CE8A53902192257600377503 /* HTMLInterstitialAdTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE8A538E2192254F00377503 /* HTMLInterstitialAdTests.swift */; }; + CE8A539321924B9400377503 /* MRAIDInterstitialAdTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE8A539121924B8F00377503 /* MRAIDInterstitialAdTests.swift */; }; + CE8A53962193AB6000377503 /* NativeAdTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE8A53942193AB5900377503 /* NativeAdTests.swift */; }; + CEBF664E2187BD1500284500 /* CallbackFunctionNames.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEBF664C2187BC2700284500 /* CallbackFunctionNames.swift */; }; + CEBF66532187C99C00284500 /* IPhoneHome.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEBF664F2187C92400284500 /* IPhoneHome.swift */; }; + CEBF66542187C99C00284500 /* SafariApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEBF66512187C94500284500 /* SafariApp.swift */; }; + CEBF6712218A467800284500 /* MRAIDBannerAdTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEBF6710218A465400284500 /* MRAIDBannerAdTests.swift */; }; + CEC115C521BDE9C000152CF8 /* NativeVideoTableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC115C421BDE9C000152CF8 /* NativeVideoTableTests.swift */; }; + CEC115C721BEF8D000152CF8 /* RewardedRichMediaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC115C621BEF8D000152CF8 /* RewardedRichMediaTests.swift */; }; + CED56816216D2F2F00C0E2A5 /* HTMLBannerAdTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CED56814216D2F1400C0E2A5 /* HTMLBannerAdTests.swift */; }; + DE0F2F349F83D71EFA10F713 /* Pods_Internal_Application.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 22E0C64A8C1276259F1261BA /* Pods_Internal_Application.framework */; }; + EC0BF26F2279EBF0003DB141 /* NativeAdRendererManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC0BF26E2279EBF0003DB141 /* NativeAdRendererManagerTests.swift */; }; + EC32524D224ECF8E00D955C3 /* UserDefaults+Subscript.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC32524C224ECF8E00D955C3 /* UserDefaults+Subscript.swift */; }; + EC325253224ED56F00D955C3 /* UserDefaults+Subscript.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC32524C224ECF8E00D955C3 /* UserDefaults+Subscript.swift */; }; + EC325259225273AD00D955C3 /* TextAndToggleTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC325257225273AD00D955C3 /* TextAndToggleTableViewCell.swift */; }; + EC32525A225273AD00D955C3 /* TextAndToggleTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC325257225273AD00D955C3 /* TextAndToggleTableViewCell.swift */; }; + EC32525B225273AD00D955C3 /* TextAndToggleTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = EC325258225273AD00D955C3 /* TextAndToggleTableViewCell.xib */; }; + EC32525C225273AD00D955C3 /* TextAndToggleTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = EC325258225273AD00D955C3 /* TextAndToggleTableViewCell.xib */; }; + EC32525E225276F500D955C3 /* NSObject+Utility.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC32525D225276F500D955C3 /* NSObject+Utility.swift */; }; + EC32525F225276F500D955C3 /* NSObject+Utility.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC32525D225276F500D955C3 /* NSObject+Utility.swift */; }; + EC32526422527BCB00D955C3 /* UITableView+Utility.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC32526322527BCB00D955C3 /* UITableView+Utility.swift */; }; + EC32526522527BCB00D955C3 /* UITableView+Utility.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC32526322527BCB00D955C3 /* UITableView+Utility.swift */; }; + EC469C072260EF1D006CABAA /* TypedNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC469C03226006F4006CABAA /* TypedNotification.swift */; }; + EC469C082260EF1E006CABAA /* TypedNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC469C03226006F4006CABAA /* TypedNotification.swift */; }; + EC469C0A2260F4F2006CABAA /* Notification+Token.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC469C092260F4F2006CABAA /* Notification+Token.swift */; }; + EC469C0B2260F4F2006CABAA /* Notification+Token.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC469C092260F4F2006CABAA /* Notification+Token.swift */; }; + EC469C1D22614639006CABAA /* SavedAdsManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC469C1C22614639006CABAA /* SavedAdsManagerTests.swift */; }; + EC79994A230F8C1500589ED4 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC799949230F8C1500589ED4 /* SceneDelegate.swift */; }; + EC79994B230F8C1500589ED4 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC799949230F8C1500589ED4 /* SceneDelegate.swift */; }; + EC7999542311917100589ED4 /* UIViewController+Utility.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC7999532311917100589ED4 /* UIViewController+Utility.swift */; }; + EC7999562311917400589ED4 /* UIViewController+Utility.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC7999532311917100589ED4 /* UIViewController+Utility.swift */; }; + ECF218042278AE94008BD940 /* Array+Sort.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECF218032278AE94008BD940 /* Array+Sort.swift */; }; + ECF218052278AE94008BD940 /* Array+Sort.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECF218032278AE94008BD940 /* Array+Sort.swift */; }; + ECF218072278B274008BD940 /* ArraySortExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECF218062278B274008BD940 /* ArraySortExtensionTests.swift */; }; + ECF2180A2278D168008BD940 /* MPNativeAdRendererConfiguration+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECF218092278D168008BD940 /* MPNativeAdRendererConfiguration+Utilities.swift */; }; + ECF2180B2278D168008BD940 /* MPNativeAdRendererConfiguration+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECF218092278D168008BD940 /* MPNativeAdRendererConfiguration+Utilities.swift */; }; + ECF218122278D5B8008BD940 /* NativeAdRendererMenuDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECF218112278D5B8008BD940 /* NativeAdRendererMenuDataSource.swift */; }; + ECF218132278D5B8008BD940 /* NativeAdRendererMenuDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECF218112278D5B8008BD940 /* NativeAdRendererMenuDataSource.swift */; }; + ECF218152278D9A4008BD940 /* OrderPreferenceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECF218142278D9A4008BD940 /* OrderPreferenceViewController.swift */; }; + ECF218162278D9A4008BD940 /* OrderPreferenceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECF218142278D9A4008BD940 /* OrderPreferenceViewController.swift */; }; + ECF2181D227900C2008BD940 /* NativeAdRendererManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECF2181C227900C2008BD940 /* NativeAdRendererManager.swift */; }; + ECF2181E227900C2008BD940 /* NativeAdRendererManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECF2181C227900C2008BD940 /* NativeAdRendererManager.swift */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + BC014495220B6E2F0090F497 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BC33E02B1FBB627B0060ECBE /* Project object */; + proxyType = 1; + remoteGlobalIDString = BC33E0321FBB627B0060ECBE; + remoteInfo = Canary; + }; + EC2C1A6B22710A8E00B1B594 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BC33E02B1FBB627B0060ECBE /* Project object */; + proxyType = 1; + remoteGlobalIDString = BC33E04A1FBB63260060ECBE; + remoteInfo = "Internal Application"; + }; + EC469C1522613063006CABAA /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BC33E02B1FBB627B0060ECBE /* Project object */; + proxyType = 1; + remoteGlobalIDString = BC33E04A1FBB63260060ECBE; + remoteInfo = "Internal Application"; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXCopyFilesBuildPhase section */ BC4A99D5200EBCDD00E3EB07 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; @@ -173,15 +267,26 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 22E0C64A8C1276259F1261BA /* Pods_Internal_Application.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Internal_Application.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 2783EDBC05C4535EE4245274 /* Pods-CanaryUnitTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CanaryUnitTests.release.xcconfig"; path = "Target Support Files/Pods-CanaryUnitTests/Pods-CanaryUnitTests.release.xcconfig"; sourceTree = ""; }; + 2A1F52F322160A0300933277 /* ManualEntryInterfaceViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManualEntryInterfaceViewController.swift; sourceTree = ""; }; + 2A1F52F52216230300933277 /* UIAlertController+Picker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIAlertController+Picker.swift"; sourceTree = ""; }; + 2A35FAA821B5DA1C00DC8805 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; }; + 2A35FAAA21B5DA6200DC8805 /* QRCodeCameraInterfaceViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeCameraInterfaceViewController.swift; sourceTree = ""; }; + 2A49E1FF2298766C0049B61B /* TestingMenuDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestingMenuDataSource.swift; sourceTree = ""; }; + 2A49E20122987B530049B61B /* SafeAreaTestViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafeAreaTestViewController.swift; sourceTree = ""; }; 2A4D35E1211D074800BE9377 /* APIEndpointMenuDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIEndpointMenuDataSource.swift; sourceTree = ""; }; - 3FA62C94B44B098C968278EE /* Pods-Canary (Internal).debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Canary (Internal).debug.xcconfig"; path = "Pods/Target Support Files/Pods-Canary (Internal)/Pods-Canary (Internal).debug.xcconfig"; sourceTree = ""; }; - 55F769B196AF032A147C6387 /* Pods-Canary.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Canary.release.xcconfig"; path = "Pods/Target Support Files/Pods-Canary/Pods-Canary.release.xcconfig"; sourceTree = ""; }; + 38B93E7C897560D8A670BE08 /* Pods-AppStore Application.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AppStore Application.release.xcconfig"; path = "Target Support Files/Pods-AppStore Application/Pods-AppStore Application.release.xcconfig"; sourceTree = ""; }; + 6C40A5887B79C7007EC58F2F /* Pods_CanaryUnitTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_CanaryUnitTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 7E7C4E30CE724CAC318447FB /* Pods_AppStore_Application.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_AppStore_Application.framework; sourceTree = BUILT_PRODUCTS_DIR; }; B2564EE020AB4F2B000B9F7A /* Internal-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Internal-Info.plist"; sourceTree = ""; }; B275DB2C200FD64000F053F8 /* SavedAdsDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SavedAdsDataSource.swift; sourceTree = ""; }; B2D3EEB4200DAF94009FEBC9 /* SavedAdsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SavedAdsManager.swift; sourceTree = ""; }; - BBA3359CC86A7CE1AF32934B /* Pods-Canary (Internal).release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Canary (Internal).release.xcconfig"; path = "Pods/Target Support Files/Pods-Canary (Internal)/Pods-Canary (Internal).release.xcconfig"; sourceTree = ""; }; BC003AFC20DC42FE00CD1FEB /* LogingLevelMenuDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogingLevelMenuDataSource.swift; sourceTree = ""; }; - BC00C5B7208FCF46008E0AEB /* AppDelegate+Consent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+Consent.swift"; sourceTree = ""; }; + BC014490220B6E2F0090F497 /* CanaryScreenshots.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CanaryScreenshots.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + BC014492220B6E2F0090F497 /* CanaryScreenshots.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CanaryScreenshots.swift; sourceTree = ""; }; + BC014494220B6E2F0090F497 /* Snapshots-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Snapshots-Info.plist"; sourceTree = ""; }; + BC01449A220B6EBF0090F497 /* SnapshotHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SnapshotHelper.swift; path = ../../../../fastlane/SnapshotHelper.swift; sourceTree = ""; }; BC01F64F2004191400D6898B /* InterstitialAdViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InterstitialAdViewController.swift; sourceTree = ""; }; BC01F65220042BD600D6898B /* RewardedAdViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RewardedAdViewController.swift; sourceTree = ""; }; BC04944220F904F200CFD9C2 /* AdUnitTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdUnitTableViewController.swift; sourceTree = ""; }; @@ -190,7 +295,7 @@ BC04944B20F91BFC00CFD9C2 /* NetworkAds.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = NetworkAds.plist; sourceTree = ""; }; BC04944C20F91BFC00CFD9C2 /* NetworkAdsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkAdsViewController.swift; sourceTree = ""; }; BC04945920F91C9E00CFD9C2 /* StoryboardInstantiable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StoryboardInstantiable.swift; sourceTree = ""; }; - BC04945C20F9266A00CFD9C2 /* Internal.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Internal.swift; sourceTree = ""; }; + BC04945C20F9266A00CFD9C2 /* InternalState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InternalState.swift; sourceTree = ""; }; BC16904C201F8F8E000EA77F /* AdFormat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdFormat.swift; sourceTree = ""; }; BC243C3C20A9F5B800DE6EA9 /* MenuViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuViewController.swift; sourceTree = ""; }; BC2B97E620F41D3E00D58F79 /* AdUnitTableViewHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdUnitTableViewHeader.swift; sourceTree = ""; }; @@ -205,7 +310,7 @@ BC33E03F1FBB627B0060ECBE /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; BC33E0421FBB627B0060ECBE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; BC33E0441FBB627B0060ECBE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - BC33E0571FBB63260060ECBE /* Canary (Internal).app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Canary (Internal).app"; sourceTree = BUILT_PRODUCTS_DIR; }; + BC33E0571FBB63260060ECBE /* Canary.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Canary.app; sourceTree = BUILT_PRODUCTS_DIR; }; BC3B0C8820058290002D28B1 /* NativeAdViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeAdViewController.swift; sourceTree = ""; }; BC3B0C8B20058D7C002D28B1 /* NativeAdView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeAdView.swift; sourceTree = ""; }; BC3B0C8E20058DBB002D28B1 /* NativeAdView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = NativeAdView.xib; sourceTree = ""; }; @@ -213,23 +318,32 @@ BC3B0C942007DAA5002D28B1 /* NativeAdCollectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeAdCollectionViewController.swift; sourceTree = ""; }; BC3B0C972007DBAA002D28B1 /* TweetCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TweetCollectionViewCell.swift; sourceTree = ""; }; BC3B0C9A2007DBC8002D28B1 /* TweetCollectionViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TweetCollectionViewCell.xib; sourceTree = ""; }; - BC467DB0206465CB0083ECC4 /* AppDelegate+AdvancedBidders.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+AdvancedBidders.swift"; sourceTree = ""; }; BC4CE298213604DA00CA0220 /* NativeAdTableViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = NativeAdTableViewController.xib; sourceTree = ""; }; BC4CE29B2136054500CA0220 /* NativeAdTableDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeAdTableDataSource.swift; sourceTree = ""; }; BC4CE29E2136FB6100CA0220 /* AdViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdViewController.swift; sourceTree = ""; }; BC4CE2A1213733EB00CA0220 /* NativeAdCollectionViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = NativeAdCollectionViewController.xib; sourceTree = ""; }; BC4CE2A42137343C00CA0220 /* NativeAdCollectionDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeAdCollectionDataSource.swift; sourceTree = ""; }; BC4E235E20A5FE2E000BA519 /* ContainerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContainerViewController.swift; sourceTree = ""; }; + BC4FE3022208C57000B5D240 /* AdapterVersionsMenuDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdapterVersionsMenuDataSource.swift; sourceTree = ""; }; + BC4FE3062208E8E500B5D240 /* CollapsibleAdapterInfoTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollapsibleAdapterInfoTableViewCell.swift; sourceTree = ""; }; + BC4FE3092208E90800B5D240 /* CollapsibleAdapterInfoTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = CollapsibleAdapterInfoTableViewCell.xib; sourceTree = ""; }; BC525EC7212F15A5007B1761 /* MediumRectangleAdViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediumRectangleAdViewController.swift; sourceTree = ""; }; - BC525ED0212F229A007B1761 /* LeaderboardAdViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LeaderboardAdViewController.swift; sourceTree = ""; }; BC525ED3212F2D71007B1761 /* InterstitialAdDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InterstitialAdDataSource.swift; sourceTree = ""; }; BC525ED6212F50E3007B1761 /* RewardedAdDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RewardedAdDataSource.swift; sourceTree = ""; }; BC525EDF2130A362007B1761 /* NativeAdDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeAdDataSource.swift; sourceTree = ""; }; BC525EE22130A61D007B1761 /* BaseNativeAdDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseNativeAdDataSource.swift; sourceTree = ""; }; BC525EE82135BA4E007B1761 /* PreferredWidthLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferredWidthLabel.swift; sourceTree = ""; }; BC59F25C20F524E30051DA00 /* AdUnitDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AdUnitDataSource.swift; sourceTree = ""; }; + BC5F735C221F4C1D00EED041 /* FilteredAdUnitDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilteredAdUnitDataSource.swift; sourceTree = ""; }; + BC5F735F2220AF6800EED041 /* FilterableAdUnitTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterableAdUnitTableViewController.swift; sourceTree = ""; }; BC63B9211FBD14A00033ACD6 /* AdUnit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdUnit.swift; sourceTree = ""; }; BC647D9920F967C800FAA12C /* BaseSplitViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseSplitViewController.swift; sourceTree = ""; }; + BC713BAD21700BBC003655B2 /* ReleaseTestingSplitViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReleaseTestingSplitViewController.swift; sourceTree = ""; }; + BC713BB021700C9B003655B2 /* ReleaseTestingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReleaseTestingViewController.swift; sourceTree = ""; }; + BC7892E8220B9C22007A6218 /* AccessibilityIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessibilityIdentifier.swift; sourceTree = ""; }; + BC7892EC220CBAAD007A6218 /* XCTestCase+Waiting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCTestCase+Waiting.swift"; sourceTree = ""; }; + BC7892EE220CC0FB007A6218 /* Screenshot.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Screenshot.swift; sourceTree = ""; }; + BC8D8EC4222F234B003BE094 /* Default.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Default.xcconfig; sourceTree = ""; }; BC95D23B2097B9180030C230 /* MainTabBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainTabBarController.swift; sourceTree = ""; }; BC96A33920AE306F00AC3266 /* PrivacyMenuDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyMenuDataSource.swift; sourceTree = ""; }; BC96A33C20AE32DB00AC3266 /* BasicMenuTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasicMenuTableViewCell.swift; sourceTree = ""; }; @@ -254,21 +368,70 @@ BCE738601FBB707500093CCD /* CoalMine.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = CoalMine.framework; sourceTree = BUILT_PRODUCTS_DIR; }; BCEB5DA52008270300FE5165 /* CGFloat+Random.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "CGFloat+Random.swift"; path = "Canary/Extensions/CGFloat+Random.swift"; sourceTree = SOURCE_ROOT; }; BCEB5DA82008279900FE5165 /* UIColor+Random.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "UIColor+Random.swift"; path = "Canary/Extensions/UIColor+Random.swift"; sourceTree = SOURCE_ROOT; }; + BCF6F7E321752DDF00FDE594 /* ReleaseTestingAds.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = ReleaseTestingAds.plist; sourceTree = ""; }; BCF7095E1FBCCF50009A3981 /* SampleAds.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = SampleAds.plist; sourceTree = ""; }; BCFE5B361FE4469200D760E9 /* AdUnitTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdUnitTableViewCell.swift; sourceTree = ""; }; BCFE5B3A1FE446D600D760E9 /* AdUnitTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AdUnitTableViewCell.xib; sourceTree = ""; }; BCFE5B421FE84B5200D760E9 /* BannerAdViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BannerAdViewController.swift; sourceTree = ""; }; - C8048ED6DE0E405574BB074C /* Pods-Canary.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Canary.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Canary/Pods-Canary.debug.xcconfig"; sourceTree = ""; }; - DD29E9B2187F9B97C60CAB75 /* Pods_Canary__Internal_.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Canary__Internal_.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - E125D60FEE1A91995D3FDF82 /* Pods_Canary.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Canary.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + CA1DEC059DCDC57801D88A60 /* Pods-AppStore Application.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AppStore Application.debug.xcconfig"; path = "Target Support Files/Pods-AppStore Application/Pods-AppStore Application.debug.xcconfig"; sourceTree = ""; }; + CE237F1E216BF2C800A8134A /* CanaryUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CanaryUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + CE237F22216BF2C800A8134A /* UITests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "UITests-Info.plist"; sourceTree = ""; }; + CE237F2D216C028B00A8134A /* MoPubBaseTestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MoPubBaseTestCase.swift; sourceTree = ""; }; + CE237F32216C02BF00A8134A /* AdListPage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AdListPage.swift; sourceTree = ""; }; + CE237F33216C02BF00A8134A /* AdDetailPage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AdDetailPage.swift; sourceTree = ""; }; + CE237F36216C02C700A8134A /* BasePage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BasePage.swift; sourceTree = ""; }; + CE237F38216C02CE00A8134A /* BannerAdLabels.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BannerAdLabels.swift; sourceTree = ""; }; + CE464D2F21AE39F300A13424 /* NativeCollectionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeCollectionTests.swift; sourceTree = ""; }; + CE4E2E2421AF2F6500368EFD /* NativeTableTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeTableTests.swift; sourceTree = ""; }; + CE6BBCB6219A23580055F3B0 /* NativeVideoAdTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeVideoAdTests.swift; sourceTree = ""; }; + CE8A538E2192254F00377503 /* HTMLInterstitialAdTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTMLInterstitialAdTests.swift; sourceTree = ""; }; + CE8A539121924B8F00377503 /* MRAIDInterstitialAdTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MRAIDInterstitialAdTests.swift; sourceTree = ""; }; + CE8A53942193AB5900377503 /* NativeAdTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeAdTests.swift; sourceTree = ""; }; + CEBF664C2187BC2700284500 /* CallbackFunctionNames.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallbackFunctionNames.swift; sourceTree = ""; }; + CEBF664F2187C92400284500 /* IPhoneHome.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IPhoneHome.swift; sourceTree = ""; }; + CEBF66512187C94500284500 /* SafariApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariApp.swift; sourceTree = ""; }; + CEBF6710218A465400284500 /* MRAIDBannerAdTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MRAIDBannerAdTests.swift; sourceTree = ""; }; + CEC115C421BDE9C000152CF8 /* NativeVideoTableTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeVideoTableTests.swift; sourceTree = ""; }; + CEC115C621BEF8D000152CF8 /* RewardedRichMediaTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RewardedRichMediaTests.swift; sourceTree = ""; }; + CED56814216D2F1400C0E2A5 /* HTMLBannerAdTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTMLBannerAdTests.swift; sourceTree = ""; }; + E280CCD44F6A8B4AF8F3766C /* Pods-Internal Application.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Internal Application.debug.xcconfig"; path = "Target Support Files/Pods-Internal Application/Pods-Internal Application.debug.xcconfig"; sourceTree = ""; }; + E2E466BCBD3F681948D93068 /* Pods-CanaryUnitTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CanaryUnitTests.debug.xcconfig"; path = "Target Support Files/Pods-CanaryUnitTests/Pods-CanaryUnitTests.debug.xcconfig"; sourceTree = ""; }; + EC0BF26E2279EBF0003DB141 /* NativeAdRendererManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeAdRendererManagerTests.swift; sourceTree = ""; }; + EC32524C224ECF8E00D955C3 /* UserDefaults+Subscript.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserDefaults+Subscript.swift"; sourceTree = ""; }; + EC325257225273AD00D955C3 /* TextAndToggleTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextAndToggleTableViewCell.swift; sourceTree = ""; }; + EC325258225273AD00D955C3 /* TextAndToggleTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TextAndToggleTableViewCell.xib; sourceTree = ""; }; + EC32525D225276F500D955C3 /* NSObject+Utility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSObject+Utility.swift"; sourceTree = ""; }; + EC32526322527BCB00D955C3 /* UITableView+Utility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITableView+Utility.swift"; sourceTree = ""; }; + EC469C03226006F4006CABAA /* TypedNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypedNotification.swift; sourceTree = ""; }; + EC469C092260F4F2006CABAA /* Notification+Token.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Notification+Token.swift"; sourceTree = ""; }; + EC469C1022613063006CABAA /* CanaryUnitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CanaryUnitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + EC469C1422613063006CABAA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + EC469C1C22614639006CABAA /* SavedAdsManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SavedAdsManagerTests.swift; sourceTree = ""; }; + EC799949230F8C1500589ED4 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + EC7999532311917100589ED4 /* UIViewController+Utility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Utility.swift"; sourceTree = ""; }; + ECF218032278AE94008BD940 /* Array+Sort.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+Sort.swift"; sourceTree = ""; }; + ECF218062278B274008BD940 /* ArraySortExtensionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArraySortExtensionTests.swift; sourceTree = ""; }; + ECF218092278D168008BD940 /* MPNativeAdRendererConfiguration+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MPNativeAdRendererConfiguration+Utilities.swift"; sourceTree = ""; }; + ECF218112278D5B8008BD940 /* NativeAdRendererMenuDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeAdRendererMenuDataSource.swift; sourceTree = ""; }; + ECF218142278D9A4008BD940 /* OrderPreferenceViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderPreferenceViewController.swift; sourceTree = ""; }; + ECF2181C227900C2008BD940 /* NativeAdRendererManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeAdRendererManager.swift; sourceTree = ""; }; + F8AD0C2B4CE6D8A1D096CD2F /* Pods-Internal Application.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Internal Application.release.xcconfig"; path = "Target Support Files/Pods-Internal Application/Pods-Internal Application.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + BC01448D220B6E2F0090F497 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; BC33E0301FBB627B0060ECBE /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - D1EA998EABF442DAD42BE461 /* Pods_Canary.framework in Frameworks */, + 2A35FAA921B5DA1C00DC8805 /* AVFoundation.framework in Frameworks */, + 5A5CDC8C0D154CB3FBD61596 /* Pods_AppStore_Application.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -276,30 +439,56 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 3C669C6604F60567A169BAC2 /* Pods_Canary__Internal_.framework in Frameworks */, + DE0F2F349F83D71EFA10F713 /* Pods_Internal_Application.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + CE237F1B216BF2C800A8134A /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + EC469C0D22613063006CABAA /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 1DE64A0B3BDD8FCD47D78D70 /* Pods_CanaryUnitTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 2A4D35E0211D06ED00BE9377 /* APIEndpoint */ = { + 0579E20B6B7F9CB8F99A5FA4 /* Pods */ = { isa = PBXGroup; children = ( - 2A4D35E1211D074800BE9377 /* APIEndpointMenuDataSource.swift */, + CA1DEC059DCDC57801D88A60 /* Pods-AppStore Application.debug.xcconfig */, + 38B93E7C897560D8A670BE08 /* Pods-AppStore Application.release.xcconfig */, + E2E466BCBD3F681948D93068 /* Pods-CanaryUnitTests.debug.xcconfig */, + 2783EDBC05C4535EE4245274 /* Pods-CanaryUnitTests.release.xcconfig */, + E280CCD44F6A8B4AF8F3766C /* Pods-Internal Application.debug.xcconfig */, + F8AD0C2B4CE6D8A1D096CD2F /* Pods-Internal Application.release.xcconfig */, ); - path = APIEndpoint; + path = Pods; sourceTree = ""; }; - B0C7C0257843E1CF62319AB8 /* Pods */ = { + 2A49E1FE229876310049B61B /* Testing */ = { isa = PBXGroup; children = ( - C8048ED6DE0E405574BB074C /* Pods-Canary.debug.xcconfig */, - BBA3359CC86A7CE1AF32934B /* Pods-Canary (Internal).release.xcconfig */, - 55F769B196AF032A147C6387 /* Pods-Canary.release.xcconfig */, - 3FA62C94B44B098C968278EE /* Pods-Canary (Internal).debug.xcconfig */, + 2A49E1FF2298766C0049B61B /* TestingMenuDataSource.swift */, + 2A49E20122987B530049B61B /* SafeAreaTestViewController.swift */, ); - name = Pods; + path = Testing; + sourceTree = ""; + }; + 2A4D35E0211D06ED00BE9377 /* APIEndpoint */ = { + isa = PBXGroup; + children = ( + 2A4D35E1211D074800BE9377 /* APIEndpointMenuDataSource.swift */, + ); + path = APIEndpoint; sourceTree = ""; }; B275DB2B200FD5F500F053F8 /* SavedAds */ = { @@ -320,6 +509,18 @@ path = LoggingLevel; sourceTree = ""; }; + BC014491220B6E2F0090F497 /* CanaryScreenshots */ = { + isa = PBXGroup; + children = ( + BC014492220B6E2F0090F497 /* CanaryScreenshots.swift */, + BC7892EC220CBAAD007A6218 /* XCTestCase+Waiting.swift */, + BC01449A220B6EBF0090F497 /* SnapshotHelper.swift */, + BC014494220B6E2F0090F497 /* Snapshots-Info.plist */, + BC7892EE220CC0FB007A6218 /* Screenshot.swift */, + ); + path = CanaryScreenshots; + sourceTree = ""; + }; BC04944520F911C900CFD9C2 /* Base */ = { isa = PBXGroup; children = ( @@ -327,6 +528,8 @@ BC59F25C20F524E30051DA00 /* AdUnitDataSource.swift */, BC04944220F904F200CFD9C2 /* AdUnitTableViewController.swift */, BC647D9920F967C800FAA12C /* BaseSplitViewController.swift */, + BC5F735F2220AF6800EED041 /* FilterableAdUnitTableViewController.swift */, + BC5F735C221F4C1D00EED041 /* FilteredAdUnitDataSource.swift */, ); path = Base; sourceTree = ""; @@ -345,9 +548,10 @@ isa = PBXGroup; children = ( BC33E0351FBB627B0060ECBE /* Canary */, + EC469C1122613063006CABAA /* CanaryUnitTests */, BC33E0341FBB627B0060ECBE /* Products */, BD565F79D171D27FE1BAD7EB /* Frameworks */, - B0C7C0257843E1CF62319AB8 /* Pods */, + 0579E20B6B7F9CB8F99A5FA4 /* Pods */, ); sourceTree = ""; }; @@ -355,7 +559,10 @@ isa = PBXGroup; children = ( BC33E0331FBB627B0060ECBE /* Canary.app */, - BC33E0571FBB63260060ECBE /* Canary (Internal).app */, + BC33E0571FBB63260060ECBE /* Canary.app */, + CE237F1E216BF2C800A8134A /* CanaryUITests.xctest */, + BC014490220B6E2F0090F497 /* CanaryScreenshots.xctest */, + EC469C1022613063006CABAA /* CanaryUnitTests.xctest */, ); name = Products; sourceTree = ""; @@ -363,45 +570,81 @@ BC33E0351FBB627B0060ECBE /* Canary */ = { isa = PBXGroup; children = ( + BC7892E8220B9C22007A6218 /* AccessibilityIdentifier.swift */, + BC4FE3052208C57A00B5D240 /* AdapterVersions */, BC33E0361FBB627B0060ECBE /* AppDelegate.swift */, - BC467DB0206465CB0083ECC4 /* AppDelegate+AdvancedBidders.swift */, - BC00C5B7208FCF46008E0AEB /* AppDelegate+Consent.swift */, - BCB4DF33211CD970005BD171 /* TableViewCellRegisterable.swift */, - BCA042EE211E049D001B1AF5 /* RoundedButton.swift */, - BC525EE82135BA4E007B1761 /* PreferredWidthLabel.swift */, BC33E03F1FBB627B0060ECBE /* Assets.xcassets */, BC04944520F911C900CFD9C2 /* Base */, BCFE5B391FE4469D00D760E9 /* Cells */, + BC8D8EC5222F238F003BE094 /* Configuration */, BC4E235E20A5FE2E000BA519 /* ContainerViewController.swift */, BCFE5B541FEB168E00D760E9 /* Extensions */, BCFE5B411FE491C400D760E9 /* Formats */, - BC33E0441FBB627B0060ECBE /* Info.plist */, BC7057B620F81396007E292E /* Internal */, BC33E0411FBB627B0060ECBE /* LaunchScreen.storyboard */, BC003AFF20DC430400CD1FEB /* LoggingLevel */, BC33E03C1FBB627B0060ECBE /* Main.storyboard */, BC95D23B2097B9180030C230 /* MainTabBarController.swift */, + 2A1F52F322160A0300933277 /* ManualEntryInterfaceViewController.swift */, BCB63FE420AB3D8100C22C7F /* Menu */, - BCE7078B20B373A500DA4BCB /* Privacy */, + BC525EE82135BA4E007B1761 /* PreferredWidthLabel.swift */, + 2A35FAAA21B5DA6200DC8805 /* QRCodeCameraInterfaceViewController.swift */, + ECF2181F227900C7008BD940 /* Renderer */, + BCA042EE211E049D001B1AF5 /* RoundedButton.swift */, BCFE5B3D1FE44F6D00D760E9 /* Samples */, B275DB2B200FD5F500F053F8 /* SavedAds */, + EC799949230F8C1500589ED4 /* SceneDelegate.swift */, BC04945920F91C9E00CFD9C2 /* StoryboardInstantiable.swift */, + BCB4DF33211CD970005BD171 /* TableViewCellRegisterable.swift */, + EC469C06226006FC006CABAA /* Utility */, ); path = Canary; sourceTree = ""; }; + BC4FE3052208C57A00B5D240 /* AdapterVersions */ = { + isa = PBXGroup; + children = ( + BC4FE3022208C57000B5D240 /* AdapterVersionsMenuDataSource.swift */, + ); + path = AdapterVersions; + sourceTree = ""; + }; BC7057B620F81396007E292E /* Internal */ = { isa = PBXGroup; children = ( + BC014491220B6E2F0090F497 /* CanaryScreenshots */, 2A4D35E0211D06ED00BE9377 /* APIEndpoint */, + CE237F1F216BF2C800A8134A /* CanaryUITests */, B2564EE020AB4F2B000B9F7A /* Internal-Info.plist */, - BC04945C20F9266A00CFD9C2 /* Internal.swift */, BC04944620F91BF400CFD9C2 /* Internal.storyboard */, + BC04945C20F9266A00CFD9C2 /* InternalState.swift */, + BC713BAF21700BC6003655B2 /* ReleaseTesting */, + 2A49E1FE229876310049B61B /* Testing */, BC04944920F91BFC00CFD9C2 /* NetworkAds */, + BCE7078B20B373A500DA4BCB /* Privacy */, ); path = Internal; sourceTree = ""; }; + BC713BAF21700BC6003655B2 /* ReleaseTesting */ = { + isa = PBXGroup; + children = ( + BC713BAD21700BBC003655B2 /* ReleaseTestingSplitViewController.swift */, + BC713BB021700C9B003655B2 /* ReleaseTestingViewController.swift */, + BCF6F7E321752DDF00FDE594 /* ReleaseTestingAds.plist */, + ); + path = ReleaseTesting; + sourceTree = ""; + }; + BC8D8EC5222F238F003BE094 /* Configuration */ = { + isa = PBXGroup; + children = ( + BC8D8EC4222F234B003BE094 /* Default.xcconfig */, + BC33E0441FBB627B0060ECBE /* Info.plist */, + ); + path = Configuration; + sourceTree = ""; + }; BCB63FE420AB3D8100C22C7F /* Menu */ = { isa = PBXGroup; children = ( @@ -433,8 +676,12 @@ BC2B97E920F41D7800D58F79 /* AdUnitTableViewHeader.xib */, BC96A33C20AE32DB00AC3266 /* BasicMenuTableViewCell.swift */, BC96A33F20AE335C00AC3266 /* BasicMenuTableViewCell.xib */, + BC4FE3062208E8E500B5D240 /* CollapsibleAdapterInfoTableViewCell.swift */, + BC4FE3092208E90800B5D240 /* CollapsibleAdapterInfoTableViewCell.xib */, BCB4DF2D211CC265005BD171 /* StatusTableViewCell.swift */, BCB4DF30211CC27D005BD171 /* StatusTableViewCell.xib */, + EC325257225273AD00D955C3 /* TextAndToggleTableViewCell.swift */, + EC325258225273AD00D955C3 /* TextAndToggleTableViewCell.xib */, BC2CEFB5211CF12C00EAA99D /* TextEntryTableViewCell.swift */, BC2CEFB8211CF14600EAA99D /* TextEntryTableViewCell.xib */, BC3B0C972007DBAA002D28B1 /* TweetCollectionViewCell.swift */, @@ -465,7 +712,6 @@ BC525EE22130A61D007B1761 /* BaseNativeAdDataSource.swift */, BC525ED3212F2D71007B1761 /* InterstitialAdDataSource.swift */, BC01F64F2004191400D6898B /* InterstitialAdViewController.swift */, - BC525ED0212F229A007B1761 /* LeaderboardAdViewController.swift */, BC525EC7212F15A5007B1761 /* MediumRectangleAdViewController.swift */, BC4CE2A42137343C00CA0220 /* NativeAdCollectionDataSource.swift */, BC3B0C942007DAA5002D28B1 /* NativeAdCollectionViewController.swift */, @@ -486,11 +732,19 @@ BCFE5B541FEB168E00D760E9 /* Extensions */ = { isa = PBXGroup; children = ( - BCD0506B2003F1FF00FFC36D /* UIView+Nib.swift */, + ECF218032278AE94008BD940 /* Array+Sort.swift */, BCEB5DA52008270300FE5165 /* CGFloat+Random.swift */, - BCEB5DA82008279900FE5165 /* UIColor+Random.swift */, BCE7078520B34C3400DA4BCB /* MPBool+Description.swift */, BCE7078820B34C7A00DA4BCB /* MPConsentStatus+Description.swift */, + ECF218092278D168008BD940 /* MPNativeAdRendererConfiguration+Utilities.swift */, + EC469C092260F4F2006CABAA /* Notification+Token.swift */, + EC32525D225276F500D955C3 /* NSObject+Utility.swift */, + 2A1F52F52216230300933277 /* UIAlertController+Picker.swift */, + BCEB5DA82008279900FE5165 /* UIColor+Random.swift */, + EC32526322527BCB00D955C3 /* UITableView+Utility.swift */, + BCD0506B2003F1FF00FFC36D /* UIView+Nib.swift */, + EC7999532311917100589ED4 /* UIViewController+Utility.swift */, + EC32524C224ECF8E00D955C3 /* UserDefaults+Subscript.swift */, ); path = Extensions; sourceTree = ""; @@ -498,74 +752,275 @@ BD565F79D171D27FE1BAD7EB /* Frameworks */ = { isa = PBXGroup; children = ( + 2A35FAA821B5DA1C00DC8805 /* AVFoundation.framework */, BCE738601FBB707500093CCD /* CoalMine.framework */, - E125D60FEE1A91995D3FDF82 /* Pods_Canary.framework */, - DD29E9B2187F9B97C60CAB75 /* Pods_Canary__Internal_.framework */, + 7E7C4E30CE724CAC318447FB /* Pods_AppStore_Application.framework */, + 6C40A5887B79C7007EC58F2F /* Pods_CanaryUnitTests.framework */, + 22E0C64A8C1276259F1261BA /* Pods_Internal_Application.framework */, ); name = Frameworks; sourceTree = ""; }; + CE237F1F216BF2C800A8134A /* CanaryUITests */ = { + isa = PBXGroup; + children = ( + CE237F2A216C024400A8134A /* Framework */, + CE237F2B216C025900A8134A /* Tests */, + CE237F22216BF2C800A8134A /* UITests-Info.plist */, + ); + path = CanaryUITests; + sourceTree = ""; + }; + CE237F2A216C024400A8134A /* Framework */ = { + isa = PBXGroup; + children = ( + CE237F30216C02AA00A8134A /* Base */, + CE237F31216C02B200A8134A /* Models */, + CE237F2F216C02A300A8134A /* Pages */, + ); + path = Framework; + sourceTree = ""; + }; + CE237F2B216C025900A8134A /* Tests */ = { + isa = PBXGroup; + children = ( + CE237F2C216C026700A8134A /* Base */, + CED56814216D2F1400C0E2A5 /* HTMLBannerAdTests.swift */, + CEBF6710218A465400284500 /* MRAIDBannerAdTests.swift */, + CE8A538E2192254F00377503 /* HTMLInterstitialAdTests.swift */, + CE8A539121924B8F00377503 /* MRAIDInterstitialAdTests.swift */, + CE8A53942193AB5900377503 /* NativeAdTests.swift */, + CE6BBCB6219A23580055F3B0 /* NativeVideoAdTests.swift */, + CE464D2F21AE39F300A13424 /* NativeCollectionTests.swift */, + CE4E2E2421AF2F6500368EFD /* NativeTableTests.swift */, + CEC115C421BDE9C000152CF8 /* NativeVideoTableTests.swift */, + CEC115C621BEF8D000152CF8 /* RewardedRichMediaTests.swift */, + ); + path = Tests; + sourceTree = ""; + }; + CE237F2C216C026700A8134A /* Base */ = { + isa = PBXGroup; + children = ( + CE237F2D216C028B00A8134A /* MoPubBaseTestCase.swift */, + ); + path = Base; + sourceTree = ""; + }; + CE237F2F216C02A300A8134A /* Pages */ = { + isa = PBXGroup; + children = ( + CE237F33216C02BF00A8134A /* AdDetailPage.swift */, + CE237F32216C02BF00A8134A /* AdListPage.swift */, + CEBF664F2187C92400284500 /* IPhoneHome.swift */, + CEBF66512187C94500284500 /* SafariApp.swift */, + ); + path = Pages; + sourceTree = ""; + }; + CE237F30216C02AA00A8134A /* Base */ = { + isa = PBXGroup; + children = ( + CE237F36216C02C700A8134A /* BasePage.swift */, + ); + path = Base; + sourceTree = ""; + }; + CE237F31216C02B200A8134A /* Models */ = { + isa = PBXGroup; + children = ( + CE237F38216C02CE00A8134A /* BannerAdLabels.swift */, + CEBF664C2187BC2700284500 /* CallbackFunctionNames.swift */, + ); + path = Models; + sourceTree = ""; + }; + EC0BF26D2279EBD5003DB141 /* Renderer */ = { + isa = PBXGroup; + children = ( + EC0BF26E2279EBF0003DB141 /* NativeAdRendererManagerTests.swift */, + ); + path = Renderer; + sourceTree = ""; + }; + EC469C06226006FC006CABAA /* Utility */ = { + isa = PBXGroup; + children = ( + EC469C03226006F4006CABAA /* TypedNotification.swift */, + ); + path = Utility; + sourceTree = ""; + }; + EC469C1122613063006CABAA /* CanaryUnitTests */ = { + isa = PBXGroup; + children = ( + ECF218082278B283008BD940 /* Extensions */, + EC0BF26D2279EBD5003DB141 /* Renderer */, + EC469C1E2261463F006CABAA /* SavedAds */, + EC469C1422613063006CABAA /* Info.plist */, + ); + path = CanaryUnitTests; + sourceTree = ""; + }; + EC469C1E2261463F006CABAA /* SavedAds */ = { + isa = PBXGroup; + children = ( + EC469C1C22614639006CABAA /* SavedAdsManagerTests.swift */, + ); + path = SavedAds; + sourceTree = ""; + }; + ECF218082278B283008BD940 /* Extensions */ = { + isa = PBXGroup; + children = ( + ECF218062278B274008BD940 /* ArraySortExtensionTests.swift */, + ); + path = Extensions; + sourceTree = ""; + }; + ECF2181F227900C7008BD940 /* Renderer */ = { + isa = PBXGroup; + children = ( + ECF2181C227900C2008BD940 /* NativeAdRendererManager.swift */, + ECF218112278D5B8008BD940 /* NativeAdRendererMenuDataSource.swift */, + ECF218142278D9A4008BD940 /* OrderPreferenceViewController.swift */, + ); + path = Renderer; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ - BC33E0321FBB627B0060ECBE /* Canary */ = { + BC01448F220B6E2F0090F497 /* CanaryScreenshots */ = { isa = PBXNativeTarget; - buildConfigurationList = BC33E0471FBB627B0060ECBE /* Build configuration list for PBXNativeTarget "Canary" */; + buildConfigurationList = BC014499220B6E2F0090F497 /* Build configuration list for PBXNativeTarget "CanaryScreenshots" */; buildPhases = ( - E7266716C35EA4759CA33871 /* [CP] Check Pods Manifest.lock */, + BC01448C220B6E2F0090F497 /* Sources */, + BC01448D220B6E2F0090F497 /* Frameworks */, + BC01448E220B6E2F0090F497 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + BC014496220B6E2F0090F497 /* PBXTargetDependency */, + ); + name = CanaryScreenshots; + productName = CanaryScreenshots; + productReference = BC014490220B6E2F0090F497 /* CanaryScreenshots.xctest */; + productType = "com.apple.product-type.bundle.ui-testing"; + }; + BC33E0321FBB627B0060ECBE /* AppStore Application */ = { + isa = PBXNativeTarget; + buildConfigurationList = BC33E0471FBB627B0060ECBE /* Build configuration list for PBXNativeTarget "AppStore Application" */; + buildPhases = ( + 7350EAF1AF6ACE42BEAAC86B /* [CP] Check Pods Manifest.lock */, BC33E02F1FBB627B0060ECBE /* Sources */, BC33E0301FBB627B0060ECBE /* Frameworks */, BC33E0311FBB627B0060ECBE /* Resources */, - D9B5F3C7C1B93E72AACE00E0 /* [CP] Embed Pods Frameworks */, - 89659F9C7E702CF4A26D6D9E /* [CP] Copy Pods Resources */, + F13E46A693BE60715BE1DE50 /* [CP] Embed Pods Frameworks */, + 8370782BC7BDAFE77D68D311 /* [CP] Copy Pods Resources */, ); buildRules = ( ); dependencies = ( ); - name = Canary; + name = "AppStore Application"; productName = Canary; productReference = BC33E0331FBB627B0060ECBE /* Canary.app */; productType = "com.apple.product-type.application"; }; - BC33E04A1FBB63260060ECBE /* Canary (Internal) */ = { + BC33E04A1FBB63260060ECBE /* Internal Application */ = { isa = PBXNativeTarget; - buildConfigurationList = BC33E0541FBB63260060ECBE /* Build configuration list for PBXNativeTarget "Canary (Internal)" */; + buildConfigurationList = BC33E0541FBB63260060ECBE /* Build configuration list for PBXNativeTarget "Internal Application" */; buildPhases = ( - ECBDCB3F2D0131BAABBBAD09 /* [CP] Check Pods Manifest.lock */, + A31D1F12840809F9FE1C6487 /* [CP] Check Pods Manifest.lock */, BC33E04B1FBB63260060ECBE /* Sources */, BC33E04F1FBB63260060ECBE /* Frameworks */, BC33E0501FBB63260060ECBE /* Resources */, BC4A99D5200EBCDD00E3EB07 /* Embed Frameworks */, - 0DBB44327E78A1A0DBA77326 /* [CP] Embed Pods Frameworks */, - 9EC2E1AA95D72F2B86EC33AE /* [CP] Copy Pods Resources */, + ADDF9FE7C64C325F8798C08D /* [CP] Embed Pods Frameworks */, + 7D04CED96FDEE549DEA336F2 /* [CP] Copy Pods Resources */, ); buildRules = ( ); dependencies = ( ); - name = "Canary (Internal)"; + name = "Internal Application"; productName = Canary; - productReference = BC33E0571FBB63260060ECBE /* Canary (Internal).app */; + productReference = BC33E0571FBB63260060ECBE /* Canary.app */; productType = "com.apple.product-type.application"; }; + CE237F1D216BF2C800A8134A /* CanaryUITests */ = { + isa = PBXNativeTarget; + buildConfigurationList = CE237F27216BF2C800A8134A /* Build configuration list for PBXNativeTarget "CanaryUITests" */; + buildPhases = ( + CE237F1A216BF2C800A8134A /* Sources */, + CE237F1B216BF2C800A8134A /* Frameworks */, + CE237F1C216BF2C800A8134A /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + EC2C1A6C22710A8E00B1B594 /* PBXTargetDependency */, + ); + name = CanaryUITests; + productName = CanaryUITests; + productReference = CE237F1E216BF2C800A8134A /* CanaryUITests.xctest */; + productType = "com.apple.product-type.bundle.ui-testing"; + }; + EC469C0F22613063006CABAA /* CanaryUnitTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = EC469C1922613063006CABAA /* Build configuration list for PBXNativeTarget "CanaryUnitTests" */; + buildPhases = ( + DDB6F252CAC6B77CAA9926BD /* [CP] Check Pods Manifest.lock */, + EC469C0C22613063006CABAA /* Sources */, + EC469C0D22613063006CABAA /* Frameworks */, + EC469C0E22613063006CABAA /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + EC469C1622613063006CABAA /* PBXTargetDependency */, + ); + name = CanaryUnitTests; + productName = CanaryUnitTests; + productReference = EC469C1022613063006CABAA /* CanaryUnitTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ BC33E02B1FBB627B0060ECBE /* Project object */ = { isa = PBXProject; attributes = { - LastSwiftUpdateCheck = 0910; + DefaultBuildSystemTypeForWorkspace = Original; + LastSwiftUpdateCheck = 1010; LastUpgradeCheck = 0940; ORGANIZATIONNAME = MoPub; TargetAttributes = { + BC01448F220B6E2F0090F497 = { + CreatedOnToolsVersion = 10.1; + ProvisioningStyle = Automatic; + TestTargetID = BC33E0321FBB627B0060ECBE; + }; BC33E0321FBB627B0060ECBE = { CreatedOnToolsVersion = 9.1; ProvisioningStyle = Automatic; }; BC33E04A1FBB63260060ECBE = { - LastSwiftMigration = 1000; + LastSwiftMigration = 1020; + ProvisioningStyle = Automatic; + }; + CE237F1D216BF2C800A8134A = { + CreatedOnToolsVersion = 10.0; + LastSwiftMigration = 1020; ProvisioningStyle = Automatic; + TestTargetID = BC33E04A1FBB63260060ECBE; + }; + EC469C0F22613063006CABAA = { + CreatedOnToolsVersion = 10.2; + ProvisioningStyle = Automatic; + TestTargetID = BC33E04A1FBB63260060ECBE; }; }; }; @@ -582,23 +1037,35 @@ projectDirPath = ""; projectRoot = ""; targets = ( - BC33E0321FBB627B0060ECBE /* Canary */, - BC33E04A1FBB63260060ECBE /* Canary (Internal) */, + BC33E0321FBB627B0060ECBE /* AppStore Application */, + BC33E04A1FBB63260060ECBE /* Internal Application */, + BC01448F220B6E2F0090F497 /* CanaryScreenshots */, + CE237F1D216BF2C800A8134A /* CanaryUITests */, + EC469C0F22613063006CABAA /* CanaryUnitTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + BC01448E220B6E2F0090F497 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; BC33E0311FBB627B0060ECBE /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( BC3B0C8F20058DBB002D28B1 /* NativeAdView.xib in Resources */, + EC32525B225273AD00D955C3 /* TextAndToggleTableViewCell.xib in Resources */, BC33E0431FBB627B0060ECBE /* LaunchScreen.storyboard in Resources */, BCA042EC211E0084001B1AF5 /* AdActionsTableViewCell.xib in Resources */, BC2B97EA20F41D7800D58F79 /* AdUnitTableViewHeader.xib in Resources */, + BC4FE30A2208E90800B5D240 /* CollapsibleAdapterInfoTableViewCell.xib in Resources */, + BCB8CFB321C06230007F7F6C /* Assets.xcassets in Resources */, BC2CEFB9211CF14600EAA99D /* TextEntryTableViewCell.xib in Resources */, - BC33E0401FBB627B0060ECBE /* Assets.xcassets in Resources */, BCB4DF2B211CB9C4005BD171 /* AdTableViewController.xib in Resources */, BC3B0C9B2007DBC8002D28B1 /* TweetCollectionViewCell.xib in Resources */, BC4CE2A2213733EB00CA0220 /* NativeAdCollectionViewController.xib in Resources */, @@ -618,196 +1085,363 @@ BC04945220F91BFC00CFD9C2 /* NetworkAds.plist in Resources */, B2564EE320AB5039000B9F7A /* SampleAds.plist in Resources */, BC3B0C9020058DBB002D28B1 /* NativeAdView.xib in Resources */, + BC4FE30B2208E90800B5D240 /* CollapsibleAdapterInfoTableViewCell.xib in Resources */, BCA042ED211E0084001B1AF5 /* AdActionsTableViewCell.xib in Resources */, BC2B97EB20F41D7800D58F79 /* AdUnitTableViewHeader.xib in Resources */, BC2CEFBA211CF14600EAA99D /* TextEntryTableViewCell.xib in Resources */, BC33E0511FBB63260060ECBE /* LaunchScreen.storyboard in Resources */, + EC32525C225273AD00D955C3 /* TextAndToggleTableViewCell.xib in Resources */, BCB4DF2C211CB9C4005BD171 /* AdTableViewController.xib in Resources */, - BC33E0521FBB63260060ECBE /* Assets.xcassets in Resources */, BC4CE2A3213733EB00CA0220 /* NativeAdCollectionViewController.xib in Resources */, BC3B0C9C2007DBC8002D28B1 /* TweetCollectionViewCell.xib in Resources */, BCFE5B3C1FE446D600D760E9 /* AdUnitTableViewCell.xib in Resources */, + BCF6F7E421752DDF00FDE594 /* ReleaseTestingAds.plist in Resources */, BC4CE29A213604DA00CA0220 /* NativeAdTableViewController.xib in Resources */, BCB4DF32211CC27D005BD171 /* StatusTableViewCell.xib in Resources */, BC33E0531FBB63260060ECBE /* Main.storyboard in Resources */, BC96A34120AE335C00AC3266 /* BasicMenuTableViewCell.xib in Resources */, + BCB8CFB421C06237007F7F6C /* Assets.xcassets in Resources */, BC04944820F91BF400CFD9C2 /* Internal.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; + CE237F1C216BF2C800A8134A /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + EC469C0E22613063006CABAA /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 0DBB44327E78A1A0DBA77326 /* [CP] Embed Pods Frameworks */ = { + 7350EAF1AF6ACE42BEAAC86B /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); + inputFileListPaths = ( + ); inputPaths = ( - "${SRCROOT}/Pods/Target Support Files/Pods-Canary (Internal)/Pods-Canary (Internal)-frameworks.sh", - "${BUILT_PRODUCTS_DIR}/Flurry-iOS-SDK/Flurry_iOS_SDK.framework", - "${BUILT_PRODUCTS_DIR}/LoremIpsum/LoremIpsum.framework", - "${BUILT_PRODUCTS_DIR}/mopub-ios-sdk/MoPub.framework", + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( ); - name = "[CP] Embed Pods Frameworks"; outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flurry_iOS_SDK.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/LoremIpsum.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MoPub.framework", + "$(DERIVED_FILE_DIR)/Pods-AppStore Application-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Canary (Internal)/Pods-Canary (Internal)-frameworks.sh\"\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 89659F9C7E702CF4A26D6D9E /* [CP] Copy Pods Resources */ = { + 7D04CED96FDEE549DEA336F2 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( - "${SRCROOT}/Pods/Target Support Files/Pods-Canary/Pods-Canary-resources.sh", - "${PODS_ROOT}/TapjoySDK/TapjoySDK_iOS_v12.0.0/Libraries/Tapjoy.embeddedframework/Resources/TapjoyResources.bundle", - "${PODS_ROOT}/TapjoySDK/TapjoySDK_iOS_v12.0.0/Libraries/Tapjoy.embeddedframework/Tapjoy.framework/Versions/A/Resources/TapjoyResources.bundle", + "${PODS_ROOT}/Target Support Files/Pods-Internal Application/Pods-Internal Application-resources.sh", + "${PODS_ROOT}/AppLovinSDK/applovin-ios-sdk-6.8.1/AppLovinSDKResources.bundle", + "${PODS_ROOT}/TapjoySDK/TapjoySDK_iOS_v12.3.1/Libraries/Tapjoy.embeddedframework/Resources/TapjoyResources.bundle", + "${PODS_ROOT}/TapjoySDK/TapjoySDK_iOS_v12.3.1/Libraries/Tapjoy.embeddedframework/Tapjoy.framework/Versions/A/Resources/TapjoyResources.bundle", ); name = "[CP] Copy Pods Resources"; outputPaths = ( + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AppLovinSDKResources.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/TapjoyResources.bundle", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Canary/Pods-Canary-resources.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Internal Application/Pods-Internal Application-resources.sh\"\n"; showEnvVarsInLog = 0; }; - 9EC2E1AA95D72F2B86EC33AE /* [CP] Copy Pods Resources */ = { + 8370782BC7BDAFE77D68D311 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( - "${SRCROOT}/Pods/Target Support Files/Pods-Canary (Internal)/Pods-Canary (Internal)-resources.sh", - "${PODS_ROOT}/TapjoySDK/TapjoySDK_iOS_v12.0.0/Libraries/Tapjoy.embeddedframework/Resources/TapjoyResources.bundle", - "${PODS_ROOT}/TapjoySDK/TapjoySDK_iOS_v12.0.0/Libraries/Tapjoy.embeddedframework/Tapjoy.framework/Versions/A/Resources/TapjoyResources.bundle", + "${PODS_ROOT}/Target Support Files/Pods-AppStore Application/Pods-AppStore Application-resources.sh", + "${PODS_ROOT}/AppLovinSDK/applovin-ios-sdk-6.8.1/AppLovinSDKResources.bundle", + "${PODS_ROOT}/TapjoySDK/TapjoySDK_iOS_v12.3.1/Libraries/Tapjoy.embeddedframework/Resources/TapjoyResources.bundle", + "${PODS_ROOT}/TapjoySDK/TapjoySDK_iOS_v12.3.1/Libraries/Tapjoy.embeddedframework/Tapjoy.framework/Versions/A/Resources/TapjoyResources.bundle", ); name = "[CP] Copy Pods Resources"; outputPaths = ( + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AppLovinSDKResources.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/TapjoyResources.bundle", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Canary (Internal)/Pods-Canary (Internal)-resources.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-AppStore Application/Pods-AppStore Application-resources.sh\"\n"; showEnvVarsInLog = 0; }; - D9B5F3C7C1B93E72AACE00E0 /* [CP] Embed Pods Frameworks */ = { + A31D1F12840809F9FE1C6487 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); + inputFileListPaths = ( + ); inputPaths = ( - "${SRCROOT}/Pods/Target Support Files/Pods-Canary/Pods-Canary-frameworks.sh", + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Internal Application-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + ADDF9FE7C64C325F8798C08D /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Internal Application/Pods-Internal Application-frameworks.sh", + "${BUILT_PRODUCTS_DIR}/FBSDKCoreKit/FBSDKCoreKit.framework", "${BUILT_PRODUCTS_DIR}/Flurry-iOS-SDK/Flurry_iOS_SDK.framework", + "${BUILT_PRODUCTS_DIR}/GoogleUtilities/GoogleUtilities.framework", "${BUILT_PRODUCTS_DIR}/LoremIpsum/LoremIpsum.framework", + "${PODS_ROOT}/Verizon-Ads-Core/VerizonAdsCore.framework", + "${PODS_ROOT}/Verizon-Ads-InlinePlacement/VerizonAdsInlinePlacement.framework", + "${PODS_ROOT}/Verizon-Ads-InlineWebAdapter/VerizonAdsInlineWebAdapter.framework", + "${PODS_ROOT}/Verizon-Ads-InterstitialPlacement/VerizonAdsInterstitialPlacement.framework", + "${PODS_ROOT}/Verizon-Ads-InterstitialVASTAdapter/VerizonAdsInterstitialVASTAdapter.framework", + "${PODS_ROOT}/Verizon-Ads-InterstitialWebAdapter/VerizonAdsInterstitialWebAdapter.framework", + "${PODS_ROOT}/Verizon-Ads-NativePlacement/VerizonAdsNativePlacement.framework", + "${PODS_ROOT}/Verizon-Ads-NativeVerizonNativeAdapter/VerizonAdsNativeVerizonNativeAdapter.framework", + "${PODS_ROOT}/Verizon-Ads-OMSDK/VerizonAdsOMSDK.framework", + "${PODS_ROOT}/Verizon-Ads-StandardEdition/VerizonAdsStandardEdition.framework", + "${PODS_ROOT}/Verizon-Ads-Support/VerizonAdsSupport.framework", + "${PODS_ROOT}/Verizon-Ads-URIExperience/VerizonAdsURIExperience.framework", + "${PODS_ROOT}/Verizon-Ads-VASTController/VerizonAdsVASTController.framework", + "${PODS_ROOT}/Verizon-Ads-VerizonNativeController/VerizonAdsVerizonNativeController.framework", + "${PODS_ROOT}/Verizon-Ads-VerizonSSPConfigProvider/VerizonAdsVerizonSSPConfigProvider.framework", + "${PODS_ROOT}/Verizon-Ads-VerizonSSPReporter/VerizonAdsVerizonSSPReporter.framework", + "${PODS_ROOT}/Verizon-Ads-VerizonSSPWaterfallProvider/VerizonAdsVerizonSSPWaterfallProvider.framework", + "${PODS_ROOT}/Verizon-Ads-VideoPlayer/VerizonAdsVideoPlayer.framework", + "${PODS_ROOT}/Verizon-Ads-WebController/VerizonAdsWebController.framework", + "${PODS_ROOT}/Verizon-Ads-WebView/VerizonAdsWebView.framework", "${BUILT_PRODUCTS_DIR}/mopub-ios-sdk/MoPub.framework", + "${BUILT_PRODUCTS_DIR}/nanopb/nanopb.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FBSDKCoreKit.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flurry_iOS_SDK.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleUtilities.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/LoremIpsum.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/VerizonAdsCore.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/VerizonAdsInlinePlacement.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/VerizonAdsInlineWebAdapter.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/VerizonAdsInterstitialPlacement.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/VerizonAdsInterstitialVASTAdapter.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/VerizonAdsInterstitialWebAdapter.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/VerizonAdsNativePlacement.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/VerizonAdsNativeVerizonNativeAdapter.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/VerizonAdsOMSDK.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/VerizonAdsStandardEdition.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/VerizonAdsSupport.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/VerizonAdsURIExperience.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/VerizonAdsVASTController.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/VerizonAdsVerizonNativeController.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/VerizonAdsVerizonSSPConfigProvider.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/VerizonAdsVerizonSSPReporter.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/VerizonAdsVerizonSSPWaterfallProvider.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/VerizonAdsVideoPlayer.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/VerizonAdsWebController.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/VerizonAdsWebView.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MoPub.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/nanopb.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Canary/Pods-Canary-frameworks.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Internal Application/Pods-Internal Application-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - E7266716C35EA4759CA33871 /* [CP] Check Pods Manifest.lock */ = { + DDB6F252CAC6B77CAA9926BD /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); + inputFileListPaths = ( + ); inputPaths = ( "${PODS_PODFILE_DIR_PATH}/Podfile.lock", "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Canary-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-CanaryUnitTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - ECBDCB3F2D0131BAABBBAD09 /* [CP] Check Pods Manifest.lock */ = { + F13E46A693BE60715BE1DE50 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", + "${PODS_ROOT}/Target Support Files/Pods-AppStore Application/Pods-AppStore Application-frameworks.sh", + "${BUILT_PRODUCTS_DIR}/FBSDKCoreKit/FBSDKCoreKit.framework", + "${BUILT_PRODUCTS_DIR}/Flurry-iOS-SDK/Flurry_iOS_SDK.framework", + "${BUILT_PRODUCTS_DIR}/GoogleUtilities/GoogleUtilities.framework", + "${BUILT_PRODUCTS_DIR}/LoremIpsum/LoremIpsum.framework", + "${PODS_ROOT}/Verizon-Ads-Core/VerizonAdsCore.framework", + "${PODS_ROOT}/Verizon-Ads-InlinePlacement/VerizonAdsInlinePlacement.framework", + "${PODS_ROOT}/Verizon-Ads-InlineWebAdapter/VerizonAdsInlineWebAdapter.framework", + "${PODS_ROOT}/Verizon-Ads-InterstitialPlacement/VerizonAdsInterstitialPlacement.framework", + "${PODS_ROOT}/Verizon-Ads-InterstitialVASTAdapter/VerizonAdsInterstitialVASTAdapter.framework", + "${PODS_ROOT}/Verizon-Ads-InterstitialWebAdapter/VerizonAdsInterstitialWebAdapter.framework", + "${PODS_ROOT}/Verizon-Ads-NativePlacement/VerizonAdsNativePlacement.framework", + "${PODS_ROOT}/Verizon-Ads-NativeVerizonNativeAdapter/VerizonAdsNativeVerizonNativeAdapter.framework", + "${PODS_ROOT}/Verizon-Ads-OMSDK/VerizonAdsOMSDK.framework", + "${PODS_ROOT}/Verizon-Ads-StandardEdition/VerizonAdsStandardEdition.framework", + "${PODS_ROOT}/Verizon-Ads-Support/VerizonAdsSupport.framework", + "${PODS_ROOT}/Verizon-Ads-URIExperience/VerizonAdsURIExperience.framework", + "${PODS_ROOT}/Verizon-Ads-VASTController/VerizonAdsVASTController.framework", + "${PODS_ROOT}/Verizon-Ads-VerizonNativeController/VerizonAdsVerizonNativeController.framework", + "${PODS_ROOT}/Verizon-Ads-VerizonSSPConfigProvider/VerizonAdsVerizonSSPConfigProvider.framework", + "${PODS_ROOT}/Verizon-Ads-VerizonSSPReporter/VerizonAdsVerizonSSPReporter.framework", + "${PODS_ROOT}/Verizon-Ads-VerizonSSPWaterfallProvider/VerizonAdsVerizonSSPWaterfallProvider.framework", + "${PODS_ROOT}/Verizon-Ads-VideoPlayer/VerizonAdsVideoPlayer.framework", + "${PODS_ROOT}/Verizon-Ads-WebController/VerizonAdsWebController.framework", + "${PODS_ROOT}/Verizon-Ads-WebView/VerizonAdsWebView.framework", + "${BUILT_PRODUCTS_DIR}/mopub-ios-sdk/MoPub.framework", + "${BUILT_PRODUCTS_DIR}/nanopb/nanopb.framework", ); - name = "[CP] Check Pods Manifest.lock"; + name = "[CP] Embed Pods Frameworks"; outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Canary (Internal)-checkManifestLockResult.txt", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FBSDKCoreKit.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flurry_iOS_SDK.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleUtilities.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/LoremIpsum.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/VerizonAdsCore.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/VerizonAdsInlinePlacement.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/VerizonAdsInlineWebAdapter.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/VerizonAdsInterstitialPlacement.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/VerizonAdsInterstitialVASTAdapter.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/VerizonAdsInterstitialWebAdapter.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/VerizonAdsNativePlacement.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/VerizonAdsNativeVerizonNativeAdapter.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/VerizonAdsOMSDK.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/VerizonAdsStandardEdition.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/VerizonAdsSupport.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/VerizonAdsURIExperience.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/VerizonAdsVASTController.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/VerizonAdsVerizonNativeController.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/VerizonAdsVerizonSSPConfigProvider.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/VerizonAdsVerizonSSPReporter.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/VerizonAdsVerizonSSPWaterfallProvider.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/VerizonAdsVideoPlayer.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/VerizonAdsWebController.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/VerizonAdsWebView.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MoPub.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/nanopb.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-AppStore Application/Pods-AppStore Application-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + BC01448C220B6E2F0090F497 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + BC7892ED220CBAAD007A6218 /* XCTestCase+Waiting.swift in Sources */, + BC014493220B6E2F0090F497 /* CanaryScreenshots.swift in Sources */, + BC7892EB220CB896007A6218 /* AccessibilityIdentifier.swift in Sources */, + BC01449B220B6EBF0090F497 /* SnapshotHelper.swift in Sources */, + BC7892EF220CC0FB007A6218 /* Screenshot.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; BC33E02F1FBB627B0060ECBE /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( BCEB5DA92008279900FE5165 /* UIColor+Random.swift in Sources */, + ECF2180A2278D168008BD940 /* MPNativeAdRendererConfiguration+Utilities.swift in Sources */, BCB4DF34211CD970005BD171 /* TableViewCellRegisterable.swift in Sources */, B2D3EEB5200DAF94009FEBC9 /* SavedAdsManager.swift in Sources */, + BC5F735D221F4C1D00EED041 /* FilteredAdUnitDataSource.swift in Sources */, BC2B97E720F41D3E00D58F79 /* AdUnitTableViewHeader.swift in Sources */, + BC4FE3072208E8E500B5D240 /* CollapsibleAdapterInfoTableViewCell.swift in Sources */, BC243C3D20A9F5B800DE6EA9 /* MenuViewController.swift in Sources */, + ECF2181D227900C2008BD940 /* NativeAdRendererManager.swift in Sources */, BC4CE2A52137343C00CA0220 /* NativeAdCollectionDataSource.swift in Sources */, BC525EE32130A61D007B1761 /* BaseNativeAdDataSource.swift in Sources */, BCE7078920B34C7A00DA4BCB /* MPConsentStatus+Description.swift in Sources */, BC01F6502004191400D6898B /* InterstitialAdViewController.swift in Sources */, BCFE5B431FE84B5200D760E9 /* BannerAdViewController.swift in Sources */, - BC525ED1212F229A007B1761 /* LeaderboardAdViewController.swift in Sources */, BC01F65320042BD600D6898B /* RewardedAdViewController.swift in Sources */, BC3B0C8920058290002D28B1 /* NativeAdViewController.swift in Sources */, BC4E235F20A5FE2E000BA519 /* ContainerViewController.swift in Sources */, BC33E03B1FBB627B0060ECBE /* SavedAdsViewController.swift in Sources */, + ECF218042278AE94008BD940 /* Array+Sort.swift in Sources */, BCE7078620B34C3400DA4BCB /* MPBool+Description.swift in Sources */, BC525ED7212F50E3007B1761 /* RewardedAdDataSource.swift in Sources */, BCA042E9211E004D001B1AF5 /* AdActionsTableViewCell.swift in Sources */, - BC00C5B8208FCF46008E0AEB /* AppDelegate+Consent.swift in Sources */, + 2A1F52F422160A0300933277 /* ManualEntryInterfaceViewController.swift in Sources */, + BC4FE3032208C57000B5D240 /* AdapterVersionsMenuDataSource.swift in Sources */, BC3B0C952007DAA5002D28B1 /* NativeAdCollectionViewController.swift in Sources */, BCFE5B371FE4469200D760E9 /* AdUnitTableViewCell.swift in Sources */, BC647D9A20F967C800FAA12C /* BaseSplitViewController.swift in Sources */, + EC32525E225276F500D955C3 /* NSObject+Utility.swift in Sources */, + BC5F73602220AF6800EED041 /* FilterableAdUnitTableViewController.swift in Sources */, B275DB2D200FD64000F053F8 /* SavedAdsDataSource.swift in Sources */, BCEB5DA62008270300FE5165 /* CGFloat+Random.swift in Sources */, BC003AFD20DC42FE00CD1FEB /* LogingLevelMenuDataSource.swift in Sources */, BC525EE92135BA4E007B1761 /* PreferredWidthLabel.swift in Sources */, + ECF218152278D9A4008BD940 /* OrderPreferenceViewController.swift in Sources */, + 2A1F52F62216230300933277 /* UIAlertController+Picker.swift in Sources */, + ECF218122278D5B8008BD940 /* NativeAdRendererMenuDataSource.swift in Sources */, BC4CE29C2136054500CA0220 /* NativeAdTableDataSource.swift in Sources */, BC04945A20F91C9E00CFD9C2 /* StoryboardInstantiable.swift in Sources */, - BCE7078320B3424E00DA4BCB /* PrivacyInfoDataSource.swift in Sources */, BC525ED4212F2D71007B1761 /* InterstitialAdDataSource.swift in Sources */, + EC7999542311917100589ED4 /* UIViewController+Utility.swift in Sources */, BC33E0371FBB627B0060ECBE /* AppDelegate.swift in Sources */, BCA042EF211E049D001B1AF5 /* RoundedButton.swift in Sources */, - BC96A33A20AE306F00AC3266 /* PrivacyMenuDataSource.swift in Sources */, BC3B0C982007DBAA002D28B1 /* TweetCollectionViewCell.swift in Sources */, + EC325259225273AD00D955C3 /* TextAndToggleTableViewCell.swift in Sources */, BC59F25D20F524E40051DA00 /* AdUnitDataSource.swift in Sources */, BCB4DF28211CB62F005BD171 /* AdTableViewController.swift in Sources */, BC33E0391FBB627B0060ECBE /* SampleAdsViewController.swift in Sources */, + EC79994A230F8C1500589ED4 /* SceneDelegate.swift in Sources */, BCB63FE220AA442B00C22C7F /* MenuDataSource.swift in Sources */, + EC32524D224ECF8E00D955C3 /* UserDefaults+Subscript.swift in Sources */, BCA042F2211E15FB001B1AF5 /* BannerAdDataSource.swift in Sources */, BCB63FDF20AA426300C22C7F /* MenuDisplayable.swift in Sources */, BC2CEFB6211CF12C00EAA99D /* TextEntryTableViewCell.swift in Sources */, + EC469C0A2260F4F2006CABAA /* Notification+Token.swift in Sources */, BC525EE02130A362007B1761 /* NativeAdDataSource.swift in Sources */, BC04944320F904F200CFD9C2 /* AdUnitTableViewController.swift in Sources */, BC525EC8212F15A5007B1761 /* MediumRectangleAdViewController.swift in Sources */, - BC467DB1206465CB0083ECC4 /* AppDelegate+AdvancedBidders.swift in Sources */, BC95D23C2097B9180030C230 /* MainTabBarController.swift in Sources */, BC16904D201F8F8E000EA77F /* AdFormat.swift in Sources */, BCD0506C2003F1FF00FFC36D /* UIView+Nib.swift in Sources */, @@ -815,10 +1449,13 @@ BC63B9221FBD14A00033ACD6 /* AdUnit.swift in Sources */, BC4CE29F2136FB6100CA0220 /* AdViewController.swift in Sources */, BC3B0C8C20058D7C002D28B1 /* NativeAdView.swift in Sources */, + EC469C072260EF1D006CABAA /* TypedNotification.swift in Sources */, + 2A35FAAB21B5DA6200DC8805 /* QRCodeCameraInterfaceViewController.swift in Sources */, BCB4DF25211B913F005BD171 /* AdDataSource.swift in Sources */, + BC7892E9220B9C22007A6218 /* AccessibilityIdentifier.swift in Sources */, BCB4DF2E211CC265005BD171 /* StatusTableViewCell.swift in Sources */, + EC32526422527BCB00D955C3 /* UITableView+Utility.swift in Sources */, BC96A33D20AE32DB00AC3266 /* BasicMenuTableViewCell.swift in Sources */, - BCE7078020B3392100DA4BCB /* PrivacyInfoViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -826,60 +1463,82 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + BC7892EA220B9C22007A6218 /* AccessibilityIdentifier.swift in Sources */, + ECF218052278AE94008BD940 /* Array+Sort.swift in Sources */, BCEB5DAA2008279900FE5165 /* UIColor+Random.swift in Sources */, BCB4DF35211CD970005BD171 /* TableViewCellRegisterable.swift in Sources */, + 2A1F52F8221628AA00933277 /* UIAlertController+Picker.swift in Sources */, B2D3EEB6200DAF94009FEBC9 /* SavedAdsManager.swift in Sources */, BC2B97E820F41D3E00D58F79 /* AdUnitTableViewHeader.swift in Sources */, BC243C3E20A9F5B800DE6EA9 /* MenuViewController.swift in Sources */, BC4CE2A62137343C00CA0220 /* NativeAdCollectionDataSource.swift in Sources */, + EC7999562311917400589ED4 /* UIViewController+Utility.swift in Sources */, + BC5F735E221F4C1D00EED041 /* FilteredAdUnitDataSource.swift in Sources */, BC525EE42130A61D007B1761 /* BaseNativeAdDataSource.swift in Sources */, BCE7078A20B34C7A00DA4BCB /* MPConsentStatus+Description.swift in Sources */, BC01F6512004191400D6898B /* InterstitialAdViewController.swift in Sources */, BCFE5B441FE84B5200D760E9 /* BannerAdViewController.swift in Sources */, - BC525ED2212F229A007B1761 /* LeaderboardAdViewController.swift in Sources */, BC01F65420042BD600D6898B /* RewardedAdViewController.swift in Sources */, BC3B0C8A20058290002D28B1 /* NativeAdViewController.swift in Sources */, BC4E236020A5FE2E000BA519 /* ContainerViewController.swift in Sources */, + BC4FE3082208E8E500B5D240 /* CollapsibleAdapterInfoTableViewCell.swift in Sources */, + EC32525A225273AD00D955C3 /* TextAndToggleTableViewCell.swift in Sources */, BC04945020F91BFC00CFD9C2 /* NetworkAdsSplitViewController.swift in Sources */, BC63B9231FBD14A00033ACD6 /* AdUnit.swift in Sources */, BCE7078720B34C3400DA4BCB /* MPBool+Description.swift in Sources */, BC525ED8212F50E3007B1761 /* RewardedAdDataSource.swift in Sources */, BCA042EA211E004D001B1AF5 /* AdActionsTableViewCell.swift in Sources */, - BC00C5B9208FCF46008E0AEB /* AppDelegate+Consent.swift in Sources */, BC3B0C962007DAA5002D28B1 /* NativeAdCollectionViewController.swift in Sources */, + EC325253224ED56F00D955C3 /* UserDefaults+Subscript.swift in Sources */, + 2A49E2002298766C0049B61B /* TestingMenuDataSource.swift in Sources */, + ECF218162278D9A4008BD940 /* OrderPreferenceViewController.swift in Sources */, + BC713BB121700C9B003655B2 /* ReleaseTestingViewController.swift in Sources */, + ECF218132278D5B8008BD940 /* NativeAdRendererMenuDataSource.swift in Sources */, BCFE5B381FE4469200D760E9 /* AdUnitTableViewCell.swift in Sources */, BC647D9B20F967C800FAA12C /* BaseSplitViewController.swift in Sources */, B275DB2E200FD64000F053F8 /* SavedAdsDataSource.swift in Sources */, BCEB5DA72008270300FE5165 /* CGFloat+Random.swift in Sources */, + BC713BAE21700BBC003655B2 /* ReleaseTestingSplitViewController.swift in Sources */, BC003AFE20DC42FE00CD1FEB /* LogingLevelMenuDataSource.swift in Sources */, BC525EEA2135BA4E007B1761 /* PreferredWidthLabel.swift in Sources */, + 2A35FAAC21B5DA6200DC8805 /* QRCodeCameraInterfaceViewController.swift in Sources */, + EC469C0B2260F4F2006CABAA /* Notification+Token.swift in Sources */, BC4CE29D2136054500CA0220 /* NativeAdTableDataSource.swift in Sources */, BC04945B20F91C9E00CFD9C2 /* StoryboardInstantiable.swift in Sources */, 2A4D35E3211D074800BE9377 /* APIEndpointMenuDataSource.swift in Sources */, BCE7078420B3424E00DA4BCB /* PrivacyInfoDataSource.swift in Sources */, + BC5F73612220AF6800EED041 /* FilterableAdUnitTableViewController.swift in Sources */, BC525ED5212F2D71007B1761 /* InterstitialAdDataSource.swift in Sources */, + ECF2180B2278D168008BD940 /* MPNativeAdRendererConfiguration+Utilities.swift in Sources */, BC33E04C1FBB63260060ECBE /* SavedAdsViewController.swift in Sources */, + BC4FE3042208C57000B5D240 /* AdapterVersionsMenuDataSource.swift in Sources */, BCA042F0211E049D001B1AF5 /* RoundedButton.swift in Sources */, BC96A33B20AE306F00AC3266 /* PrivacyMenuDataSource.swift in Sources */, BC3B0C992007DBAA002D28B1 /* TweetCollectionViewCell.swift in Sources */, BC59F25E20F524E40051DA00 /* AdUnitDataSource.swift in Sources */, - BC04945E20F9266A00CFD9C2 /* Internal.swift in Sources */, + BC04945E20F9266A00CFD9C2 /* InternalState.swift in Sources */, + EC469C082260EF1E006CABAA /* TypedNotification.swift in Sources */, BCB4DF29211CB62F005BD171 /* AdTableViewController.swift in Sources */, BC33E04D1FBB63260060ECBE /* AppDelegate.swift in Sources */, BCB63FE320AA442B00C22C7F /* MenuDataSource.swift in Sources */, BC04945420F91BFC00CFD9C2 /* NetworkAdsViewController.swift in Sources */, + EC32526522527BCB00D955C3 /* UITableView+Utility.swift in Sources */, BCA042F3211E15FB001B1AF5 /* BannerAdDataSource.swift in Sources */, + 2A49E20222987B530049B61B /* SafeAreaTestViewController.swift in Sources */, BCB63FE020AA426300C22C7F /* MenuDisplayable.swift in Sources */, BC2CEFB7211CF12C00EAA99D /* TextEntryTableViewCell.swift in Sources */, BC525EE12130A362007B1761 /* NativeAdDataSource.swift in Sources */, BC04944420F904F200CFD9C2 /* AdUnitTableViewController.swift in Sources */, BC525EC9212F15A5007B1761 /* MediumRectangleAdViewController.swift in Sources */, - BC467DB2206465CB0083ECC4 /* AppDelegate+AdvancedBidders.swift in Sources */, + ECF2181E227900C2008BD940 /* NativeAdRendererManager.swift in Sources */, BC95D23D2097B9180030C230 /* MainTabBarController.swift in Sources */, BC16904E201F8F8E000EA77F /* AdFormat.swift in Sources */, + 2A1F52F7221628A000933277 /* ManualEntryInterfaceViewController.swift in Sources */, + EC32525F225276F500D955C3 /* NSObject+Utility.swift in Sources */, BCD0506D2003F20400FFC36D /* UIView+Nib.swift in Sources */, BC3B0C932006E168002D28B1 /* NativeAdTableViewController.swift in Sources */, BC33E04E1FBB63260060ECBE /* SampleAdsViewController.swift in Sources */, + EC79994B230F8C1500589ED4 /* SceneDelegate.swift in Sources */, BC4CE2A02136FB6100CA0220 /* AdViewController.swift in Sources */, BC3B0C8D20058D7C002D28B1 /* NativeAdView.swift in Sources */, BCB4DF26211B913F005BD171 /* AdDataSource.swift in Sources */, @@ -889,8 +1548,61 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + CE237F1A216BF2C800A8134A /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + CED56816216D2F2F00C0E2A5 /* HTMLBannerAdTests.swift in Sources */, + CE237F3A216C04A800A8134A /* MoPubBaseTestCase.swift in Sources */, + CEBF6712218A467800284500 /* MRAIDBannerAdTests.swift in Sources */, + CE4E2E2621AF2F6A00368EFD /* NativeTableTests.swift in Sources */, + CEC115C521BDE9C000152CF8 /* NativeVideoTableTests.swift in Sources */, + CE464D3121AE3A1900A13424 /* NativeCollectionTests.swift in Sources */, + CEC115C721BEF8D000152CF8 /* RewardedRichMediaTests.swift in Sources */, + CE237F3E216C04BC00A8134A /* BasePage.swift in Sources */, + CE237F3D216C04B800A8134A /* BannerAdLabels.swift in Sources */, + CE237F3C216C04B300A8134A /* AdListPage.swift in Sources */, + CE8A539321924B9400377503 /* MRAIDInterstitialAdTests.swift in Sources */, + CE6BBCB8219A369E0055F3B0 /* NativeVideoAdTests.swift in Sources */, + CEBF66542187C99C00284500 /* SafariApp.swift in Sources */, + CEBF664E2187BD1500284500 /* CallbackFunctionNames.swift in Sources */, + CE237F3B216C04B300A8134A /* AdDetailPage.swift in Sources */, + CEBF66532187C99C00284500 /* IPhoneHome.swift in Sources */, + CE8A53902192257600377503 /* HTMLInterstitialAdTests.swift in Sources */, + CE8A53962193AB6000377503 /* NativeAdTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + EC469C0C22613063006CABAA /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + EC469C1D22614639006CABAA /* SavedAdsManagerTests.swift in Sources */, + ECF218072278B274008BD940 /* ArraySortExtensionTests.swift in Sources */, + EC0BF26F2279EBF0003DB141 /* NativeAdRendererManagerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + BC014496220B6E2F0090F497 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = BC33E0321FBB627B0060ECBE /* AppStore Application */; + targetProxy = BC014495220B6E2F0090F497 /* PBXContainerItemProxy */; + }; + EC2C1A6C22710A8E00B1B594 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = BC33E04A1FBB63260060ECBE /* Internal Application */; + targetProxy = EC2C1A6B22710A8E00B1B594 /* PBXContainerItemProxy */; + }; + EC469C1622613063006CABAA /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = BC33E04A1FBB63260060ECBE /* Internal Application */; + targetProxy = EC469C1522613063006CABAA /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin PBXVariantGroup section */ BC33E03C1FBB627B0060ECBE /* Main.storyboard */ = { isa = PBXVariantGroup; @@ -911,8 +1623,46 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + BC014497220B6E2F0090F497 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_OBJC_WEAK = YES; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 4S7XS533V3; + INFOPLIST_FILE = "Canary/Internal/CanaryScreenshots/Snapshots-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 12.1; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.mopub.CanaryScreenshots; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 4.2; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = "AppStore Application"; + }; + name = Debug; + }; + BC014498220B6E2F0090F497 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_OBJC_WEAK = YES; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 4S7XS533V3; + INFOPLIST_FILE = "Canary/Internal/CanaryScreenshots/Snapshots-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 12.1; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.mopub.CanaryScreenshots; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 4.2; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = "AppStore Application"; + }; + name = Release; + }; BC33E0451FBB627B0060ECBE /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = BC8D8EC4222F234B003BE094 /* Default.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; @@ -972,6 +1722,7 @@ }; BC33E0461FBB627B0060ECBE /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = BC8D8EC4222F234B003BE094 /* Default.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; @@ -1024,16 +1775,16 @@ }; BC33E0481FBB627B0060ECBE /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = C8048ED6DE0E405574BB074C /* Pods-Canary.debug.xcconfig */; + baseConfigurationReference = CA1DEC059DCDC57801D88A60 /* Pods-AppStore Application.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 5.4.0; + CURRENT_PROJECT_VERSION = 5.9.0; DEVELOPMENT_TEAM = 4S7XS533V3; - INFOPLIST_FILE = Canary/Info.plist; + INFOPLIST_FILE = Canary/Configuration/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.mopub.Canary; - PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; @@ -1042,16 +1793,16 @@ }; BC33E0491FBB627B0060ECBE /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 55F769B196AF032A147C6387 /* Pods-Canary.release.xcconfig */; + baseConfigurationReference = 38B93E7C897560D8A670BE08 /* Pods-AppStore Application.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 5.4.0; + CURRENT_PROJECT_VERSION = 5.9.0; DEVELOPMENT_TEAM = 4S7XS533V3; - INFOPLIST_FILE = Canary/Info.plist; + INFOPLIST_FILE = Canary/Configuration/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.mopub.Canary; - PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; @@ -1060,18 +1811,18 @@ }; BC33E0551FBB63260060ECBE /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 3FA62C94B44B098C968278EE /* Pods-Canary (Internal).debug.xcconfig */; + baseConfigurationReference = E280CCD44F6A8B4AF8F3766C /* Pods-Internal Application.debug.xcconfig */; buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon.Internal; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 5.4.0; + CURRENT_PROJECT_VERSION = 5.9.0; DEVELOPMENT_TEAM = 4S7XS533V3; INFOPLIST_FILE = "Canary/Internal/Internal-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; OTHER_SWIFT_FLAGS = "-DINTERNAL"; PRODUCT_BUNDLE_IDENTIFIER = com.mopub.Canary; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 4.2; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; }; @@ -1079,26 +1830,115 @@ }; BC33E0561FBB63260060ECBE /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = BBA3359CC86A7CE1AF32934B /* Pods-Canary (Internal).release.xcconfig */; + baseConfigurationReference = F8AD0C2B4CE6D8A1D096CD2F /* Pods-Internal Application.release.xcconfig */; buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon.Internal; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 5.4.0; + CURRENT_PROJECT_VERSION = 5.9.0; DEVELOPMENT_TEAM = 4S7XS533V3; INFOPLIST_FILE = "Canary/Internal/Internal-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; OTHER_SWIFT_FLAGS = "-DINTERNAL"; PRODUCT_BUNDLE_IDENTIFIER = com.mopub.Canary; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 4.2; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; }; name = Release; }; + CE237F25216BF2C800A8134A /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_OBJC_WEAK = YES; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 4S7XS533V3; + INFOPLIST_FILE = "Canary/Internal/CanaryUITests/UITests-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.twitter.CanaryUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = "Internal Application"; + }; + name = Debug; + }; + CE237F26216BF2C800A8134A /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_OBJC_WEAK = YES; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 4S7XS533V3; + INFOPLIST_FILE = "Canary/Internal/CanaryUITests/UITests-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.twitter.CanaryUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = "Internal Application"; + }; + name = Release; + }; + EC469C1722613063006CABAA /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = E2E466BCBD3F681948D93068 /* Pods-CanaryUnitTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_ENABLE_OBJC_WEAK = YES; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 4S7XS533V3; + INFOPLIST_FILE = CanaryUnitTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.mopub.Canary.UnitTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + STRIP_STYLE = "non-global"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Canary.app/Canary"; + }; + name = Debug; + }; + EC469C1822613063006CABAA /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 2783EDBC05C4535EE4245274 /* Pods-CanaryUnitTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_ENABLE_OBJC_WEAK = YES; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 4S7XS533V3; + INFOPLIST_FILE = CanaryUnitTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.mopub.Canary.UnitTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + STRIP_STYLE = "non-global"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Canary.app/Canary"; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + BC014499220B6E2F0090F497 /* Build configuration list for PBXNativeTarget "CanaryScreenshots" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + BC014497220B6E2F0090F497 /* Debug */, + BC014498220B6E2F0090F497 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; BC33E02E1FBB627B0060ECBE /* Build configuration list for PBXProject "Canary" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -1108,7 +1948,7 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - BC33E0471FBB627B0060ECBE /* Build configuration list for PBXNativeTarget "Canary" */ = { + BC33E0471FBB627B0060ECBE /* Build configuration list for PBXNativeTarget "AppStore Application" */ = { isa = XCConfigurationList; buildConfigurations = ( BC33E0481FBB627B0060ECBE /* Debug */, @@ -1117,7 +1957,7 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - BC33E0541FBB63260060ECBE /* Build configuration list for PBXNativeTarget "Canary (Internal)" */ = { + BC33E0541FBB63260060ECBE /* Build configuration list for PBXNativeTarget "Internal Application" */ = { isa = XCConfigurationList; buildConfigurations = ( BC33E0551FBB63260060ECBE /* Debug */, @@ -1126,6 +1966,24 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + CE237F27216BF2C800A8134A /* Build configuration list for PBXNativeTarget "CanaryUITests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + CE237F25216BF2C800A8134A /* Debug */, + CE237F26216BF2C800A8134A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + EC469C1922613063006CABAA /* Build configuration list for PBXNativeTarget "CanaryUnitTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + EC469C1722613063006CABAA /* Debug */, + EC469C1822613063006CABAA /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ }; rootObject = BC33E02B1FBB627B0060ECBE /* Project object */; diff --git a/Canary/Canary.xcodeproj/xcshareddata/xcschemes/Canary Screenshots.xcscheme b/Canary/Canary.xcodeproj/xcshareddata/xcschemes/Canary Screenshots.xcscheme new file mode 100644 index 000000000..9f042f53b --- /dev/null +++ b/Canary/Canary.xcodeproj/xcshareddata/xcschemes/Canary Screenshots.xcscheme @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/MoPubSampleApp/MoPubSampleApp.xcodeproj/xcshareddata/xcschemes/MoPubSampleApp.xcscheme b/Canary/Canary.xcodeproj/xcshareddata/xcschemes/Canary-AppStore Application.xcscheme similarity index 63% rename from MoPubSampleApp/MoPubSampleApp.xcodeproj/xcshareddata/xcschemes/MoPubSampleApp.xcscheme rename to Canary/Canary.xcodeproj/xcshareddata/xcschemes/Canary-AppStore Application.xcscheme index d78321188..b3d76709e 100644 --- a/MoPubSampleApp/MoPubSampleApp.xcodeproj/xcshareddata/xcschemes/MoPubSampleApp.xcscheme +++ b/Canary/Canary.xcodeproj/xcshareddata/xcschemes/Canary-AppStore Application.xcscheme @@ -1,6 +1,6 @@ + BlueprintIdentifier = "BC33E0321FBB627B0060ECBE" + BuildableName = "MoPub.app" + BlueprintName = "AppStore Application" + ReferencedContainer = "container:Canary.xcodeproj"> @@ -26,27 +26,33 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - language = "" shouldUseLaunchSchemeArgsEnv = "YES"> - - + BlueprintIdentifier = "BC33E0321FBB627B0060ECBE" + BuildableName = "MoPub.app" + BlueprintName = "AppStore Application" + ReferencedContainer = "container:Canary.xcodeproj"> - - + + + + + + + BlueprintIdentifier = "BC33E0321FBB627B0060ECBE" + BuildableName = "MoPub.app" + BlueprintName = "AppStore Application" + ReferencedContainer = "container:Canary.xcodeproj"> - - + BlueprintIdentifier = "BC33E0321FBB627B0060ECBE" + BuildableName = "MoPub.app" + BlueprintName = "AppStore Application" + ReferencedContainer = "container:Canary.xcodeproj"> diff --git a/Canary/Canary.xcodeproj/xcshareddata/xcschemes/Canary-Internal Application.xcscheme b/Canary/Canary.xcodeproj/xcshareddata/xcschemes/Canary-Internal Application.xcscheme new file mode 100644 index 000000000..ef73b2f5b --- /dev/null +++ b/Canary/Canary.xcodeproj/xcshareddata/xcschemes/Canary-Internal Application.xcscheme @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Canary/Canary.xcodeproj/xcshareddata/xcschemes/Canary-Unit Tests.xcscheme b/Canary/Canary.xcodeproj/xcshareddata/xcschemes/Canary-Unit Tests.xcscheme new file mode 100644 index 000000000..86565994c --- /dev/null +++ b/Canary/Canary.xcodeproj/xcshareddata/xcschemes/Canary-Unit Tests.xcscheme @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Canary/Canary.xcodeproj/xcshareddata/xcschemes/CanaryUITests.xcscheme b/Canary/Canary.xcodeproj/xcshareddata/xcschemes/CanaryUITests.xcscheme new file mode 100644 index 000000000..bc6ba3872 --- /dev/null +++ b/Canary/Canary.xcodeproj/xcshareddata/xcschemes/CanaryUITests.xcscheme @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Canary/Canary/AccessibilityIdentifier.swift b/Canary/Canary/AccessibilityIdentifier.swift new file mode 100644 index 000000000..f642e1929 --- /dev/null +++ b/Canary/Canary/AccessibilityIdentifier.swift @@ -0,0 +1,20 @@ +// +// AccessibilityIdentifier.swift +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +import Foundation + +/** + Accessibility identifiers to facilitate UI testing. + - Note: Please keep this struct alphabetically sorted to facilitate finding tags. + */ +struct AccessibilityIdentifier { + static let adActionsLoad: String = "Load" + static let adActionsShow: String = "Show" + static let nativeAdImageView: String = "NativeAdImageView" + static let notificationButton: String = "NotificationButton" +} diff --git a/Canary/Canary/AdapterVersions/AdapterVersionsMenuDataSource.swift b/Canary/Canary/AdapterVersions/AdapterVersionsMenuDataSource.swift new file mode 100644 index 000000000..f01c75365 --- /dev/null +++ b/Canary/Canary/AdapterVersions/AdapterVersionsMenuDataSource.swift @@ -0,0 +1,160 @@ +// +// AdapterVersionsMenuDataSource.swift +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +import UIKit +import MoPub + +class AdapterVersionsMenuDataSource { + // MARK: - Properties + + private enum Item { + /** + This cell represents the "no adapters initialized" state. + */ + case noAdapters + + /** + This cell represents an adpater. The associated value of this case is the name of adapter. + */ + case adapter(name: String) + + /** + This cell has a Clear Cached Network toggle. + */ + case clearCachedNetowrksToggle + } + + /** + This `Item` array represents the cells in the table view. + */ + private var items: [Item] = [] + + /** + Set of adapter names that are currently in an expanded state. + */ + private var expandedAdapters: Set = Set() +} + +extension AdapterVersionsMenuDataSource: MenuDisplayable { + /** + Number of menu items available + */ + var count: Int { + return items.count + } + + /** + Human-readable title for the menu grouping + */ + var title: String { + return "Adapters" + } + + /** + Provides the rendered cell for the menu item + - Parameter index: Menu item index assumed to be in bounds + - Parameter tableView: `UITableView` that will render the cell + - Returns: A configured `UITableViewCell` + */ + func cell(forItem index: Int, inTableView tableView: UITableView) -> UITableViewCell { + switch items[index] { + case .noAdapters: + let cell = tableView.dequeueCellFromNib(cellType: CollapsibleAdapterInfoTableViewCell.self) + cell.titleLabel.text = "No adapters initialized" + return cell + + case let .adapter(name): + let cell = tableView.dequeueCellFromNib(cellType: CollapsibleAdapterInfoTableViewCell.self) + + // There exist some initialized adapters + guard let adapter: MPAdapterConfiguration = MoPub.sharedInstance().adapterConfigurationNamed(name) else { + cell.update(title: name) + return cell + } + + let isCollapsed: Bool = !expandedAdapters.contains(name) + cell.update(adapterName: name , info: adapter, isCollapsed: isCollapsed) + return cell + + case .clearCachedNetowrksToggle: + let cell = tableView.dequeueCellFromNib(cellType: TextAndToggleTableViewCell.self) + cell.configure(title: "Clear Cached Networks", toggleIsOn: UserDefaults.standard.shouldClearCachedNetworks) { shouldClearCachedNetworks in + UserDefaults.standard.shouldClearCachedNetworks = shouldClearCachedNetworks + } + return cell + } + } + + /** + Query if the menu item is selectable + - Parameter index: Menu item index assumed to be in bounds + - Parameter tableView: `UITableView` that rendered the item + - Returns: `true` if selectable; `false` otherwise + */ + func canSelect(itemAt index: Int, inTableView tableView: UITableView) -> Bool { + // Selection is only valid if there are adapters present. + switch items[index] { + case .noAdapters, .clearCachedNetowrksToggle: + return false + case .adapter: + return true + } + } + + /** + Performs an optional selection action for the menu item + - Parameter indexPath: Menu item indexPath assumed to be in bounds + - Parameter tableView: `UITableView` that rendered the item + - Parameter viewController: Presenting view controller + - Returns: `true` if the menu should collapse when selected; `false` otherwise. + */ + func didSelect(itemAt indexPath: IndexPath, inTableView tableView: UITableView, presentFrom viewController: UIViewController) -> Bool { + switch items[indexPath.row] { + case .noAdapters: + return false + + case let .adapter(name): + // Toggle the expanded state for the adapter + if expandedAdapters.contains(name) { + expandedAdapters.remove(name) + } + else { + expandedAdapters.insert(name) + } + + // Notify the table view that it needs to refresh the layout for the selected cell. + tableView.beginUpdates() + tableView.reloadRows(at: [indexPath], with: .automatic) + tableView.endUpdates() + return false + + case .clearCachedNetowrksToggle: + return false + } + } + + /** + Updates the data source if needed. + - Returns: `true` update happened; `false` otherwise. + */ + func updateIfNeeded() -> Bool { + let previousAdapterNames: [String] = items.compactMap { + guard case let .adapter(name) = $0 else { + return nil + } + return name + } + + let currentAdapterNames = MoPub.sharedInstance().availableAdapterClassNames()?.sorted() ?? [] + var items: [Item] = currentAdapterNames.isEmpty ? [.noAdapters] : currentAdapterNames.map { .adapter(name: $0) } + items.append(.clearCachedNetowrksToggle) + self.items = items + + return previousAdapterNames != currentAdapterNames + } +} diff --git a/Canary/Canary/AppDelegate+AdvancedBidders.swift b/Canary/Canary/AppDelegate+AdvancedBidders.swift deleted file mode 100644 index 753e39601..000000000 --- a/Canary/Canary/AppDelegate+AdvancedBidders.swift +++ /dev/null @@ -1,42 +0,0 @@ -// -// AppDelegate+AdvancedBidders.swift -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -import Foundation -import MoPub - -extension AppDelegate { - /** - Generates a list of advanced bidders supported by the app. - - Returns: A list of advanced bidders or `nil` if none are found. - */ - func supportedAdvancedBidders() -> [MPAdvancedBidder.Type]? { - var bidders: [MPAdvancedBidder.Type] = [] - - // AdColony advanced bidder - if let adColonyBidderType = NSClassFromString("AdColonyAdvancedBidder") as? MPAdvancedBidder.Type { - bidders.append(adColonyBidderType) - } - - // AppLovin advanced bidder - if let appLovinBidderType = NSClassFromString("AppLovinAdvancedBidder") as? MPAdvancedBidder.Type { - bidders.append(appLovinBidderType) - } - - // Facebook advanced bidder - if let facebookBidderType = NSClassFromString("FacebookAdvancedBidder") as? MPAdvancedBidder.Type { - bidders.append(facebookBidderType) - } - - // Tapjoy advanced bidder - if let tapjoyBidderType = NSClassFromString("TapjoyAdvancedBidder") as? MPAdvancedBidder.Type { - bidders.append(tapjoyBidderType) - } - - return bidders.count > 0 ? bidders : nil - } -} diff --git a/Canary/Canary/AppDelegate+Consent.swift b/Canary/Canary/AppDelegate+Consent.swift deleted file mode 100644 index bfed6402a..000000000 --- a/Canary/Canary/AppDelegate+Consent.swift +++ /dev/null @@ -1,35 +0,0 @@ -// -// AppDelegate+Consent.swift -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -import Foundation -import MoPub - -extension AppDelegate { - /** - Loads the consent request dialog (if not already loaded), and presents the dialog - from the specified view controller. If user consent is not needed, nothing is done. - - Parameter presentingViewController: `UIViewController` used for presenting the dialog - */ - func displayConsentDialog(from presentingViewController: UIViewController) { - // Verify that we need to acquire consent. - guard MoPub.sharedInstance().shouldShowConsentDialog else { - return - } - - // Load the consent dialog if it's not available. If it is already available, - // the completion block will immediately fire. - MoPub.sharedInstance().loadConsentDialog(completion: { (error: Error?) in - guard error == nil else { - print("Consent dialog failed to load: \(String(describing: error?.localizedDescription))") - return - } - - MoPub.sharedInstance().showConsentDialog(from: presentingViewController, completion: nil) - }) - } -} diff --git a/Canary/Canary/AppDelegate.swift b/Canary/Canary/AppDelegate.swift index d394ba7d4..e345226ca 100644 --- a/Canary/Canary/AppDelegate.swift +++ b/Canary/Canary/AppDelegate.swift @@ -1,124 +1,131 @@ // // AppDelegate.swift // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // import UIKit -import MoPub - -let kAppId = "112358" -let kAdUnitId = "0ac59b0996d947309c33f59d6676399f" @UIApplicationMain -class AppDelegate: UIResponder, UIApplicationDelegate { +class AppDelegate: UIResponder { + static var shared: AppDelegate { + guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { + fatalError() + } + return appDelegate + } + + /** + This default `SceneDelegate` is created for pre-iOS 13 backward compatibility as a single scene app. + */ + private lazy var _sceneDelegate = SceneDelegate() + private var sceneDelegate: SceneDelegate { + get { + if #available(iOS 13, *) { + fatalError("Handle multi-scene in `SceneDelegate` for iOS 13+") + } else { + return _sceneDelegate + } + } + } + /** - Main application window. + Application window. */ - var window: UIWindow? + var window: UIWindow? { + get { + if #available(iOS 13, *) { + // Handle multi-scene in `SceneDelegate` for iOS 13+. Return `nil` instead of + // `fatalError` because iOS 13 UIKit calls this getter regardlessly. + return nil + } else { + return sceneDelegate.window + } + } + set { + if #available(iOS 13, *) { + fatalError("Handle multi-scene in `SceneDelegate` for iOS 13+") + } else { + sceneDelegate.window = newValue + } + } + } /** - Main application's container controller. + Application container controller. */ - var containerViewController: ContainerViewController! + var containerViewController: ContainerViewController? { + if #available(iOS 13, *) { + fatalError("Handle multi-scene in `SceneDelegate` for iOS 13+") + } else { + switch sceneDelegate.mode { + case .adViewScene, .unknown: + print("`containerViewController` is only available for the main scene") + return nil + case .mainScene(let mainSceneState): + return mainSceneState.containerViewController + } + } + } /** Saved ads split view controller. */ - var savedAdSplitViewController: UISplitViewController? + var savedAdSplitViewController: UISplitViewController? { + if #available(iOS 13, *) { + fatalError("Handle multi-scene in `SceneDelegate` for iOS 13+") + } else { + switch sceneDelegate.mode { + case .adViewScene, .unknown: + print("`savedAdSplitViewController` is only available for the main scene") + return nil + case .mainScene(let mainSceneState): + return mainSceneState.savedAdSplitViewController + } + } + } +} - // MARK: - UIApplicationDelegate +// MARK: - UIApplicationDelegate +/* +For future `UIApplicationDelegate` implementation, if there is a `UIWindowSceneDelegate` counterpart, +we should share the implementation in `SceneDelegate` for both `UIWindowSceneDelegate` and +`UIApplicationDelegate`. +*/ +extension AppDelegate: UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - // Extract the UI elements for easier manipulation later. - // Calls to `loadViewIfNeeded()` are needed to load any children view controllers - // before `viewDidLoad()` occurs. - containerViewController = (window?.rootViewController as! ContainerViewController) - containerViewController.loadViewIfNeeded() - savedAdSplitViewController = containerViewController.mainTabBarController?.viewControllers?[1] as? UISplitViewController - - // Additional configuration for internal target. - #if INTERNAL - Internal.sharedInstance.initialize(with: containerViewController) - #endif - - // MoPub SDK initialization - let sdkConfig = MPMoPubConfiguration(adUnitIdForAppInitialization: kAdUnitId) - sdkConfig.advancedBidders = supportedAdvancedBidders() - sdkConfig.globalMediationSettings = [] - sdkConfig.mediatedNetworks = MoPub.sharedInstance().allCachedNetworks() - - MoPub.sharedInstance().initializeSdk(with: sdkConfig) { - // Request user consent to collect personally identifiable information - // used for targeted ads - if let tabBarController = self.containerViewController.mainTabBarController { - self.displayConsentDialog(from: tabBarController) - } - - print("SDK completed initialization") + if #available(iOS 13, *) { + // Do nothing here. App launch will be handled by `SceneDelegate.scene(_:willConnectTo:options:)` + } else { + sceneDelegate.handleMainSceneStart() } - - // Conversion tracking - MPAdConversionTracker.shared().reportApplicationOpen(forApplicationID: kAppId) - return true } func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool { - if url.scheme == "mopub" && url.host == "load" { - return openMoPubUrl(url: url, onto: savedAdSplitViewController, shouldSave: true) + if #available(iOS 13, *) { + fatalError("Handle multi-scene in `SceneDelegate` for iOS 13+") + } else { + return sceneDelegate.openURL(url) } - return true } - - // MARK: - Deep Links +} - /** - Attempts to open a valid `mopub://` scheme deep link URL - - Parameter url: MoPub deep link URL - - Parameter splitViewController: Split view controller that will present the opened deep link - - Parameter shouldSave: Flag indicating that the ad unit that was opened should be saved - */ - func openMoPubUrl(url: URL, onto splitViewController: UISplitViewController?, shouldSave: Bool) -> Bool { - // Validate that the URL contains the required query parameters: - // 1. adUnitId (must be non-nil in value) - // 2. format (must be a valid format string) - guard let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false), - let queryItems = urlComponents.queryItems, - queryItems.contains(where: { $0.name == AdUnitKey.Id }), - let formatString: String = queryItems.filter({ $0.name == "format" }).first?.value, - let format = AdFormat(rawValue: formatString) else { - return false - } - - // Generate an `AdUnit` from the query parameters and extracted ad format. - let params: [String: String] = queryItems.reduce(into: [:], { (result, queryItem) in - result[queryItem.name] = queryItem.value ?? "" - }) - - guard let adUnit: AdUnit = AdUnit(info: params, defaultViewControllerClassName: format.renderingViewController) else { - return false - } - - // Generate the destinate view controller and attempt to push the destination to the - // Saved Ads navigation controller. - guard let vcClass = NSClassFromString(adUnit.viewControllerClassName) as? AdViewController.Type, - let destination: UIViewController = vcClass.instantiateFromNib(adUnit: adUnit) as? UIViewController else { - return false - } - - DispatchQueue.main.async { - // If the ad unit should be saved, we will switch the tab to the saved ads - // tab and then push the view controller on that navigation stack. - if shouldSave { - self.containerViewController.mainTabBarController?.selectedIndex = 1 - SavedAdsManager.sharedInstance.addSavedAd(adUnit: adUnit) - } - - splitViewController?.showDetailViewController(destination, sender: splitViewController) +// MARK: - UISceneSession Lifecycle + +@available(iOS 13, *) +extension AppDelegate { + func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { + // Called when a new scene session is being created. + // Use this method to select a configuration to create the new scene with. + if let adUnit = AdUnit.adUnitFromSceneConnectionOptions(options) { + print("\(#function) open ad unit [\(adUnit.name): \(adUnit.id)]") + return UISceneConfiguration(name: "Open Ad View", sessionRole: connectingSceneSession.role) + } else { + return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) } - return true } } diff --git a/Canary/Canary/Assets.xcassets/AppIcon.Internal.appiconset/Contents.json b/Canary/Canary/Assets.xcassets/AppIcon.Internal.appiconset/Contents.json new file mode 100644 index 000000000..a31b91059 --- /dev/null +++ b/Canary/Canary/Assets.xcassets/AppIcon.Internal.appiconset/Contents.json @@ -0,0 +1,116 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom": "iphone", + "filename" : "MoPub_logo_square_white on blue (1)-20@2x.png", + "scale": "2x" + }, + { + "size" : "20x20", + "idiom": "iphone", + "filename" : "MoPub_logo_square_white on blue (1)-20@3x.png", + "scale": "3x" + }, + { + "size" : "20x20", + "idiom": "ipad", + "filename" : "MoPub_logo_square_white on blue (1)-20.png", + "scale": "1x" + }, + { + "size" : "20x20", + "idiom": "ipad", + "filename" : "MoPub_logo_square_white on blue (1)-20@2x.png", + "scale": "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "MoPub_logo_square_white on blue (1)-29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "MoPub_logo_square_white on blue (1)-29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "MoPub_logo_square_white on blue (1)-40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "MoPub_logo_square_white on blue (1)-40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "MoPub_logo_square_white on blue (1)-60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "MoPub_logo_square_white on blue (1)-60@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "MoPub_logo_square_white on blue (1)-29.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "MoPub_logo_square_white on blue (1)-29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "MoPub_logo_square_white on blue (1)-40.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "MoPub_logo_square_white on blue (1)-40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "MoPub_logo_square_white on blue (1)-76.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "MoPub_logo_square_white on blue (1)-76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "MoPub_logo_square_white on blue (1)-83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "MoPub_logo_square_white on blue (1)-1024.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/Canary/Canary/Assets.xcassets/AppIcon.Internal.appiconset/MoPub_logo_square_white on blue (1)-1024.png b/Canary/Canary/Assets.xcassets/AppIcon.Internal.appiconset/MoPub_logo_square_white on blue (1)-1024.png new file mode 100644 index 000000000..add79ff76 Binary files /dev/null and b/Canary/Canary/Assets.xcassets/AppIcon.Internal.appiconset/MoPub_logo_square_white on blue (1)-1024.png differ diff --git a/Canary/Canary/Assets.xcassets/AppIcon.Internal.appiconset/MoPub_logo_square_white on blue (1)-20.png b/Canary/Canary/Assets.xcassets/AppIcon.Internal.appiconset/MoPub_logo_square_white on blue (1)-20.png new file mode 100644 index 000000000..b99dd9396 Binary files /dev/null and b/Canary/Canary/Assets.xcassets/AppIcon.Internal.appiconset/MoPub_logo_square_white on blue (1)-20.png differ diff --git a/Canary/Canary/Assets.xcassets/AppIcon.Internal.appiconset/MoPub_logo_square_white on blue (1)-20@2x.png b/Canary/Canary/Assets.xcassets/AppIcon.Internal.appiconset/MoPub_logo_square_white on blue (1)-20@2x.png new file mode 100644 index 000000000..9d028818b Binary files /dev/null and b/Canary/Canary/Assets.xcassets/AppIcon.Internal.appiconset/MoPub_logo_square_white on blue (1)-20@2x.png differ diff --git a/Canary/Canary/Assets.xcassets/AppIcon.Internal.appiconset/MoPub_logo_square_white on blue (1)-20@3x.png b/Canary/Canary/Assets.xcassets/AppIcon.Internal.appiconset/MoPub_logo_square_white on blue (1)-20@3x.png new file mode 100644 index 000000000..6d11b1f34 Binary files /dev/null and b/Canary/Canary/Assets.xcassets/AppIcon.Internal.appiconset/MoPub_logo_square_white on blue (1)-20@3x.png differ diff --git a/Canary/Canary/Assets.xcassets/AppIcon.Internal.appiconset/MoPub_logo_square_white on blue (1)-29.png b/Canary/Canary/Assets.xcassets/AppIcon.Internal.appiconset/MoPub_logo_square_white on blue (1)-29.png new file mode 100644 index 000000000..8315443e4 Binary files /dev/null and b/Canary/Canary/Assets.xcassets/AppIcon.Internal.appiconset/MoPub_logo_square_white on blue (1)-29.png differ diff --git a/Canary/Canary/Assets.xcassets/AppIcon.Internal.appiconset/MoPub_logo_square_white on blue (1)-29@2x.png b/Canary/Canary/Assets.xcassets/AppIcon.Internal.appiconset/MoPub_logo_square_white on blue (1)-29@2x.png new file mode 100644 index 000000000..ca78c2965 Binary files /dev/null and b/Canary/Canary/Assets.xcassets/AppIcon.Internal.appiconset/MoPub_logo_square_white on blue (1)-29@2x.png differ diff --git a/Canary/Canary/Assets.xcassets/AppIcon.Internal.appiconset/MoPub_logo_square_white on blue (1)-29@3x.png b/Canary/Canary/Assets.xcassets/AppIcon.Internal.appiconset/MoPub_logo_square_white on blue (1)-29@3x.png new file mode 100644 index 000000000..a5df872eb Binary files /dev/null and b/Canary/Canary/Assets.xcassets/AppIcon.Internal.appiconset/MoPub_logo_square_white on blue (1)-29@3x.png differ diff --git a/Canary/Canary/Assets.xcassets/AppIcon.Internal.appiconset/MoPub_logo_square_white on blue (1)-40.png b/Canary/Canary/Assets.xcassets/AppIcon.Internal.appiconset/MoPub_logo_square_white on blue (1)-40.png new file mode 100644 index 000000000..9d028818b Binary files /dev/null and b/Canary/Canary/Assets.xcassets/AppIcon.Internal.appiconset/MoPub_logo_square_white on blue (1)-40.png differ diff --git a/Canary/Canary/Assets.xcassets/AppIcon.Internal.appiconset/MoPub_logo_square_white on blue (1)-40@2x.png b/Canary/Canary/Assets.xcassets/AppIcon.Internal.appiconset/MoPub_logo_square_white on blue (1)-40@2x.png new file mode 100644 index 000000000..690813e77 Binary files /dev/null and b/Canary/Canary/Assets.xcassets/AppIcon.Internal.appiconset/MoPub_logo_square_white on blue (1)-40@2x.png differ diff --git a/Canary/Canary/Assets.xcassets/AppIcon.Internal.appiconset/MoPub_logo_square_white on blue (1)-40@3x.png b/Canary/Canary/Assets.xcassets/AppIcon.Internal.appiconset/MoPub_logo_square_white on blue (1)-40@3x.png new file mode 100644 index 000000000..edf9ba91a Binary files /dev/null and b/Canary/Canary/Assets.xcassets/AppIcon.Internal.appiconset/MoPub_logo_square_white on blue (1)-40@3x.png differ diff --git a/Canary/Canary/Assets.xcassets/AppIcon.Internal.appiconset/MoPub_logo_square_white on blue (1)-60@2x.png b/Canary/Canary/Assets.xcassets/AppIcon.Internal.appiconset/MoPub_logo_square_white on blue (1)-60@2x.png new file mode 100644 index 000000000..edf9ba91a Binary files /dev/null and b/Canary/Canary/Assets.xcassets/AppIcon.Internal.appiconset/MoPub_logo_square_white on blue (1)-60@2x.png differ diff --git a/Canary/Canary/Assets.xcassets/AppIcon.Internal.appiconset/MoPub_logo_square_white on blue (1)-60@3x.png b/Canary/Canary/Assets.xcassets/AppIcon.Internal.appiconset/MoPub_logo_square_white on blue (1)-60@3x.png new file mode 100644 index 000000000..62518d70c Binary files /dev/null and b/Canary/Canary/Assets.xcassets/AppIcon.Internal.appiconset/MoPub_logo_square_white on blue (1)-60@3x.png differ diff --git a/Canary/Canary/Assets.xcassets/AppIcon.Internal.appiconset/MoPub_logo_square_white on blue (1)-76.png b/Canary/Canary/Assets.xcassets/AppIcon.Internal.appiconset/MoPub_logo_square_white on blue (1)-76.png new file mode 100644 index 000000000..9b0ce08cd Binary files /dev/null and b/Canary/Canary/Assets.xcassets/AppIcon.Internal.appiconset/MoPub_logo_square_white on blue (1)-76.png differ diff --git a/Canary/Canary/Assets.xcassets/AppIcon.Internal.appiconset/MoPub_logo_square_white on blue (1)-76@2x.png b/Canary/Canary/Assets.xcassets/AppIcon.Internal.appiconset/MoPub_logo_square_white on blue (1)-76@2x.png new file mode 100644 index 000000000..254fb3fca Binary files /dev/null and b/Canary/Canary/Assets.xcassets/AppIcon.Internal.appiconset/MoPub_logo_square_white on blue (1)-76@2x.png differ diff --git a/Canary/Canary/Assets.xcassets/AppIcon.Internal.appiconset/MoPub_logo_square_white on blue (1)-83.5@2x.png b/Canary/Canary/Assets.xcassets/AppIcon.Internal.appiconset/MoPub_logo_square_white on blue (1)-83.5@2x.png new file mode 100644 index 000000000..f25a959ef Binary files /dev/null and b/Canary/Canary/Assets.xcassets/AppIcon.Internal.appiconset/MoPub_logo_square_white on blue (1)-83.5@2x.png differ diff --git a/Canary/Canary/Assets.xcassets/AppIcon.appiconset/app_icon-1024.png b/Canary/Canary/Assets.xcassets/AppIcon.appiconset/app_icon-1024.png index 2ceb2f754..ab8b06e6c 100644 Binary files a/Canary/Canary/Assets.xcassets/AppIcon.appiconset/app_icon-1024.png and b/Canary/Canary/Assets.xcassets/AppIcon.appiconset/app_icon-1024.png differ diff --git a/Canary/Canary/Assets.xcassets/CloseButton.imageset/Contents.json b/Canary/Canary/Assets.xcassets/CloseButton.imageset/Contents.json new file mode 100644 index 000000000..4ae3cb6a0 --- /dev/null +++ b/Canary/Canary/Assets.xcassets/CloseButton.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "MPCloseButtonX.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "MPCloseButtonX@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "MPCloseButtonX@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Canary/Canary/Assets.xcassets/CloseButton.imageset/MPCloseButtonX.png b/Canary/Canary/Assets.xcassets/CloseButton.imageset/MPCloseButtonX.png new file mode 100644 index 000000000..ca4fa2981 Binary files /dev/null and b/Canary/Canary/Assets.xcassets/CloseButton.imageset/MPCloseButtonX.png differ diff --git a/Canary/Canary/Assets.xcassets/CloseButton.imageset/MPCloseButtonX@2x.png b/Canary/Canary/Assets.xcassets/CloseButton.imageset/MPCloseButtonX@2x.png new file mode 100644 index 000000000..85e39a31b Binary files /dev/null and b/Canary/Canary/Assets.xcassets/CloseButton.imageset/MPCloseButtonX@2x.png differ diff --git a/Canary/Canary/Assets.xcassets/CloseButton.imageset/MPCloseButtonX@3x.png b/Canary/Canary/Assets.xcassets/CloseButton.imageset/MPCloseButtonX@3x.png new file mode 100644 index 000000000..c6e5df439 Binary files /dev/null and b/Canary/Canary/Assets.xcassets/CloseButton.imageset/MPCloseButtonX@3x.png differ diff --git a/Canary/Canary/Assets.xcassets/ReleaseTesting.imageset/Contents.json b/Canary/Canary/Assets.xcassets/ReleaseTesting.imageset/Contents.json new file mode 100644 index 000000000..8b6a1c506 --- /dev/null +++ b/Canary/Canary/Assets.xcassets/ReleaseTesting.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Mraid.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "Mraid@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "Mraid@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Canary/Canary/Assets.xcassets/ReleaseTesting.imageset/Mraid.png b/Canary/Canary/Assets.xcassets/ReleaseTesting.imageset/Mraid.png new file mode 100644 index 000000000..a0d5f1e8a Binary files /dev/null and b/Canary/Canary/Assets.xcassets/ReleaseTesting.imageset/Mraid.png differ diff --git a/Canary/Canary/Assets.xcassets/ReleaseTesting.imageset/Mraid@2x.png b/Canary/Canary/Assets.xcassets/ReleaseTesting.imageset/Mraid@2x.png new file mode 100644 index 000000000..d22976682 Binary files /dev/null and b/Canary/Canary/Assets.xcassets/ReleaseTesting.imageset/Mraid@2x.png differ diff --git a/Canary/Canary/Assets.xcassets/ReleaseTesting.imageset/Mraid@3x.png b/Canary/Canary/Assets.xcassets/ReleaseTesting.imageset/Mraid@3x.png new file mode 100644 index 000000000..03c38f242 Binary files /dev/null and b/Canary/Canary/Assets.xcassets/ReleaseTesting.imageset/Mraid@3x.png differ diff --git a/Canary/Canary/Base.lproj/LaunchScreen.storyboard b/Canary/Canary/Base.lproj/LaunchScreen.storyboard index d514e91ae..b1446b95d 100644 --- a/Canary/Canary/Base.lproj/LaunchScreen.storyboard +++ b/Canary/Canary/Base.lproj/LaunchScreen.storyboard @@ -1,11 +1,9 @@ - - - - + + - + @@ -22,7 +20,7 @@ - + diff --git a/Canary/Canary/Base.lproj/Main.storyboard b/Canary/Canary/Base.lproj/Main.storyboard index 67ddaf443..256da284d 100644 --- a/Canary/Canary/Base.lproj/Main.storyboard +++ b/Canary/Canary/Base.lproj/Main.storyboard @@ -1,11 +1,9 @@ - - - - + + - + @@ -15,7 +13,7 @@ - + @@ -61,17 +59,69 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -80,75 +130,47 @@ - + - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + - - - - - - - - - - - - + - + - + - - - - - - + + + + + + + + - - - + @@ -159,11 +181,11 @@ - + - + @@ -187,21 +209,21 @@ - + - + - + - - + + @@ -349,7 +371,7 @@ - + @@ -382,20 +404,211 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Canary/Canary/Base/AdUnit.swift b/Canary/Canary/Base/AdUnit.swift index 257591a58..b1ae73a73 100644 --- a/Canary/Canary/Base/AdUnit.swift +++ b/Canary/Canary/Base/AdUnit.swift @@ -1,7 +1,7 @@ // // AdUnit.swift // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -18,6 +18,12 @@ public struct AdUnitKey { static let UserDataKeywords: String = "userDataKeywords" static let CustomData: String = "custom_data" static let OverrideClass: String = "override_class" + static let Format: String = "format" + + /** + Ad Unit ID to use when the current interface idiom is `pad` + */ + static let OverridePadId: String = "overridePadAdUnitId" } /** @@ -58,14 +64,18 @@ public class AdUnit : NSObject, Codable { /** Initializes an ad unit from a dictionary and a default rendering view controller. */ - public init?(info: [String: String], defaultViewControllerClassName: String) { - guard let adUnitId = info[AdUnitKey.Id], - let adUnitName = info[AdUnitKey.Name] else { + required public init?(info: [String: String], defaultViewControllerClassName: String) { + guard let adUnitId = info[AdUnitKey.Id] else { return nil } + + // Determine the iPad version of the Ad Unit ID if available. + // If no override is available, use the existing Ad Unit ID. + let isPad: Bool = (UIDevice.current.userInterfaceIdiom == .pad) + let padAdUnitId: String = info[AdUnitKey.OverridePadId] ?? adUnitId - id = adUnitId - name = adUnitName + id = (isPad ? padAdUnitId : adUnitId) + name = info[AdUnitKey.Name] ?? adUnitId keywords = info[AdUnitKey.Keywords] userDataKeywords = info[AdUnitKey.UserDataKeywords] customData = info[AdUnitKey.CustomData] @@ -77,4 +87,126 @@ public class AdUnit : NSObject, Codable { viewControllerClassName = defaultViewControllerClassName } } + + /** + Attempts to convert a `mopub://` scheme deep link URL into an `AdUnit` object. Returns nil if the URL was not able + to be converted. + - Parameter url: MoPub deep link URL + - Returns: `AdUnit` object or nil if URL was unable to be converted + */ + convenience public init?(url: URL) { + // Validate that the URL contains the required query parameters: + // 1. adUnitId (must be non-nil in value) + // 2. format (must be a valid format string) + guard let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false), + let queryItems = urlComponents.queryItems, + queryItems.contains(where: { $0.name == AdUnitKey.Id }), + let formatString: String = queryItems.filter({ $0.name == AdUnitKey.Format }).first?.value, + let format = AdFormat(rawValue: formatString) else { + return nil + } + + // Generate an `AdUnit` from the query parameters and extracted ad format. + let params: [String: String] = queryItems.reduce(into: [:], { (result, queryItem) in + result[queryItem.name] = queryItem.value ?? "" + }) + + self.init(info: params, defaultViewControllerClassName: format.renderingViewController) + } + + /** + Attempts to create an `AdUnit` object using an `adUnitId`, `format`, and optionally a `name`. Returns `nil` + if `AdUnit` object was not able to be created with information provided. + - Parameter adUnitId: ad unit ID in the form of a string + - Parameter format: `AdFormat` enum value signifying the ad format + - Parameter name: optional name in the form of the string. If `nil` is provided, adUnitId will be used instead. + - Returns: `AdUnit` or `nil` + */ + convenience init?(adUnitId: String, format: AdFormat, name: String?) { + var params: [String: String] = [AdUnitKey.Id: adUnitId, AdUnitKey.Format: format.rawValue] + if let name = name, name.count > 0 { + params[AdUnitKey.Name] = name + } + self.init(info: params, defaultViewControllerClassName: format.renderingViewController) + } +} + +extension AdUnit { + // MARK: - Filtering + + /** + Queries if the `AdUnit` contains the inputted string. The comparison is case-insensitive. + - Parameter string: String to search for. + - Returns: `true` if the `AdUnit` contains the string term; otherwise `false`. + */ + public func contains(_ string: String) -> Bool { + let nameContainsFilterTerm: Bool = (name.range(of: string, options: .caseInsensitive) != nil) + let idContainsFilterTerm: Bool = (id.range(of: string, options: .caseInsensitive) != nil) + + return nameContainsFilterTerm || idContainsFilterTerm + } +} + +// MARK: - Drag and Drop + +@available(iOS 13, *) +extension AdUnit { + enum OpenAdViewActivity { + fileprivate enum UserInfoKey { + /** + `NSUserActivity.userInfo` does not allow `AdUnit` value, thus we need to encode `AdUnit` into `Data`. + See https://developer.apple.com/documentation/foundation/nsuseractivity/1411706-userinfo + */ + static let jsonEncodedAdUnitData = "data" + } + + // Note: The `activityType` string must be included in the plist file under the `NSUserActivityTypes` array. + static let activityType = "com.mopub.canary.openAdView" + } + + /** + This is the user activity for enabling Drag & Drop to open a new scene. + `NSUserActivity.userInfo` does not allow `AdUnit` value, thus we need to encode `AdUnit` into `Data`. + See https://developer.apple.com/documentation/foundation/nsuseractivity/1411706-userinfo + */ + var openAdViewActivity: NSUserActivity { + guard let data = try? JSONEncoder().encode(self) else { + fatalError() + } + + let userActivity = NSUserActivity(activityType: AdUnit.OpenAdViewActivity.activityType) + userActivity.title = name + userActivity.userInfo = [AdUnit.OpenAdViewActivity.UserInfoKey.jsonEncodedAdUnitData: data] + return userActivity + } + + /** + Return the `AdUnit` for the "open ad view" scene. + */ + static func adUnitFromSceneConnectionOptions(_ options: UIScene.ConnectionOptions) -> AdUnit? { + guard let openAdViewActivity = options.userActivities.first(where: { $0.activityType == AdUnit.OpenAdViewActivity.activityType }), + let adUnitData = openAdViewActivity.userInfo?[AdUnit.OpenAdViewActivity.UserInfoKey.jsonEncodedAdUnitData] as? Data, + let adUnit = try? JSONDecoder().decode(AdUnit.self, from: adUnitData) else { + return nil + } + return adUnit + } + + /** + Return the root view controller for the "open ad view" scene window. + */ + static func adViewControllerForSceneConnectionOptions(_ options: UIScene.ConnectionOptions) -> UIViewController? { + guard + let adUnit = adUnitFromSceneConnectionOptions(options), + let viewControllerClass = NSClassFromString(adUnit.viewControllerClassName) as? AdViewController.Type, + let viewController = viewControllerClass.instantiateFromNib(adUnit: adUnit) as? UIViewController else { + return nil + } + viewController.loadViewIfNeeded() // has to load view first to add the Done button + viewController.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Done", + style: .done, + target: viewController, + action: #selector(UIViewController.destroySceneSession)) + return UINavigationController(rootViewController: viewController) + } } diff --git a/Canary/Canary/Base/AdUnitDataSource.swift b/Canary/Canary/Base/AdUnitDataSource.swift index a3c97d6bb..fdd2f3d83 100644 --- a/Canary/Canary/Base/AdUnitDataSource.swift +++ b/Canary/Canary/Base/AdUnitDataSource.swift @@ -1,7 +1,7 @@ // // AdUnitDataSource.swift // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -19,14 +19,19 @@ class AdUnitDataSource { */ internal var adUnits: [String: [AdUnit]] + /** + Internally stored alphabetically sorted sections. This is seperated from the + public getter `sections` to allow overriding `sections`. + */ + internal var cachedSections: [String] + /** Data source sections as human readable text meant for display as section headings to the user. */ - private(set) lazy var sections: [String] = { - // Ad unit sections sorted alphabetically - return adUnits.keys.sorted() - }() + var sections: [String] { + return cachedSections + } // MARK: - Initializers @@ -39,10 +44,12 @@ class AdUnitDataSource { required init(plistName: String, bundle: Bundle) { guard plistName.count > 0 else { adUnits = [:] + cachedSections = [] return } adUnits = AdUnitDataSource.openPlist(resourceName: plistName, bundle: bundle) ?? [:] + cachedSections = adUnits.keys.sorted() } // MARK: - Ad Unit Accessors diff --git a/Canary/Canary/Base/AdUnitTableViewController.swift b/Canary/Canary/Base/AdUnitTableViewController.swift index 230a4f98e..d351ec17f 100644 --- a/Canary/Canary/Base/AdUnitTableViewController.swift +++ b/Canary/Canary/Base/AdUnitTableViewController.swift @@ -1,16 +1,23 @@ // // AdUnitTableViewController.swift // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // import UIKit +import AVFoundation + +struct AdUnitTableViewControllerSegueIdentifier { + static let ModallyPresentCameraInterfaceSegueIdentifier = "modallyPresentCameraInterfaceViewController" + static let ModallyPresentManualEntryInterfaceSegueIdentifier = "modallyPresentManualEntryInterfaceViewController" +} class AdUnitTableViewController: UIViewController { // Outlets from `Main.storyboard` @IBOutlet weak var tableView: UITableView! + @IBOutlet var addButton: UIBarButtonItem? // Table data source. fileprivate var dataSource: AdUnitDataSource? = nil @@ -36,6 +43,12 @@ class AdUnitTableViewController: UIViewController { AdUnitTableViewHeader.register(with: tableView) tableView.dataSource = self tableView.delegate = self + + // Set up background color for Dark Mode + if #available(iOS 13.0, *) { + tableView.dragDelegate = self + view.backgroundColor = .systemBackground + } } // MARK: - Ad Loading @@ -46,7 +59,10 @@ class AdUnitTableViewController: UIViewController { return } - splitViewController?.showDetailViewController(destination, sender: self) + // Embed the destination ad view controller into a navigation controller so that + // pushing onto the navigation stack will work. + let navigationController: UINavigationController = UINavigationController(rootViewController: destination) + splitViewController?.showDetailViewController(navigationController, sender: self) } /** @@ -57,6 +73,44 @@ class AdUnitTableViewController: UIViewController { dataSource?.reloadData() tableView.reloadData() } + + // MARK: - IBActions + + @IBAction func addButtonAction(_ sender: Any) { + let cameraAuthorizationStatus = AVCaptureDevice.authorizationStatus(for: QRCodeCameraInterfaceViewController.defaultMediaType) + let showCameraButton = cameraAuthorizationStatus == .authorized || cameraAuthorizationStatus == .notDetermined ? true : false + + // If camera use is not authorized, show the manual interface without giving a choice + if !showCameraButton { + performSegue(withIdentifier: AdUnitTableViewControllerSegueIdentifier.ModallyPresentManualEntryInterfaceSegueIdentifier, sender: self) + return + } + + // If camera use is authorized, show action sheet with a choice between manual interface and camera interface + + let actionSheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) + + actionSheet.addAction(UIAlertAction(title: "Enter Ad Unit ID Manually", style: .default, handler: { [unowned self] _ in + self.performSegue(withIdentifier: AdUnitTableViewControllerSegueIdentifier.ModallyPresentManualEntryInterfaceSegueIdentifier, sender: self) + })) + + actionSheet.addAction(UIAlertAction(title: "Use QR Code", style: .default, handler: { [unowned self] _ in + self.performSegue(withIdentifier: AdUnitTableViewControllerSegueIdentifier.ModallyPresentCameraInterfaceSegueIdentifier, sender: self) + })) + + actionSheet.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) + + if let popoverPresentationController = actionSheet.popoverPresentationController { + guard let barButtonItem = sender as? UIBarButtonItem else { + assertionFailure("\(#function) sender is not `UIBarButtonItem` as expected") + return + } + // ADF-4094: app will crash if popover source is not set for popover presentation + popoverPresentationController.barButtonItem = barButtonItem + } + + present(actionSheet, animated: true, completion: nil) + } } extension AdUnitTableViewController: UITableViewDataSource { @@ -71,11 +125,12 @@ extension AdUnitTableViewController: UITableViewDataSource { } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - guard let adUnitCell: AdUnitTableViewCell = tableView.dequeueReusableCell(withIdentifier: AdUnitTableViewCell.reuseId, for: indexPath) as? AdUnitTableViewCell, - let adUnit: AdUnit = dataSource?.item(at: indexPath) else { + guard let adUnit: AdUnit = dataSource?.item(at: indexPath) else { return UITableViewCell() } + let adUnitCell = tableView.dequeueCellFromNib(cellType: AdUnitTableViewCell.self) + adUnitCell.accessibilityIdentifier = adUnit.id adUnitCell.refresh(adUnit: adUnit) adUnitCell.setNeedsLayout() return adUnitCell @@ -86,15 +141,12 @@ extension AdUnitTableViewController: UITableViewDelegate { // MARK: - UITableViewDelegate func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + // Intentionally not to deselect cell to help user to keep track of the long list guard let adUnit: AdUnit = dataSource?.item(at: indexPath) else { - tableView.deselectRow(at: indexPath, animated: true) return } loadAd(with: adUnit) - - // Unselect the row. - tableView.deselectRow(at: indexPath, animated: true) } func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { @@ -115,3 +167,25 @@ extension AdUnitTableViewController: UITableViewDelegate { return UITableView.automaticDimension } } + +// MARK: - UITableViewDragDelegate + +@available(iOS 11, *) +extension AdUnitTableViewController: UITableViewDragDelegate { + func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] { + // Drag & Drop is available since iOS 11, but multi-scene is available since iOS 13. + guard + #available(iOS 13, *), + let adUnit: AdUnit = dataSource?.item(at: indexPath)else { + return [] + } + + let itemProvider = NSItemProvider() + itemProvider.registerObject(adUnit.openAdViewActivity , visibility: .all) + + let dragItem = UIDragItem(itemProvider: itemProvider) + dragItem.localObject = adUnit + + return [dragItem] + } +} diff --git a/Canary/Canary/Base/BaseSplitViewController.swift b/Canary/Canary/Base/BaseSplitViewController.swift index a18636e9a..4981c213e 100644 --- a/Canary/Canary/Base/BaseSplitViewController.swift +++ b/Canary/Canary/Base/BaseSplitViewController.swift @@ -1,7 +1,7 @@ // // BaseSplitViewController.swift // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/Canary/Canary/Base/FilterableAdUnitTableViewController.swift b/Canary/Canary/Base/FilterableAdUnitTableViewController.swift new file mode 100644 index 000000000..3bc1f04df --- /dev/null +++ b/Canary/Canary/Base/FilterableAdUnitTableViewController.swift @@ -0,0 +1,66 @@ +// +// FilterableAdUnitTableViewController.swift +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +import UIKit + +class FilterableAdUnitTableViewController: AdUnitTableViewController { + // MARK: - Internal State + + /** + Assumes that `dataSource` will be in `viewDidLoad()` before use. + */ + private var dataSource: FilteredAdUnitDataSource! + + /** + Search controller used to filter the data source. + */ + private var searchController: UISearchController! + + // MARK: - Initialization + + /** + Initializes the view controller's data source. This must be performed before + `viewDidLoad()` is called. + - Parameter dataSource: Data source for the view controller. + */ + func initialize(with dataSource: FilteredAdUnitDataSource) { + self.dataSource = dataSource + super.initialize(with: dataSource) + } + + // MARK: - View Lifecycle + + override func viewDidLoad() { + // Load super view + super.viewDidLoad() + + // Add the search bar + searchController = UISearchController(searchResultsController: nil) + searchController.dimsBackgroundDuringPresentation = false + searchController.searchResultsUpdater = self + searchController.searchBar.placeholder = "Filter Ads" + searchController.searchBar.searchBarStyle = .minimal + + searchController.hidesNavigationBarDuringPresentation = false + navigationItem.titleView = searchController.searchBar + + // Definte presentation context so that the search bar does not remain + // on the screen if the user navigates to another view controller + // while the `UISearchController` is active. + definesPresentationContext = true + } +} + +extension FilterableAdUnitTableViewController: UISearchResultsUpdating { + // MARK: - UISearchResultsUpdating + + func updateSearchResults(for searchController: UISearchController) { + dataSource.filter = searchController.searchBar.text + tableView.reloadData() + } +} diff --git a/Canary/Canary/Base/FilteredAdUnitDataSource.swift b/Canary/Canary/Base/FilteredAdUnitDataSource.swift new file mode 100644 index 000000000..b8733c6da --- /dev/null +++ b/Canary/Canary/Base/FilteredAdUnitDataSource.swift @@ -0,0 +1,112 @@ +// +// FilteredAdUnitDataSource.swift +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +import Foundation + +/** + Class for retrieving data from data sources dealing with `AdUnit`s with + filtering capabilities. + */ +class FilteredAdUnitDataSource: AdUnitDataSource { + // MARK: - Internal State + + /** + Currently filtered AdUnits + */ + private var filteredAdUnits: [String: [AdUnit]] = [:] + + /** + Currently filtered sections + */ + private var filteredSections: [String] = [] + + // MARK: - Properties + + /** + Optional text used filter the data source. + */ + var filter: String? = nil { + didSet { + // Trim leading and trailing whitespaces and newlines out of the filter + filter = filter?.trimmingCharacters(in: .whitespacesAndNewlines) + + // No filtering in effect + guard let filter = filter, filter.count > 0 else { + filteredAdUnits = [:] + filteredSections = [] + return + } + + // Update the filtered sections. + // Filtering rules: + // 1. If a section includes the filter term, all values in the section will be available. + // 2. If a section does not include the filter term, only values that match the + // filter term will be included for that section. + filteredAdUnits = [:] + for (key, value) in adUnits { + // If a section includes the filter term, all values in the section will be available. + if key.range(of: filter, options: .caseInsensitive) != nil { + filteredAdUnits[key] = value + continue + } + + // If a section does not include the filter term, only values that match the + // filter term will be included for that section. + let filteredValues: [AdUnit] = value.filter({ return $0.contains(filter) }) + if filteredValues.count > 0 { + filteredAdUnits[key] = filteredValues + } + } + + filteredSections = filteredAdUnits.keys.sorted() + } + } + + // MARK: - Initializers + + /** + Initializes the data source with an optional plist file. + - Parameter plistName: Name of a plist file (without the extension) to initialize the + data source. + - Parameter bundle: Bundle where the plist file lives. + */ + required init(plistName: String, bundle: Bundle) { + super.init(plistName: plistName, bundle: bundle) + } + + // MARK: - Overrides + + /** + Data source sections as human readable text meant for display as section + headings to the user. + */ + override var sections: [String] { + guard let filter = filter, filter.count > 0 else { + return cachedSections + } + + return filteredSections + } + + /** + The items at the specified section. + - Parameter section: Index of the section to retrieve the ad units. + - Returns: Ad units for the section, or `nil` if out of bounds. + */ + override func items(for section: Int) -> [AdUnit]? { + guard let filter = filter, filter.count > 0 else { + return super.items(for: section) + } + + guard section >= 0, section < sections.count else { + return nil + } + + return filteredAdUnits[sections[section]] + } +} diff --git a/Canary/Canary/Cells/AdActionsTableViewCell.swift b/Canary/Canary/Cells/AdActionsTableViewCell.swift index 827382819..813fe41aa 100644 --- a/Canary/Canary/Cells/AdActionsTableViewCell.swift +++ b/Canary/Canary/Cells/AdActionsTableViewCell.swift @@ -1,23 +1,56 @@ // // AdActionsTableViewCell.swift // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // import UIKit -class AdActionsTableViewCell: UITableViewCell { +protocol AdActionsTableViewCellDelegate: AnyObject { + func requestedAdSizeUpdated(to size: CGSize) +} + +final class AdActionsTableViewCell: UITableViewCell, TableViewCellRegisterable { // MARK: - IBOutlets @IBOutlet weak var stackView: UIStackView! @IBOutlet weak var showAdButton: RoundedButton! @IBOutlet weak var loadAdButton: RoundedButton! + @IBOutlet weak var adSizeStackView: UIStackView! + @IBOutlet weak var widthTextField: UITextField! + @IBOutlet weak var heightTextField: UITextField! // MARK: - Properties + weak var delegate: AdActionsTableViewCellDelegate? = nil fileprivate var willLoadAd: AdActionHandler? = nil fileprivate var willShowAd: AdActionHandler? = nil + // MARK: - Requested Ad Size + private static let numberFormatter = NumberFormatter() + private(set) var requestedAdSize: CGSize { + get { + let width = CGFloat(truncating: Self.numberFormatter.number(from: widthTextField.text ?? "") ?? 0.0) + let height = CGFloat(truncating: Self.numberFormatter.number(from: heightTextField.text ?? "") ?? 0.0) + return CGSize(width: width, height: height) + } + set { + widthTextField.text = "\(newValue.width)" + heightTextField.text = "\(newValue.height)" + } + } + + private var isRequestedAdSizeValid: Bool { + // If the ad sizes are not required, they are always marked as valid. + guard !adSizeStackView.isHidden else { + return true + } + + let widthIsValid = Self.numberFormatter.number(from: widthTextField.text ?? "") != nil + let heightIsValid = Self.numberFormatter.number(from: heightTextField.text ?? "") != nil + return widthIsValid && heightIsValid + } + // MARK: - IBActions @IBAction func onLoad(_ sender: Any) { willLoadAd?(sender) @@ -27,17 +60,79 @@ class AdActionsTableViewCell: UITableViewCell { willShowAd?(sender) } + // MARK: - Life Cycle + + override func awakeFromNib() { + super.awakeFromNib() + + // Set text colors correctly + if #available(iOS 13.0, *) { + widthTextField.textColor = .label + heightTextField.textColor = .label + } + + // Accessibility + loadAdButton.accessibilityIdentifier = AccessibilityIdentifier.adActionsLoad + showAdButton.accessibilityIdentifier = AccessibilityIdentifier.adActionsShow + } + // MARK: - Refreshing - func refresh(loadAdHandler: AdActionHandler? = nil, showAdHandler: AdActionHandler? = nil) { + + func refresh(adSize: CGSize? = nil, + isAdLoading: Bool = false, + loadAdHandler: AdActionHandler? = nil, + showAdHandler: AdActionHandler? = nil, + showButtonEnabled: Bool = false) { + // If there is no initial ad size passed in, we can assume that this format does not + // support manual input of requested ad sizes. + if let adSize = adSize { + adSizeStackView.isHidden = false + requestedAdSize = adSize + } + else { + adSizeStackView.isHidden = true + } + willLoadAd = loadAdHandler willShowAd = showAdHandler + // Loading button state is only disabled if + // 1. the show button is enabled and has a valid handler + // OR + // 2. the ad is currently loading. + loadAdButton.isEnabled = (showAdHandler == nil || !showButtonEnabled) && !isAdLoading + widthTextField.isEnabled = loadAdButton.isEnabled + heightTextField.isEnabled = loadAdButton.isEnabled + // Showing an ad is optional. Hide it if there is no show handler. showAdButton.isHidden = (showAdHandler == nil) + showAdButton.isEnabled = showButtonEnabled + + // Require re-layout + setNeedsLayout() } } -extension AdActionsTableViewCell: TableViewCellRegisterable { - // MARK: - TableViewCellRegisterable - static private(set) var reuseId: String = "AdActionsTableViewCell" +extension AdActionsTableViewCell: UITextFieldDelegate { + func textFieldDidBeginEditing(_ textField: UITextField) { + // Since editing the text field is only possible when the load button is + // enabled, we can assume that toggling the enabled state is safe here. + loadAdButton.isEnabled = false + } + + func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { + // Done editing by pressing the return key or done key + if (string == "\n") { + textField.resignFirstResponder() + } + + return true + } + + func textFieldDidEndEditing(_ textField: UITextField) { + if isRequestedAdSizeValid { + loadAdButton.isEnabled = true + delegate?.requestedAdSizeUpdated(to: requestedAdSize) + } + } } diff --git a/Canary/Canary/Cells/AdActionsTableViewCell.xib b/Canary/Canary/Cells/AdActionsTableViewCell.xib index 584494857..0f27ea774 100644 --- a/Canary/Canary/Cells/AdActionsTableViewCell.xib +++ b/Canary/Canary/Cells/AdActionsTableViewCell.xib @@ -1,75 +1,122 @@ - - - - + + - + - + - + - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - + + + + + + + - + diff --git a/Canary/Canary/Cells/AdUnitTableViewCell.swift b/Canary/Canary/Cells/AdUnitTableViewCell.swift index 8dfa49999..cbf85637e 100644 --- a/Canary/Canary/Cells/AdUnitTableViewCell.swift +++ b/Canary/Canary/Cells/AdUnitTableViewCell.swift @@ -1,7 +1,7 @@ // // AdUnitTableViewCell.swift // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -11,7 +11,7 @@ import UIKit /** Cell for displaying ad unit information. */ -class AdUnitTableViewCell: UITableViewCell { +final class AdUnitTableViewCell: UITableViewCell, TableViewCellRegisterable { // Outlets from `AdUnitTableViewCell.xib` @IBOutlet weak var name: UILabel! @IBOutlet weak var adUnitId: UILabel! @@ -25,8 +25,3 @@ class AdUnitTableViewCell: UITableViewCell { adUnitId.text = adUnit.id } } - -extension AdUnitTableViewCell: TableViewCellRegisterable { - // MARK: - TableViewCellRegisterable - static private(set) var reuseId: String = "AdUnitTableViewCell" -} diff --git a/Canary/Canary/Cells/AdUnitTableViewHeader.swift b/Canary/Canary/Cells/AdUnitTableViewHeader.swift index 5c563421e..f40a3a84e 100644 --- a/Canary/Canary/Cells/AdUnitTableViewHeader.swift +++ b/Canary/Canary/Cells/AdUnitTableViewHeader.swift @@ -1,7 +1,7 @@ // // AdUnitTableViewHeader.swift // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/Canary/Canary/Cells/BasicMenuTableViewCell.swift b/Canary/Canary/Cells/BasicMenuTableViewCell.swift index 8fa88f0c2..afe1fbf14 100644 --- a/Canary/Canary/Cells/BasicMenuTableViewCell.swift +++ b/Canary/Canary/Cells/BasicMenuTableViewCell.swift @@ -1,13 +1,13 @@ // // BasicMenuTableViewCell.swift // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // import UIKit -class BasicMenuTableViewCell: UITableViewCell { +final class BasicMenuTableViewCell: UITableViewCell, TableViewCellRegisterable { @IBOutlet weak var title: UILabel! } diff --git a/Canary/Canary/Cells/CollapsibleAdapterInfoTableViewCell.swift b/Canary/Canary/Cells/CollapsibleAdapterInfoTableViewCell.swift new file mode 100644 index 000000000..17b982180 --- /dev/null +++ b/Canary/Canary/Cells/CollapsibleAdapterInfoTableViewCell.swift @@ -0,0 +1,90 @@ +// +// CollapsibleAdapterInfoTableViewCell.swift +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +import MoPub +import UIKit + +final class CollapsibleAdapterInfoTableViewCell: UITableViewCell, TableViewCellRegisterable { + // MARK: - Constants + struct Constants { + // The padding and spacing constant for the stack view elements + static let padding: CGFloat = 4 + } + + // MARK: - IBOutlets + @IBOutlet weak var titleLabel: UILabel! + @IBOutlet weak var stackView: UIStackView! + @IBOutlet weak var adapterVersionLabel: UILabel! + @IBOutlet weak var networkSdkVersionLabel: UILabel! + @IBOutlet weak var hasBiddingTokenLabel: UILabel! + + @IBOutlet weak var stackViewTopConstraint: NSLayoutConstraint! + var stackViewHeightConstraint: NSLayoutConstraint? = nil + + override func setNeedsUpdateConstraints() { + super.setNeedsUpdateConstraints() + + // Information stack view should be collapsed + if let stackViewHeightConstraint = stackViewHeightConstraint { + stackViewHeightConstraint.isActive = true + stackViewTopConstraint.constant = 0 + } + // Information stack view should be expanded + else { + stackViewTopConstraint.constant = Constants.padding + } + } + + // MARK: - Properties + + /** + Queries the collapsed state of the cell + */ + var isCollapsed: Bool { + get { + return (stackViewHeightConstraint != nil) + } + set { + // Collapse the information stack view + if newValue && stackViewHeightConstraint == nil { + accessoryType = .disclosureIndicator + stackView.spacing = 0 + stackViewHeightConstraint = stackView.heightAnchor.constraint(equalToConstant: 0) + setNeedsUpdateConstraints() + } + // Expand the information stack view + else if !newValue && stackViewHeightConstraint != nil { + accessoryType = .none + stackView.spacing = Constants.padding + stackViewHeightConstraint?.isActive = false + stackViewHeightConstraint = nil + setNeedsUpdateConstraints() + } + } + } + + // MARK: - Cell Updating + + func update(adapterName: String, info: MPAdapterConfiguration, isCollapsed collapsed: Bool = true) { + titleLabel.text = adapterName.replacingOccurrences(of: "AdapterConfiguration", with: "") + adapterVersionLabel.text = info.adapterVersion + networkSdkVersionLabel.text = info.networkSdkVersion + hasBiddingTokenLabel.text = info.biddingToken != nil ? "true" : "false" + isCollapsed = collapsed + setNeedsLayout() + } + + func update(title: String) { + titleLabel.text = title + adapterVersionLabel.text = nil + networkSdkVersionLabel.text = nil + hasBiddingTokenLabel.text = nil + isCollapsed = true + setNeedsLayout() + } +} diff --git a/Canary/Canary/Cells/CollapsibleAdapterInfoTableViewCell.xib b/Canary/Canary/Cells/CollapsibleAdapterInfoTableViewCell.xib new file mode 100644 index 000000000..35d165ee2 --- /dev/null +++ b/Canary/Canary/Cells/CollapsibleAdapterInfoTableViewCell.xib @@ -0,0 +1,109 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Canary/Canary/Cells/StatusTableViewCell.swift b/Canary/Canary/Cells/StatusTableViewCell.swift index 9d94bdee9..497379182 100644 --- a/Canary/Canary/Cells/StatusTableViewCell.swift +++ b/Canary/Canary/Cells/StatusTableViewCell.swift @@ -1,14 +1,14 @@ // // StatusTableViewCell.swift // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // import UIKit -class StatusTableViewCell: UITableViewCell { +final class StatusTableViewCell: UITableViewCell, TableViewCellRegisterable { // MARK: - IBOutlets @IBOutlet weak var stackView: UIStackView! @IBOutlet weak var nameLabel: UILabel! @@ -29,16 +29,16 @@ class StatusTableViewCell: UITableViewCell { messageLabel.text = error // Update text highlighted state - nameLabel.textColor = isHighlighted ? .black : .lightGray - + if #available(iOS 13.0, *) { + nameLabel.textColor = isHighlighted ? .label : .secondaryLabel + } else { + nameLabel.textColor = isHighlighted ? .black : .lightGray + } + accessoryType = isHighlighted ? .checkmark : .none + // Update the visible state of the message label messageLabel.isHidden = (error == nil) setNeedsLayout() } } - -extension StatusTableViewCell: TableViewCellRegisterable { - // MARK: - TableViewCellRegisterable - static private(set) var reuseId: String = "StatusTableViewCell" -} diff --git a/Canary/Canary/Cells/TextAndToggleTableViewCell.swift b/Canary/Canary/Cells/TextAndToggleTableViewCell.swift new file mode 100644 index 000000000..1be7ce187 --- /dev/null +++ b/Canary/Canary/Cells/TextAndToggleTableViewCell.swift @@ -0,0 +1,42 @@ +// +// TextAndToggleTableViewCell.swift +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +import UIKit + +final class TextAndToggleTableViewCell: UITableViewCell, TableViewCellRegisterable { + + /** + The handler to call after the toggle is updated. The `Bool` argument represents if `isOn` state of the toggle. + */ + typealias ToggleHandler = (Bool) -> Void + + // MARK: - Properties + + @IBOutlet private weak var titleLabel: UILabel! + @IBOutlet private weak var toggle: UISwitch! + private var toggleHandler: ToggleHandler? + + // MARK: - Functions + + @IBAction private func toggleValueDidChange(_ sender: UISwitch) { + guard let toggleHandler = toggleHandler else { + assertionFailure("\(#function) `toggleHandler` is nil") + return + } + toggleHandler(sender.isOn) + } + + /** + Configure this cell after dequeuing. + */ + func configure(title: String, toggleIsOn: Bool, toggleHandler: @escaping ToggleHandler) { + titleLabel.text = title + toggle.isOn = toggleIsOn + self.toggleHandler = toggleHandler + } +} diff --git a/Canary/Canary/Cells/TextAndToggleTableViewCell.xib b/Canary/Canary/Cells/TextAndToggleTableViewCell.xib new file mode 100644 index 000000000..5c8d182db --- /dev/null +++ b/Canary/Canary/Cells/TextAndToggleTableViewCell.xib @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Canary/Canary/Cells/TextEntryTableViewCell.swift b/Canary/Canary/Cells/TextEntryTableViewCell.swift index 78b1bb650..0698fd90d 100644 --- a/Canary/Canary/Cells/TextEntryTableViewCell.swift +++ b/Canary/Canary/Cells/TextEntryTableViewCell.swift @@ -1,14 +1,14 @@ // // TextEntryTableViewCell.swift // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // import UIKit -class TextEntryTableViewCell: UITableViewCell { +final class TextEntryTableViewCell: UITableViewCell, TableViewCellRegisterable { // MARK: - IBOutlets @IBOutlet weak var titleLabel: UILabel! @IBOutlet weak var textField: UITextField! @@ -31,8 +31,3 @@ extension TextEntryTableViewCell: UITextFieldDelegate { onTextDidChange?(textField.text) } } - -extension TextEntryTableViewCell: TableViewCellRegisterable { - // MARK: - TableViewCellRegisterable - static private(set) var reuseId: String = "TextEntryTableViewCell" -} diff --git a/Canary/Canary/Cells/TweetCollectionViewCell.swift b/Canary/Canary/Cells/TweetCollectionViewCell.swift index 2ad073962..fa0c6df5f 100644 --- a/Canary/Canary/Cells/TweetCollectionViewCell.swift +++ b/Canary/Canary/Cells/TweetCollectionViewCell.swift @@ -1,7 +1,7 @@ // // TweetCollectionViewCell.swift // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -13,6 +13,7 @@ class TweetCollectionViewCell: UICollectionViewCell { @IBOutlet weak var iconImageView: UIImageView! @IBOutlet weak var titleLabel: UILabel! @IBOutlet weak var contentLabel: UILabel! + @IBOutlet weak var stackView: UIStackView! // Constraints private var widthConstraint: NSLayoutConstraint! @@ -28,6 +29,11 @@ class TweetCollectionViewCell: UICollectionViewCell { // Initially set the width constraint to be 300px. contentView.translatesAutoresizingMaskIntoConstraints = false widthConstraint = contentView.widthAnchor.constraint(equalToConstant: 300) + + // Set up background color for Dark Mode + if #available(iOS 13.0, *) { + backgroundColor = .systemBackground + } } // MARK: - Cell Registration @@ -69,6 +75,18 @@ class TweetCollectionViewCell: UICollectionViewCell { set { widthConstraint.constant = newValue widthConstraint.isActive = true + updateConstraintsIfNeeded() } } + + /** + Calculates the estimated size of the cell. + */ + var cellSize: CGSize { + let contentSize: CGSize = stackView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize) + let margin: CGFloat = iconImageView.frame.origin.y + let height: CGFloat = max(contentSize.height, iconImageView.bounds.size.height).rounded(.up) + 2 * margin + + return CGSize(width: widthConstraint.constant, height: height) + } } diff --git a/Canary/Canary/Cells/TweetCollectionViewCell.xib b/Canary/Canary/Cells/TweetCollectionViewCell.xib index eb6c7fa86..d756bda0a 100644 --- a/Canary/Canary/Cells/TweetCollectionViewCell.xib +++ b/Canary/Canary/Cells/TweetCollectionViewCell.xib @@ -1,12 +1,11 @@ - + - - + @@ -28,21 +27,27 @@ - + - + + + + + + @@ -53,6 +58,7 @@ + @@ -60,6 +66,7 @@ + diff --git a/Canary/Canary/Configuration/Default.xcconfig b/Canary/Canary/Configuration/Default.xcconfig new file mode 100644 index 000000000..4652caeb7 --- /dev/null +++ b/Canary/Canary/Configuration/Default.xcconfig @@ -0,0 +1,18 @@ +// +// Default.xcconfig +// Canary +// +// Created by Kelly Dun on 3/5/19. +// Copyright © 2019 MoPub. All rights reserved. +// + +// Configuration settings file format documentation can be found at: +// https://help.apple.com/xcode/#/dev745c5c974 + +// The default `CFBundleName` is `$(PRODUCT_NAME)`, and the original `PRODUCT_NAME` is `Canary`. +// Later on, `CFBundleName` should be changed to `MoPub` instead of `Canary`, but we cannot use +// `MoPub` for `PRODUCT_NAME` since this introduces a namespace collision with the MoPub SDK module. +// To solve this namespace collision issue, `PRODUCT_NAME` stays the same as `Canary`, but +// `BUNDLE_NAME` is created and assigned to `CFBundleName` as `$(BUNDLE_NAME)` in the plist files. +BUNDLE_NAME = MoPub +PRODUCT_NAME = Canary diff --git a/Canary/Canary/Info.plist b/Canary/Canary/Configuration/Info.plist similarity index 54% rename from Canary/Canary/Info.plist rename to Canary/Canary/Configuration/Info.plist index ef008b71a..efc68681a 100644 --- a/Canary/Canary/Info.plist +++ b/Canary/Canary/Configuration/Info.plist @@ -11,11 +11,11 @@ CFBundleInfoDictionaryVersion 6.0 CFBundleName - $(PRODUCT_NAME) + $(BUNDLE_NAME) CFBundlePackageType APPL CFBundleShortVersionString - 5.4.0 + 5.9.0 CFBundleURLTypes @@ -28,7 +28,11 @@ CFBundleVersion - 5.4.0 + 5.9.0 + GADApplicationIdentifier + ca-app-pub-3940256099942544~1458002511 + ITSAppUsesNonExemptEncryption + LSRequiresIPhoneOS NSAppTransportSecurity @@ -36,6 +40,43 @@ NSAllowsArbitraryLoads + NSBluetoothPeripheralUsageDescription + Bluetooth access requested on behalf of Verizon Ads Network. + NSCalendarsUsageDescription + Calendar access requested on behalf of Verizon Ads Network. + NSCameraUsageDescription + The MoPub Canary app uses the camera for the ease-of-use feature of reading MoPub ad QR codes. + NSPhotoLibraryUsageDescription + Photo access requested on behalf of Verizon Ads Network. + NSUserActivityTypes + + com.mopub.canary.openAdView + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + UISceneStoryboardFile + Main + + + UISceneConfigurationName + Open Ad View + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + + + + UILaunchStoryboardName LaunchScreen UIMainStoryboardFile @@ -44,6 +85,8 @@ armv7 + UIStatusBarHidden + UIStatusBarTintParameters UINavigationBar diff --git a/Canary/Canary/ContainerViewController.swift b/Canary/Canary/ContainerViewController.swift index 558ec80f5..7343554f6 100644 --- a/Canary/Canary/ContainerViewController.swift +++ b/Canary/Canary/ContainerViewController.swift @@ -1,7 +1,7 @@ // // ContainerViewController.swift // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -9,13 +9,32 @@ import UIKit class ContainerViewController: UIViewController { + // Constants + struct Constants { + static let menuAnimationDuration: TimeInterval = 0.25 //seconds + } + // MARK: - IBOutlets - @IBOutlet weak var scrollView: UIScrollView! + @IBOutlet weak var menuContainerLeadingEdgeConstraint: NSLayoutConstraint! @IBOutlet weak var menuContainerWidthConstraint: NSLayoutConstraint! - @IBOutlet weak var menuDismissButton: UIButton! + + // MARK: - Menu Gesture Recognizers + + private var menuCloseGestureRecognizer: UISwipeGestureRecognizer! + private var menuCloseTapGestureRecognizer: UITapGestureRecognizer! + private var menuOpenGestureRecognizer: UISwipeGestureRecognizer! // MARK: - Properties + /** + Current collection of override traits for mainTabBarController. + */ + var forcedTraitCollection: UITraitCollection? = nil { + didSet { + updateForcedTraitCollection() + } + } + /** Main TabBar Controller of the app. */ @@ -26,6 +45,27 @@ class ContainerViewController: UIViewController { */ private(set) var menuViewController: MenuViewController? = nil + // MARK: - Forced Traits + + func setForcedTraits(for size: CGSize) { + let device = traitCollection.userInterfaceIdiom + let isPortrait: Bool = view.bounds.size.width < view.bounds.size.height + + switch (device, isPortrait) { + case (.pad, true): forcedTraitCollection = UITraitCollection(horizontalSizeClass: .compact) + default: forcedTraitCollection = nil + } + } + + /** + Updates the Main Tab Bar controller with the new trait overrides. + */ + func updateForcedTraitCollection() { + if let vc = mainTabBarController { + setOverrideTraitCollection(forcedTraitCollection, forChild: vc) + } + } + // MARK: - Segues override func prepare(for segue: UIStoryboardSegue, sender: Any?) { @@ -47,81 +87,110 @@ class ContainerViewController: UIViewController { // MARK: - View Life Cycle override func viewDidLoad() { - if #available(iOS 11.0, *) { - // Do not adjust the content insets of the scroll view to accommodate - // the safe area since we want the container scroll view to go edge - // to edge. - scrollView.contentInsetAdjustmentBehavior = .never - } + super.viewDidLoad() - // Initially close menu programmatically. This needs to be done on the main thread initially in order to work. - DispatchQueue.main.async() { - self.closeMenu(animated: false) - } + // Setup trait overrides + setForcedTraits(for: view.bounds.size) + + // Initialize the gesture recognizers and attach them to the view. + menuCloseGestureRecognizer = UISwipeGestureRecognizer(target: self, action: #selector(swipeMenuClose(_:))) + menuCloseGestureRecognizer.direction = .left + view.addGestureRecognizer(menuCloseGestureRecognizer) + + menuCloseTapGestureRecognizer = UITapGestureRecognizer (target: self, action: #selector(tapMenuClose(_:))) + menuCloseTapGestureRecognizer.isEnabled = false + menuCloseTapGestureRecognizer.delegate = self + view.addGestureRecognizer(menuCloseTapGestureRecognizer) + + menuOpenGestureRecognizer = UISwipeGestureRecognizer(target: self, action: #selector(swipeMenuOpen(_:))) + menuOpenGestureRecognizer.direction = .right + view.addGestureRecognizer(menuOpenGestureRecognizer) } override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { super.viewWillTransition(to: size, with: coordinator) - - coordinator.animate(alongsideTransition: { (context) -> Void in - self.closeMenu(animated: true) + coordinator.animate(alongsideTransition: { _ in + self.setForcedTraits(for: size) }, completion: nil) } // MARK: - Menu - func closeMenu(animated: Bool = true) { - // Use scrollview content offset-x to slide the menu. - scrollView.setContentOffset(CGPoint(x: menuContainerWidthConstraint.constant, y: 0), animated: animated) - mainTabBarController?.view.isUserInteractionEnabled = true - menuDismissButton.isUserInteractionEnabled = false + /** + Closes the menu if it open. + */ + func closeMenu() { + swipeMenuClose(menuCloseGestureRecognizer) } - var isMenuOpen: Bool { - return scrollView.contentOffset.x < menuContainerWidthConstraint.constant + @objc func swipeMenuClose(_ sender: UISwipeGestureRecognizer) { + // Do nothing if the menu is not fully open since it may either + // be closed, or in the process of being closed. + guard menuContainerLeadingEdgeConstraint.constant == 0 else { + return + } + + // Disable the tap outside of menu to close gesture recognizer. + menuCloseTapGestureRecognizer.isEnabled = false + + // Close the menu by setting the leading edge constraint to the negative width, + // which will put it offscreen. + UIView.animate(withDuration: Constants.menuAnimationDuration, animations: { + self.menuContainerLeadingEdgeConstraint.constant = -self.menuContainerWidthConstraint.constant + self.view.layoutIfNeeded() + }) { _ in + // Re-enable user interaction for the main content container. + self.mainTabBarController?.view.isUserInteractionEnabled = true + } } - // Open is the natural state of the menu because of how the storyboard is setup. - func openMenu() { - scrollView.setContentOffset(CGPoint(x: 0, y: 0), animated: true) - mainTabBarController?.view.isUserInteractionEnabled = false - menuDismissButton.isUserInteractionEnabled = true + @objc func swipeMenuOpen(_ sender: UISwipeGestureRecognizer) { + // Do nothing if the menu is already open or in the process of opening. + guard (menuContainerWidthConstraint.constant + menuContainerLeadingEdgeConstraint.constant) == 0 else { + return + } + + // Disable user interaction for the main content container. + self.mainTabBarController?.view.isUserInteractionEnabled = false + + // Open the menu by setting the leading edge constraint back to zero. + UIView.animate(withDuration: Constants.menuAnimationDuration, animations: { + self.menuContainerLeadingEdgeConstraint.constant = 0 + self.view.layoutIfNeeded() + }) { _ in + // Enable the tap outside of menu to close gesture recognizer. + self.menuCloseTapGestureRecognizer.isEnabled = true + } } - @IBAction func onDismissMenu(_ sender: Any) { - if isMenuOpen { - closeMenu(animated: true) - } + @objc func tapMenuClose(_ sender: UITapGestureRecognizer) { + // Allow any previously queued animations to finish before attempting to close the menu + view.layoutIfNeeded() + + // Close the menu + closeMenu() } } -extension ContainerViewController : UIScrollViewDelegate { - // http://www.4byte.cn/question/49110/uiscrollview-change-contentoffset-when-change-frame.html - // When paging is enabled on a Scroll View, - // a private method _adjustContentOffsetIfNecessary gets called, - // presumably when present whatever controller is called. - // The idea is to disable paging. - // But we rely on paging to snap the slideout menu in place - // (if you're relying on the built-in pan gesture). - // So the approach is to keep paging disabled. - // But enable it at the last minute during scrollViewWillBeginDragging. - // And then turn it off once the scroll view stops moving. - // - // Approaches that don't work: - // 1. automaticallyAdjustsScrollViewInsets -- don't bother - // 2. overriding _adjustContentOffsetIfNecessary -- messing with private methods is a bad idea - // 3. disable paging altogether. works, but at the loss of a feature - // 4. nest the scrollview inside UIView, so UIKit doesn't mess with it. may have worked before, - // but not anymore. - func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { - scrollView.isPagingEnabled = true - mainTabBarController?.view.isUserInteractionEnabled = false - menuDismissButton.isUserInteractionEnabled = true - } +extension ContainerViewController: UIGestureRecognizerDelegate { + // MARK: - UIGestureRecognizerDelegate - func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { - scrollView.isPagingEnabled = false - mainTabBarController?.view.isUserInteractionEnabled = !isMenuOpen - menuDismissButton.isUserInteractionEnabled = isMenuOpen + func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { + // Only handle the menu tap to close gesture + guard gestureRecognizer == menuCloseTapGestureRecognizer else { + return true + } + + // If the menu is not fully open, disregard the tap. + guard menuContainerLeadingEdgeConstraint.constant == 0 else { + return false + } + + // If the tap intersects the open menu, disregard the tap. + guard gestureRecognizer.location(in: view).x > menuContainerWidthConstraint.constant else { + return false + } + + return true } } diff --git a/Canary/Canary/Extensions/Array+Sort.swift b/Canary/Canary/Extensions/Array+Sort.swift new file mode 100644 index 000000000..1c3f0dcb3 --- /dev/null +++ b/Canary/Canary/Extensions/Array+Sort.swift @@ -0,0 +1,55 @@ +// +// Array+Sort.swift +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +import Foundation + +/** + Conforming objects can provided a @c String as a key for various purpose, such as making a key + value pair for dictionary. + */ +protocol StringKeyable { + var key: String { get } +} + +extension Sequence where Element == StringKeyable { + /** + Given each `Element` as `StringKeyable` can provided a `key`, return a sorted array of `Element` + that is sorted in the same order as the provided `keys` array. + + * Note 1: If the source `Sequence` has extra elements that are not in the reference `keys` array, + the extra elements are appended to the end with undefined order. + + * Note 2: The returned array eliminates duplicate elements in the source `Sequence`, if there is + any duplicate. + + - Parameter keys: An array of keys that represents the expected sort order + - Returns: An sorted array of all original elements + */ + func sorted(inTheSameOrderAs keys:[String]) -> [Element] { + var dictionary = [String: Element]() + forEach { + guard let element = $0 as? Element else { + assertionFailure("\(#function) element [\($0)] is not in the expected type \(Element.self)") + return + } + dictionary[$0.key] = element + } + + var result = [Element]() + keys.forEach { + guard let value = dictionary[$0] else { + return + } + result.append(value) + dictionary.removeValue(forKey: $0) + } + result.append(contentsOf: dictionary.values) // for remaining values not in `orderedKeys` + + return result + } +} diff --git a/Canary/Canary/Extensions/CGFloat+Random.swift b/Canary/Canary/Extensions/CGFloat+Random.swift index 33752cc7f..e9e35c904 100644 --- a/Canary/Canary/Extensions/CGFloat+Random.swift +++ b/Canary/Canary/Extensions/CGFloat+Random.swift @@ -1,7 +1,7 @@ // // CGFloat+Random.swift // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/Canary/Canary/Extensions/MPBool+Description.swift b/Canary/Canary/Extensions/MPBool+Description.swift index d9e435454..ba0159515 100644 --- a/Canary/Canary/Extensions/MPBool+Description.swift +++ b/Canary/Canary/Extensions/MPBool+Description.swift @@ -1,7 +1,7 @@ // // MPBool+Description.swift // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -10,11 +10,12 @@ import Foundation import MoPub public extension MPBool { - public var description: String { + var description: String { switch self { case .unknown: return "unknown" case .yes: return "true" case .no: return "false" + @unknown default: fatalError("\(#function) unexpected enum case") } } } diff --git a/Canary/Canary/Extensions/MPConsentStatus+Description.swift b/Canary/Canary/Extensions/MPConsentStatus+Description.swift index 837938064..4fab7fe3b 100644 --- a/Canary/Canary/Extensions/MPConsentStatus+Description.swift +++ b/Canary/Canary/Extensions/MPConsentStatus+Description.swift @@ -1,7 +1,7 @@ // // MPConsentStatus+Description.swift // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -13,13 +13,14 @@ public extension MPConsentStatus { /** Human readable description of the status. */ - public var description: String { + var description: String { switch self { case .consented: return "Consented" case .denied: return "Denied" case .doNotTrack: return "Do not track" case .potentialWhitelist: return "Potentially whitelisted" case .unknown: return "Unknown" + @unknown default: fatalError("\(#function) unexpected enum case") } } } diff --git a/Canary/Canary/Extensions/MPNativeAdRendererConfiguration+Utilities.swift b/Canary/Canary/Extensions/MPNativeAdRendererConfiguration+Utilities.swift new file mode 100644 index 000000000..6ae552a4b --- /dev/null +++ b/Canary/Canary/Extensions/MPNativeAdRendererConfiguration+Utilities.swift @@ -0,0 +1,28 @@ +// +// MPNativeAdRendererConfiguration+Utilities.swift +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +import Foundation +import MoPub + +extension MPNativeAdRendererConfiguration { + var rendererClassName: String { + // If `rendererClass` is not unwrapped, the class name is wrapped by "Optional(...)" undesirably + guard let rendererClass = rendererClass else { + assertionFailure("\(#function) rendererClass is nil") + return "" + } + return String(describing: rendererClass.self) + } +} + +// Conform to `StringKeyable` to make `MPNativeAdRendererConfiguration` sortable. +extension MPNativeAdRendererConfiguration: StringKeyable { + var key: String { + return rendererClassName + } +} diff --git a/Canary/Canary/Extensions/NSObject+Utility.swift b/Canary/Canary/Extensions/NSObject+Utility.swift new file mode 100644 index 000000000..a01e51c37 --- /dev/null +++ b/Canary/Canary/Extensions/NSObject+Utility.swift @@ -0,0 +1,18 @@ +// +// NSObject+Utility.swift +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +import Foundation + +extension NSObject { + /** + A string that represents the name of the class. + */ + static var className: String { + return String(describing: self) + } +} diff --git a/Canary/Canary/Extensions/Notification+Token.swift b/Canary/Canary/Extensions/Notification+Token.swift new file mode 100644 index 000000000..54a90a72d --- /dev/null +++ b/Canary/Canary/Extensions/Notification+Token.swift @@ -0,0 +1,42 @@ +// +// Notification+Token.swift +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +import Foundation + +extension Notification { + /** + This `Token` class is a wrapper of `rawToken: NSObjectProtocol`, which is the returned value of + `NotificationCenter.addObserver(forName:object:queue:using) -> NSObjectProtocol`. When this + `Token` is deallocated, it automatically removes the original observer from notification center. + To take advantage of the automatic observer removal feature of this `Token`, keep a strong + reference to this `Token` in an instance variable of the observer (in a collection or not), + so that this `Token` is deallocated together with the observer, and consequently triggers + `NotificationCenter.removeObserver`. + */ + final class Token { + /** + An opaque object to act as the observer, returned by `NotificationCenter.addObserver(forName + name:object:queue:using)`. + */ + let rawToken: NSObjectProtocol + + /** + The targeted notification center. + */ + let notificationCenter: NotificationCenter + + init(rawToken: NSObjectProtocol, notificationCenter: NotificationCenter) { + self.rawToken = rawToken + self.notificationCenter = notificationCenter + } + + deinit { + notificationCenter.removeObserver(rawToken) + } + } +} diff --git a/Canary/Canary/Extensions/UIAlertController+Picker.swift b/Canary/Canary/Extensions/UIAlertController+Picker.swift new file mode 100644 index 000000000..69f5eaf81 --- /dev/null +++ b/Canary/Canary/Extensions/UIAlertController+Picker.swift @@ -0,0 +1,53 @@ +// +// UIAlertController+Picker.swift +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +import UIKit + +extension UIAlertController { + // Creates a `UIAlertController` in the action sheet style with a `UIPickerView` attached + public convenience init(title: String?, message: String?, pickerViewDelegate: UIPickerViewDelegate?, pickerViewDataSource: UIPickerViewDataSource?, sender: Any?) { + self.init(title: title, message: message, preferredStyle: .actionSheet) + + isModalInPopover = true + + // Make picker view + let pickerView: UIPickerView = UIPickerView(frame: CGRect(x: 0, y: 0, width: 320, height: 200)) + pickerView.dataSource = pickerViewDataSource + pickerView.delegate = pickerViewDelegate + + // Configure popover appearance. + if let popoverController = popoverPresentationController, + let showButton: UIButton = sender as? UIButton { + popoverController.sourceView = showButton + popoverController.sourceRect = showButton.bounds + popoverController.permittedArrowDirections = [.up, .down] + } + + // We need to add the pickerView to contentViewController of the + // UIAlertController so that it occupies the content section instead of + // being a subview (which will obscure the action buttons). + let container = UIViewController() + container.preferredContentSize = pickerView.bounds.size + container.view.addSubview(pickerView) + setValue(container, forKey: "contentViewController") + + // We need to constrain the edges of the pickerView to the UIAlertController + // view so that the pickerView is aligned properly. + let constraints: [NSLayoutConstraint] = [ + pickerView.leftAnchor.constraint(equalTo: container.view.leftAnchor), + pickerView.rightAnchor.constraint(equalTo: container.view.rightAnchor), + pickerView.topAnchor.constraint(equalTo: container.view.topAnchor), + pickerView.bottomAnchor.constraint(equalTo: container.view.bottomAnchor), + ] + NSLayoutConstraint.activate(constraints) + + // Select the first row by default. + pickerView.selectRow(0, inComponent: 0, animated: false) + pickerViewDelegate?.pickerView?(pickerView, didSelectRow: 0, inComponent: 0) + } +} diff --git a/Canary/Canary/Extensions/UIColor+Random.swift b/Canary/Canary/Extensions/UIColor+Random.swift index 98aa53008..2e2e4396d 100644 --- a/Canary/Canary/Extensions/UIColor+Random.swift +++ b/Canary/Canary/Extensions/UIColor+Random.swift @@ -1,7 +1,7 @@ // // UIColor+Random.swift // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/Canary/Canary/Extensions/UITableView+Utility.swift b/Canary/Canary/Extensions/UITableView+Utility.swift new file mode 100644 index 000000000..e3b5de3e2 --- /dev/null +++ b/Canary/Canary/Extensions/UITableView+Utility.swift @@ -0,0 +1,31 @@ +// +// UITableView+Utility.swift +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +import UIKit + +extension UITableView { + /** + Dequeue a nib cell from the table view, and register the cell if the first dequeue attempt fails. + Note: This function only works if the nib file has the same name as the cell. + - Parameter cellType: The type of expected cell. + - Returns: A reusable instance of `cellType`. + */ + func dequeueCellFromNib(cellType: T.Type) -> T { + let cellName = T.className + + if let cell = dequeueReusableCell(withIdentifier: cellName) as? T { + return cell + } else { + register(UINib(nibName: cellName, bundle: nil), forCellReuseIdentifier: cellName) + guard let cell = dequeueReusableCell(withIdentifier: cellName) as? T else { + fatalError("\(#function) failed to dequeue cell \(cellName) after cell registration") + } + return cell + } + } +} diff --git a/Canary/Canary/Extensions/UIView+Nib.swift b/Canary/Canary/Extensions/UIView+Nib.swift index 23eb0a0c2..248827aa3 100644 --- a/Canary/Canary/Extensions/UIView+Nib.swift +++ b/Canary/Canary/Extensions/UIView+Nib.swift @@ -1,7 +1,7 @@ // // UIView+Nib.swift // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -21,7 +21,7 @@ extension UIView { func sizeFitting(view: UIView) -> CGSize { var fittingSize: CGSize = .zero - if #available(iOS 11.0, *) { + if #available(iOS 11, *) { fittingSize = CGSize(width: view.bounds.width - (view.safeAreaInsets.left + view.safeAreaInsets.right), height: 0) } else { diff --git a/Canary/Canary/Extensions/UIViewController+Utility.swift b/Canary/Canary/Extensions/UIViewController+Utility.swift new file mode 100644 index 000000000..ab19fd7b3 --- /dev/null +++ b/Canary/Canary/Extensions/UIViewController+Utility.swift @@ -0,0 +1,64 @@ +// +// UIViewController+Utility.swift +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +import UIKit + +// MARK: - UIScene Compatibility + +extension UIViewController { + /** + Scene container controller. + */ + var containerViewController: ContainerViewController? { + if #available(iOS 13.0, *) { + guard let sceneDelegate = view.window?.windowScene?.delegate as? SceneDelegate else { + fatalError() + } + switch sceneDelegate.mode { + case .adViewScene, .unknown: + print("`containerViewController` is only available for the main scene") + return nil + case .mainScene(let mainSceneState): + return mainSceneState.containerViewController + } + } else { + return AppDelegate.shared.containerViewController + } + } + + /** + Saved ads split view controller. + */ + var savedAdSplitViewController: UISplitViewController? { + if #available(iOS 13.0, *) { + guard let sceneDelegate = view.window?.windowScene?.delegate as? SceneDelegate else { + fatalError() + } + switch sceneDelegate.mode { + case .adViewScene, .unknown: + print("`savedAdSplitViewController` is only available for the main scene") + return nil + case .mainScene(let mainSceneState): + return mainSceneState.savedAdSplitViewController + } + } else { + return AppDelegate.shared.savedAdSplitViewController + } + } + + @available(iOS 13.0, *) + @objc func destroySceneSession() { + guard let session = view.window?.windowScene?.session else { + fatalError() + } + + UIApplication.shared.requestSceneSessionDestruction(session, options: nil) { error in + print("\(#function) error: \(error)") + } + } +} diff --git a/Canary/Canary/Extensions/UserDefaults+Subscript.swift b/Canary/Canary/Extensions/UserDefaults+Subscript.swift new file mode 100644 index 000000000..d4523b4f6 --- /dev/null +++ b/Canary/Canary/Extensions/UserDefaults+Subscript.swift @@ -0,0 +1,85 @@ +// +// UserDefaults+Subscript.swift +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +import Foundation + +// MARK: - Subscript support + +extension UserDefaults { + /** + A generic key that enables value type and default value specification. + */ + struct Key { + /** + This `String` representation of the key. + */ + let stringValue: String + + /** + If the value does not exist in `UserDefaults`, return this default value instead. + */ + let defaultValue: ValueType + + init(_ stringValue: String, defaultValue: ValueType) { + self.stringValue = stringValue + self.defaultValue = defaultValue + } + } + + subscript(key: Key) -> T { + get { + return value(forKey: key.stringValue) as? T ?? key.defaultValue + } + set { + set(newValue, forKey: key.stringValue) + } + } +} + +// MARK: - UserDefaults with subscript + +extension UserDefaults { + /** + The private `UserDefaults.Key` for accessing `shouldClearCachedNetworks`. + */ + private var shouldClearCachedNetworksKey: Key { + return Key("shouldClearCachedNetworks", defaultValue: false) + } + + /** + If `true`, call `MoPub.sharedInstance().clearCachedNetworks()` before initializing the MoPub SDK; + otherwise, do nothing. + */ + var shouldClearCachedNetworks: Bool { + get { + return self[shouldClearCachedNetworksKey] + } + set { + self[shouldClearCachedNetworksKey] = newValue + } + } + + /** + The private `UserDefaults.Key` for accessing `cachedAdUnitId`. + */ + private var cachedAdUnitIdKey: Key { + return Key("cachedAdUnitIdKey", defaultValue: "") + } + + /** + Cached ad unit ID used for MoPub SDK initialization + */ + var cachedAdUnitId: String { + get { + return self[cachedAdUnitIdKey] + } + set { + self[cachedAdUnitIdKey] = newValue + } + } +} diff --git a/Canary/Canary/Formats/AdDataSource.swift b/Canary/Canary/Formats/AdDataSource.swift index 132234dbd..0b8825d73 100644 --- a/Canary/Canary/Formats/AdDataSource.swift +++ b/Canary/Canary/Formats/AdDataSource.swift @@ -1,7 +1,7 @@ // // AdDataSource.swift // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -43,6 +43,7 @@ enum AdEvent { case clicked case willLeaveApp case shouldRewardUser + case didTrackImpression } /** @@ -69,7 +70,7 @@ protocol AdDataSourcePresentationDelegate: class { /** Protocol to specifying an ad's rendering on screen. */ -protocol AdDataSource { +protocol AdDataSource: class { /** Delegate used for presenting the data source's ad. This must be specified as `weak`. */ @@ -95,6 +96,11 @@ protocol AdDataSource { */ var events: [AdEvent] { get } + /** + Table of which events were triggered. + */ + var eventTriggered: [AdEvent: Bool] { get set } + /** Ad unit associated with the ad. */ @@ -105,6 +111,32 @@ protocol AdDataSource { */ var adContainerView: UIView? { get } + /** + Queries if the data source has an ad loaded. + */ + var isAdLoaded: Bool { get } + + /** + Queries if the data source currently requesting an ad. + */ + var isAdLoading: Bool { get } + + /** + Status event titles that correspond to the events found in the ad's delegate protocol. + */ + var title: [AdEvent: String] { get } + + /** + Optional status messages that correspond to the events found in the ad's delegate protocol. + These are reset as part of `clearStatus`. + */ + var messages: [AdEvent: String] { get set } + + /** + Optional ad size used for requesting inline ads. This should be `nil` for non-inline ads. + */ + var requestedAdSize: CGSize? { get set } + /** Retrieves the display status for the event. - Parameter event: Status event. @@ -116,9 +148,10 @@ protocol AdDataSource { Sets the status for the event to highlighted. If the status is already highlighted, nothing is done. - Parameter event: Status event. + - Parameter message: optional string containing status message. - Parameter complete: Completion closure. */ - func setStatus(for event: AdEvent, complete:(() -> Swift.Void)) + func setStatus(for event: AdEvent, message: String?, complete:(() -> Swift.Void)) /** Clears the highlighted state for all status events. @@ -126,3 +159,66 @@ protocol AdDataSource { */ func clearStatus(complete:(() -> Swift.Void)) } + +extension AdDataSource { + /** + The status events available for the ad. + */ + var events: [AdEvent] { + get { + return [.didTrackImpression] + } + } + + /** + The ad unit information sections available for the ad. + */ + var information: [AdInformation] { + get { + return [.id, .keywords, .userDataKeywords] + } + } + + /** + The actions available for the ad. + */ + var actions: [AdAction] { + get { + return [.load, .show] + } + } + + /** + Retrieves the display status for the event. + - Parameter event: Status event. + - Returns: A tuple containing the status display title, optional message, and highlighted state. + */ + func status(for event: AdEvent) -> (title: String, message: String?, isHighlighted: Bool) { + let message = messages[event] + let isHighlighted = (eventTriggered[event] ?? false) + return (title: title[event] ?? "", message: message, isHighlighted: isHighlighted) + } + + /** + Sets the status for the event to highlighted. If the status is already highlighted, + nothing is done. + - Parameter event: Status event. + - Parameter message: optional string containing status message. + - Parameter complete: Completion closure. + */ + func setStatus(for event: AdEvent, message: String? = nil, complete:(() -> Swift.Void)) { + eventTriggered[event] = true + messages[event] = message + complete() + } + + /** + Clears the highlighted state for all status events. + - Parameter complete: Completion closure. + */ + func clearStatus(complete:(() -> Swift.Void)) { + eventTriggered = [:] + messages = [:] + complete() + } +} diff --git a/Canary/Canary/Formats/AdFormat.swift b/Canary/Canary/Formats/AdFormat.swift index 31453757e..e7bdc2fa1 100644 --- a/Canary/Canary/Formats/AdFormat.swift +++ b/Canary/Canary/Formats/AdFormat.swift @@ -1,7 +1,7 @@ // // AdFormat.swift // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -11,7 +11,7 @@ import Foundation /** Provides a mapping of ad format to a view controller that can render it. */ -enum AdFormat: String { +enum AdFormat: String, CaseIterable { /** 320x50 banner */ @@ -21,16 +21,11 @@ enum AdFormat: String { Full screen interstitial */ case Interstitial = "Interstitial" - - /** - 728x90 leaderboard banner - */ - case Leaderboard = "Leaderboard" - + /** - 320x250 medium rectangle banner + Medium rectangle */ - case MRect = "MRect" + case MediumRectangle = "MediumRectangle" /** Native ad @@ -61,8 +56,7 @@ enum AdFormat: String { switch self { case .Banner: return "BannerAdViewController" case .Interstitial: return "InterstitialAdViewController" - case .Leaderboard: return "LeaderboardAdViewController" - case .MRect: return "MediumRectangleAdViewController" + case .MediumRectangle: return "MediumRectangleAdViewController" case .Native: return "NativeAdViewController" case .NativeCollectionPlacer: return "NativeAdCollectionViewController" case .NativeTablePlacer: return "NativeAdTableViewController" diff --git a/Canary/Canary/Formats/AdTableViewController.swift b/Canary/Canary/Formats/AdTableViewController.swift index f68dab60d..f763e549b 100644 --- a/Canary/Canary/Formats/AdTableViewController.swift +++ b/Canary/Canary/Formats/AdTableViewController.swift @@ -1,7 +1,7 @@ // // AdTableViewController.swift // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -92,6 +92,15 @@ class AdTableViewController: UIViewController, AdViewController { // Update the title title = dataSource.adUnit.name + + // Add split view controller collapse button if applicable + navigationItem.leftBarButtonItem = splitViewController?.displayModeButtonItem + navigationItem.leftItemsSupplementBackButton = true + + // Set the background color for Dark Mode + if #available(iOS 13.0, *) { + view.backgroundColor = .systemBackground + } } override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { @@ -174,13 +183,12 @@ extension AdTableViewController: UITableViewDataSource { Retrieves a table cell that displays the ad unit ID. */ func tableView(_ tableView: UITableView, adUnitIdCellForRowAt indexPath: IndexPath) -> UITableViewCell { - guard let cell: AdUnitTableViewCell = tableView.dequeueReusableCell(withIdentifier: AdUnitTableViewCell.reuseId, for: indexPath) as? AdUnitTableViewCell else { - return UITableViewCell() - } - - cell.name.text = "ID" - cell.adUnitId.text = dataSource.adUnit.id + let cell = tableView.dequeueCellFromNib(cellType: AdUnitTableViewCell.self) + cell.accessibilityIdentifier = dataSource.adUnit.id cell.accessoryType = .none + cell.adUnitId.text = dataSource.adUnit.id + cell.name.text = "ID" + return cell } @@ -188,14 +196,12 @@ extension AdTableViewController: UITableViewDataSource { Retrieves a table cell that displays the keywords of the ad unit. */ func tableView(_ tableView: UITableView, keywordsCellForRowAt indexPath: IndexPath) -> UITableViewCell { - guard let cell: TextEntryTableViewCell = tableView.dequeueReusableCell(withIdentifier: TextEntryTableViewCell.reuseId, for: indexPath) as? TextEntryTableViewCell else { - return UITableViewCell() - } - + let cell = tableView.dequeueCellFromNib(cellType: TextEntryTableViewCell.self) cell.refresh(title: "Keywords", text: dataSource.adUnit.keywords) { [weak self] (keywords: String?) in self?.dataSource.adUnit.keywords = keywords } + cell.accessibilityIdentifier = dataSource.adUnit.keywords return cell } @@ -203,14 +209,12 @@ extension AdTableViewController: UITableViewDataSource { Retrieves a table cell that displays the user data keywords of the ad unit. */ func tableView(_ tableView: UITableView, userDataKeywordsCellForRowAt indexPath: IndexPath) -> UITableViewCell { - guard let cell: TextEntryTableViewCell = tableView.dequeueReusableCell(withIdentifier: TextEntryTableViewCell.reuseId, for: indexPath) as? TextEntryTableViewCell else { - return UITableViewCell() - } - - cell.refresh(title: "User Data Keywords", text: dataSource.adUnit.keywords) { [weak self] (piiKeywords: String?) in + let cell = tableView.dequeueCellFromNib(cellType: TextEntryTableViewCell.self) + cell.refresh(title: "User Data Keywords", text: dataSource.adUnit.userDataKeywords) { [weak self] (piiKeywords: String?) in self?.dataSource.adUnit.userDataKeywords = piiKeywords } + cell.accessibilityIdentifier = dataSource.adUnit.userDataKeywords return cell } @@ -218,14 +222,12 @@ extension AdTableViewController: UITableViewDataSource { Retrieves a table cell that displays the custom data for the ad unit. */ func tableView(_ tableView: UITableView, customDataCellForRowAt indexPath: IndexPath) -> UITableViewCell { - guard let cell: TextEntryTableViewCell = tableView.dequeueReusableCell(withIdentifier: TextEntryTableViewCell.reuseId, for: indexPath) as? TextEntryTableViewCell else { - return UITableViewCell() - } - + let cell = tableView.dequeueCellFromNib(cellType: TextEntryTableViewCell.self) cell.refresh(title: "Custom Data", text: dataSource.adUnit.customData) { [weak self] (customData: String?) in self?.dataSource.adUnit.customData = customData } + cell.accessibilityIdentifier = dataSource.adUnit.customData return cell } @@ -235,13 +237,14 @@ extension AdTableViewController: UITableViewDataSource { Retrieves the ad unit actions cell. */ func tableView(_ tableView: UITableView, actionCellForRowAt indexPath: IndexPath) -> UITableViewCell { - guard let cell: AdActionsTableViewCell = tableView.dequeueReusableCell(withIdentifier: AdActionsTableViewCell.reuseId, for: indexPath) as? AdActionsTableViewCell else { - return UITableViewCell() - } + let cell = tableView.dequeueCellFromNib(cellType: AdActionsTableViewCell.self) + cell.delegate = self + let isAdLoading = dataSource.isAdLoading let loadHandler = dataSource.actionHandlers[.load] let showHandler = dataSource.actionHandlers[.show] - cell.refresh(loadAdHandler: loadHandler, showAdHandler: showHandler) + let showEnabled = dataSource.isAdLoaded + cell.refresh(adSize: dataSource.requestedAdSize, isAdLoading: isAdLoading, loadAdHandler: loadHandler, showAdHandler: showHandler, showButtonEnabled: showEnabled) return cell } @@ -252,19 +255,26 @@ extension AdTableViewController: UITableViewDataSource { `dataSource`. */ func tableView(_ tableView: UITableView, eventStatusCellForRowAt indexPath: IndexPath) -> UITableViewCell { - guard let cell: StatusTableViewCell = tableView.dequeueReusableCell(withIdentifier: StatusTableViewCell.reuseId, for: indexPath) as? StatusTableViewCell else { - return UITableViewCell() - } + let cell = tableView.dequeueCellFromNib(cellType: StatusTableViewCell.self) // Update the state of the cell let event = dataSource.events[indexPath.row] let status = dataSource.status(for: event) cell.update(status: status.title, error: status.message, isHighlighted: status.isHighlighted) + cell.accessibilityIdentifier = status.title return cell } } +extension AdTableViewController: AdActionsTableViewCellDelegate { + // MARK: - AdActionsTableViewCellDelegate + + func requestedAdSizeUpdated(to size: CGSize) { + dataSource.requestedAdSize = size + } +} + extension AdTableViewController: UITableViewDelegate { // MARK: - UITableViewDelegate diff --git a/Canary/Canary/Formats/AdViewController.swift b/Canary/Canary/Formats/AdViewController.swift index b6530a6a3..a0bb1e830 100644 --- a/Canary/Canary/Formats/AdViewController.swift +++ b/Canary/Canary/Formats/AdViewController.swift @@ -1,7 +1,7 @@ // // AdViewController.swift // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/Canary/Canary/Formats/BannerAdDataSource.swift b/Canary/Canary/Formats/BannerAdDataSource.swift index 1eb3a01b2..21da66a64 100644 --- a/Canary/Canary/Formats/BannerAdDataSource.swift +++ b/Canary/Canary/Formats/BannerAdDataSource.swift @@ -1,7 +1,7 @@ // // BannerAdDataSource.swift // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -27,33 +27,40 @@ class BannerAdDataSource: NSObject, AdDataSource { /** Table of which events were triggered. */ - private var eventTriggered: [AdEvent: Bool] = [:] + var eventTriggered: [AdEvent: Bool] = [:] /** - Reason for load failure. + The maximum desired ad size to request on a load */ - private var loadFailureReason: String? = nil + private var maxDesiredAdSize: CGSize = kMPPresetMaxAdSizeMatchFrame /** Status event titles that correspond to the events found in `MPAdViewDelegate` */ - private lazy var title: [AdEvent: String] = { + lazy var title: [AdEvent: String] = { var titleStrings: [AdEvent: String] = [:] - titleStrings[.didLoad] = "adViewDidLoadAd(_:)" - titleStrings[.didFailToLoad] = "adViewDidFailToLoadAd(_:)" + titleStrings[.didLoad] = "adViewDidLoadAd(_:, adSize _:)" + titleStrings[.didFailToLoad] = "adView(_:, didFailToLoadAdWithError _:)" titleStrings[.willPresentModal] = "willPresentModalViewForAd(_:)" titleStrings[.didDismissModal] = "didDismissModalViewForAd(_:)" titleStrings[.clicked] = "willLeaveApplicationFromAd(_:)" + titleStrings[.didTrackImpression] = "mopubAd(_:, didTrackImpressionWith _:)" return titleStrings }() + /** + Optional status messages that correspond to the events found in the ad's delegate protocol. + These are reset as part of `clearStatus`. + */ + var messages: [AdEvent: String] = [:] + // MARK: - Initialization /** Initializes the Banner ad data source. - Parameter adUnit: Banner ad unit. - - Parameter size: Banner ad size. + - Parameter size: Maximum desired ad size. */ init(adUnit: AdUnit, bannerSize size: CGSize) { super.init() @@ -61,23 +68,18 @@ class BannerAdDataSource: NSObject, AdDataSource { // Instantiate the banner. adView = { - let view: MPAdView = MPAdView(adUnitId: adUnit.id, size: size) + let view: MPAdView = MPAdView(adUnitId: adUnit.id) view.delegate = self view.backgroundColor = .lightGray - + view.translatesAutoresizingMaskIntoConstraints = false return view }() + + maxDesiredAdSize = size } // MARK: - AdDataSource - /** - The ad unit information sections available for the ad. - */ - lazy var information: [AdInformation] = { - return [.id, .keywords, .userDataKeywords] - }() - /** The actions available for the ad. */ @@ -101,7 +103,7 @@ class BannerAdDataSource: NSObject, AdDataSource { The status events available for the ad. */ lazy var events: [AdEvent] = { - return [.didLoad, .didFailToLoad, .willPresentModal, .didDismissModal, .clicked] + return [.didLoad, .didFailToLoad, .willPresentModal, .didDismissModal, .clicked, .didTrackImpression] }() /** @@ -117,40 +119,40 @@ class BannerAdDataSource: NSObject, AdDataSource { } /** - Retrieves the display status for the event. - - Parameter event: Status event. - - Returns: A tuple containing the status display title, optional message, and highlighted state. + Queries if the data source has an ad loaded. */ - func status(for event: AdEvent) -> (title: String, message: String?, isHighlighted: Bool) { - let message = (event == .didFailToLoad ? loadFailureReason : nil) - let isHighlighted = (eventTriggered[event] ?? false) - return (title: title[event] ?? "", message: message, isHighlighted: isHighlighted) - } + private(set) var isAdLoaded: Bool = false /** - Sets the status for the event to highlighted. If the status is already highlighted, - nothing is done. - - Parameter event: Status event. - - Parameter complete: Completion closure. + Queries if the data source currently requesting an ad. */ - func setStatus(for event: AdEvent, complete:(() -> Swift.Void)) { - eventTriggered[event] = true - complete() - } + private(set) var isAdLoading: Bool = false /** - Clears the highlighted state for all status events. - - Parameter complete: Completion closure. - */ - func clearStatus(complete:(() -> Swift.Void)) { - loadFailureReason = nil - eventTriggered = [:] - complete() + Optional ad size used for requesting inline ads. This should be `nil` for non-inline ads. + */ + var requestedAdSize: CGSize? { + get { + return maxDesiredAdSize + } + set { + guard let newValue = newValue else { + maxDesiredAdSize = kMPPresetMaxAdSizeMatchFrame + return + } + + maxDesiredAdSize = newValue + } } // MARK: - Ad Loading private func loadAd() { + guard !isAdLoading else { + return + } + + isAdLoading = true clearStatus { [weak self] in self?.delegate?.adPresentationTableView.reloadData() } @@ -159,7 +161,7 @@ class BannerAdDataSource: NSObject, AdDataSource { // to load. adView.keywords = adUnit.keywords adView.userDataKeywords = adUnit.userDataKeywords - adView.loadAd() + adView.loadAd(withMaxAdSize: maxDesiredAdSize) } } @@ -170,22 +172,24 @@ extension BannerAdDataSource: MPAdViewDelegate { return delegate?.adPresentationViewController } - func adViewDidLoadAd(_ view: MPAdView!) { - setStatus(for: .didLoad) { [weak self] in - if let strongSelf = self { - strongSelf.loadFailureReason = nil - strongSelf.delegate?.adPresentationTableView.reloadData() - } + func adViewDidLoadAd(_ view: MPAdView!, adSize: CGSize) { + isAdLoading = false + isAdLoaded = true + + // Resize the MPAdView frame to match the creative height + view.frame.size.height = adSize.height + delegate?.adPresentationViewController?.view.setNeedsLayout() + + setStatus(for: .didLoad, message: "The ad size is \(adSize)") { [weak self] in + self?.delegate?.adPresentationTableView.reloadData() } } - func adViewDidFail(toLoadAd view: MPAdView!) { - setStatus(for: .didFailToLoad) { [weak self] in - if let strongSelf = self { - // The banner load failure doesn't give back an error reason; assume clear response - strongSelf.loadFailureReason = "No ad available" - strongSelf.delegate?.adPresentationTableView.reloadData() - } + func adView(_ view: MPAdView!, didFailToLoadAdWithError error: Error!) { + isAdLoading = false + isAdLoaded = false + setStatus(for: .didFailToLoad, message: "\(error!.localizedDescription)") { [weak self] in + self?.delegate?.adPresentationTableView.reloadData() } } @@ -206,4 +210,11 @@ extension BannerAdDataSource: MPAdViewDelegate { self?.delegate?.adPresentationTableView.reloadData() } } + + func mopubAd(_ ad: MPMoPubAd, didTrackImpressionWith impressionData: MPImpressionData?) { + let message = impressionData?.description ?? "No impression data" + setStatus(for: .didTrackImpression, message: message) { [weak self] in + self?.delegate?.adPresentationTableView.reloadData() + } + } } diff --git a/Canary/Canary/Formats/BannerAdViewController.swift b/Canary/Canary/Formats/BannerAdViewController.swift index a99ace220..a034f2e84 100644 --- a/Canary/Canary/Formats/BannerAdViewController.swift +++ b/Canary/Canary/Formats/BannerAdViewController.swift @@ -1,7 +1,7 @@ // // BannerAdViewController.swift // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -19,11 +19,18 @@ class BannerAdViewController: AdTableViewController { } set { // Create a new banner specific data source with the new ad unit. - let bannerDataSource: BannerAdDataSource = BannerAdDataSource(adUnit: newValue, bannerSize: MOPUB_BANNER_SIZE) + // We are requesting the maximum desired banner size. + let bannerDataSource: BannerAdDataSource = BannerAdDataSource(adUnit: newValue, bannerSize: kMPPresetMaxAdSize250Height) dataSource = bannerDataSource } } + /** + Inline ad height constraint. This should only be set by `viewDidLoad()` and `viewDidLayoutSubviews()` if a + table header view exists. + */ + private var heightConstraint: NSLayoutConstraint? = nil + // MARK: - View Lifecycle override func viewDidLoad() { @@ -37,6 +44,43 @@ class BannerAdViewController: AdTableViewController { // Invoke the super class to finish loading the view. super.viewDidLoad() + + // Fix the banner height so that Auto Layout will correctly resize the table header. + if let header = tableView.tableHeaderView { + // Initialize the height constraint to the minimum supported banner height + heightConstraint = header.heightAnchor.constraint(equalToConstant: kMPPresetMaxAdSize50Height.height) + + let constraints = [ + header.widthAnchor.constraint(equalTo: tableView.widthAnchor), + header.leadingAnchor.constraint(equalTo: tableView.leadingAnchor), + header.trailingAnchor.constraint(equalTo: tableView.trailingAnchor), + header.topAnchor.constraint(equalTo: tableView.topAnchor), + heightConstraint! + ] + NSLayoutConstraint.activate(constraints) + tableView.layoutIfNeeded() + } + } + + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + + // No table header to update + guard let headerView = tableView.tableHeaderView else { + return + } + + // Update the height constraint to match the height of the inline ad + if let constraint = heightConstraint { + constraint.constant = headerView.frame.height + } + // Create a new height constraint + else { + heightConstraint = headerView.heightAnchor.constraint(equalToConstant: headerView.frame.height) + heightConstraint?.isActive = true + } + + tableView.layoutIfNeeded() } } diff --git a/Canary/Canary/Formats/BaseNativeAdDataSource.swift b/Canary/Canary/Formats/BaseNativeAdDataSource.swift index 8b9214631..67a6070d1 100644 --- a/Canary/Canary/Formats/BaseNativeAdDataSource.swift +++ b/Canary/Canary/Formats/BaseNativeAdDataSource.swift @@ -1,83 +1,22 @@ // // BaseNativeAdDataSource.swift // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // +import Foundation import MoPub -import MoPub_AdMob_Adapters -import MoPub_Flurry_Adapters -import UIKit /** Base native ad data source to share ad rendering configurations */ class BaseNativeAdDataSource: NSObject { - // MARK: - Native Ad Rendering Configurations - - /** - MoPub static ad renderer settings - */ - var mopubRendererSettings: MPStaticNativeAdRendererSettings { - // MoPub static renderer - let mopubSettings: MPStaticNativeAdRendererSettings = MPStaticNativeAdRendererSettings() - mopubSettings.renderingViewClass = NativeAdView.self - mopubSettings.viewSizeHandler = { (width) -> CGSize in - return CGSize(width: width, height: 275) - } - - return mopubSettings - } - - /** - MoPub video ad renderer settings - */ - var mopubVideoRendererSettings: MOPUBNativeVideoAdRendererSettings { - // MoPub video renderer - let mopubVideoSettings: MOPUBNativeVideoAdRendererSettings = MOPUBNativeVideoAdRendererSettings() - mopubVideoSettings.renderingViewClass = NativeAdView.self - mopubVideoSettings.viewSizeHandler = { (width) -> CGSize in - return CGSize(width: width, height: 275) - } - - return mopubVideoSettings - } - /** Ad renderer configurations. */ var rendererConfigurations: [MPNativeAdRendererConfiguration] { - // Array of rendering configurations - var configs: [MPNativeAdRendererConfiguration] = [] - configs.append(MPStaticNativeAdRenderer.rendererConfiguration(with: mopubRendererSettings)) - configs.append(MOPUBNativeVideoAdRenderer.rendererConfiguration(with: mopubVideoRendererSettings)) - - // Add the renderers for mediated networks - configs = configs + networkRenderers - - return configs - } - - // MARK: - Network Renderers - - /** - Renderers for mediated networks - */ - internal var networkRenderers: [MPNativeAdRendererConfiguration] { - var renderers: [MPNativeAdRendererConfiguration] = [] - - // OPTIONAL: AdMob native renderer - if let admobConfig = MPGoogleAdMobNativeRenderer.rendererConfiguration(with: mopubRendererSettings) { - renderers.append(admobConfig) - } - - // OPTIONAL: Flurry native video renderer - if let flurryConfig = FlurryNativeVideoAdRenderer.rendererConfiguration(with: mopubVideoRendererSettings) { - renderers.append(flurryConfig) - } - - return renderers + return NativeAdRendererManager.shared.enabledRendererConfigurations } } diff --git a/Canary/Canary/Formats/InterstitialAdDataSource.swift b/Canary/Canary/Formats/InterstitialAdDataSource.swift index 468d87760..18a9c1a24 100644 --- a/Canary/Canary/Formats/InterstitialAdDataSource.swift +++ b/Canary/Canary/Formats/InterstitialAdDataSource.swift @@ -1,7 +1,7 @@ // // InterstitialAdDataSource.swift // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -27,30 +27,32 @@ class InterstitialAdDataSource: NSObject, AdDataSource { /** Table of which events were triggered. */ - private var eventTriggered: [AdEvent: Bool] = [:] - - /** - Reason for load failure. - */ - private var loadFailureReason: String? = nil + var eventTriggered: [AdEvent: Bool] = [:] /** Status event titles that correspond to the events found in `MPInterstitialAdControllerDelegate` */ - private lazy var title: [AdEvent: String] = { + lazy var title: [AdEvent: String] = { var titleStrings: [AdEvent: String] = [:] - titleStrings[.didLoad] = "interstitialDidLoadAd(_:)" - titleStrings[.didFailToLoad] = "interstitialDidFailToLoadAd(_:)" - titleStrings[.willAppear] = "interstitialWillAppear(_:)" - titleStrings[.didAppear] = "interstitialDidAppear(_:)" - titleStrings[.willDisappear] = "interstitialWillDisappear(_:)" - titleStrings[.didDisappear] = "interstitialDidDisappear(_:)" - titleStrings[.didExpire] = "interstitialDidExpire(_:)" - titleStrings[.clicked] = "interstitialDidReceiveTapEvent(_:)" + titleStrings[.didLoad] = "interstitialDidLoadAd(_:)" + titleStrings[.didFailToLoad] = "interstitialDidFailToLoadAd(_:)" + titleStrings[.willAppear] = "interstitialWillAppear(_:)" + titleStrings[.didAppear] = "interstitialDidAppear(_:)" + titleStrings[.willDisappear] = "interstitialWillDisappear(_:)" + titleStrings[.didDisappear] = "interstitialDidDisappear(_:)" + titleStrings[.didExpire] = "interstitialDidExpire(_:)" + titleStrings[.clicked] = "interstitialDidReceiveTapEvent(_:)" + titleStrings[.didTrackImpression] = "mopubAd(_:, didTrackImpressionWith _:)" return titleStrings }() + /** + Optional status messages that correspond to the events found in the ad's delegate protocol. + These are reset as part of `clearStatus`. + */ + var messages: [AdEvent: String] = [:] + // MARK: - Initialization /** @@ -68,20 +70,6 @@ class InterstitialAdDataSource: NSObject, AdDataSource { // MARK: - AdDataSource - /** - The ad unit information sections available for the ad. - */ - lazy var information: [AdInformation] = { - return [.id, .keywords, .userDataKeywords] - }() - - /** - The actions available for the ad. - */ - lazy var actions: [AdAction] = { - return [.load, .show] - }() - /** Closures associated with each available ad action. */ @@ -102,7 +90,7 @@ class InterstitialAdDataSource: NSObject, AdDataSource { The status events available for the ad. */ lazy var events: [AdEvent] = { - return [.didLoad, .didFailToLoad, .willAppear, .didAppear, .willDisappear, .didDisappear, .didExpire, .clicked] + return [.didLoad, .didFailToLoad, .willAppear, .didAppear, .willDisappear, .didDisappear, .didExpire, .clicked, .didTrackImpression] }() /** @@ -118,40 +106,30 @@ class InterstitialAdDataSource: NSObject, AdDataSource { } /** - Retrieves the display status for the event. - - Parameter event: Status event. - - Returns: A tuple containing the status display title, optional message, and highlighted state. + Queries if the data source has an ad loaded. */ - func status(for event: AdEvent) -> (title: String, message: String?, isHighlighted: Bool) { - let message = (event == .didFailToLoad ? loadFailureReason : nil) - let isHighlighted = (eventTriggered[event] ?? false) - return (title: title[event] ?? "", message: message, isHighlighted: isHighlighted) + var isAdLoaded: Bool { + return interstitialAd.ready } /** - Sets the status for the event to highlighted. If the status is already highlighted, - nothing is done. - - Parameter event: Status event. - - Parameter complete: Completion closure. + Queries if the data source currently requesting an ad. */ - func setStatus(for event: AdEvent, complete:(() -> Swift.Void)) { - eventTriggered[event] = true - complete() - } + private(set) var isAdLoading: Bool = false /** - Clears the highlighted state for all status events. - - Parameter complete: Completion closure. - */ - func clearStatus(complete:(() -> Swift.Void)) { - loadFailureReason = nil - eventTriggered = [:] - complete() - } + Optional ad size used for requesting inline ads. This should be `nil` for non-inline ads. + */ + var requestedAdSize: CGSize? = nil // MARK: - Ad Loading private func loadAd() { + guard !isAdLoading else { + return + } + + isAdLoading = true clearStatus { [weak self] in self?.delegate?.adPresentationTableView.reloadData() } @@ -177,21 +155,17 @@ extension InterstitialAdDataSource: MPInterstitialAdControllerDelegate { // MARK: - MPInterstitialAdControllerDelegate func interstitialDidLoadAd(_ interstitial: MPInterstitialAdController!) { + isAdLoading = false setStatus(for: .didLoad) { [weak self] in - if let strongSelf = self { - strongSelf.loadFailureReason = nil - strongSelf.delegate?.adPresentationTableView.reloadData() - } + self?.delegate?.adPresentationTableView.reloadData() } } func interstitialDidFail(toLoadAd interstitial: MPInterstitialAdController!) { - setStatus(for: .didFailToLoad) { [weak self] in - if let strongSelf = self { - // The interstitial load failure doesn't give back an error reason; assume clear response - strongSelf.loadFailureReason = "No ad available" - strongSelf.delegate?.adPresentationTableView.reloadData() - } + isAdLoading = false + // The interstitial load failure doesn't give back an error reason; assume clear response + setStatus(for: .didFailToLoad, message: "No ad available") { [weak self] in + self?.delegate?.adPresentationTableView.reloadData() } } @@ -230,4 +204,11 @@ extension InterstitialAdDataSource: MPInterstitialAdControllerDelegate { self?.delegate?.adPresentationTableView.reloadData() } } + + func mopubAd(_ ad: MPMoPubAd, didTrackImpressionWith impressionData: MPImpressionData?) { + let message = impressionData?.description ?? "No impression data" + setStatus(for: .didTrackImpression, message: message) { [weak self] in + self?.delegate?.adPresentationTableView.reloadData() + } + } } diff --git a/Canary/Canary/Formats/InterstitialAdViewController.swift b/Canary/Canary/Formats/InterstitialAdViewController.swift index 68625d55a..22a3769e4 100644 --- a/Canary/Canary/Formats/InterstitialAdViewController.swift +++ b/Canary/Canary/Formats/InterstitialAdViewController.swift @@ -1,7 +1,7 @@ // // InterstitialAdViewController.swift // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/Canary/Canary/Formats/LeaderboardAdViewController.swift b/Canary/Canary/Formats/LeaderboardAdViewController.swift deleted file mode 100644 index d3fc436ce..000000000 --- a/Canary/Canary/Formats/LeaderboardAdViewController.swift +++ /dev/null @@ -1,59 +0,0 @@ -// -// LeaderboardAdViewController.swift -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -import UIKit -import MoPub - -@objc(LeaderboardAdViewController) -class LeaderboardAdViewController: AdTableViewController { - // MARK: - Properties - - override var adUnit: AdUnit { - get { - return dataSource.adUnit - } - set { - // Create a new leaderboard specific data source with the new ad unit. - let bannerDataSource: BannerAdDataSource = BannerAdDataSource(adUnit: newValue, bannerSize: MOPUB_LEADERBOARD_SIZE) - dataSource = bannerDataSource - } - } - - // MARK: - View Lifecycle - - override func viewDidLoad() { - // Past this point, the data source must be valid. - guard dataSource != nil else { - return - } - - // Finish setting up the data source - dataSource.delegate = self - - // Invoke the super class to finish loading the view. - super.viewDidLoad() - } -} - -extension LeaderboardAdViewController: AdDataSourcePresentationDelegate { - // MARK: - AdDataSourcePresentationDelegate - - /** - View controller used to present models (either the ad itself or any click through destination). - */ - var adPresentationViewController: UIViewController? { - return self - } - - /** - Table view used to present the contents of the data source. - */ - var adPresentationTableView: UITableView { - return tableView - } -} diff --git a/Canary/Canary/Formats/MediumRectangleAdViewController.swift b/Canary/Canary/Formats/MediumRectangleAdViewController.swift index 1438c5c3d..b806dadef 100644 --- a/Canary/Canary/Formats/MediumRectangleAdViewController.swift +++ b/Canary/Canary/Formats/MediumRectangleAdViewController.swift @@ -1,7 +1,7 @@ // // MediumRectangleAdViewController.swift // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -19,11 +19,18 @@ class MediumRectangleAdViewController: AdTableViewController { } set { // Create a new medium rectangle specific data source with the new ad unit. - let bannerDataSource: BannerAdDataSource = BannerAdDataSource(adUnit: newValue, bannerSize: MOPUB_MEDIUM_RECT_SIZE) + // We are requesting the maximum desired medium rectangle size. + let bannerDataSource: BannerAdDataSource = BannerAdDataSource(adUnit: newValue, bannerSize: kMPPresetMaxAdSize280Height) dataSource = bannerDataSource } } + /** + Inline ad height constraint. This should only be set by `viewDidLoad()` and `viewDidLayoutSubviews()` if a + table header view exists. + */ + private var heightConstraint: NSLayoutConstraint? = nil + // MARK: - View Lifecycle override func viewDidLoad() { @@ -37,6 +44,43 @@ class MediumRectangleAdViewController: AdTableViewController { // Invoke the super class to finish loading the view. super.viewDidLoad() + + // Fix the medium rectangle height so that Auto Layout will correctly resize the table header. + if let header = tableView.tableHeaderView { + // Initialize the height constraint to the minimum supported medium rectangle height + heightConstraint = header.heightAnchor.constraint(equalToConstant: kMPPresetMaxAdSize250Height.height) + + let constraints = [ + header.widthAnchor.constraint(equalTo: tableView.widthAnchor), + header.leadingAnchor.constraint(equalTo: tableView.leadingAnchor), + header.trailingAnchor.constraint(equalTo: tableView.trailingAnchor), + header.topAnchor.constraint(equalTo: tableView.topAnchor), + heightConstraint! + ] + NSLayoutConstraint.activate(constraints) + tableView.layoutIfNeeded() + } + } + + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + + // No table header to update + guard let headerView = tableView.tableHeaderView else { + return + } + + // Update the height constraint to match the height of the inline ad + if let constraint = heightConstraint { + constraint.constant = headerView.frame.height + } + // Create a new height constraint + else { + heightConstraint = headerView.heightAnchor.constraint(equalToConstant: headerView.frame.height) + heightConstraint?.isActive = true + } + + tableView.layoutIfNeeded() } } diff --git a/Canary/Canary/Formats/NativeAdCollectionDataSource.swift b/Canary/Canary/Formats/NativeAdCollectionDataSource.swift index bf4379470..431e2c9de 100644 --- a/Canary/Canary/Formats/NativeAdCollectionDataSource.swift +++ b/Canary/Canary/Formats/NativeAdCollectionDataSource.swift @@ -1,7 +1,7 @@ // // NativeAdCollectionDataSource.swift // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -56,7 +56,7 @@ class NativeAdCollectionDataSource: BaseNativeAdDataSource { /** Computed native ad targetting settings. */ - var targetting: MPNativeAdRequestTargeting { + var targeting: MPNativeAdRequestTargeting { let target: MPNativeAdRequestTargeting = MPNativeAdRequestTargeting() target.desiredAssets = Set(arrayLiteral: kAdTitleKey, kAdTextKey, kAdCTATextKey, kAdIconImageKey, kAdMainImageKey, kAdStarRatingKey, kAdIconImageViewKey, kAdMainMediaViewKey) target.keywords = adUnit.keywords diff --git a/Canary/Canary/Formats/NativeAdCollectionViewController.swift b/Canary/Canary/Formats/NativeAdCollectionViewController.swift index 0631d580a..21389406a 100644 --- a/Canary/Canary/Formats/NativeAdCollectionViewController.swift +++ b/Canary/Canary/Formats/NativeAdCollectionViewController.swift @@ -1,7 +1,7 @@ // // NativeAdCollectionViewController.swift // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -12,6 +12,11 @@ import MoPub @objc(NativeAdCollectionViewController) class NativeAdCollectionViewController: UIViewController, AdViewController { + // MARK: - Constants + struct Constants { + static let iconSize: CGSize = CGSize(width: 50.0, height: 50.0) + } + // MARK: - IBOutlets @IBOutlet weak var collectionView: UICollectionView! @IBOutlet weak var collectionViewLayout: UICollectionViewFlowLayout! @@ -62,6 +67,12 @@ class NativeAdCollectionViewController: UIViewController, AdViewController { // Set the title title = adUnit.name + + // Set collection/view background color for Dark Mode + if #available(iOS 13.0, *) { + collectionView.backgroundColor = .systemBackground + view.backgroundColor = .systemBackground + } } override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { @@ -113,7 +124,7 @@ class NativeAdCollectionViewController: UIViewController, AdViewController { Loads the ads for table placer */ fileprivate func loadAds() { - collectionPlacer.loadAds(forAdUnitID: adUnit.id, targeting: dataSource.targetting) + collectionPlacer.loadAds(forAdUnitID: adUnit.id, targeting: dataSource.targeting) } } @@ -123,7 +134,7 @@ extension NativeAdCollectionViewController: UICollectionViewDataSource, UICollec func update(cell: TweetCollectionViewCell, at indexPath: IndexPath) -> TweetCollectionViewCell { let data = dataSource.data[indexPath.row] - let icon: UIImage = data.color.image(size: CGSize(width: 50, height: 50)) + let icon: UIImage = data.color.image(size: Constants.iconSize) // Only for Regular:Regular size class will we attempt to display // the cells at half width (2 column) format. @@ -134,6 +145,7 @@ extension NativeAdCollectionViewController: UICollectionViewDataSource, UICollec // Update the contents of the cell cell.refresh(userIcon: icon, userName: data.name, tweet: data.tweet) + cell.layoutIfNeeded() return cell } @@ -163,7 +175,7 @@ extension NativeAdCollectionViewController: UICollectionViewDataSource, UICollec // Update the sizing cell with the current information and determine the minimum amount of // screen realestate needed to properly display the cell. let updatedSizingCell = update(cell: sizingCell, at: indexPath) - var size = updatedSizingCell.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize) + var size = updatedSizingCell.cellSize // Round up any fractional sizes to improve rendering performance. size.width.round(.down) diff --git a/Canary/Canary/Formats/NativeAdDataSource.swift b/Canary/Canary/Formats/NativeAdDataSource.swift index f79c66463..89a71aa39 100644 --- a/Canary/Canary/Formats/NativeAdDataSource.swift +++ b/Canary/Canary/Formats/NativeAdDataSource.swift @@ -1,7 +1,7 @@ // // NativeAdDataSource.swift // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -32,27 +32,29 @@ class NativeAdDataSource: BaseNativeAdDataSource, AdDataSource { /** Table of which events were triggered. */ - private var eventTriggered: [AdEvent: Bool] = [:] - - /** - Reason for load failure. - */ - private var loadFailureReason: String? = nil + var eventTriggered: [AdEvent: Bool] = [:] /** Status event titles that correspond to the events found in `MPNativeAdDelegate` */ - private lazy var title: [AdEvent: String] = { + lazy var title: [AdEvent: String] = { var titleStrings: [AdEvent: String] = [:] - titleStrings[.didLoad] = "nativeAdDidLoad(_:)" - titleStrings[.didFailToLoad] = "nativeAdDidFailToLoad(_:_:)" - titleStrings[.willPresentModal] = "willPresentModal(_:)" - titleStrings[.didDismissModal] = "didDismissModal(_:)" - titleStrings[.willLeaveApp] = "willLeaveApplication(_:)" + titleStrings[.didLoad] = "nativeAdDidLoad(_:)" + titleStrings[.didFailToLoad] = "nativeAdDidFailToLoad(_:_:)" + titleStrings[.willPresentModal] = "willPresentModal(_:)" + titleStrings[.didDismissModal] = "didDismissModal(_:)" + titleStrings[.willLeaveApp] = "willLeaveApplication(_:)" + titleStrings[.didTrackImpression] = "mopubAd(_:, didTrackImpressionWith _:)" return titleStrings }() + /** + Optional status messages that correspond to the events found in the ad's delegate protocol. + These are reset as part of `clearStatus`. + */ + var messages: [AdEvent: String] = [:] + // MARK: - Initialization /** @@ -69,13 +71,6 @@ class NativeAdDataSource: BaseNativeAdDataSource, AdDataSource { // MARK: - AdDataSource - /** - The ad unit information sections available for the ad. - */ - lazy var information: [AdInformation] = { - return [.id, .keywords, .userDataKeywords] - }() - /** The actions available for the ad. */ @@ -99,7 +94,7 @@ class NativeAdDataSource: BaseNativeAdDataSource, AdDataSource { The status events available for the ad. */ lazy var events: [AdEvent] = { - return [.didLoad, .didFailToLoad, .willPresentModal, .didDismissModal, .willLeaveApp] + return [.didLoad, .didFailToLoad, .willPresentModal, .didDismissModal, .willLeaveApp, .didTrackImpression] }() /** @@ -115,36 +110,19 @@ class NativeAdDataSource: BaseNativeAdDataSource, AdDataSource { } /** - Retrieves the display status for the event. - - Parameter event: Status event. - - Returns: A tuple containing the status display title, optional message, and highlighted state. + Queries if the data source has an ad loaded. */ - func status(for event: AdEvent) -> (title: String, message: String?, isHighlighted: Bool) { - let message = (event == .didFailToLoad ? loadFailureReason : nil) - let isHighlighted = (eventTriggered[event] ?? false) - return (title: title[event] ?? "", message: message, isHighlighted: isHighlighted) - } + private(set) var isAdLoaded: Bool = false /** - Sets the status for the event to highlighted. If the status is already highlighted, - nothing is done. - - Parameter event: Status event. - - Parameter complete: Completion closure. + Queries if the data source currently requesting an ad. */ - func setStatus(for event: AdEvent, complete:(() -> Swift.Void)) { - eventTriggered[event] = true - complete() - } + private(set) var isAdLoading: Bool = false /** - Clears the highlighted state for all status events. - - Parameter complete: Completion closure. - */ - func clearStatus(complete:(() -> Swift.Void)) { - loadFailureReason = nil - eventTriggered = [:] - complete() - } + Optional ad size used for requesting inline ads. This should be `nil` for non-inline ads. + */ + var requestedAdSize: CGSize? = nil // MARK: - Ad Loading @@ -198,6 +176,11 @@ class NativeAdDataSource: BaseNativeAdDataSource, AdDataSource { } private func loadAd() { + guard !isAdLoading else { + return + } + + isAdLoading = true clearStatus { [weak self] in self?.delegate?.adPresentationTableView.reloadData() } @@ -207,10 +190,13 @@ class NativeAdDataSource: BaseNativeAdDataSource, AdDataSource { adRequest.targeting = targetting adRequest.start { [weak self] (request, nativeAd, error) in if let strongSelf = self { + // No longer loading regardless of result + strongSelf.isAdLoading = false + // Error loading the native ad guard error == nil else { - strongSelf.loadFailureReason = error?.localizedDescription - strongSelf.setStatus(for: .didFailToLoad) { [weak self] in + strongSelf.isAdLoaded = false + strongSelf.setStatus(for: .didFailToLoad, message: error?.localizedDescription) { [weak self] in self?.delegate?.adPresentationTableView.reloadData() } return @@ -223,6 +209,7 @@ class NativeAdDataSource: BaseNativeAdDataSource, AdDataSource { strongSelf.addToAdContainer(view: nativeAdView) } + strongSelf.isAdLoaded = true strongSelf.setStatus(for: .didLoad) { [weak self] in self?.delegate?.adPresentationTableView.reloadData() } @@ -255,4 +242,11 @@ extension NativeAdDataSource: MPNativeAdDelegate { func viewControllerForPresentingModalView() -> UIViewController! { return delegate?.adPresentationViewController } + + func mopubAd(_ ad: MPMoPubAd, didTrackImpressionWith impressionData: MPImpressionData?) { + let message = impressionData?.description ?? "No impression data" + setStatus(for: .didTrackImpression, message: message) { [weak self] in + self?.delegate?.adPresentationTableView.reloadData() + } + } } diff --git a/Canary/Canary/Formats/NativeAdTableDataSource.swift b/Canary/Canary/Formats/NativeAdTableDataSource.swift index d53c1c283..fa7631d54 100644 --- a/Canary/Canary/Formats/NativeAdTableDataSource.swift +++ b/Canary/Canary/Formats/NativeAdTableDataSource.swift @@ -1,7 +1,7 @@ // // NativeAdTableDataSource.swift // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/Canary/Canary/Formats/NativeAdTableViewController.swift b/Canary/Canary/Formats/NativeAdTableViewController.swift index 3ebf8e7ca..7d2dbba9d 100644 --- a/Canary/Canary/Formats/NativeAdTableViewController.swift +++ b/Canary/Canary/Formats/NativeAdTableViewController.swift @@ -1,7 +1,7 @@ // // NativeAdTableViewController.swift // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -50,6 +50,12 @@ class NativeAdTableViewController: UIViewController, AdViewController { // Set the title title = adUnit.name + + // Set the background color for Dark Mode + if #available(iOS 13.0, *) { + tableView.backgroundColor = .systemBackground + view.backgroundColor = .systemBackground + } } // MARK: - DisplayableAd @@ -104,9 +110,7 @@ extension NativeAdTableViewController: UITableViewDataSource, UITableViewDelegat } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - guard let cell: StatusTableViewCell = tableView.mp_dequeueReusableCell(withIdentifier: StatusTableViewCell.reuseId, for: indexPath) as? StatusTableViewCell else { - return UITableViewCell() - } + let cell = tableView.dequeueCellFromNib(cellType: StatusTableViewCell.self) // Update the cell let fontName: String = dataSource.data[indexPath.row] diff --git a/Canary/Canary/Formats/NativeAdView.swift b/Canary/Canary/Formats/NativeAdView.swift index ffe74d1f3..767e4d7bd 100644 --- a/Canary/Canary/Formats/NativeAdView.swift +++ b/Canary/Canary/Formats/NativeAdView.swift @@ -1,7 +1,7 @@ // // NativeAdView.swift // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -51,8 +51,14 @@ class NativeAdView: UIView { return } + // Accessibility + mainImageView.accessibilityIdentifier = AccessibilityIdentifier.nativeAdImageView + // Size the nib's view to the container and add it as a subview. view.frame = bounds + if #available(iOS 13.0, *) { + view.backgroundColor = .systemBackground + } addSubview(view) contentView = view diff --git a/Canary/Canary/Formats/NativeAdView.xib b/Canary/Canary/Formats/NativeAdView.xib index 408bf6a58..206ce5a18 100644 --- a/Canary/Canary/Formats/NativeAdView.xib +++ b/Canary/Canary/Formats/NativeAdView.xib @@ -1,12 +1,11 @@ - + - - + @@ -45,15 +44,18 @@ + + + - - - + + + diff --git a/Canary/Canary/Formats/NativeAdViewController.swift b/Canary/Canary/Formats/NativeAdViewController.swift index a8e694ea7..69a138e3b 100644 --- a/Canary/Canary/Formats/NativeAdViewController.swift +++ b/Canary/Canary/Formats/NativeAdViewController.swift @@ -1,7 +1,7 @@ // // NativeAdViewController.swift // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/Canary/Canary/Formats/RewardedAdDataSource.swift b/Canary/Canary/Formats/RewardedAdDataSource.swift index f4fac413d..a52c21a1f 100644 --- a/Canary/Canary/Formats/RewardedAdDataSource.swift +++ b/Canary/Canary/Formats/RewardedAdDataSource.swift @@ -1,7 +1,7 @@ // // RewardedAdDataSource.swift // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -24,30 +24,15 @@ class RewardedAdDataSource: NSObject, AdDataSource { */ private var selectedReward: MPRewardedVideoReward? = nil - /** - Rewarded that was granted to the user. - */ - private var grantedReward: MPRewardedVideoReward? = nil - /** Table of which events were triggered. */ - private var eventTriggered: [AdEvent: Bool] = [:] - - /** - Reason for load failure. - */ - private var loadFailureReason: String? = nil - - /** - Reason for playback failure. - */ - private var playFailureReason: String? = nil + var eventTriggered: [AdEvent: Bool] = [:] /** Status event titles that correspond to the events found in `MPRewardedVideoDelegate` */ - private lazy var title: [AdEvent: String] = { + lazy var title: [AdEvent: String] = { var titleStrings: [AdEvent: String] = [:] titleStrings[.didLoad] = "rewardedVideoAdDidLoad(_:)" titleStrings[.didFailToLoad] = "rewardedVideoAdDidFailToLoad(_:_:)" @@ -60,10 +45,17 @@ class RewardedAdDataSource: NSObject, AdDataSource { titleStrings[.clicked] = "rewardedVideoAdDidReceiveTapEvent(_:)" titleStrings[.willLeaveApp] = "rewardedVideoAdWillLeaveApplication(_:)" titleStrings[.shouldRewardUser] = "rewardedVideoAdShouldReward(_:_:)" + titleStrings[.didTrackImpression] = "mopubAd(_:, didTrackImpressionWith _:)" return titleStrings }() + /** + Optional status messages that correspond to the events found in the ad's delegate protocol. + These are reset as part of `clearStatus`. + */ + var messages: [AdEvent: String] = [:] + // MARK: - Initialization /** @@ -91,13 +83,6 @@ class RewardedAdDataSource: NSObject, AdDataSource { return [.id, .keywords, .userDataKeywords, .customData] }() - /** - The actions available for the ad. - */ - lazy var actions: [AdAction] = { - return [.load, .show] - }() - /** Closures associated with each available ad action. */ @@ -118,7 +103,7 @@ class RewardedAdDataSource: NSObject, AdDataSource { The status events available for the ad. */ lazy var events: [AdEvent] = { - return [.didLoad, .didFailToLoad, .didFailToPlay, .willAppear, .didAppear, .willDisappear, .didDisappear, .didExpire, .clicked, .willLeaveApp, .shouldRewardUser] + return [.didLoad, .didFailToLoad, .didFailToPlay, .willAppear, .didAppear, .willDisappear, .didDisappear, .didExpire, .clicked, .willLeaveApp, .shouldRewardUser, .didTrackImpression] }() /** @@ -134,47 +119,21 @@ class RewardedAdDataSource: NSObject, AdDataSource { } /** - Retrieves the display status for the event. - - Parameter event: Status event. - - Returns: A tuple containing the status display title, optional message, and highlighted state. + Queries if the data source has an ad loaded. */ - func status(for event: AdEvent) -> (title: String, message: String?, isHighlighted: Bool) { - var message: String? = nil - if event == .didFailToLoad { - message = loadFailureReason - } - else if event == .didFailToPlay { - message = playFailureReason - } - else if event == .shouldRewardUser, let amount = grantedReward?.amount, let currency = grantedReward?.currencyType { - message = "\(amount) \(currency)" - } - - let isHighlighted = (eventTriggered[event] ?? false) - return (title: title[event] ?? "", message: message, isHighlighted: isHighlighted) + var isAdLoaded: Bool { + return MPRewardedVideo.hasAdAvailable(forAdUnitID: adUnit.id) } /** - Sets the status for the event to highlighted. If the status is already highlighted, - nothing is done. - - Parameter event: Status event. - - Parameter complete: Completion closure. + Queries if the data source currently requesting an ad. */ - func setStatus(for event: AdEvent, complete:(() -> Swift.Void)) { - eventTriggered[event] = true - complete() - } + private(set) var isAdLoading: Bool = false /** - Clears the highlighted state for all status events. - - Parameter complete: Completion closure. - */ - func clearStatus(complete:(() -> Swift.Void)) { - loadFailureReason = nil - playFailureReason = nil - eventTriggered = [:] - complete() - } + Optional ad size used for requesting inline ads. This should be `nil` for non-inline ads. + */ + var requestedAdSize: CGSize? = nil // MARK: - Reward Selection @@ -190,51 +149,14 @@ class RewardedAdDataSource: NSObject, AdDataSource { return } - // It's really a supported behavior to have a `UIPickerView` as a subview - // of `UIAlertController`. To make it work, the width of the alert view - // (as specified by `preferredContentSize`) should be the same as the - // picker view. - // Create the alert. - let alert: UIAlertController = UIAlertController(title: "Choose Reward", message: nil, preferredStyle: .actionSheet) - alert.isModalInPopover = true - alert.preferredContentSize = CGSize(width: 320, height: 250) + let alert = UIAlertController(title: "Choose Reward", message: nil, pickerViewDelegate: self, pickerViewDataSource: self, sender: sender) // Create the selection button. alert.addAction(UIAlertAction(title: "Select", style: .default, handler: { _ in complete() })) - // Reward picker view - let pickerView: UIPickerView = UIPickerView(frame: CGRect(x: 0, y: 0, width: 320, height: 200)) - pickerView.dataSource = self - pickerView.delegate = self - - // Configure popover appearance. - if let popoverController = alert.popoverPresentationController, - let showButton: UIButton = sender as? UIButton { - popoverController.sourceView = showButton - popoverController.sourceRect = showButton.bounds - popoverController.permittedArrowDirections = [.up, .down] - } - - alert.view.addSubview(pickerView) - - // The bottom constraint of the picker view is -44 from the bottom anchor of the - // alert view so that it doesn't cover the selection button. If the selection - // button is covered, it cannot be tapped. - let constraints: [NSLayoutConstraint] = [ - pickerView.leadingAnchor.constraint(equalTo: alert.view.leadingAnchor, constant: 0), - pickerView.trailingAnchor.constraint(equalTo: alert.view.trailingAnchor, constant: 0), - pickerView.topAnchor.constraint(equalTo: alert.view.topAnchor), - pickerView.bottomAnchor.constraint(equalTo: alert.view.bottomAnchor, constant: -44), - ] - NSLayoutConstraint.activate(constraints) - - // Select the first reward by default. - pickerView.selectRow(0, inComponent: 0, animated: false) - self.pickerView(pickerView, didSelectRow: 0, inComponent: 0) - // Present the alert delegate?.adPresentationViewController?.present(alert, animated: true, completion: nil) } @@ -242,13 +164,17 @@ class RewardedAdDataSource: NSObject, AdDataSource { // MARK: - Ad Loading private func loadAd() { + guard !isAdLoading else { + return + } + + isAdLoading = true clearStatus { [weak self] in self?.delegate?.adPresentationTableView.reloadData() } // Clear out previous reward. selectedReward = nil - grantedReward = nil // Load the rewarded ad. MPRewardedVideo.loadAd(withAdUnitID: adUnit.id, keywords: adUnit.keywords, userDataKeywords: adUnit.userDataKeywords, location: nil, mediationSettings: nil) @@ -313,30 +239,22 @@ extension RewardedAdDataSource: MPRewardedVideoDelegate { // MARK: - MPRewardedVideoDelegate func rewardedVideoAdDidLoad(forAdUnitID adUnitID: String!) { + isAdLoading = false setStatus(for: .didLoad) { [weak self] in - if let strongSelf = self { - strongSelf.loadFailureReason = nil - strongSelf.playFailureReason = nil - strongSelf.delegate?.adPresentationTableView.reloadData() - } + self?.delegate?.adPresentationTableView.reloadData() } } func rewardedVideoAdDidFailToLoad(forAdUnitID adUnitID: String!, error: Error!) { - setStatus(for: .didFailToLoad) { [weak self] in - if let strongSelf = self { - strongSelf.loadFailureReason = error.localizedDescription - strongSelf.delegate?.adPresentationTableView.reloadData() - } + isAdLoading = false + setStatus(for: .didFailToLoad, message: error.localizedDescription) { [weak self] in + self?.delegate?.adPresentationTableView.reloadData() } } func rewardedVideoAdDidFailToPlay(forAdUnitID adUnitID: String!, error: Error!) { - setStatus(for: .didFailToPlay) { [weak self] in - if let strongSelf = self { - strongSelf.playFailureReason = error.localizedDescription - strongSelf.delegate?.adPresentationTableView.reloadData() - } + setStatus(for: .didFailToPlay, message: error.localizedDescription) { [weak self] in + self?.delegate?.adPresentationTableView.reloadData() } } @@ -383,11 +301,16 @@ extension RewardedAdDataSource: MPRewardedVideoDelegate { } func rewardedVideoAdShouldReward(forAdUnitID adUnitID: String!, reward: MPRewardedVideoReward!) { - setStatus(for: .shouldRewardUser) { [weak self] in - if let strongSelf = self { - strongSelf.grantedReward = reward - strongSelf.delegate?.adPresentationTableView.reloadData() - } + let message = reward?.description ?? "No reward specified" + setStatus(for: .shouldRewardUser, message: message) { [weak self] in + self?.delegate?.adPresentationTableView.reloadData() + } + } + + func didTrackImpression(withAdUnitID adUnitID: String!, impressionData: MPImpressionData!) { + let message = impressionData?.description ?? "No impression data" + setStatus(for: .didTrackImpression, message: message) { [weak self] in + self?.delegate?.adPresentationTableView.reloadData() } } } diff --git a/Canary/Canary/Formats/RewardedAdViewController.swift b/Canary/Canary/Formats/RewardedAdViewController.swift index a8451504e..9d8216093 100644 --- a/Canary/Canary/Formats/RewardedAdViewController.swift +++ b/Canary/Canary/Formats/RewardedAdViewController.swift @@ -1,7 +1,7 @@ // // RewardedAdViewController.swift // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/Canary/Canary/LoggingLevel/LogingLevelMenuDataSource.swift b/Canary/Canary/LoggingLevel/LogingLevelMenuDataSource.swift index 1e0954f5b..49da208ff 100644 --- a/Canary/Canary/LoggingLevel/LogingLevelMenuDataSource.swift +++ b/Canary/Canary/LoggingLevel/LogingLevelMenuDataSource.swift @@ -1,7 +1,7 @@ // // LogingLevelMenuDataSource.swift // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -10,31 +10,21 @@ import UIKit import MoPub fileprivate enum LoggingLevelMenuOptions: String { - case all = "All" - case trace = "Trace" case debug = "Debug" case info = "Informational" - case warn = "Warnings" - case error = "Errors" - case fatal = "Fatal" - case off = "Off" + case none = "None" - var logLevel: MPLogLevel { + var logLevel: MPBLogLevel { switch self { - case .all: return MPLogLevelAll - case .trace: return MPLogLevelTrace - case .debug: return MPLogLevelDebug - case .info: return MPLogLevelInfo - case .warn: return MPLogLevelWarn - case .error: return MPLogLevelError - case .fatal: return MPLogLevelFatal - case .off: return MPLogLevelOff + case .debug: return MPBLogLevel.debug + case .info: return MPBLogLevel.info + case .none: return MPBLogLevel.none } } } class LogingLevelMenuDataSource { - fileprivate let items: [LoggingLevelMenuOptions] = [.all, .trace, .debug, .info, .warn, .error, .fatal, .off] + fileprivate let items: [LoggingLevelMenuOptions] = [.debug, .info, .none] } extension LogingLevelMenuDataSource: MenuDisplayable { @@ -59,9 +49,9 @@ extension LogingLevelMenuDataSource: MenuDisplayable { - Returns: A configured `UITableViewCell` */ func cell(forItem index: Int, inTableView tableView: UITableView) -> UITableViewCell { - let cell: BasicMenuTableViewCell = basicMenuCell(inTableView: tableView) + let cell = tableView.dequeueCellFromNib(cellType: BasicMenuTableViewCell.self) let item: LoggingLevelMenuOptions = items[index] - let currentLogLevel: MPLogLevel = MoPub.sharedInstance().logLevel + let currentLogLevel: MPBLogLevel = MPLogging.consoleLogLevel cell.accessoryType = (currentLogLevel == item.logLevel ? .checkmark : .none) cell.title.text = item.rawValue @@ -81,14 +71,16 @@ extension LogingLevelMenuDataSource: MenuDisplayable { /** Performs an optional selection action for the menu item - - Parameter index: Menu item index assumed to be in bounds + - Parameter indexPath: Menu item indexPath assumed to be in bounds - Parameter tableView: `UITableView` that rendered the item - Parameter viewController: Presenting view controller + - Returns: `true` if the menu should collapse when selected; `false` otherwise. */ - func didSelect(itemAt index: Int, inTableView tableView: UITableView, presentFrom viewController: UIViewController) -> Swift.Void { - let item: LoggingLevelMenuOptions = items[index] - MoPub.sharedInstance().logLevel = item.logLevel + func didSelect(itemAt indexPath: IndexPath, inTableView tableView: UITableView, presentFrom viewController: UIViewController) -> Bool { + let item: LoggingLevelMenuOptions = items[indexPath.row] + MPLogging.consoleLogLevel = item.logLevel tableView.reloadData() + return true } } diff --git a/Canary/Canary/MainTabBarController.swift b/Canary/Canary/MainTabBarController.swift index 6a9ed78f6..508c655a3 100644 --- a/Canary/Canary/MainTabBarController.swift +++ b/Canary/Canary/MainTabBarController.swift @@ -1,13 +1,12 @@ // // MainTabBarController.swift // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // import UIKit -import MoPub fileprivate enum Constants { /** @@ -20,7 +19,7 @@ class MainTabBarController: UITabBarController { /** Button used for displaying status notifications. */ - private var notificationButton: UIButton = UIButton() + private var notificationButton: UIButton = UIButton(type: .custom) // MARK: - View Lifecycle @@ -28,21 +27,26 @@ class MainTabBarController: UITabBarController { super.viewDidLoad() // Configure the notification label + notificationButton.accessibilityIdentifier = AccessibilityIdentifier.notificationButton notificationButton.alpha = 0.0 notificationButton.contentEdgeInsets = UIEdgeInsets.init(top: 5, left: 10, bottom: 5, right: 10) notificationButton.addTarget(self, action: #selector(self.dismissNotification), for: .touchUpInside) view.addSubview(notificationButton) + // Set notification label to word wrap + notificationButton.titleLabel?.numberOfLines = 0 + notificationButton.titleLabel?.lineBreakMode = .byWordWrapping + // Constrain the notification label notificationButton.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ notificationButton.leadingAnchor.constraint(equalTo: view.leadingAnchor), notificationButton.trailingAnchor.constraint(equalTo: view.trailingAnchor), - notificationButton.bottomAnchor.constraint(equalTo: tabBar.topAnchor) + notificationButton.bottomAnchor.constraint(equalTo: tabBar.topAnchor), + // Lock notification button height to height of label + notificationButton.heightAnchor.constraint(equalTo: notificationButton.titleLabel!.heightAnchor, + constant: notificationButton.contentEdgeInsets.top + notificationButton.contentEdgeInsets.bottom) ]) - - // Register for the consent broadcast notifications - NotificationCenter.default.addObserver(self, selector: #selector(MainTabBarController.onConsentChangedNotification(notification:)), name: NSNotification.Name.mpConsentChanged, object: nil) } // MARK: - Notifications @@ -73,39 +77,4 @@ class MainTabBarController: UITabBarController { self.notificationButton.alpha = 0.0 } } - - // MARK: - Notification Listeners - - /** - Listens for changes in consent status and PII collection status. - - Parameter notification: Notification with payload in `userInfo`. - */ - @objc - func onConsentChangedNotification(notification: NSNotification) { - // Extract the notification payload - if let payload: [String: NSNumber] = notification.userInfo as? [String: NSNumber], - let oldStatusNumber: NSNumber = payload[kMPConsentChangedInfoPreviousConsentStatusKey], - let newStatusNumber: NSNumber = payload[kMPConsentChangedInfoNewConsentStatusKey], - let canCollectPii: Bool = payload[kMPConsentChangedInfoCanCollectPersonalInfoKey]?.boolValue, - let oldStatus: MPConsentStatus = MPConsentStatus(rawValue: oldStatusNumber.intValue), - let newStatus: MPConsentStatus = MPConsentStatus(rawValue: newStatusNumber.intValue) { - // Text to display - var notificationText: String - - // There was a change in status; display the new status - if oldStatus != newStatus { - notificationText = "Consent changed to \(newStatus.description)" - } - // There was a change in the ability to collect PII - else if canCollectPii { - notificationText = "PII can be collected" - } - // Not allowed to collect PII - else { - notificationText = "PII is not allowed to be collected" - } - - showNotification(withText: notificationText) - } - } } diff --git a/Canary/Canary/ManualEntryInterfaceViewController.swift b/Canary/Canary/ManualEntryInterfaceViewController.swift new file mode 100644 index 000000000..23627074c --- /dev/null +++ b/Canary/Canary/ManualEntryInterfaceViewController.swift @@ -0,0 +1,125 @@ +// +// ManualEntryInterfaceViewController.swift +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +import UIKit + +class ManualEntryInterfaceViewController: UIViewController { + + var selectedFormat: AdFormat = AdFormat.allCases[0] { + didSet { + adFormatButton?.setTitle(selectedFormat.rawValue, for: .normal) + } + } + + override func viewDidLoad() { + super.viewDidLoad() + + // Set button text to default ad format text + adFormatButton.setTitle(selectedFormat.rawValue, for: .normal) + + // Set background color for Dark Mode + if #available(iOS 13.0, *) { + view.backgroundColor = .systemBackground + } + } + + fileprivate func dismissAndShowAd(shouldSave: Bool) { + guard let savedAdSplitViewController = savedAdSplitViewController else { + return + } + + // Attempt to create ad unit object + let adUnit: AdUnit + + // Try creating the ad unit object from deep link URL first + if let deepLinkURLText = deepLinkURLTextField.text, + let deepLinkURL = URL(string: deepLinkURLText), + let anAdUnit = AdUnit(url: deepLinkURL) { + adUnit = anAdUnit + } + // If that doesn't work, try creating the ad unit object from manual fields + else if let adUnitId = adUnitIdTextField.text, + let anAdUnit = AdUnit(adUnitId: adUnitId, format: selectedFormat, name: nameTextField.text) { + adUnit = anAdUnit + } + // If that doesn't work, show an alert to the user and return + else { + let alert = UIAlertController(title: nil, message: "Ad Unit ID or deep link URL is malformed or blank.", preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) + present(alert, animated: true, completion: nil) + + return + } + + // If everything is formed correctly, dismiss and show ad. + dismiss(animated: true) { + SceneDelegate.openMoPubAdUnit(adUnit: adUnit, + onto: savedAdSplitViewController, + shouldSave: shouldSave) + } + } + + // MARK: - IBOutlets + @IBOutlet weak var adFormatButton: UIButton! + @IBOutlet weak var adUnitIdTextField: UITextField! + @IBOutlet weak var nameTextField: UITextField! + @IBOutlet weak var deepLinkURLTextField: UITextField! + + // MARK: - IBActions + @IBAction func adFormatButtonAction(_ sender: Any) { + // Create the alert. + let alert = UIAlertController(title: "Choose Ad Type", message: nil, pickerViewDelegate: self, pickerViewDataSource: self, sender: sender) + + // Create the selection button. + alert.addAction(UIAlertAction(title: "Select", style: .default, handler: nil)) + + // Present the alert + present(alert, animated: true, completion: nil) + } + + @IBAction func showAction(_ sender: Any) { + dismissAndShowAd(shouldSave: false) + } + + @IBAction func showAndSaveAction(_ sender: Any) { + dismissAndShowAd(shouldSave: true) + } + + @IBAction func cancelButtonAction(_ sender: Any) { + navigationController?.dismiss(animated: true, completion: nil) + } +} + +extension ManualEntryInterfaceViewController: UIPickerViewDataSource, UIPickerViewDelegate { + // MARK: - UIPickerViewDataSource + + // There will always be a single column ad formats + func numberOfComponents(in pickerView: UIPickerView) -> Int { + return 1 + } + + func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { + return AdFormat.allCases.count + } + + // MARK: - UIPickerViewDelegate + + func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { + return AdFormat.allCases[row].rawValue + } + + func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { + selectedFormat = AdFormat.allCases[row] + } +} + +extension ManualEntryInterfaceViewController: UITextFieldDelegate { + func textFieldShouldReturn(_ textField: UITextField) -> Bool { + return textField.resignFirstResponder() + } +} diff --git a/Canary/Canary/Menu/MenuDataSource.swift b/Canary/Canary/Menu/MenuDataSource.swift index 124147560..d9cfb5fcd 100644 --- a/Canary/Canary/Menu/MenuDataSource.swift +++ b/Canary/Canary/Menu/MenuDataSource.swift @@ -1,7 +1,7 @@ // // MenuDataSource.swift // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -14,7 +14,11 @@ class MenuDataSource { /** Ordered section titles */ - private(set) var sections: [String] = [] + private(set) var sections: [String] = [] { + didSet { + sections.sort() + } + } /** Internal data sources for each menu grouping @@ -24,8 +28,9 @@ class MenuDataSource { // MARK: - Initialization init() { - add(menu: PrivacyMenuDataSource()) + add(menu: AdapterVersionsMenuDataSource()) add(menu: LogingLevelMenuDataSource()) + add(menu: NativeAdRendererMenuDataSource()) } // MARK: - Data Source @@ -43,6 +48,14 @@ class MenuDataSource { sources[menu.title] = menu } + /** + Updates all data sources if needed. + - Returns: `true` update happened; `false` otherwise. + */ + func updateIfNeeded() -> Bool { + return sources.values.reduce(false) { $0 || $1.updateIfNeeded() } + } + // MARK: - Accessors func cell(forIndexPath indexPath: IndexPath, inTableView tableView: UITableView) -> UITableViewCell { @@ -57,12 +70,13 @@ class MenuDataSource { return sources[sections[index]]?.count ?? 0 } + func canSelect(itemAtIndexPath indexPath: IndexPath, inTableView tableView: UITableView) -> Bool { + return sources[sections[indexPath.section]]?.canSelect(itemAt: indexPath.row, inTableView: tableView) ?? false + } + func didSelect(itemAtIndexPath indexPath: IndexPath, inTableView tableView: UITableView, presentingFrom viewController: UIViewController) -> Bool { - let canSelect = sources[sections[indexPath.section]]?.canSelect(itemAt: indexPath.row, inTableView: tableView) ?? false - if canSelect { - sources[sections[indexPath.section]]?.didSelect(itemAt: indexPath.row, inTableView: tableView, presentFrom: viewController) - } + let shouldCloseMenu: Bool = sources[sections[indexPath.section]]?.didSelect(itemAt: indexPath, inTableView: tableView, presentFrom: viewController) ?? true - return canSelect + return shouldCloseMenu } } diff --git a/Canary/Canary/Menu/MenuDisplayable.swift b/Canary/Canary/Menu/MenuDisplayable.swift index 3f55a0a30..21191a0be 100644 --- a/Canary/Canary/Menu/MenuDisplayable.swift +++ b/Canary/Canary/Menu/MenuDisplayable.swift @@ -1,7 +1,7 @@ // // MenuDisplayable.swift // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -37,20 +37,18 @@ protocol MenuDisplayable { /** Performs an optional selection action for the menu item - - Parameter index: Menu item index assumed to be in bounds + - Parameter indexPath: Menu item indexPath assumed to be in bounds - Parameter tableView: `UITableView` that rendered the item - Parameter viewController: Presenting view controller + - Returns: `true` if the menu should collapse when selected; `false` otherwise. */ - func didSelect(itemAt index: Int, inTableView tableView: UITableView, presentFrom viewController: UIViewController) -> Swift.Void - - // MARK: - Menu Cells + func didSelect(itemAt indexPath: IndexPath, inTableView tableView: UITableView, presentFrom viewController: UIViewController) -> Bool /** - Provides a reusable basic menu cell that can be further customized. - - Parameter tableView: `UITableView` to retrieve the cell from - - Returns: A `BasicMenuTableViewCell` + Updates the data source if needed. + - Returns: `true` update happened; `false` otherwise. */ - func basicMenuCell(inTableView tableView: UITableView) -> BasicMenuTableViewCell + func updateIfNeeded() -> Bool } extension MenuDisplayable { @@ -59,19 +57,11 @@ extension MenuDisplayable { return false } - func didSelect(itemAt index: Int, inTableView tableView: UITableView, presentFrom viewController: UIViewController) -> Swift.Void { - return + func didSelect(itemAt indexPath: IndexPath, inTableView tableView: UITableView, presentFrom viewController: UIViewController) -> Bool { + return true } - func basicMenuCell(inTableView tableView: UITableView) -> BasicMenuTableViewCell { - let basicCellReuseIdentifier: String = "BasicMenuTableViewCell" - - var cell = tableView.dequeueReusableCell(withIdentifier: basicCellReuseIdentifier) as? BasicMenuTableViewCell - if cell == nil { - tableView.register(UINib(nibName: basicCellReuseIdentifier, bundle: nil), forCellReuseIdentifier: basicCellReuseIdentifier) - cell = tableView.dequeueReusableCell(withIdentifier: basicCellReuseIdentifier) as? BasicMenuTableViewCell - } - - return cell! + func updateIfNeeded() -> Bool { + return false } } diff --git a/Canary/Canary/Menu/MenuViewController.swift b/Canary/Canary/Menu/MenuViewController.swift index 4f451a773..504614eb0 100644 --- a/Canary/Canary/Menu/MenuViewController.swift +++ b/Canary/Canary/Menu/MenuViewController.swift @@ -1,7 +1,7 @@ // // MenuViewController.swift // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -43,6 +43,17 @@ class MenuViewController: UIViewController { func add(menu: MenuDisplayable) { dataSource.add(menu: menu) } + + /** + Updates the data source if needed. + */ + func updateIfNeeded() { + if dataSource.updateIfNeeded() { + DispatchQueue.main.async { + self.tableView.reloadData() + } + } + } } extension MenuViewController: UITableViewDataSource { @@ -75,6 +86,11 @@ extension MenuViewController: UITableViewDelegate { func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return UITableView.automaticDimension } + + func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? { + let canSelectRow: Bool = dataSource.canSelect(itemAtIndexPath: indexPath, inTableView: tableView) + return (canSelectRow ? indexPath : nil) + } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { defer { @@ -83,10 +99,12 @@ extension MenuViewController: UITableViewDelegate { } // If the menu item was successfully selected, close the menu after presentation - if let container = (UIApplication.shared.delegate as? AppDelegate)?.containerViewController, - let presentingController = container.mainTabBarController, - dataSource.didSelect(itemAtIndexPath: indexPath, inTableView: tableView, presentingFrom: presentingController) { - container.closeMenu(animated: true) + if let containerViewController = containerViewController, + let presentingController = containerViewController.mainTabBarController { + let shouldCloseMenu: Bool = dataSource.didSelect(itemAtIndexPath: indexPath, inTableView: tableView, presentingFrom: presentingController) + if shouldCloseMenu { + containerViewController.closeMenu() + } } } } diff --git a/Canary/Canary/PreferredWidthLabel.swift b/Canary/Canary/PreferredWidthLabel.swift index d25096d44..6a60b10af 100644 --- a/Canary/Canary/PreferredWidthLabel.swift +++ b/Canary/Canary/PreferredWidthLabel.swift @@ -1,7 +1,7 @@ // // PreferredWidthLabel.swift // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/Canary/Canary/Privacy/PrivacyInfoDataSource.swift b/Canary/Canary/Privacy/PrivacyInfoDataSource.swift deleted file mode 100644 index e2be0c896..000000000 --- a/Canary/Canary/Privacy/PrivacyInfoDataSource.swift +++ /dev/null @@ -1,126 +0,0 @@ -// -// PrivacyInfoDataSource.swift -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -import Foundation -import MoPub - -/** - Privacy information section - */ -enum PrivacyInfoSection: String { - case allowableDataCollection = "Allowable Data Collection" - case consented = "Consented Versions" - case current = "Current Versions" -} - -/** - Consent and privacy related keys associated with human readable strings. - The key is named after the property that is being read for the value. - */ -fileprivate enum PrivacyInfo: String { - case canCollectPersonalInfo = "Can Collect PII?" - case currentConsentStatus = "Consent Status" - case isGDPRApplicable = "Is GDPR Applicable?" - case shouldShowConsentDialog = "Should Show Consent Dialog?" - case isWhitelisted = "Is Whitelisted?" - - case currentConsentPrivacyPolicyUrl = "Current Privacy Policy Url" - case currentConsentVendorListUrl = "Current Vendor List Url" - case currentConsentIabVendorListFormat = "Current IAB Vendor List Format" - case currentConsentPrivacyPolicyVersion = "Current Privacy Policy Version" - case currentConsentVendorListVersion = "Current Vendor List Version" - - case previouslyConsentedIabVendorListFormat = "Consented IAB Vendor List Format" - case previouslyConsentedPrivacyPolicyVersion = "Consented Privacy Policy Version" - case previouslyConsentedVendorListVersion = "Consented Vendor List Version" -} - -class PrivacyInfoDataSource { - /** - Section ordering - */ - let sections: [PrivacyInfoSection] = [.allowableDataCollection, .current, .consented] - - /** - Internal dictionary of arrays containing the data mappings. - */ - fileprivate let dataSource: [PrivacyInfoSection: [PrivacyInfo]] - - // MARK: - Initialization - - init() { - // Initialize the data source - dataSource = [ - .allowableDataCollection: [.isGDPRApplicable, - .currentConsentStatus, - .canCollectPersonalInfo, - .shouldShowConsentDialog, - .isWhitelisted], - .consented: [.previouslyConsentedVendorListVersion, - .previouslyConsentedPrivacyPolicyVersion, - .previouslyConsentedIabVendorListFormat], - .current: [.currentConsentVendorListUrl, - .currentConsentVendorListVersion, - .currentConsentPrivacyPolicyUrl, - .currentConsentPrivacyPolicyVersion, - .currentConsentIabVendorListFormat] - ] - } - - // MARK: - Data Retrieval - - /** - Number of items for the section - - Parameter section: Section to query - - Returns: Number of items in the section; otherwise `0` - */ - func count(section: PrivacyInfoSection) -> Int { - return dataSource[section]?.count ?? 0 - } - - /** - Retrieves the name-value item pair for the given index path. - - Parameter indexPath: Index path corresponding to the section and position within the section of the - item to retrieve. - - Returns: A name-value tuple if successful; `nil` otherwise. - */ - func item(atIndexPath indexPath: IndexPath) -> (name: String, value: String)? { - guard let infoItem: PrivacyInfo = dataSource[sections[indexPath.section]]?[indexPath.row] else { - return nil - } - - let value: String = valueForKey(infoItem) - return (name: infoItem.rawValue, value: value) - } - - /** - Retrieves the current value for the given key. - - Parameter key: Privacy info key - - Returns: The value as a `String`. - */ - fileprivate func valueForKey(_ key: PrivacyInfo) -> String { - let mopub = MoPub.sharedInstance() - - switch key { - case .canCollectPersonalInfo: return String(mopub.canCollectPersonalInfo) - case .currentConsentIabVendorListFormat: return mopub.currentConsentIabVendorListFormat ?? "" - case .currentConsentPrivacyPolicyUrl: return mopub.currentConsentPrivacyPolicyUrl()?.absoluteString ?? "" - case .currentConsentPrivacyPolicyVersion: return mopub.currentConsentPrivacyPolicyVersion ?? "" - case .currentConsentStatus: return mopub.currentConsentStatus.description - case .currentConsentVendorListUrl: return mopub.currentConsentVendorListUrl()?.absoluteString ?? "" - case .currentConsentVendorListVersion: return mopub.currentConsentVendorListVersion ?? "" - case .isGDPRApplicable: return mopub.isGDPRApplicable.description - case .previouslyConsentedIabVendorListFormat: return mopub.previouslyConsentedIabVendorListFormat ?? "" - case .previouslyConsentedPrivacyPolicyVersion: return mopub.previouslyConsentedPrivacyPolicyVersion ?? "" - case .previouslyConsentedVendorListVersion: return mopub.previouslyConsentedVendorListVersion ?? "" - case .shouldShowConsentDialog: return String(mopub.shouldShowConsentDialog) - case .isWhitelisted: return String(MPConsentManager.shared().isWhitelisted) - } - } -} - diff --git a/Canary/Canary/Privacy/PrivacyInfoViewController.swift b/Canary/Canary/Privacy/PrivacyInfoViewController.swift deleted file mode 100644 index 52e82ac87..000000000 --- a/Canary/Canary/Privacy/PrivacyInfoViewController.swift +++ /dev/null @@ -1,72 +0,0 @@ -// -// PrivacyInfoViewController.swift -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -import UIKit - -class PrivacyInfoViewController: UIViewController { - // MARK: - IBOutlets - @IBOutlet weak var buttonToolbar: UIToolbar! - - // MARK: - Properties - - /** - Data source for privacy information. - */ - private let dataSource: PrivacyInfoDataSource = PrivacyInfoDataSource() - - // MARK: - View Life Cycle - - override func viewDidLoad() { - super.viewDidLoad() - - // Configure the toolbar to have a transparent background - buttonToolbar.setBackgroundImage(UIImage(), forToolbarPosition: .any, barMetrics: .default) - buttonToolbar.setShadowImage(UIImage(), forToolbarPosition: .any) - } - - // MARK: - IBActions - - @IBAction func onCloseButtonPressed(_ sender: Any) { - presentingViewController?.dismiss(animated: true, completion: nil) - } -} - -extension PrivacyInfoViewController: UITableViewDataSource { - // MARK: - UITableViewDataSource - - func numberOfSections(in tableView: UITableView) -> Int { - return dataSource.sections.count - } - - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return dataSource.count(section: dataSource.sections[section]) - } - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - guard let item = dataSource.item(atIndexPath: indexPath) else { - return UITableViewCell() - } - - let section = dataSource.sections[indexPath.section] - let reuseIdentifier: String = (section == .allowableDataCollection ? "InfoDisplayTableViewCell" : "LongInfoDisplayTableViewCell") - let cell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: reuseIdentifier, for: indexPath) - cell.textLabel?.text = item.name - cell.detailTextLabel?.text = item.value - return cell - } - - func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { - return dataSource.sections[section].rawValue - } -} - -extension PrivacyInfoViewController: UITableViewDelegate { - func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { - return UITableView.automaticDimension - } -} diff --git a/Canary/Canary/Privacy/PrivacyMenuDataSource.swift b/Canary/Canary/Privacy/PrivacyMenuDataSource.swift deleted file mode 100644 index 25ff89982..000000000 --- a/Canary/Canary/Privacy/PrivacyMenuDataSource.swift +++ /dev/null @@ -1,90 +0,0 @@ -// -// PrivacyMenuDataSource.swift -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -import UIKit -import MoPub - -fileprivate enum PrivacyMenuOptions: String { - case information = "Information" - case grantConsent = "Grant Consent" - case revokeConsent = "Revoke Consent" - case forceGDPRApplies = "Force GDPR Applicable" -} - -class PrivacyMenuDataSource { - fileprivate let items: [PrivacyMenuOptions] = [.information, .grantConsent, .revokeConsent, .forceGDPRApplies] -} - -extension PrivacyMenuDataSource: MenuDisplayable { - /** - Number of menu items available - */ - var count: Int { - return items.count - } - - /** - Human-readable title for the menu grouping - */ - var title: String { - return "Privacy" - } - - /** - Provides the rendered cell for the menu item - - Parameter index: Menu item index assumed to be in bounds - - Parameter tableView: `UITableView` that will render the cell - - Returns: A configured `UITableViewCell` - */ - func cell(forItem index: Int, inTableView tableView: UITableView) -> UITableViewCell { - let cell: BasicMenuTableViewCell = basicMenuCell(inTableView: tableView) - let item: PrivacyMenuOptions = items[index] - - cell.accessoryType = (item == .information ? .disclosureIndicator : .none) - cell.title.text = item.rawValue - - return cell - } - - /** - Query if the menu item is selectable - - Parameter index: Menu item index assumed to be in bounds - - Parameter tableView: `UITableView` that rendered the item - - Returns: `true` if selectable; `false` otherwise - */ - func canSelect(itemAt index: Int, inTableView tableView: UITableView) -> Bool { - return true - } - - /** - Performs an optional selection action for the menu item - - Parameter index: Menu item index assumed to be in bounds - - Parameter tableView: `UITableView` that rendered the item - - Parameter viewController: Presenting view controller - */ - func didSelect(itemAt index: Int, inTableView tableView: UITableView, presentFrom viewController: UIViewController) -> Swift.Void { - switch items[index] { - case .information: - guard let privacyInfoViewController: PrivacyInfoViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "PrivacyInfoViewController") as? PrivacyInfoViewController else { - break - } - - viewController.present(privacyInfoViewController, animated: true, completion: nil) - break - case .grantConsent: - MoPub.sharedInstance().grantConsent() - break - case .revokeConsent: - MoPub.sharedInstance().revokeConsent() - break - case .forceGDPRApplies: - MoPub.sharedInstance().forceGDPRApplicable() - break; - } - } -} diff --git a/Canary/Canary/QRCodeCameraInterfaceViewController.swift b/Canary/Canary/QRCodeCameraInterfaceViewController.swift new file mode 100644 index 000000000..d2ef217cd --- /dev/null +++ b/Canary/Canary/QRCodeCameraInterfaceViewController.swift @@ -0,0 +1,201 @@ +// +// QRCodeCameraInterfaceViewController.swift +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +import UIKit +import AVFoundation + +class QRCodeCameraInterfaceViewController: UIViewController { + + static let defaultMediaType = AVMediaType.video + + /** + This view encapsulates the camera feed. + */ + @IBOutlet weak var cameraFeedView: UIView! + + /** + The AVCaptureSession object used in capturing the camera feed. + */ + let captureSession: AVCaptureSession + + /** + The `AVCaptureVideoPreviewLayer` object used to display the camera feed. `AVCaptureVideoPreviewLayer` subclasses + `CALayer`, and `videoPreviewLayer` is attached to `cameraFeedView` by `setUpCamera()`. + */ + let videoPreviewLayer: AVCaptureVideoPreviewLayer + + /** + The dispatch queue used for `metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection)` delegate method calls + */ + let cameraDispatchQueue = DispatchQueue(label: "Camera Dispatch Queue") + + // Always leave the status bar hidden because this is a fullscreen camera feed. + override var prefersStatusBarHidden: Bool { + return true + } + + // Lock to portrait. While the camera feed view will not rotate with the phone, the camera feed will rotate + // with the phone, and QR codes will read regardless of orientation. + override var supportedInterfaceOrientations: UIInterfaceOrientationMask { + return .portrait + } + + required init?(coder aDecoder: NSCoder) { + captureSession = AVCaptureSession() + videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession) + + super.init(coder: aDecoder) + } + + override func viewDidLoad() { + super.viewDidLoad() + + // Start setting up the camera feed + requestCameraAccess(authorized: setUpCamera, denied: { + self.dismissCamera(completion: nil) + }) + } + + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + + // Lay out the camera feed layer when autolayout kicks in + layoutVideoPreviewLayer() + } + + /** + When the close button is pressed, dismiss the camera. + */ + @IBAction func closeButtonAction(_ sender: Any) { + dismissCamera(completion: nil) + } + + /** + If needed, requests access to camera, and then calls the `authorized` block or the `denied` block depending on + whether access was granted or denied. If access has already been granted or denied, `authorized` or `denied` are + called immediately. + */ + fileprivate func requestCameraAccess(authorized: (() -> Void)?, denied: (() -> Void)?) { + // Call `authorized` block if access has already been authorized + if AVCaptureDevice.authorizationStatus(for: QRCodeCameraInterfaceViewController.defaultMediaType) == .authorized { + authorized?() + return + } + + // Call `denied` block if access has already been denied + if AVCaptureDevice.authorizationStatus(for: QRCodeCameraInterfaceViewController.defaultMediaType) == .denied { + denied?() + return + } + + // If access is `notDetermined`, request access + guard AVCaptureDevice.authorizationStatus(for: QRCodeCameraInterfaceViewController.defaultMediaType) == .notDetermined else { + return + } + + AVCaptureDevice.requestAccess(for: QRCodeCameraInterfaceViewController.defaultMediaType) { granted in + DispatchQueue.main.async { + if granted { + authorized?() + } else { + denied?() + } + } + } + } + + /** + Starts up the camera. If the camera cannot be set up successfully, `dismissCamera` is called. + */ + fileprivate func setUpCamera() { + // Get the default video capture device object + guard let captureDevice = AVCaptureDevice.default(for: QRCodeCameraInterfaceViewController.defaultMediaType) else { + dismissCamera(completion: nil) + return + } + + // Make an input stream with the capture device + let input: AVCaptureDeviceInput + do { + input = try AVCaptureDeviceInput(device: captureDevice) + } catch { + dismissCamera(completion: nil) + return + } + // Add the input to the capture session + captureSession.addInput(input) + + // Make an output stream to capture QR codes + let output = AVCaptureMetadataOutput() + captureSession.addOutput(output) + output.setMetadataObjectsDelegate(self, queue: cameraDispatchQueue) + output.metadataObjectTypes = [.qr] + + // Set up the video preview layer in the camera feed view + videoPreviewLayer.videoGravity = .resizeAspectFill + layoutVideoPreviewLayer() + cameraFeedView.layer.addSublayer(videoPreviewLayer) + + // Start the capture session + captureSession.startRunning() + } + + /** + Lays out the `videoPreviewLayer` layer within `cameraFeedView` + */ + fileprivate func layoutVideoPreviewLayer() { + videoPreviewLayer.frame = cameraFeedView.bounds + } + + /** + Ends the capture session if a capture session is running, then dismisses the view controller. + - Parameter completion: completion closure called after the view controller dismisses. + */ + fileprivate func dismissCamera(completion: (() -> Void)?) { + if captureSession.isRunning { + captureSession.stopRunning() + } + dismiss(animated: true, completion: completion) + } + +} + +extension QRCodeCameraInterfaceViewController: AVCaptureMetadataOutputObjectsDelegate { + + func metadataOutput(_ output: AVCaptureMetadataOutput, + didOutput metadataObjects: [AVMetadataObject], + from connection: AVCaptureConnection) { + guard let savedAdSplitViewController = savedAdSplitViewController else { + return + } + + for metadataObject in metadataObjects { + // We do not care about metadata objects that cannot be converted to URLs or that are not QR codes, and we + // do not care about URLs that cannot be validated into ad units. + guard let metadataObject = metadataObject as? AVMetadataMachineReadableCodeObject, + let urlString = metadataObject.stringValue, + let url = URL(string: urlString), + let adUnit = AdUnit(url: url), + metadataObject.type == .qr else { + continue + } + + // If we find a usable URL, open it, close the camera, and break the loop. + DispatchQueue.main.async { + self.dismissCamera { + // Open `adUnit` via the same path that deep linking uses + SceneDelegate.openMoPubAdUnit(adUnit: adUnit, + onto: savedAdSplitViewController, + shouldSave: true) + } + } + break + } + } + +} diff --git a/Canary/Canary/Renderer/NativeAdRendererManager.swift b/Canary/Canary/Renderer/NativeAdRendererManager.swift new file mode 100644 index 000000000..92a0a27ce --- /dev/null +++ b/Canary/Canary/Renderer/NativeAdRendererManager.swift @@ -0,0 +1,184 @@ +// +// NativeAdRendererManager.swift +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +import Foundation +import MoPub +import MoPub_AdMob_Adapters +import MoPub_FacebookAudienceNetwork_Adapters +import MoPub_Flurry_Adapters + +final class NativeAdRendererManager { + static let shared = NativeAdRendererManager() + private let userDefaults: UserDefaults + + /** + Class name of enabled renderers. + */ + var enabledRendererClassNames: [String] { + get { + return userDefaults.enabledAdRenderers + } + set { + userDefaults.enabledAdRenderers = newValue + } + } + + /** + Class name of disabled renderers. + */ + var disabledRendererClassNames: [String] { + get { + return userDefaults.disabledAdRenderers + } + set { + userDefaults.disabledAdRenderers = newValue + } + } + + init(userDefaults: UserDefaults = .standard) { + self.userDefaults = userDefaults + + let existedRendererClassNames = Set(enabledRendererClassNames + disabledRendererClassNames) + let newRendererClassNames = rendererConfigurations.compactMap { + existedRendererClassNames.contains($0.rendererClassName) ? nil : $0.rendererClassName + } + if !newRendererClassNames.isEmpty { + // enable all new renderers by default + enabledRendererClassNames += newRendererClassNames + } + } + + /** + Enabled ad renderer configurations. + */ + var enabledRendererConfigurations: [MPNativeAdRendererConfiguration] { + let disabledAdRenderers = Set(disabledRendererClassNames) + let configs = rendererConfigurations.filter { + !disabledAdRenderers.contains($0.rendererClassName) + } + return (configs as [StringKeyable]).sorted(inTheSameOrderAs: enabledRendererClassNames) + } +} + +// MARK: - Private + +private extension NativeAdRendererManager { + /** + Ad renderer configurations. + */ + var rendererConfigurations: [MPNativeAdRendererConfiguration] { + let networkRendererConfigurations = self.networkRendererConfigurations // cache computed var result + var configs = [MPNativeAdRendererConfiguration]() + configs.append(MOPUBNativeVideoAdRenderer.rendererConfiguration(with: mopubVideoRendererSettings)) + configs.append(contentsOf: networkRendererConfigurations) + configs.append({ // add `MPStaticNativeAdRenderer` + var networkSupportedCustomEvents = Set() // add the custom event names to `MPStaticNativeAdRenderer` + networkRendererConfigurations.forEach { + networkSupportedCustomEvents.formUnion($0.supportedCustomEvents as? [String] ?? []) + } + return MPStaticNativeAdRenderer.rendererConfiguration(with: mopubRendererSettings, + additionalSupportedCustomEvents: Array(networkSupportedCustomEvents)) + }()) + + return configs + } + + /** + MoPub static ad renderer settings + */ + var mopubRendererSettings: MPStaticNativeAdRendererSettings { + // MoPub static renderer + let mopubSettings: MPStaticNativeAdRendererSettings = MPStaticNativeAdRendererSettings() + mopubSettings.renderingViewClass = NativeAdView.self + mopubSettings.viewSizeHandler = { (width) -> CGSize in + return CGSize(width: width, height: 275) + } + + return mopubSettings + } + + /** + MoPub video ad renderer settings + */ + var mopubVideoRendererSettings: MOPUBNativeVideoAdRendererSettings { + // MoPub video renderer + let mopubVideoSettings: MOPUBNativeVideoAdRendererSettings = MOPUBNativeVideoAdRendererSettings() + mopubVideoSettings.renderingViewClass = NativeAdView.self + mopubVideoSettings.viewSizeHandler = { (width) -> CGSize in + return CGSize(width: width, height: 275) + } + + return mopubVideoSettings + } + + /** + Renderers for mediated networks + */ + var networkRendererConfigurations: [MPNativeAdRendererConfiguration] { + var renderers: [MPNativeAdRendererConfiguration] = [] + + // OPTIONAL: AdMob native renderer + if let admobConfig = MPGoogleAdMobNativeRenderer.rendererConfiguration(with: mopubRendererSettings) { + renderers.append(admobConfig) + } + + renderers.append(FacebookNativeAdRenderer.rendererConfiguration(with: mopubRendererSettings)) + + // OPTIONAL: Flurry native video renderer + if let flurryConfig = FlurryNativeVideoAdRenderer.rendererConfiguration(with: mopubVideoRendererSettings) { + renderers.append(flurryConfig) + } + + return renderers + } +} + +// MARK: - Private UserDefaults Storage + +private extension UserDefaults { + /** + The private `UserDefaults.Key` for accessing `enabledAdRenderers`. + */ + private var enabledAdRenderersKey: Key<[String]> { + return Key<[String]>("enabledAdRenderers", defaultValue: []) + } + + /** + A list class names of enabled native ad renderer. When picking a renderer for native ads, the + first match in this list should be picked. + */ + var enabledAdRenderers: [String] { + get { + return self[enabledAdRenderersKey] + } + set { + self[enabledAdRenderersKey] = newValue + } + } +} + +private extension UserDefaults { + /** + The private `UserDefaults.Key` for accessing `disabledAdRenderers`. + */ + private var disabledAdRenderersKey: Key<[String]> { + return Key<[String]>("disabledAdRenderers", defaultValue: []) + } + + /** + A list of class names of disabled native ad renderer. + */ + var disabledAdRenderers: [String] { + get { + return self[disabledAdRenderersKey] + } + set { + self[disabledAdRenderersKey] = newValue + } + } +} diff --git a/Canary/Canary/Renderer/NativeAdRendererMenuDataSource.swift b/Canary/Canary/Renderer/NativeAdRendererMenuDataSource.swift new file mode 100644 index 000000000..0f04cdac4 --- /dev/null +++ b/Canary/Canary/Renderer/NativeAdRendererMenuDataSource.swift @@ -0,0 +1,99 @@ +// +// NativeAdRendererMenuDataSource.swift +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +import MoPub +import UIKit + +struct NativeAdRendererMenuDataSource { + enum Item: CaseIterable { + case changeRendererPreferenceOrder + + var cellAccessoryType: UITableViewCell.AccessoryType { + switch self { + case .changeRendererPreferenceOrder: + return .disclosureIndicator + } + } + + var cellTitle: String { + switch self { + case .changeRendererPreferenceOrder: + return "Change Order" + } + } + } +} + +extension NativeAdRendererMenuDataSource: MenuDisplayable { + /** + Human-readable title for the menu grouping + */ + var title: String { + return "Native Renderer" + } + + /** + Number of menu items available + */ + var count: Int { + return Item.allCases.count + } + + /** + Provides the rendered cell for the menu item + - Parameter index: Menu item index assumed to be in bounds + - Parameter tableView: `UITableView` that will render the cell + - Returns: A configured `UITableViewCell` + */ + func cell(forItem index: Int, inTableView tableView: UITableView) -> UITableViewCell { + let item = Item.allCases[index] + let cell = tableView.dequeueCellFromNib(cellType: BasicMenuTableViewCell.self) + cell.accessoryType = item.cellAccessoryType + cell.title.text = item.cellTitle + return cell + } + + /** + Query if the menu item is selectable + - Parameter index: Menu item index assumed to be in bounds + - Parameter tableView: `UITableView` that rendered the item + - Returns: `true` if selectable; `false` otherwise + */ + func canSelect(itemAt index: Int, inTableView tableView: UITableView) -> Bool { + return true + } + + /** + Performs an optional selection action for the menu item + - Parameter indexPath: Menu item indexPath assumed to be in bounds + - Parameter tableView: `UITableView` that rendered the item + - Parameter viewController: Presenting view controller + - Returns: `true` if the menu should collapse when selected; `false` otherwise. + */ + func didSelect(itemAt indexPath: IndexPath, inTableView tableView: UITableView, presentFrom viewController: UIViewController) -> Bool { + let sections = [OrderPreferenceViewController.DataSource.Section(header: "Enabled (first match is used)", + items: NativeAdRendererManager.shared.enabledRendererClassNames), + OrderPreferenceViewController.DataSource.Section(header: "Disabled", + items: NativeAdRendererManager.shared.disabledRendererClassNames)] + let dataSource = OrderPreferenceViewController.DataSource(title: "Native Ad Renderer Preference", sections: sections) + + let vc = OrderPreferenceViewController.viewController(dataSource: dataSource, orderChangedHandler: { dataSource in + if dataSource.sections.count == 2, // enabled and disabled + dataSource.sections[0].items.contains(MPStaticNativeAdRenderer.className) { + NativeAdRendererManager.shared.enabledRendererClassNames = dataSource.sections[0].items + NativeAdRendererManager.shared.disabledRendererClassNames = dataSource.sections[1].items + return (true, nil) + } else { + return (false, "`\(MPStaticNativeAdRenderer.className)` has to be enabled") + } + }) + viewController.present(vc, animated: true, completion: nil) + + return true + } +} diff --git a/Canary/Canary/Renderer/OrderPreferenceViewController.swift b/Canary/Canary/Renderer/OrderPreferenceViewController.swift new file mode 100644 index 000000000..412a7d105 --- /dev/null +++ b/Canary/Canary/Renderer/OrderPreferenceViewController.swift @@ -0,0 +1,141 @@ +// +// OrderPreferenceViewController.swift +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +import UIKit + +/** + This is a multi-section table view controller for reordering items. + */ +final class OrderPreferenceViewController: UIViewController { + struct DataSource { + struct Section { + let header: String? + var items: [String] + } + + let title: String + var sections: [Section] + } + + typealias OrderChangedHandlerResponse = (shouldDismiss: Bool, message: String?) + typealias OrderChangedHandler = (DataSource) -> OrderChangedHandlerResponse + + private var dataSource: DataSource + private let orderChangedHandler: OrderChangedHandler + + private lazy var tableView: UITableView = { + let tableView = UITableView(frame: .zero, style: .grouped) + tableView.dataSource = self + tableView.delegate = self + tableView.isEditing = true + tableView.register(UITableViewCell.self, forCellReuseIdentifier: UITableViewCell.className) + return tableView + }() + + required init?(coder aDecoder: NSCoder) { + fatalError("Programmatic instantiation only") + } + + private init(dataSource: DataSource, orderChangedHandler: @escaping OrderChangedHandler) { + self.dataSource = dataSource + self.orderChangedHandler = orderChangedHandler + super.init(nibName: nil, bundle: nil) + self.title = dataSource.title + setUpView() + modalPresentationStyle = .formSheet + } + + /** + Convenience instantiation function. Use this instead of `init`. + */ + static func viewController(dataSource: DataSource, orderChangedHandler: @escaping OrderChangedHandler) -> UIViewController { + let viewController = OrderPreferenceViewController(dataSource: dataSource, orderChangedHandler: orderChangedHandler) + let navigationController = UINavigationController(rootViewController: viewController) + return navigationController + } +} + +// MARK: - UITableViewDataSource + +extension OrderPreferenceViewController: UITableViewDataSource { + func numberOfSections(in tableView: UITableView) -> Int { + return dataSource.sections.count + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return dataSource.sections[section].items.count + } + + func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { + return dataSource.sections[section].header + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: UITableViewCell.className, for: indexPath) + cell.textLabel?.text = dataSource.sections[indexPath.section].items[indexPath.item] + return cell + } + + func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) { + let movedItem = dataSource.sections[sourceIndexPath.section].items.remove(at: sourceIndexPath.item) + dataSource.sections[destinationIndexPath.section].items.insert(movedItem, at: destinationIndexPath.item) + } +} + +// MARK: - UITableViewDelegate + +extension OrderPreferenceViewController: UITableViewDelegate { + func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle { + return .none + } + + func tableView(_ tableView: UITableView, shouldIndentWhileEditingRowAt indexPath: IndexPath) -> Bool { + return false + } +} + +// MARK: - Private + +private extension OrderPreferenceViewController { + func setUpView() { + + navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Cancel", + style: .plain, + target: self, + action: #selector(didHitCancelButton)) + + navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Done", + style: .done, + target: self, + action: #selector(didHitDoneButton)) + + view.addSubview(tableView) + tableView.translatesAutoresizingMaskIntoConstraints = false + tableView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true + tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true + tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true + tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true + } + + @objc func didHitCancelButton() { + dismiss(animated: true, completion: nil) + } + + @objc func didHitDoneButton() { + let response = orderChangedHandler(dataSource) + if response.shouldDismiss { + dismiss(animated: true, completion: nil) + } else { + let alertController = UIAlertController(title: "Sorry", + message: response.message, + preferredStyle: .alert) + alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) + present(alertController, animated: true, completion: nil) + } + } +} diff --git a/Canary/Canary/RoundedButton.swift b/Canary/Canary/RoundedButton.swift index 0df331d55..4ddf5d867 100644 --- a/Canary/Canary/RoundedButton.swift +++ b/Canary/Canary/RoundedButton.swift @@ -1,7 +1,7 @@ // // RoundedButton.swift // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -43,4 +43,51 @@ import UIKit func updateCornerRadius() { layer.cornerRadius = rounded ? frame.size.height / 2 : 0 } + + // MARK: - Overrides + + /** + Previous border color set when the button is disabled + */ + private var originalBorderColor: UIColor? = nil + + /** + Previous background color set when the button is disabled + */ + private var originalBackgroundColor: UIColor? = nil + + override var isEnabled: Bool { + didSet { + // Transition from enabled to disabled + if oldValue && !isEnabled { + // Border color exists, save it's current color and apply the + // disabled color scheme + if let border = borderColor { + originalBorderColor = border + borderColor = titleColor(for: .disabled) + } + + // Background color exists, save it's current color and apply + // the disabled color scheme + if let bgColor = backgroundColor { + originalBackgroundColor = bgColor + backgroundColor = UIColor.lightGray + } + } + // Transition from disabled to enabled + else if !oldValue && isEnabled { + // Border color previously existed, reapply it + if let border = originalBorderColor { + borderColor = border + originalBorderColor = nil + } + + // Background color previously existed, reapply it + if let bgColor = originalBackgroundColor { + backgroundColor = bgColor + originalBackgroundColor = nil + } + } + } + } } diff --git a/Canary/Canary/Samples/SampleAds.plist b/Canary/Canary/Samples/SampleAds.plist index dfe83e58e..50f969381 100644 --- a/Canary/Canary/Samples/SampleAds.plist +++ b/Canary/Canary/Samples/SampleAds.plist @@ -18,11 +18,11 @@ name MRAID Banner adUnitId - 23b49916add211e281c11231392559e4 + ef078b27e11c49bbb87080617a69b970 name - HTML MRECT Banner + HTML Medium Rectangle adUnitId 2aae44d2ab91424d9850870af33e5af7 override_class @@ -46,7 +46,7 @@ name MRAID Interstitial adUnitId - 3aba0056add211e281c11231392559e4 + 9f2859c6726447aa9eaaa43a35ae8682 diff --git a/Canary/Canary/Samples/SampleAdsViewController.swift b/Canary/Canary/Samples/SampleAdsViewController.swift index ee46e447e..cfc48046a 100644 --- a/Canary/Canary/Samples/SampleAdsViewController.swift +++ b/Canary/Canary/Samples/SampleAdsViewController.swift @@ -1,7 +1,7 @@ // // SampleAdsViewController.swift // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/Canary/Canary/SavedAds/SavedAdsDataSource.swift b/Canary/Canary/SavedAds/SavedAdsDataSource.swift index c98cda96e..bef1cb6f3 100644 --- a/Canary/Canary/SavedAds/SavedAdsDataSource.swift +++ b/Canary/Canary/SavedAds/SavedAdsDataSource.swift @@ -1,7 +1,7 @@ // // SavedAdsDataSource.swift // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -11,7 +11,9 @@ import Foundation /** Saved ad units data source */ -class SavedAdsDataSource: AdUnitDataSource { +final class SavedAdsDataSource: AdUnitDataSource { + private let savedAdSectionTitle = "Saved Ads" + // MARK: - Overrides /** @@ -22,13 +24,20 @@ class SavedAdsDataSource: AdUnitDataSource { */ required init(plistName: String = "", bundle: Bundle = Bundle.main) { super.init(plistName: plistName, bundle: bundle) - self.adUnits = ["Saved Ads": SavedAdsManager.sharedInstance.loadSavedAds()] + self.adUnits = [savedAdSectionTitle: SavedAdsManager.sharedInstance.savedAds] } /** Reloads the data source. */ override func reloadData() { - self.adUnits = ["Saved Ads": SavedAdsManager.sharedInstance.loadSavedAds()] + self.adUnits = [savedAdSectionTitle: SavedAdsManager.sharedInstance.savedAds] + } + + /** + Data source sections as human readable text meant for display as section headings to the user. + */ + override var sections: [String] { + return [savedAdSectionTitle] } } diff --git a/Canary/Canary/SavedAds/SavedAdsManager.swift b/Canary/Canary/SavedAds/SavedAdsManager.swift index c2ead8ebc..ed033fbf4 100644 --- a/Canary/Canary/SavedAds/SavedAdsManager.swift +++ b/Canary/Canary/SavedAds/SavedAdsManager.swift @@ -1,76 +1,88 @@ // // SavedAdsManager.swift // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // import Foundation -class SavedAdsManager { - - static let savedAdsKey = "com.mopub.adunitids" +final class SavedAdsManager { + /** + Notify when `savedAds` is changed. + */ + struct DataUpdatedNotification: TypedNotification { + static let name = Notification.Name(rawValue: String(describing: DataUpdatedNotification.self)) + } + /** + Use a singleton to represent the global resource stored in `UserDefaults.standard`. + */ static let sharedInstance = SavedAdsManager() - private var savedAds: [AdUnit] = Array() - - // If a new Ad is added or removed from savedAdsManager, isDirty will be marked as true, which means - // persistent storage (UserDefault) and memory data (stored in savedAds array) have inconsistent data. - // To get updated saved ads, caller needs to invoke method loadSavedAds() again. - var isDirty: Bool - - private init() { - isDirty = false + /** + An sorted array `AdUnit` with the most recently saved ad comes first. + */ + private(set) var savedAds: [AdUnit] + + private let userDefaults: UserDefaults + + init(userDefaults: UserDefaults = .standard) { + self.userDefaults = userDefaults + savedAds = userDefaults.persistentAdUnits } + /** + Add the ad unit and remove duplicate if there is any. + Note: A `DataUpdatedNotification` is posted before returning. + */ func addSavedAd(adUnit: AdUnit) { - removeSavedAd(adUnit: adUnit) - savedAds.append(adUnit) - persistSavedAds() - isDirty = true + savedAds.removeAll(where: { adUnit.id == $0.id }) // avoid duplicate + savedAds.insert(adUnit, at: 0) // latest first + userDefaults.persistentAdUnits = savedAds + DataUpdatedNotification().post() } + + /** + Remove the provided ad unti from persistent storage. + Note: A `DataUpdatedNotification` is posted before returning. + */ + func removeSavedAd(adUnit: AdUnit) { + savedAds.removeAll(where: { adUnit.id == $0.id }) + DataUpdatedNotification().post() + } +} - func loadSavedAds() -> [AdUnit] { - savedAds = [] - guard let encodedSavedAdsData = UserDefaults.standard.object(forKey: SavedAdsManager.savedAdsKey) as? Data else { - return [] - } +// MARK: - Persistent storage with `UserDefaults` - guard let savedAdsFromPersistentStore: [AdUnit] = try? JSONDecoder().decode(Array.self, from: encodedSavedAdsData) else { - return [] - } - savedAds += savedAdsFromPersistentStore - isDirty = false - return savedAds +private extension UserDefaults { + /** + The private `UserDefaults.Key` for accessing `persistentAdUnits`. + */ + private var adUnitDataKey: Key { + return Key("AdUnitData", defaultValue: nil) } - - func removeSavedAd(adUnit: AdUnit) { - if let targetAdUnit = savedAdForId(adId: adUnit.id) { - if let index = savedAds.index(of:targetAdUnit) { - savedAds.remove(at: index) + + var persistentAdUnits: [AdUnit] { + get { + do { + guard let data = self[adUnitDataKey] else { + return [] + } + return try JSONDecoder().decode([AdUnit].self, from: data) + } catch { + print("\(#function) caught error: \(error)") + return [] } - isDirty = true } - } - - private func persistSavedAds() { - let jsonEncoder = JSONEncoder() - let persistData = try? jsonEncoder.encode(savedAds) - let defaults = UserDefaults.standard - defaults.set(persistData, forKey: SavedAdsManager.savedAdsKey) - defaults.synchronize() - } - - private func savedAdForId(adId: String) -> AdUnit? { - var savedAd: AdUnit? - for ad in savedAds { - if ad.id == adId { - savedAd = ad - break + set { + do { + let data = try JSONEncoder().encode(newValue) + self[adUnitDataKey] = data + } catch { + print("\(#function) caught error: \(error)") } } - return savedAd } } diff --git a/Canary/Canary/SavedAds/SavedAdsViewController.swift b/Canary/Canary/SavedAds/SavedAdsViewController.swift index f73bf0118..fd35822be 100644 --- a/Canary/Canary/SavedAds/SavedAdsViewController.swift +++ b/Canary/Canary/SavedAds/SavedAdsViewController.swift @@ -1,32 +1,27 @@ // // SavedAdsViewController.swift // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // import UIKit -class SavedAdsViewController: AdUnitTableViewController { +final class SavedAdsViewController: AdUnitTableViewController { + private var notificationTokens = [Notification.Token]() + // MARK: - View Lifecycle override func viewDidLoad() { - // Initialize the data source before invoking the base class's - // `viewDidLoad()` method. - let dataSource: SavedAdsDataSource = SavedAdsDataSource() - super.initialize(with: dataSource) - + // Initialize the data source before invoking the base class's `viewDidLoad()` method. + super.initialize(with: SavedAdsDataSource()) super.viewDidLoad() - } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - - // If persistent storage (UserDefault) and memory data (stored in savedAds array) have inconsistent data, - // reload tableView data. - if SavedAdsManager.sharedInstance.isDirty { - reloadData() - } + + reloadData() + + notificationTokens.append(SavedAdsManager.DataUpdatedNotification.addObserver { [weak self] _ in + self?.reloadData() + }) } } diff --git a/Canary/Canary/SceneDelegate.swift b/Canary/Canary/SceneDelegate.swift new file mode 100644 index 000000000..cdbfd3cb2 --- /dev/null +++ b/Canary/Canary/SceneDelegate.swift @@ -0,0 +1,322 @@ +// +// SceneDelegate.swift +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +import MoPub +import UIKit + +private let kAppId = "112358" +private let kAdUnitId = "0ac59b0996d947309c33f59d6676399f" + +class SceneDelegate: UIResponder { + /** + Possible modes of this scene delegate. + */ + enum Mode { + /** + This `SceneDelegate` is instantiated but not yet assgined to a particular scene. + */ + case unknown + + /** + This represents the one & only main scene of this app. + */ + case mainScene(mainSceneState: MainSceneState) + + /** + This represents a dedicated scene for showing a ad. + */ + case adViewScene + } + + /** + This is the data container for `Mode.mainScene`. + */ + struct MainSceneState { + /** + Scene container controller. Assignment deferred to `handleMainSceneStart`. + */ + let containerViewController: ContainerViewController + + /** + Saved ads split view controller. Assignment deferred to `handleMainSceneStart`. + */ + let savedAdSplitViewController: UISplitViewController + + #if INTERNAL + let internalState = InternalState() + #endif + + init(containerViewController: ContainerViewController, + savedAdSplitViewController: UISplitViewController) { + self.containerViewController = containerViewController + self.savedAdSplitViewController = savedAdSplitViewController + #if INTERNAL + internalState.initialize(with: containerViewController) + #endif + } + } + + /** + Use this to handle the one-off app init events. + */ + static var didHandleAppInit = false + + /** + Scene window. + */ + var window: UIWindow? + + /** + Current mode of the scene delegates. Should be assigned in `scene(_:willConnectTo:options:)`. + */ + private(set) var mode: Mode = .unknown + + /** + Handle the start event of the main scene. + + Call this to when: + * Pre iOS 13: application did finish launching (as single scene) + * iOS 13+: scene will connect to session + + - Parameter mopub: the target `MoPub` instance + - Parameter adConversionTracker: the target `MPAdConversionTracker` instance + - Parameter userDefaults: the target `UserDefaults` instance + */ + func handleMainSceneStart(mopub: MoPub = .sharedInstance(), + adConversionTracker: MPAdConversionTracker = .shared(), + userDefaults: UserDefaults = .standard) { + // Extract the UI elements for easier manipulation later. Calls to `loadViewIfNeeded()` are + // needed to load any children view controllers before `viewDidLoad()` occurs. + guard let containerViewController = window?.rootViewController as? ContainerViewController else { + fatalError() + } + containerViewController.loadViewIfNeeded() + + guard + let tabBarChildren = containerViewController.mainTabBarController?.viewControllers, + let savedAdSplitViewController = tabBarChildren.first(where: { + $0.tabBarItem.title == "Saved Ads" + }) as? UISplitViewController else { + fatalError() + } + + mode = .mainScene(mainSceneState: MainSceneState(containerViewController: containerViewController, + savedAdSplitViewController: savedAdSplitViewController)) + + if userDefaults.shouldClearCachedNetworks { + mopub.clearCachedNetworks() // do this before initializing the MoPub SDK + print("\(#function) cached networks are cleared") + } + + // Make one-off calls here + if (SceneDelegate.didHandleAppInit == false) { + SceneDelegate.didHandleAppInit = true + + // MoPub SDK initialization + checkAndInitializeSdk(containerViewController: containerViewController) + + // Conversion tracking + adConversionTracker.reportApplicationOpen(forApplicationID: kAppId) + } + } + + /** + Attempts to open a URL. + - Parameter url: the URL to open + - Returns: `true` if successfully open, `false` if not + */ + @discardableResult + func openURL(_ url: URL) -> Bool { + switch mode { + case .mainScene(let mainSceneState): + guard + url.scheme == "mopub", + url.host == "load", + let adUnit = AdUnit(url: url) else { + return false + } + return SceneDelegate.openMoPubAdUnit(adUnit: adUnit, + onto: mainSceneState.savedAdSplitViewController, + shouldSave: true) + case .adViewScene, .unknown: + return false + } + } + + /** + Attempts to open a valid `AdUnit` object instance + - Parameter adUnit: MoPub `AdUnit` object instance + - Parameter splitViewController: Split view controller that will present the opened deep link + - Parameter shouldSave: Flag indicating that the ad unit that was opened should be saved + - Parameter savedAdsManager: The manager for saving the ad unit + - Returns: `true` if successfully shown, `false` if not + */ + @discardableResult + static func openMoPubAdUnit(adUnit: AdUnit, + onto splitViewController: UISplitViewController, + shouldSave: Bool, + savedAdsManager: SavedAdsManager = .sharedInstance) -> Bool { + // Generate the destinate view controller and attempt to push the destination to the + // Saved Ads navigation controller. + guard + let vcClass = NSClassFromString(adUnit.viewControllerClassName) as? AdViewController.Type, + let destination: UIViewController = vcClass.instantiateFromNib(adUnit: adUnit) as? UIViewController else { + return false + } + + DispatchQueue.main.async { + // If the ad unit should be saved, we will switch the tab to the saved ads + // tab and then push the view controller on that navigation stack. + splitViewController.containerViewController?.mainTabBarController?.selectedIndex = 1 + if shouldSave { + savedAdsManager.addSavedAd(adUnit: adUnit) + } + splitViewController.showDetailViewController(destination, sender: splitViewController) + } + return true + } +} + +// MARK: - UIWindowSceneDelegate + +/* + For future `UIWindowSceneDelegate` implementation, if there is a `UIApplicationDelegate` counterpart, + we should share the implementation in `SceneDelegate` for both `UIWindowSceneDelegate` and + `UIApplicationDelegate`. + */ +@available(iOS 13, *) +extension SceneDelegate: UIWindowSceneDelegate { + func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { + // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. + // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. + // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). + guard let windowScene = scene as? UIWindowScene else { + return + } + + if let rootViewController = AdUnit.adViewControllerForSceneConnectionOptions(connectionOptions) { + // load the view programmatically + let window = UIWindow(windowScene: windowScene) + window.rootViewController = rootViewController + window.makeKeyAndVisible() + self.window = window + self.mode = .adViewScene + } else { + handleMainSceneStart() + } + } + + func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { + // Options are specified in the UIApplication.h section for openURL options. + // An empty options dictionary will result in the same behavior as the older openURL call, + // aside from the fact that this is asynchronous and calls the completion handler rather + // than returning a result. The completion handler is called on the main queue. + for urlContext in URLContexts { + openURL(urlContext.url) + } + } +} + +// MARK: - Private App Init + +private extension SceneDelegate { + /** + Check if the Canary app has a cached ad unit ID for consent. If not, the app will present an alert dialog allowing custom ad unit ID entry. + - Parameter containerViewController: the main container view controller + - Parameter userDefaults: the target `UserDefaults` instance + */ + func checkAndInitializeSdk(containerViewController: ContainerViewController, userDefaults: UserDefaults = .standard) { + // Already have a valid cached ad unit ID for consent. Just initialize the SDK. + if userDefaults.cachedAdUnitId.isEmpty { + // Need to prompt for an ad unit. + let prompt = UIAlertController(title: "MoPub SDK initialization", + message: "Enter an ad unit ID to use for consent:", + preferredStyle: .alert) + var adUnitIdTextField: UITextField? + prompt.addTextField { textField in + textField.placeholder = "Ad Unit ID" + adUnitIdTextField = textField // Capture the text field so we can later read the value + } + prompt.addAction(UIAlertAction(title: "Use default ID", style: .destructive) { _ in + userDefaults.cachedAdUnitId = kAdUnitId; + self.initializeMoPubSdk(adUnitIdForConsent: kAdUnitId, + containerViewController: containerViewController) + }) + prompt.addAction(UIAlertAction(title: "Use inputted ID", style: .default) { _ in + let adUnitID = adUnitIdTextField?.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""; + userDefaults.cachedAdUnitId = adUnitID; + self.initializeMoPubSdk(adUnitIdForConsent: adUnitID, + containerViewController: containerViewController); + }) + + DispatchQueue.main.async { + containerViewController.present(prompt, animated: true, completion: nil) + } + } else { // has a cached ad unit ID + initializeMoPubSdk(adUnitIdForConsent: userDefaults.cachedAdUnitId, + containerViewController: containerViewController) + } + } + + /** + Initializes the MoPub SDK with the given ad unit ID used for consent management. + - Parameter adUnitIdForConsent: This value must be a valid ad unit ID associated with your app. + - Parameter containerViewController: the main container view controller + - Parameter mopub: the target `MoPub` instance + */ + func initializeMoPubSdk(adUnitIdForConsent: String, + containerViewController: ContainerViewController, + mopub: MoPub = .sharedInstance()) { + // MoPub SDK initialization + let sdkConfig = MPMoPubConfiguration(adUnitIdForAppInitialization: adUnitIdForConsent) + sdkConfig.globalMediationSettings = [] + sdkConfig.loggingLevel = .info + + mopub.initializeSdk(with: sdkConfig) { + // Update the state of the menu now that the SDK has completed initialization. + if let menuController = containerViewController.menuViewController { + menuController.updateIfNeeded() + } + + // Request user consent to collect personally identifiable information + // used for targeted ads + if let tabBarController = containerViewController.mainTabBarController { + SceneDelegate.displayConsentDialog(from: tabBarController) + } + } + } +} + +// MARK: - Private Helpers + +private extension SceneDelegate { + /** + Loads the consent request dialog (if not already loaded), and presents the dialog + from the specified view controller. If user consent is not needed, nothing is done. + - Parameter presentingViewController: `UIViewController` used for presenting the dialog + - Parameter mopub: the target `MoPub` instance + */ + static func displayConsentDialog(from presentingViewController: UIViewController, + mopub: MoPub = .sharedInstance()) { + // Verify that we need to acquire consent. + guard mopub.shouldShowConsentDialog else { + return + } + + // Load the consent dialog if it's not available. If it is already available, + // the completion block will immediately fire. + mopub.loadConsentDialog { (error: Error?) in + guard error == nil else { + print("Consent dialog failed to load: \(String(describing: error?.localizedDescription))") + return + } + + mopub.showConsentDialog(from: presentingViewController, completion: nil) + } + } +} diff --git a/Canary/Canary/StoryboardInstantiable.swift b/Canary/Canary/StoryboardInstantiable.swift index 9e445c51e..c7073bc15 100644 --- a/Canary/Canary/StoryboardInstantiable.swift +++ b/Canary/Canary/StoryboardInstantiable.swift @@ -1,7 +1,7 @@ // // StoryboardInstantiable.swift // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/Canary/Canary/TableViewCellRegisterable.swift b/Canary/Canary/TableViewCellRegisterable.swift index 4c9541f7c..0d60423e9 100644 --- a/Canary/Canary/TableViewCellRegisterable.swift +++ b/Canary/Canary/TableViewCellRegisterable.swift @@ -1,7 +1,7 @@ // // TableViewCellRegisterable.swift // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -9,25 +9,23 @@ import UIKit /** - Provides a standard way of registering a `UITableViewCell` with associated - Nib with a `UITableView`. + Provides a standard way of registering a `UITableViewCell` with associated Nib with a `UITableView`. + + Note: The default implementation of `register(with tableView:)` expects the cell to have a + corresponding nib file that has the file name that matches the class name. The class name is also + provided to the table view as reuse identifier when registring the cell. */ -protocol TableViewCellRegisterable { +protocol TableViewCellRegisterable: UITableViewCell { /** - Constant representing a default reuseable table cell ID. - */ - static var reuseId: String { get } - - /** - Registers this table cell with a given table using the `reuseId` constant. + Registers this table cell with a given table using `className`. - Parameter tableView: A valid table to register this cell. */ - static func register(with tableView: UITableView) -> Void + static func register(with tableView: UITableView) } extension TableViewCellRegisterable { - static func register(with tableView: UITableView) -> Void { - let nib = UINib(nibName: reuseId, bundle: nil) - tableView.register(nib, forCellReuseIdentifier: reuseId) + static func register(with tableView: UITableView) { + let nib = UINib(nibName: className, bundle: nil) + tableView.register(nib, forCellReuseIdentifier: className) } } diff --git a/Canary/Canary/Utility/TypedNotification.swift b/Canary/Canary/Utility/TypedNotification.swift new file mode 100644 index 000000000..b918b2c18 --- /dev/null +++ b/Canary/Canary/Utility/TypedNotification.swift @@ -0,0 +1,86 @@ +// +// TypedNotification.swift +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +import Foundation + +protocol TypedNotification { + /** + Name of the notification. + */ + static var name: Notification.Name { get } + + /** + An optional object that acts as the one posting the notification. + */ + var postingObject: Any? { get } + + /** + The target notification center. + */ + var notificationCenter: NotificationCenter { get } +} + +// MARK: - Default `TypedNotification` implementation + +extension TypedNotification { + /** + This is the key for accessing the `TypedNotification` stored in `Notification.userInfo`. + */ + private static var userInfoKey: String { + return "TypedNotification" + } + + /** + Default value: nil + */ + var postingObject: Any? { + return nil + } + + /** + Default value: `.default` + */ + var notificationCenter: NotificationCenter { + return .default + } + + /** + Post the notification. + */ + func post() { + notificationCenter.post(name: Self.name, object: postingObject, userInfo: [Self.userInfoKey: self]) + } + + /** + Add an observer for the notification. + + Note 1: The default handling queue is `OperationQueue.main`. + + Note 2: The returned `Notification.Token` has to be retained to keep the observation active. If + the returned token is deallocated, the observer is removed from notification center as well. + + - Parameter notificationCenter: targeted `NotificationCenter`, default to `.default` + - Parameter postingObject: an `Any?` object that acts as the one posting the notification + - Parameter queue: the `OperationQueue` that invokes the handler + - Parameter handler: a notification handler that provides the posted `TypedNotification` + - Returns: a `Notification.Token` that strongly refer to the observer + */ + static func addObserver(notificationCenter: NotificationCenter = .default, + postingObject: Any? = nil, + queue: OperationQueue? = .main, + handler: @escaping (Self) -> Void) -> Notification.Token { + let rawToken = notificationCenter.addObserver(forName: name, object: postingObject, queue: queue) { notification in + guard let userInfo = notification.userInfo, let typedNotification = userInfo[userInfoKey] as? Self else { + assertionFailure("\(#function) unable to obtain a `TypedNotification` from `userInfo`") + return + } + handler(typedNotification) + } + return Notification.Token(rawToken: rawToken, notificationCenter: notificationCenter) + } +} diff --git a/Canary/CanaryUnitTests/Extensions/ArraySortExtensionTests.swift b/Canary/CanaryUnitTests/Extensions/ArraySortExtensionTests.swift new file mode 100644 index 000000000..c6b8290c1 --- /dev/null +++ b/Canary/CanaryUnitTests/Extensions/ArraySortExtensionTests.swift @@ -0,0 +1,147 @@ +// +// ArraySortExtensionTests.swift +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +import XCTest +@testable import Canary + +// Test helper extension +extension String: StringKeyable { + public var key: String { + return self + } +} + +final class ArraySortExtensionTests: XCTestCase { + let referenceKeys = ["1", "2", "3", "4", "5"] + + /** + Test sorting arrays with the same elements. + */ + func testSortingArrayWithSameElementsAsReferenceArray() { + let arraysToSort = [["1", "2", "3", "4", "5"], + ["3", "1", "2", "4", "5"], + ["5", "4", "1", "3", "2"], + ["4", "5", "1", "2", "3"]] + + arraysToSort.forEach { + let result: [String] = ($0 as [StringKeyable]).sorted(inTheSameOrderAs: referenceKeys) + XCTAssertEqual(result, referenceKeys) + } + } + + /** + Test sorting an empty array. + */ + func testSortingEmptyArray() { + XCTAssertTrue(([StringKeyable]().sorted(inTheSameOrderAs: referenceKeys) as [String]).isEmpty) + } + + /** + Test sorting arrays with empty reference array. + */ + func testSortingWithEmptyReferenceArray() { + let arraysToSort = [["1", "2", "3", "4", "5"], + ["3", "1", "2", "4", "5"], + ["5", "4", "1", "3", "2"], + ["4", "5", "1", "2", "3"]] + + arraysToSort.forEach { + let result: [String] = ($0 as [StringKeyable]).sorted(inTheSameOrderAs: []) + XCTAssertEqual(Set(result).count, 5) // order is undefined, so only test the count + } + } + + /** + Test sorting arrays with one extra element that is not in the reference array. + */ + func testSortingArrayWithOneUnexpectedElementThanReferenceArray() { + let extraElement = "extra" + let arraysToSort = [["1", "2", "3", "4", "5", extraElement], + ["3", "1", "2", extraElement, "4", "5"], + [extraElement, "5", "4", "1", "3", "2"]] + let expectedResult = referenceKeys + [extraElement] // the extra one is expected to be at the end + + arraysToSort.forEach { + let result: [String] = ($0 as [StringKeyable]).sorted(inTheSameOrderAs: referenceKeys) + XCTAssertEqual(result, expectedResult) + } + } + + /** + Test sorting arrays with two extra elements that are not in the reference array. + */ + func testSortingArrayWithTwoUnexpectedElementsThanReferenceArray() { + let extraElement1 = "extra1" + let extraElement2 = "extra2" + let arraysToSort = [[extraElement1, "1", "2", "3", "4", "5", extraElement2], + [extraElement2, "3", "1", "2", extraElement1, "4", "5"], + ["5", "4", extraElement2, "1", "3", "2", extraElement1]] + + // The order of unexpected extra elements is undefined (and thus random) + let possibleResult1 = referenceKeys + [extraElement1, extraElement2] + let possibleResult2 = referenceKeys + [extraElement2, extraElement1] + + arraysToSort.forEach { + let result: [String] = ($0 as [StringKeyable]).sorted(inTheSameOrderAs: referenceKeys) + XCTAssertTrue(result == possibleResult1 || result == possibleResult2) + } + } + + /** + Test sorting arrays with less element than the reference array. + */ + func testSortingArrayWithLessElementsThanReferenceArray() { + let arraysToSort = [["1", "3", "5"], + ["3", "1", "5"], + ["5", "1", "3"], + ["1", "5", "3"]] + let expectedResult = ["1", "3", "5"] + + arraysToSort.forEach { + let result: [String] = ($0 as [StringKeyable]).sorted(inTheSameOrderAs: referenceKeys) + XCTAssertEqual(result, expectedResult) + } + } + + /** + Test sorting arrays that are mixed with unexpected elements, and with less element than the reference array. + */ + func testSortingArrayMixedWithUnexpectedElementsAndWithLessElementsThanReferenceArray() { + let extraElement1 = "extra1" + let extraElement2 = "extra2" + let arraysToSort = [[extraElement1, "1", "3", "5", extraElement2], + ["3", extraElement1, "1", extraElement2, "5"], + [extraElement1, "5", extraElement2, "1", "3"], + ["1", extraElement1, "5", "3", extraElement2]] + + // The order of unexpected extra elements is undefined (and thus random) + let possibleResult1 = ["1", "3", "5"] + [extraElement1, extraElement2] + let possibleResult2 = ["1", "3", "5"] + [extraElement2, extraElement1] + + arraysToSort.forEach { + let result: [String] = ($0 as [StringKeyable]).sorted(inTheSameOrderAs: referenceKeys) + XCTAssertTrue(result == possibleResult1 || result == possibleResult2) + } + } + + /** + Test sorting arrays that have duplicate elements in it. + */ + func testSortingArrayWithDuplicateElementsReferenceArray() { + let arraysToSort = [["1", "2", "3", "4", "5", "1", "2", "3", "4", "5"], + ["3", "1", "2", "4", "5", "3", "1", "2", "4", "5"], + ["5", "4", "1", "3", "2", "1", "1", "1", "1", "1"], + ["1", "1", "1", "1", "1", "4", "5", "1", "2", "3"]] + let expectedResult = ["1", "2", "3", "4", "5"] + + arraysToSort.forEach { + let result: [String] = ($0 as [StringKeyable]).sorted(inTheSameOrderAs: referenceKeys) + XCTAssertEqual(result, expectedResult) + } + } +} diff --git a/Canary/CanaryUnitTests/Info.plist b/Canary/CanaryUnitTests/Info.plist new file mode 100644 index 000000000..456f445ec --- /dev/null +++ b/Canary/CanaryUnitTests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(BUNDLE_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 5.9.0 + CFBundleVersion + 5.9.0 + + diff --git a/Canary/CanaryUnitTests/Renderer/NativeAdRendererManagerTests.swift b/Canary/CanaryUnitTests/Renderer/NativeAdRendererManagerTests.swift new file mode 100644 index 000000000..5ff7cccef --- /dev/null +++ b/Canary/CanaryUnitTests/Renderer/NativeAdRendererManagerTests.swift @@ -0,0 +1,87 @@ +// +// NativeAdRendererManagerTests.swift +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +import XCTest +@testable import Canary + +final class NativeAdRendererManagerTests: XCTestCase { + private var userDefaults: UserDefaults! + private var nativeAdRendererManager: NativeAdRendererManager! + + private let defaultRenderers = ["MOPUBNativeVideoAdRenderer", + "MPGoogleAdMobNativeRenderer", + "FacebookNativeAdRenderer", + "FlurryNativeVideoAdRenderer", + "MPStaticNativeAdRenderer"] + + override func setUp() { + userDefaults = UserDefaults(suiteName: #file) + userDefaults.removePersistentDomain(forName: #file) + nativeAdRendererManager = NativeAdRendererManager(userDefaults: userDefaults) + } + + override func tearDown() { + userDefaults.removePersistentDomain(forName: #file) + userDefaults = nil + nativeAdRendererManager = nil + } + + /** + This test expects all default renderers to be enabled. + */ + func testDefaultEnabledRenderers() { + XCTAssertEqual(nativeAdRendererManager.enabledRendererClassNames, defaultRenderers) + } + + /** + This test expects all default renderers to be disabled. + */ + func testDefaultDisabledRenderers() { + XCTAssertEqual(nativeAdRendererManager.disabledRendererClassNames, []) + } + + /** + Test the manager is returning expected renderer configurations + */ + func testDefaultEnabledRenderersConfigurations() { + XCTAssertEqual(nativeAdRendererManager.enabledRendererConfigurations.map { $0.rendererClassName }, defaultRenderers) + } + + /** + Test reading and writing `NativeAdRendererManager.enabledRendererClassNames` and + `NativeAdRendererManager.disabledRendererClassNames`. + */ + func testReadWriteRenderers() { + var enabledRenderers = defaultRenderers + var disabledRenderers = [String]() + + // first pass: disable renderers one by one + repeat { + nativeAdRendererManager.disabledRendererClassNames = disabledRenderers + XCTAssertEqual(nativeAdRendererManager.disabledRendererClassNames, disabledRenderers) + + nativeAdRendererManager.enabledRendererClassNames = enabledRenderers + XCTAssertEqual(nativeAdRendererManager.enabledRendererClassNames, enabledRenderers) + XCTAssertEqual(nativeAdRendererManager.enabledRendererConfigurations.map { $0.rendererClassName }, enabledRenderers) + + disabledRenderers.append(enabledRenderers.removeLast()) + } while !enabledRenderers.isEmpty + + // second pass: enable renderers one by one + repeat { + nativeAdRendererManager.disabledRendererClassNames = disabledRenderers + XCTAssertEqual(nativeAdRendererManager.disabledRendererClassNames, disabledRenderers) + + nativeAdRendererManager.enabledRendererClassNames = enabledRenderers + XCTAssertEqual(nativeAdRendererManager.enabledRendererClassNames, enabledRenderers) + XCTAssertEqual(nativeAdRendererManager.enabledRendererConfigurations.map { $0.rendererClassName }, enabledRenderers) + + enabledRenderers.append(disabledRenderers.removeLast()) + } while !disabledRenderers.isEmpty + } +} diff --git a/Canary/CanaryUnitTests/Renderer/NativeAdRendererManagerTests.swift.plist b/Canary/CanaryUnitTests/Renderer/NativeAdRendererManagerTests.swift.plist new file mode 100644 index 000000000..3967e063f Binary files /dev/null and b/Canary/CanaryUnitTests/Renderer/NativeAdRendererManagerTests.swift.plist differ diff --git a/Canary/CanaryUnitTests/SavedAds/SavedAdsManagerTests.swift b/Canary/CanaryUnitTests/SavedAds/SavedAdsManagerTests.swift new file mode 100644 index 000000000..329b93f3c --- /dev/null +++ b/Canary/CanaryUnitTests/SavedAds/SavedAdsManagerTests.swift @@ -0,0 +1,97 @@ +// +// SavedAdsManagerTests.swift +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +import XCTest +@testable import Canary + +final class CanaryUnitTests: XCTestCase { + private var savedAdsManager: SavedAdsManager! + private var userDefaults: UserDefaults! + private var notificationToken: Notification.Token? + private let sampleAdUnits = [AdUnit(url: URL(string: "mopub://load?adUnitId=100&format=Banner&name=Ad0")!)!, + AdUnit(url: URL(string: "mopub://load?adUnitId=101&format=Banner&name=Ad1")!)!, + AdUnit(url: URL(string: "mopub://load?adUnitId=102&format=Banner&name=Ad2")!)!] + + override func setUp() { + userDefaults = UserDefaults(suiteName: #file) + userDefaults.removePersistentDomain(forName: #file) + savedAdsManager = SavedAdsManager(userDefaults: userDefaults) + } + + override func tearDown() { + userDefaults.removePersistentDomain(forName: #file) + userDefaults = nil + savedAdsManager = nil + } + + /** + This test adds all sample ads to `savedAdsManager` and verify the result after each addition, + and then removes all sample ads and verify the result after each deletion. In addition of + testing the source of truth `savedAdsManager.savedAds`, this test also verify the + `SavedAdsManager.DataUpdatedNotification` posted after each addition and deletion. + */ + func testAddAndRemoveSavedAds() { + var notificationCount = 0 + + // test adding ads + sampleAdUnits.enumerated().forEach { (index, adUnit) in + let verifyAddition = { + XCTAssertEqual(index + 1, self.savedAdsManager.savedAds.count) + XCTAssertTrue(self.savedAdsManager.savedAds.contains(adUnit)) + } + notificationToken = SavedAdsManager.DataUpdatedNotification.addObserver { _ in + verifyAddition() + notificationCount += 1 + } + + savedAdsManager.addSavedAd(adUnit: adUnit) + verifyAddition() + notificationToken = nil + } + XCTAssertEqual(notificationCount, sampleAdUnits.count) // number of additions + + // test deleting ads + sampleAdUnits.enumerated().forEach { (index, adUnit) in + let verifyDeletion = { + XCTAssertEqual(index + 1, self.sampleAdUnits.count - self.savedAdsManager.savedAds.count) + XCTAssertFalse(self.savedAdsManager.savedAds.contains(adUnit)) + } + notificationToken = SavedAdsManager.DataUpdatedNotification.addObserver { _ in + verifyDeletion() + notificationCount += 1 + } + + savedAdsManager.removeSavedAd(adUnit: adUnit) + verifyDeletion() + notificationToken = nil + } + XCTAssertEqual(notificationCount, sampleAdUnits.count * 2) // number of additions + deletions + } + + /** + This test is to verify duplicate ads are impossible. + */ + func testAddDuplicateAd() { + for _ in 0...1 { // repeat once + sampleAdUnits.forEach { + savedAdsManager.addSavedAd(adUnit: $0) + } + XCTAssertEqual(sampleAdUnits.count, savedAdsManager.savedAds.count) + } + } + + /** + This test is to verify removing ads that are not saved would not cause any trouble. + */ + func testRemoveNotSavedAd() { + sampleAdUnits.forEach { + savedAdsManager.removeSavedAd(adUnit: $0) + } + XCTAssertEqual(0, savedAdsManager.savedAds.count) + } +} diff --git a/Canary/CanaryUnitTests/SavedAds/SavedAdsManagerTests.swift.plist b/Canary/CanaryUnitTests/SavedAds/SavedAdsManagerTests.swift.plist new file mode 100644 index 000000000..3967e063f Binary files /dev/null and b/Canary/CanaryUnitTests/SavedAds/SavedAdsManagerTests.swift.plist differ diff --git a/Canary/Podfile b/Canary/Podfile index 02830f6f1..760e93198 100644 --- a/Canary/Podfile +++ b/Canary/Podfile @@ -5,26 +5,80 @@ inhibit_all_warnings! use_frameworks! workspace 'Canary' -pod 'mopub-ios-sdk', :path => '../' +# Class to encapsulate environment variables and provide convenience methods to +# convert the ENV value string to its appropriate type +class MyEnv + TRUTHY_VALUES = %w(t true yes y 1).freeze + + attr_reader :value + + def initialize(name) + @value = ENV[name].to_s.downcase + end + + def to_boolean + return true if TRUTHY_VALUES.include?(value.to_s) + return false + end +end + +# Pods available to all targets in Canary.xcworkspace pod 'LoremIpsum' -# Network mediation adapters -pod 'MoPub-AdColony-Adapters' -pod 'MoPub-AdMob-Adapters' -pod 'MoPub-Applovin-Adapters' -pod 'MoPub-Chartboost-Adapters' -pod 'MoPub-FacebookAudienceNetwork-Adapters' -pod 'MoPub-Flurry-Adapters' -pod 'MoPub-TapJoy-Adapters' -pod 'MoPub-UnityAds-Adapters' -pod 'MoPub-Vungle-Adapters' - -target 'Canary' do - # Pods for Canary +# CocoaPods does not support the ability to include the same pod +# in different targets using different pathing. To support the +# varied needs of CI and local development, the pathing to the +# MoPub owned and operated pods are configured using environment +# variables. +mediation_path = MyEnv.new("DEVELOPMENT_MOPUB_MEDIATION_REPO_PATH").value +use_production_mediation = MyEnv.new("USE_PRODUCTION_MOPUB_MEDIATION").to_boolean +use_production_sdk = MyEnv.new("USE_PRODUCTION_MOPUB_SDK").to_boolean +# MoPub SDK +if use_production_sdk + pod 'mopub-ios-sdk' +else + pod 'mopub-ios-sdk', :path => '../' end -target 'Canary (Internal)' do - # Pods for Canary (Internal) +# Network mediation adapters +if Dir.exists?(mediation_path) && !use_production_mediation + pod 'MoPub-AdColony-Adapters', :path => mediation_path + pod 'MoPub-AdMob-Adapters', :path => mediation_path + pod 'MoPub-Applovin-Adapters', :path => mediation_path + pod 'MoPub-Chartboost-Adapters', :path => mediation_path + pod 'MoPub-FacebookAudienceNetwork-Adapters', :path => mediation_path + pod 'MoPub-Flurry-Adapters', :path => mediation_path + pod 'MoPub-IronSource-Adapters', :path => mediation_path + pod 'MoPub-TapJoy-Adapters', :path => mediation_path + pod 'MoPub-UnityAds-Adapters', :path => mediation_path + pod 'MoPub-Verizon-Adapters', :path => mediation_path + pod 'MoPub-Vungle-Adapters', :path => mediation_path +else + pod 'MoPub-AdColony-Adapters' + pod 'MoPub-AdMob-Adapters' + pod 'MoPub-Applovin-Adapters' + pod 'MoPub-Chartboost-Adapters' + pod 'MoPub-FacebookAudienceNetwork-Adapters' + pod 'MoPub-Flurry-Adapters' + pod 'MoPub-IronSource-Adapters' + pod 'MoPub-TapJoy-Adapters' + pod 'MoPub-UnityAds-Adapters' + pod 'MoPub-Verizon-Adapters' + pod 'MoPub-Vungle-Adapters' +end + +# AppStore target. +target 'AppStore Application' do + # Pods for 'AppStore Application' +end +# Internal target +target 'Internal Application' do + # Pods for 'Internal Application' + + target 'CanaryUnitTests' do + # Unit test target won't be able to link without inheriting the search paths + inherit! :search_paths + end end diff --git a/Gemfile b/Gemfile deleted file mode 100644 index 7e7493d50..000000000 --- a/Gemfile +++ /dev/null @@ -1,4 +0,0 @@ -source "https://rubygems.org" - -gem "cocoapods" -gem "fastlane" diff --git a/Gemfile.lock b/Gemfile.lock deleted file mode 100644 index 921af828e..000000000 --- a/Gemfile.lock +++ /dev/null @@ -1,205 +0,0 @@ -GEM - remote: https://rubygems.org/ - specs: - CFPropertyList (3.0.0) - activesupport (4.2.10) - i18n (~> 0.7) - minitest (~> 5.1) - thread_safe (~> 0.3, >= 0.3.4) - tzinfo (~> 1.1) - addressable (2.5.2) - public_suffix (>= 2.0.2, < 4.0) - atomos (0.1.2) - babosa (1.0.2) - claide (1.0.2) - cocoapods (1.5.3) - activesupport (>= 4.0.2, < 5) - claide (>= 1.0.2, < 2.0) - cocoapods-core (= 1.5.3) - cocoapods-deintegrate (>= 1.0.2, < 2.0) - cocoapods-downloader (>= 1.2.0, < 2.0) - cocoapods-plugins (>= 1.0.0, < 2.0) - cocoapods-search (>= 1.0.0, < 2.0) - cocoapods-stats (>= 1.0.0, < 2.0) - cocoapods-trunk (>= 1.3.0, < 2.0) - cocoapods-try (>= 1.1.0, < 2.0) - colored2 (~> 3.1) - escape (~> 0.0.4) - fourflusher (~> 2.0.1) - gh_inspector (~> 1.0) - molinillo (~> 0.6.5) - nap (~> 1.0) - ruby-macho (~> 1.1) - xcodeproj (>= 1.5.7, < 2.0) - cocoapods-core (1.5.3) - activesupport (>= 4.0.2, < 6) - fuzzy_match (~> 2.0.4) - nap (~> 1.0) - cocoapods-deintegrate (1.0.2) - cocoapods-downloader (1.2.0) - cocoapods-plugins (1.0.0) - nap - cocoapods-search (1.0.0) - cocoapods-stats (1.0.0) - cocoapods-trunk (1.3.0) - nap (>= 0.8, < 2.0) - netrc (~> 0.11) - cocoapods-try (1.1.0) - colored (1.2) - colored2 (3.1.2) - commander-fastlane (4.4.6) - highline (~> 1.7.2) - concurrent-ruby (1.0.5) - declarative (0.0.10) - declarative-option (0.1.0) - domain_name (0.5.20180417) - unf (>= 0.0.5, < 1.0.0) - dotenv (2.5.0) - emoji_regex (0.1.1) - escape (0.0.4) - excon (0.62.0) - faraday (0.15.2) - multipart-post (>= 1.2, < 3) - faraday-cookie_jar (0.0.6) - faraday (>= 0.7.4) - http-cookie (~> 1.0.0) - faraday_middleware (0.12.2) - faraday (>= 0.7.4, < 1.0) - fastimage (2.1.3) - fastlane (2.98.0) - CFPropertyList (>= 2.3, < 4.0.0) - addressable (>= 2.3, < 3.0.0) - babosa (>= 1.0.2, < 2.0.0) - bundler (>= 1.12.0, < 2.0.0) - colored - commander-fastlane (>= 4.4.6, < 5.0.0) - dotenv (>= 2.1.1, < 3.0.0) - emoji_regex (~> 0.1) - excon (>= 0.45.0, < 1.0.0) - faraday (~> 0.9) - faraday-cookie_jar (~> 0.0.6) - faraday_middleware (~> 0.9) - fastimage (>= 2.1.0, < 3.0.0) - gh_inspector (>= 1.1.2, < 2.0.0) - google-api-client (>= 0.21.2, < 0.22.0) - highline (>= 1.7.2, < 2.0.0) - json (< 3.0.0) - mini_magick (~> 4.5.1) - multi_json - multi_xml (~> 0.5) - multipart-post (~> 2.0.0) - plist (>= 3.1.0, < 4.0.0) - public_suffix (~> 2.0.0) - rubyzip (>= 1.2.1, < 2.0.0) - security (= 0.1.3) - simctl (~> 1.6.3) - slack-notifier (>= 2.0.0, < 3.0.0) - terminal-notifier (>= 1.6.2, < 2.0.0) - terminal-table (>= 1.4.5, < 2.0.0) - tty-screen (>= 0.6.3, < 1.0.0) - tty-spinner (>= 0.8.0, < 1.0.0) - word_wrap (~> 1.0.0) - xcodeproj (>= 1.5.7, < 2.0.0) - xcpretty (~> 0.2.8) - xcpretty-travis-formatter (>= 0.0.3) - fourflusher (2.0.1) - fuzzy_match (2.0.4) - gh_inspector (1.1.3) - google-api-client (0.21.2) - addressable (~> 2.5, >= 2.5.1) - googleauth (>= 0.5, < 0.7.0) - httpclient (>= 2.8.1, < 3.0) - mime-types (~> 3.0) - representable (~> 3.0) - retriable (>= 2.0, < 4.0) - googleauth (0.6.2) - faraday (~> 0.12) - jwt (>= 1.4, < 3.0) - logging (~> 2.0) - memoist (~> 0.12) - multi_json (~> 1.11) - os (~> 0.9) - signet (~> 0.7) - highline (1.7.10) - http-cookie (1.0.3) - domain_name (~> 0.5) - httpclient (2.8.3) - i18n (0.9.5) - concurrent-ruby (~> 1.0) - json (2.1.0) - jwt (2.1.0) - little-plugger (1.1.4) - logging (2.2.2) - little-plugger (~> 1.1) - multi_json (~> 1.10) - memoist (0.16.0) - mime-types (3.1) - mime-types-data (~> 3.2015) - mime-types-data (3.2016.0521) - mini_magick (4.5.1) - minitest (5.10.3) - molinillo (0.6.5) - multi_json (1.13.1) - multi_xml (0.6.0) - multipart-post (2.0.0) - nanaimo (0.2.5) - nap (1.1.0) - naturally (2.2.0) - netrc (0.11.0) - os (0.9.6) - plist (3.4.0) - public_suffix (2.0.5) - representable (3.0.4) - declarative (< 0.1.0) - declarative-option (< 0.2.0) - uber (< 0.2.0) - retriable (3.1.2) - rouge (2.0.7) - ruby-macho (1.1.0) - rubyzip (1.2.1) - security (0.1.3) - signet (0.8.1) - addressable (~> 2.3) - faraday (~> 0.9) - jwt (>= 1.5, < 3.0) - multi_json (~> 1.10) - simctl (1.6.5) - CFPropertyList - naturally - slack-notifier (2.3.2) - terminal-notifier (1.8.0) - terminal-table (1.8.0) - unicode-display_width (~> 1.1, >= 1.1.1) - thread_safe (0.3.6) - tty-cursor (0.5.0) - tty-screen (0.6.4) - tty-spinner (0.8.0) - tty-cursor (>= 0.5.0) - tzinfo (1.2.5) - thread_safe (~> 0.1) - uber (0.1.0) - unf (0.1.4) - unf_ext - unf_ext (0.0.7.5) - unicode-display_width (1.4.0) - word_wrap (1.0.0) - xcodeproj (1.5.9) - CFPropertyList (>= 2.3.3, < 4.0) - atomos (~> 0.1.2) - claide (>= 1.0.2, < 2.0) - colored2 (~> 3.1) - nanaimo (~> 0.2.5) - xcpretty (0.2.8) - rouge (~> 2.0.7) - xcpretty-travis-formatter (1.0.0) - xcpretty (~> 0.2, >= 0.0.7) - -PLATFORMS - ruby - -DEPENDENCIES - cocoapods - fastlane - -BUNDLED WITH - 1.16.1 diff --git a/MoPubResources/Info.plist b/MoPubResources/Info.plist index 5c3264f9f..597fecc54 100644 --- a/MoPubResources/Info.plist +++ b/MoPubResources/Info.plist @@ -13,7 +13,7 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 5.4.0 + 5.9.0 CFBundleSignature ???? CFBundleSupportedPlatforms @@ -21,9 +21,9 @@ iPhoneOS CFBundleVersion - 5.4.0 + 5.9.0 NSHumanReadableCopyright - Copyright 2018 Twitter Inc. All rights reserved. + Copyright 2019 Twitter Inc. All rights reserved. NSPrincipalClass diff --git a/MoPubSDK.xcodeproj/project.pbxproj b/MoPubSDK.xcodeproj/project.pbxproj index fbea29a16..86752a0d4 100644 --- a/MoPubSDK.xcodeproj/project.pbxproj +++ b/MoPubSDK.xcodeproj/project.pbxproj @@ -17,7 +17,6 @@ 2A27021020214502004A72E6 /* MOPUBNativeVideoAdAdapter.m in Sources */ = {isa = PBXBuildFile; fileRef = 57AC69281BB21FA20053C556 /* MOPUBNativeVideoAdAdapter.m */; }; 2A27021120214502004A72E6 /* MOPUBNativeVideoAdConfigValues.m in Sources */ = {isa = PBXBuildFile; fileRef = 57AC692A1BB21FA20053C556 /* MOPUBNativeVideoAdConfigValues.m */; }; 2A27021220214502004A72E6 /* MOPUBNativeVideoCustomEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 57AC692C1BB21FA20053C556 /* MOPUBNativeVideoCustomEvent.m */; }; - 2A27021320214502004A72E6 /* MOPUBNativeVideoImpressionAgent.m in Sources */ = {isa = PBXBuildFile; fileRef = 57AC692E1BB21FA20053C556 /* MOPUBNativeVideoImpressionAgent.m */; }; 2A27021420214502004A72E6 /* MOPUBPlayerManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 57AC69301BB21FA20053C556 /* MOPUBPlayerManager.m */; }; 2A27021520214502004A72E6 /* MOPUBPlayerView.m in Sources */ = {isa = PBXBuildFile; fileRef = 57AC69321BB21FA20053C556 /* MOPUBPlayerView.m */; }; 2A27021620214502004A72E6 /* MOPUBPlayerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 57AC69341BB21FA20053C556 /* MOPUBPlayerViewController.m */; }; @@ -31,7 +30,6 @@ 2A27021E20214502004A72E6 /* MPRewardedVideoAdManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 57ACE3F21AA7E8D900A05633 /* MPRewardedVideoAdManager.m */; }; 2A27021F20214502004A72E6 /* MPRewardedVideo.m in Sources */ = {isa = PBXBuildFile; fileRef = 57ACE3F41AA7E8D900A05633 /* MPRewardedVideo.m */; }; 2A27022120214502004A72E6 /* MPRewardedVideoCustomEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 57ACE3F61AA7E8D900A05633 /* MPRewardedVideoCustomEvent.m */; }; - 2A27022220214502004A72E6 /* MPRewardedVideoCustomEvent+Caching.m in Sources */ = {isa = PBXBuildFile; fileRef = BC57D5FE1F0ED4240030C365 /* MPRewardedVideoCustomEvent+Caching.m */; }; 2A27022320214502004A72E6 /* MPRewardedVideoError.m in Sources */ = {isa = PBXBuildFile; fileRef = 57ACE3F81AA7E8D900A05633 /* MPRewardedVideoError.m */; }; 2A27022420214502004A72E6 /* MPRewardedVideoReward.m in Sources */ = {isa = PBXBuildFile; fileRef = 57ACE3FA1AA7E8D900A05633 /* MPRewardedVideoReward.m */; }; 2A27022520214502004A72E6 /* MPAdPlacerInvocation.m in Sources */ = {isa = PBXBuildFile; fileRef = 570F754D19820ADB00466F6F /* MPAdPlacerInvocation.m */; }; @@ -55,7 +53,6 @@ 2A27023720214502004A72E6 /* MPStreamAdPlacementData.m in Sources */ = {isa = PBXBuildFile; fileRef = A7A1CDE61974904A0082A6FA /* MPStreamAdPlacementData.m */; }; 2A27023820214502004A72E6 /* MPStreamAdPlacer.m in Sources */ = {isa = PBXBuildFile; fileRef = A7A1CDC319745F0E0082A6FA /* MPStreamAdPlacer.m */; }; 2A27023920214502004A72E6 /* MPTableViewAdPlacerCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 573A82EF1B8E488400ED4067 /* MPTableViewAdPlacerCell.m */; }; - 2A27023A20214502004A72E6 /* MPTableViewCellImpressionTracker.m in Sources */ = {isa = PBXBuildFile; fileRef = A77FBEE018C533B400531E8A /* MPTableViewCellImpressionTracker.m */; }; 2A27023B20214502004A72E6 /* MPStaticNativeAdRendererSettings.m in Sources */ = {isa = PBXBuildFile; fileRef = 578D28861B9A410C002E3905 /* MPStaticNativeAdRendererSettings.m */; }; 2A27023C20214502004A72E6 /* MPNativeAdRendererConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = 578D287C1B9A406B002E3905 /* MPNativeAdRendererConfiguration.m */; }; 2A27023D20214502004A72E6 /* MPStaticNativeAdRenderer.m in Sources */ = {isa = PBXBuildFile; fileRef = 573A82D01B8E487300ED4067 /* MPStaticNativeAdRenderer.m */; }; @@ -75,7 +72,6 @@ 2A27025220214502004A72E6 /* MPAdAlertManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 46ECD6E417E7A29500442BCA /* MPAdAlertManager.m */; }; 2A27025320214502004A72E6 /* MPActivityViewControllerHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 875390C31B04073F001F0550 /* MPActivityViewControllerHelper.m */; }; 2A27025420214502004A72E6 /* MPActivityViewControllerHelper+TweetShare.m in Sources */ = {isa = PBXBuildFile; fileRef = 875390C51B04073F001F0550 /* MPActivityViewControllerHelper+TweetShare.m */; }; - 2A27025520214502004A72E6 /* MPAdBrowserController.m in Sources */ = {isa = PBXBuildFile; fileRef = AEF9D4D5171CA895005AAA5A /* MPAdBrowserController.m */; }; 2A27025620214502004A72E6 /* MPAdConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = AEF9D4D8171CA895005AAA5A /* MPAdConfiguration.m */; }; 2A27025720214502004A72E6 /* MPAdDestinationDisplayAgent.m in Sources */ = {isa = PBXBuildFile; fileRef = AEF9D4DA171CA895005AAA5A /* MPAdDestinationDisplayAgent.m */; }; 2A27025820214502004A72E6 /* MPAdImpressionTimer.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A5275B71FCE1FCE00FF39D5 /* MPAdImpressionTimer.m */; }; @@ -118,16 +114,14 @@ 2A27027E20214502004A72E6 /* MRProperty.m in Sources */ = {isa = PBXBuildFile; fileRef = AEF9D512171CA895005AAA5A /* MRProperty.m */; }; 2A27027F20214502004A72E6 /* MRBridge.m in Sources */ = {isa = PBXBuildFile; fileRef = 57B5F15C1A0988FB00926EBD /* MRBridge.m */; }; 2A27028020214502004A72E6 /* MRError.m in Sources */ = {isa = PBXBuildFile; fileRef = 57D647FA1A0B4E4400433CFE /* MRError.m */; }; - 2A27028120214502004A72E6 /* MPLogProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 4AB5CCCF19F55596005ABBC1 /* MPLogProvider.m */; }; + 2A27028120214502004A72E6 /* MPLogManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 4AB5CCCF19F55596005ABBC1 /* MPLogManager.m */; }; 2A27028220214502004A72E6 /* NSBundle+MPAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = BCA77A2D1DCBAFAC0049768E /* NSBundle+MPAdditions.m */; }; 2A27028320214502004A72E6 /* NSHTTPURLResponse+MPAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 57098CDC1A2458D00005A153 /* NSHTTPURLResponse+MPAdditions.m */; }; 2A27028420214502004A72E6 /* NSJSONSerialization+MPAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A71895618D25FC50056F068 /* NSJSONSerialization+MPAdditions.m */; }; 2A27028520214502004A72E6 /* NSString+MPAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = BC62698F1ED4B18F00724C4A /* NSString+MPAdditions.m */; }; 2A27028620214502004A72E6 /* NSURL+MPAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = AEF9D516171CA895005AAA5A /* NSURL+MPAdditions.m */; }; - 2A27028720214502004A72E6 /* UIButton+MPAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = B28725E11B9FB5D200C0D61B /* UIButton+MPAdditions.m */; }; 2A27028820214502004A72E6 /* UIColor+MPAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = B28725E31B9FB5D200C0D61B /* UIColor+MPAdditions.m */; }; 2A27028920214502004A72E6 /* UIView+MPAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 57AC69801BB21FC30053C556 /* UIView+MPAdditions.m */; }; - 2A27028A20214502004A72E6 /* UIWebView+MPAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = AEF9D51A171CA895005AAA5A /* UIWebView+MPAdditions.m */; }; 2A27028B20214502004A72E6 /* MPAnalyticsTracker.m in Sources */ = {isa = PBXBuildFile; fileRef = AEF9D51C171CA895005AAA5A /* MPAnalyticsTracker.m */; }; 2A27028C20214502004A72E6 /* MPError.m in Sources */ = {isa = PBXBuildFile; fileRef = AEF9D51E171CA895005AAA5A /* MPError.m */; }; 2A27028D20214502004A72E6 /* MPGlobal.m in Sources */ = {isa = PBXBuildFile; fileRef = AEF9D520171CA895005AAA5A /* MPGlobal.m */; }; @@ -135,10 +129,8 @@ 2A27028F20214502004A72E6 /* MPLogging.m in Sources */ = {isa = PBXBuildFile; fileRef = AEF9D524171CA895005AAA5A /* MPLogging.m */; }; 2A27029020214502004A72E6 /* MPReachability.m in Sources */ = {isa = PBXBuildFile; fileRef = AEF9D526171CA895005AAA5A /* MPReachability.m */; }; 2A27029120214502004A72E6 /* MPSessionTracker.m in Sources */ = {isa = PBXBuildFile; fileRef = AEF9D528171CA895005AAA5A /* MPSessionTracker.m */; }; - 2A27029220214502004A72E6 /* MPStoreKitProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = AEF9D52A171CA895005AAA5A /* MPStoreKitProvider.m */; }; 2A27029320214502004A72E6 /* MPTimer.m in Sources */ = {isa = PBXBuildFile; fileRef = AEF9D52C171CA895005AAA5A /* MPTimer.m */; }; 2A27029420214502004A72E6 /* MPUserInteractionGestureRecognizer.m in Sources */ = {isa = PBXBuildFile; fileRef = 4614B0A3195B6B2200B39F8A /* MPUserInteractionGestureRecognizer.m */; }; - 2A27029520214502004A72E6 /* MPInternalUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 4678267819903BF300DB1B61 /* MPInternalUtils.m */; }; 2A27029620214502004A72E6 /* MPGeolocationProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = A7759F051A1EB19500087E00 /* MPGeolocationProvider.m */; }; 2A27029720214502004A72E6 /* MPVASTAd.m in Sources */ = {isa = PBXBuildFile; fileRef = A776A5141B5DDE7E00095706 /* MPVASTAd.m */; }; 2A27029820214502004A72E6 /* MPVASTCompanionAd.m in Sources */ = {isa = PBXBuildFile; fileRef = A776A5541B5DDFD800095706 /* MPVASTCompanionAd.m */; }; @@ -167,8 +159,12 @@ 2A2702B020214502004A72E6 /* MPInterstitialAdController.m in Sources */ = {isa = PBXBuildFile; fileRef = AEF9D537171CA895005AAA5A /* MPInterstitialAdController.m */; }; 2A2702B120214502004A72E6 /* MPInterstitialCustomEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = AEF9D539171CA895005AAA5A /* MPInterstitialCustomEvent.m */; }; 2A2702B220214502004A72E6 /* MoPub.m in Sources */ = {isa = PBXBuildFile; fileRef = A71DB8121A2FE68300D3B229 /* MoPub.m */; }; + 2A48DF3B229601C1003763C2 /* MPImpressionTrackedNotification.h in Headers */ = {isa = PBXBuildFile; fileRef = 2A48DF39229601C1003763C2 /* MPImpressionTrackedNotification.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 2A48DF3C229601C1003763C2 /* MPImpressionTrackedNotification.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A48DF3A229601C1003763C2 /* MPImpressionTrackedNotification.m */; }; + 2A48DF3D229601C1003763C2 /* MPImpressionTrackedNotification.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A48DF3A229601C1003763C2 /* MPImpressionTrackedNotification.m */; }; + 2A48DF3E229601C1003763C2 /* MPImpressionTrackedNotification.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A48DF3A229601C1003763C2 /* MPImpressionTrackedNotification.m */; }; 2A4A5D221F86DF270082FC4C /* MPAdServerCommunicatorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A4A5D211F86DF270082FC4C /* MPAdServerCommunicatorTests.m */; }; - 2A4A5D371F86E02A0082FC4C /* MPAdserverCommunicatorDelegateHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A4A5D361F86E02A0082FC4C /* MPAdserverCommunicatorDelegateHandler.m */; }; + 2A4A5D371F86E02A0082FC4C /* MPAdServerCommunicatorDelegateHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A4A5D361F86E02A0082FC4C /* MPAdServerCommunicatorDelegateHandler.m */; }; 2A4A5D441F86E2340082FC4C /* MPAdServerCommunicator+Testing.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A4A5D431F86E2340082FC4C /* MPAdServerCommunicator+Testing.m */; }; 2A4D35DC211CBF5300BE9377 /* MPCoreInstanceProvider+MRAID.h in Headers */ = {isa = PBXBuildFile; fileRef = 2A4D35DA211CBF5100BE9377 /* MPCoreInstanceProvider+MRAID.h */; }; 2A4D35DD211CBF5300BE9377 /* MPCoreInstanceProvider+MRAID.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A4D35DB211CBF5200BE9377 /* MPCoreInstanceProvider+MRAID.m */; }; @@ -182,10 +178,10 @@ 2A5275BA1FCE1FCF00FF39D5 /* MPAdImpressionTimer.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A5275B71FCE1FCE00FF39D5 /* MPAdImpressionTimer.m */; }; 2A5C4DB81F6B25F20076C08C /* MPNativeAdConfigValuesTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A5C4DB71F6B25F20076C08C /* MPNativeAdConfigValuesTests.m */; }; 2A5D06E11FCE19F100645822 /* MPAdImpressionTimer+Testing.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A5D06E01FCE19F100645822 /* MPAdImpressionTimer+Testing.m */; }; + 2A60998F225E8E020095890A /* MPMoPubAdPlacer.h in Headers */ = {isa = PBXBuildFile; fileRef = 2A60998E225E8E020095890A /* MPMoPubAdPlacer.h */; settings = {ATTRIBUTES = (Public, ); }; }; 2A6471E62087C74A001D7308 /* MPConsentDialogViewControllerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A6471E52087C74A001D7308 /* MPConsentDialogViewControllerTests.m */; }; 2A6471E92087C775001D7308 /* MPConsentDialogViewControllerDelegateHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A6471E82087C775001D7308 /* MPConsentDialogViewControllerDelegateHandler.m */; }; 2A65B0991D9C9292008E0CAD /* MPWebViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A65B0981D9C9292008E0CAD /* MPWebViewTests.m */; }; - 2A711FDC20226737007A2412 /* MPAdBrowserController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 57A1A37E1A780A55006A08DA /* MPAdBrowserController.xib */; }; 2A711FDD202267B6007A2412 /* MPCloseBtn.png in Resources */ = {isa = PBXBuildFile; fileRef = B257A2FA1BA3911300F5D320 /* MPCloseBtn.png */; }; 2A711FDE202267B6007A2412 /* MPCloseBtn@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = B257A2FB1BA3911300F5D320 /* MPCloseBtn@2x.png */; }; 2A711FDF202267B6007A2412 /* MPCloseBtn@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = B257A2FC1BA3911300F5D320 /* MPCloseBtn@3x.png */; }; @@ -205,7 +201,11 @@ 2A711FED202267B6007A2412 /* MPDAAIcon.png in Resources */ = {isa = PBXBuildFile; fileRef = 57401A421B7AAEF0000EEA64 /* MPDAAIcon.png */; }; 2A711FEE202267B6007A2412 /* MPDAAIcon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 57401A431B7AAEF0000EEA64 /* MPDAAIcon@2x.png */; }; 2A711FEF202267B6007A2412 /* MPDAAIcon@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 57401A441B7AAEF0000EEA64 /* MPDAAIcon@3x.png */; }; - 2A711FF0202267B6007A2412 /* MPCountdownTimer.html in Resources */ = {isa = PBXBuildFile; fileRef = BCF0FA8A1DC9512900ADFE4F /* MPCountdownTimer.html */; }; + 2A73E337226E43D3001FEE03 /* MPAdViewDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 2A73E336226E43D3001FEE03 /* MPAdViewDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 2A73E339226E44F8001FEE03 /* MPInterstitialAdControllerDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 2A73E338226E44F8001FEE03 /* MPInterstitialAdControllerDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 2A73E33B226E45F7001FEE03 /* MPStreamAdPlacerDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 2A73E33A226E45F4001FEE03 /* MPStreamAdPlacerDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 2A73E33D226E46B7001FEE03 /* MPCollectionViewAdPlacerDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 2A73E33C226E46B7001FEE03 /* MPCollectionViewAdPlacerDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 2A73E33F226E5851001FEE03 /* MPTableViewAdPlacerDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 2A73E33E226E5851001FEE03 /* MPTableViewAdPlacerDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; 2A75215C1F70720F0099C33C /* MPMoPubNativeAdAdapterTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A75215B1F70720F0099C33C /* MPMoPubNativeAdAdapterTests.m */; }; 2A7521691F70723E0099C33C /* MPMoPubNativeAdAdapter+Testing.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7521681F70723E0099C33C /* MPMoPubNativeAdAdapter+Testing.m */; }; 2A7F96DC1E66411700114565 /* MPViewabilityTracker.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7F96DB1E66411700114565 /* MPViewabilityTracker.m */; }; @@ -213,8 +213,27 @@ 2A7FC6AD1D8CA33000165D41 /* MPWebView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7FC6AC1D8CA33000165D41 /* MPWebView.m */; }; 2A80EAB81E675CCE00D7FDD9 /* MPViewabilityTracker+Testing.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A80EAB71E675CCE00D7FDD9 /* MPViewabilityTracker+Testing.m */; }; 2A80EAEA1E6779F500D7FDD9 /* MPWebView+Viewability.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A80EAE91E6779F500D7FDD9 /* MPWebView+Viewability.m */; }; + 2A890DE72303656D00FE683F /* SKStoreProductViewController+MPAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 2A890DE52303656D00FE683F /* SKStoreProductViewController+MPAdditions.h */; }; + 2A890DE82303656D00FE683F /* SKStoreProductViewController+MPAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A890DE62303656D00FE683F /* SKStoreProductViewController+MPAdditions.m */; }; + 2A890DE92303656D00FE683F /* SKStoreProductViewController+MPAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A890DE62303656D00FE683F /* SKStoreProductViewController+MPAdditions.m */; }; + 2A890DEA2303656D00FE683F /* SKStoreProductViewController+MPAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A890DE62303656D00FE683F /* SKStoreProductViewController+MPAdditions.m */; }; + 2A89F1492236DF2200E03010 /* MPRateLimitConfiguration.h in Headers */ = {isa = PBXBuildFile; fileRef = 2A89F1472236DF2200E03010 /* MPRateLimitConfiguration.h */; }; + 2A89F14A2236DF2200E03010 /* MPRateLimitConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A89F1482236DF2200E03010 /* MPRateLimitConfiguration.m */; }; + 2A89F14B2236DF2200E03010 /* MPRateLimitConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A89F1482236DF2200E03010 /* MPRateLimitConfiguration.m */; }; + 2A89F14C2236DF2200E03010 /* MPRateLimitConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A89F1482236DF2200E03010 /* MPRateLimitConfiguration.m */; }; + 2A89F14F2236DF3600E03010 /* MPRateLimitManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 2A89F14D2236DF3600E03010 /* MPRateLimitManager.h */; }; + 2A89F1502236DF3600E03010 /* MPRateLimitManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A89F14E2236DF3600E03010 /* MPRateLimitManager.m */; }; + 2A89F1512236DF3600E03010 /* MPRateLimitManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A89F14E2236DF3600E03010 /* MPRateLimitManager.m */; }; + 2A89F1522236DF3600E03010 /* MPRateLimitManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A89F14E2236DF3600E03010 /* MPRateLimitManager.m */; }; + 2A89F1562237136700E03010 /* MPRateLimitManagerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A89F1552237136700E03010 /* MPRateLimitManagerTests.m */; }; + 2A89F159223713A100E03010 /* MPRateLimitManager+Testing.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A89F158223713A100E03010 /* MPRateLimitManager+Testing.m */; }; + 2A89F15C22371E6500E03010 /* MPRateLimitConfiguration+Testing.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A89F15B22371E6500E03010 /* MPRateLimitConfiguration+Testing.m */; }; + 2A89F15E22371ECC00E03010 /* MPRateLimitConfigurationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A89F15D22371ECC00E03010 /* MPRateLimitConfigurationTests.m */; }; 2A922D3A1E9C4CF900F83923 /* MPInterstitialCustomEventAdapterTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A922D391E9C4CF900F83923 /* MPInterstitialCustomEventAdapterTests.m */; }; 2A9F8EA72126201B0060E1E7 /* MPVASTModelTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9F8EA62126201B0060E1E7 /* MPVASTModelTests.m */; }; + 2A9FD2D62269326500F2C33B /* MPNativeAdDelegateHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9FD2D52269326500F2C33B /* MPNativeAdDelegateHandler.m */; }; + 2A9FD2D9226935C000F2C33B /* MPNativeAd+Testing.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9FD2D8226935C000F2C33B /* MPNativeAd+Testing.m */; }; + 2AA2E2FE225FE0AB00478D5C /* MPImpressionDataTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AA2E2FD225FE0AB00478D5C /* MPImpressionDataTests.m */; }; 2AA73B9C1FCF7EDB001FB787 /* MOPUBNativeVideoAdConfigValuesTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AA73B9B1FCF7EDB001FB787 /* MOPUBNativeVideoAdConfigValuesTests.m */; }; 2AA73B9E1FCF869D001FB787 /* MOPUBNativeVideoAdAdapterTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AA73B9D1FCF869D001FB787 /* MOPUBNativeVideoAdAdapterTests.m */; }; 2AA73B9F1FCF8ACC001FB787 /* MPDAAIcon.png in Resources */ = {isa = PBXBuildFile; fileRef = 57401A421B7AAEF0000EEA64 /* MPDAAIcon.png */; }; @@ -228,6 +247,7 @@ 2ABB28681D9DEC23007EBF12 /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 57FEAA4B1BD974B9005A94B6 /* WebKit.framework */; }; 2AE45D3B1E9D882B00ED5DD2 /* MPInterstitialAdapterDelegateHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AE45D3A1E9D882B00ED5DD2 /* MPInterstitialAdapterDelegateHandler.m */; }; 2AE45D3E1E9D8FFA00ED5DD2 /* MPInterstitialCustomEventAdapter+Testing.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AE45D3D1E9D8FFA00ED5DD2 /* MPInterstitialCustomEventAdapter+Testing.m */; }; + 2AE81D5523187B06002252EF /* MPRealTimeTimer+Testing.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AE81D5423187B06002252EF /* MPRealTimeTimer+Testing.m */; }; 2AF030C92016731800909F29 /* MoPub.h in Headers */ = {isa = PBXBuildFile; fileRef = A71DB8111A2FE68300D3B229 /* MoPub.h */; settings = {ATTRIBUTES = (Public, ); }; }; 2AF031B72016767B00909F29 /* SafariServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B2D54B611ED2058E004E3C7B /* SafariServices.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; 2AF031B82016769E00909F29 /* AdSupport.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AEA97CB016EF9E9F0046D464 /* AdSupport.framework */; }; @@ -248,7 +268,6 @@ 2AF031CE2016B74400909F29 /* MOPUBNativeVideoAdRenderer.h in Headers */ = {isa = PBXBuildFile; fileRef = B2E3F53B1BAB8529000BB32D /* MOPUBNativeVideoAdRenderer.h */; settings = {ATTRIBUTES = (Public, ); }; }; 2AF031CF2016B74400909F29 /* MPRewardedVideo.h in Headers */ = {isa = PBXBuildFile; fileRef = 57ACE3F31AA7E8D900A05633 /* MPRewardedVideo.h */; settings = {ATTRIBUTES = (Public, ); }; }; 2AF031D12016B74400909F29 /* MPRewardedVideoCustomEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 57ACE3F51AA7E8D900A05633 /* MPRewardedVideoCustomEvent.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 2AF031D22016B74400909F29 /* MPRewardedVideoCustomEvent+Caching.h in Headers */ = {isa = PBXBuildFile; fileRef = BC57D5FD1F0ED4240030C365 /* MPRewardedVideoCustomEvent+Caching.h */; settings = {ATTRIBUTES = (Public, ); }; }; 2AF031D32016B74400909F29 /* MPRewardedVideoError.h in Headers */ = {isa = PBXBuildFile; fileRef = 57ACE3F71AA7E8D900A05633 /* MPRewardedVideoError.h */; settings = {ATTRIBUTES = (Public, ); }; }; 2AF031D42016B74400909F29 /* MPRewardedVideoReward.h in Headers */ = {isa = PBXBuildFile; fileRef = 57ACE3F91AA7E8D900A05633 /* MPRewardedVideoReward.h */; settings = {ATTRIBUTES = (Public, ); }; }; 2AF031D62016B74400909F29 /* MPStaticNativeAdRendererSettings.h in Headers */ = {isa = PBXBuildFile; fileRef = 578D28851B9A410C002E3905 /* MPStaticNativeAdRendererSettings.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -280,8 +299,7 @@ 2AF031F02016B74400909F29 /* MPInterstitialAdController.h in Headers */ = {isa = PBXBuildFile; fileRef = AEF9D536171CA895005AAA5A /* MPInterstitialAdController.h */; settings = {ATTRIBUTES = (Public, ); }; }; 2AF031F12016B74400909F29 /* MPInterstitialCustomEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = AEF9D538171CA895005AAA5A /* MPInterstitialCustomEvent.h */; settings = {ATTRIBUTES = (Public, ); }; }; 2AF031F22016B74400909F29 /* MPInterstitialCustomEventDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = AEF9D53A171CA895005AAA5A /* MPInterstitialCustomEventDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 2AF031F32016B74400909F29 /* MoPub-Bridging-Header.h in Headers */ = {isa = PBXBuildFile; fileRef = 5755ADCD19C79CAD0012B01F /* MoPub-Bridging-Header.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 2AF031F42016B74400909F29 /* MPLogLevel.h in Headers */ = {isa = PBXBuildFile; fileRef = BC79EC231F9810BF00FFC893 /* MPLogLevel.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 2AF031F42016B74400909F29 /* MPBLogLevel.h in Headers */ = {isa = PBXBuildFile; fileRef = BC79EC231F9810BF00FFC893 /* MPBLogLevel.h */; settings = {ATTRIBUTES = (Public, ); }; }; 2AF031F52016B78800909F29 /* MOPUBExperimentProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = B2D54B541ED20521004E3C7B /* MOPUBExperimentProvider.h */; }; 2AF031F62016B78800909F29 /* MOPUBActivityIndicatorView.h in Headers */ = {isa = PBXBuildFile; fileRef = 57AC691F1BB21FA20053C556 /* MOPUBActivityIndicatorView.h */; }; 2AF031F72016B78800909F29 /* MOPUBAVPlayer.h in Headers */ = {isa = PBXBuildFile; fileRef = 57AC69211BB21FA20053C556 /* MOPUBAVPlayer.h */; }; @@ -290,7 +308,6 @@ 2AF031FA2016B78800909F29 /* MOPUBNativeVideoAdAdapter.h in Headers */ = {isa = PBXBuildFile; fileRef = 57AC69271BB21FA20053C556 /* MOPUBNativeVideoAdAdapter.h */; }; 2AF031FB2016B78800909F29 /* MOPUBNativeVideoAdConfigValues.h in Headers */ = {isa = PBXBuildFile; fileRef = 57AC69291BB21FA20053C556 /* MOPUBNativeVideoAdConfigValues.h */; }; 2AF031FC2016B78800909F29 /* MOPUBNativeVideoCustomEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 57AC692B1BB21FA20053C556 /* MOPUBNativeVideoCustomEvent.h */; }; - 2AF031FD2016B78800909F29 /* MOPUBNativeVideoImpressionAgent.h in Headers */ = {isa = PBXBuildFile; fileRef = 57AC692D1BB21FA20053C556 /* MOPUBNativeVideoImpressionAgent.h */; }; 2AF031FE2016B78800909F29 /* MOPUBPlayerManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 57AC692F1BB21FA20053C556 /* MOPUBPlayerManager.h */; }; 2AF031FF2016B78800909F29 /* MOPUBPlayerView.h in Headers */ = {isa = PBXBuildFile; fileRef = 57AC69311BB21FA20053C556 /* MOPUBPlayerView.h */; }; 2AF032002016B78800909F29 /* MOPUBPlayerViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 57AC69331BB21FA20053C556 /* MOPUBPlayerViewController.h */; }; @@ -325,12 +342,10 @@ 2AF0321E2016B78800909F29 /* MPStreamAdPlacementData.h in Headers */ = {isa = PBXBuildFile; fileRef = A7A1CDE51974904A0082A6FA /* MPStreamAdPlacementData.h */; }; 2AF0321F2016B78800909F29 /* MPStreamAdPlacer.h in Headers */ = {isa = PBXBuildFile; fileRef = A7A1CDC219745F0E0082A6FA /* MPStreamAdPlacer.h */; settings = {ATTRIBUTES = (Public, ); }; }; 2AF032202016B78800909F29 /* MPTableViewAdPlacerCell.h in Headers */ = {isa = PBXBuildFile; fileRef = 573A82EE1B8E488400ED4067 /* MPTableViewAdPlacerCell.h */; }; - 2AF032212016B78800909F29 /* MPTableViewCellImpressionTracker.h in Headers */ = {isa = PBXBuildFile; fileRef = A77FBEDF18C533B400531E8A /* MPTableViewCellImpressionTracker.h */; }; 2AF032292016B78800909F29 /* MPAdAlertGestureRecognizer.h in Headers */ = {isa = PBXBuildFile; fileRef = 46ECD6E117E7A29500442BCA /* MPAdAlertGestureRecognizer.h */; }; 2AF0322A2016B78800909F29 /* MPAdAlertManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 46ECD6E317E7A29500442BCA /* MPAdAlertManager.h */; }; 2AF0322B2016B78800909F29 /* MPActivityViewControllerHelper.h in Headers */ = {isa = PBXBuildFile; fileRef = 875390C21B04073F001F0550 /* MPActivityViewControllerHelper.h */; }; 2AF0322C2016B78800909F29 /* MPActivityViewControllerHelper+TweetShare.h in Headers */ = {isa = PBXBuildFile; fileRef = 875390C41B04073F001F0550 /* MPActivityViewControllerHelper+TweetShare.h */; }; - 2AF0322D2016B78800909F29 /* MPAdBrowserController.h in Headers */ = {isa = PBXBuildFile; fileRef = AEF9D4D4171CA895005AAA5A /* MPAdBrowserController.h */; }; 2AF0322E2016B78800909F29 /* MPAdConfiguration.h in Headers */ = {isa = PBXBuildFile; fileRef = AEF9D4D7171CA895005AAA5A /* MPAdConfiguration.h */; }; 2AF0322F2016B78800909F29 /* MPAdDestinationDisplayAgent.h in Headers */ = {isa = PBXBuildFile; fileRef = AEF9D4D9171CA895005AAA5A /* MPAdDestinationDisplayAgent.h */; }; 2AF032302016B78800909F29 /* MPAdImpressionTimer.h in Headers */ = {isa = PBXBuildFile; fileRef = 2A5275B81FCE1FCF00FF39D5 /* MPAdImpressionTimer.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -378,16 +393,14 @@ 2AF0325C2016B78800909F29 /* MRProperty.h in Headers */ = {isa = PBXBuildFile; fileRef = AEF9D511171CA895005AAA5A /* MRProperty.h */; }; 2AF0325D2016B78800909F29 /* MRBridge.h in Headers */ = {isa = PBXBuildFile; fileRef = 57B5F15B1A0988FB00926EBD /* MRBridge.h */; }; 2AF0325E2016B78800909F29 /* MRError.h in Headers */ = {isa = PBXBuildFile; fileRef = 57D647F91A0B4E4400433CFE /* MRError.h */; }; - 2AF0325F2016B78800909F29 /* MPLogProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = 4AB5CCCE19F55596005ABBC1 /* MPLogProvider.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 2AF0325F2016B78800909F29 /* MPLogManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 4AB5CCCE19F55596005ABBC1 /* MPLogManager.h */; }; 2AF032602016B78800909F29 /* NSBundle+MPAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = BCA77A2C1DCBAFAC0049768E /* NSBundle+MPAdditions.h */; }; 2AF032612016B78800909F29 /* NSHTTPURLResponse+MPAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 57098CDB1A2458D00005A153 /* NSHTTPURLResponse+MPAdditions.h */; }; 2AF032622016B78800909F29 /* NSJSONSerialization+MPAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 4A71895518D25FC50056F068 /* NSJSONSerialization+MPAdditions.h */; }; 2AF032632016B78800909F29 /* NSString+MPAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = BC62698E1ED4B18F00724C4A /* NSString+MPAdditions.h */; }; 2AF032642016B78800909F29 /* NSURL+MPAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = AEF9D515171CA895005AAA5A /* NSURL+MPAdditions.h */; }; - 2AF032652016B78800909F29 /* UIButton+MPAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = B28725E01B9FB5D200C0D61B /* UIButton+MPAdditions.h */; }; 2AF032662016B78800909F29 /* UIColor+MPAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = B28725E21B9FB5D200C0D61B /* UIColor+MPAdditions.h */; }; 2AF032672016B78800909F29 /* UIView+MPAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 57AC697F1BB21FC30053C556 /* UIView+MPAdditions.h */; }; - 2AF032682016B78800909F29 /* UIWebView+MPAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = AEF9D519171CA895005AAA5A /* UIWebView+MPAdditions.h */; }; 2AF032692016B78800909F29 /* MPAnalyticsTracker.h in Headers */ = {isa = PBXBuildFile; fileRef = AEF9D51B171CA895005AAA5A /* MPAnalyticsTracker.h */; }; 2AF0326A2016B78800909F29 /* MPError.h in Headers */ = {isa = PBXBuildFile; fileRef = AEF9D51D171CA895005AAA5A /* MPError.h */; settings = {ATTRIBUTES = (Public, ); }; }; 2AF0326B2016B78800909F29 /* MPGlobal.h in Headers */ = {isa = PBXBuildFile; fileRef = AEF9D51F171CA895005AAA5A /* MPGlobal.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -395,10 +408,8 @@ 2AF0326D2016B78800909F29 /* MPLogging.h in Headers */ = {isa = PBXBuildFile; fileRef = AEF9D523171CA895005AAA5A /* MPLogging.h */; settings = {ATTRIBUTES = (Public, ); }; }; 2AF0326E2016B78800909F29 /* MPReachability.h in Headers */ = {isa = PBXBuildFile; fileRef = AEF9D525171CA895005AAA5A /* MPReachability.h */; }; 2AF0326F2016B78800909F29 /* MPSessionTracker.h in Headers */ = {isa = PBXBuildFile; fileRef = AEF9D527171CA895005AAA5A /* MPSessionTracker.h */; }; - 2AF032702016B78800909F29 /* MPStoreKitProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = AEF9D529171CA895005AAA5A /* MPStoreKitProvider.h */; }; 2AF032712016B78800909F29 /* MPTimer.h in Headers */ = {isa = PBXBuildFile; fileRef = AEF9D52B171CA895005AAA5A /* MPTimer.h */; }; 2AF032722016B78800909F29 /* MPUserInteractionGestureRecognizer.h in Headers */ = {isa = PBXBuildFile; fileRef = 4614B0A4195B6B2200B39F8A /* MPUserInteractionGestureRecognizer.h */; }; - 2AF032732016B78800909F29 /* MPInternalUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 4678267719903BF300DB1B61 /* MPInternalUtils.h */; }; 2AF032742016B78800909F29 /* MPGeolocationProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = A7759F041A1EB19500087E00 /* MPGeolocationProvider.h */; }; 2AF032752016B78800909F29 /* MPVASTAd.h in Headers */ = {isa = PBXBuildFile; fileRef = A776A5131B5DDE7E00095706 /* MPVASTAd.h */; }; 2AF032762016B78800909F29 /* MPVASTCompanionAd.h in Headers */ = {isa = PBXBuildFile; fileRef = A776A5531B5DDFD800095706 /* MPVASTCompanionAd.h */; }; @@ -425,10 +436,14 @@ 2AF177411E984AD700A600BD /* MPRewardedVideoAdapter+Testing.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AF177401E984AD700A600BD /* MPRewardedVideoAdapter+Testing.m */; }; 2AF177441E984DED00A600BD /* MPRewardedVideoAdapterDelegateHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AF177431E984DED00A600BD /* MPRewardedVideoAdapterDelegateHandler.m */; }; 2AF21E6621349EB000CC12D8 /* MPURLRequest+Testing.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AF21E6521349EB000CC12D8 /* MPURLRequest+Testing.m */; }; + 2AFEE723225BC38400DD82C8 /* MPImpressionData.h in Headers */ = {isa = PBXBuildFile; fileRef = 2AFEE721225BC38400DD82C8 /* MPImpressionData.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 2AFEE724225BC38400DD82C8 /* MPImpressionData.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AFEE722225BC38400DD82C8 /* MPImpressionData.m */; }; + 2AFEE725225BC38400DD82C8 /* MPImpressionData.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AFEE722225BC38400DD82C8 /* MPImpressionData.m */; }; + 2AFEE726225BC38400DD82C8 /* MPImpressionData.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AFEE722225BC38400DD82C8 /* MPImpressionData.m */; }; + 2AFEE727225C130B00DD82C8 /* MPMoPubAd.h in Headers */ = {isa = PBXBuildFile; fileRef = 2AFEE720225BC22300DD82C8 /* MPMoPubAd.h */; settings = {ATTRIBUTES = (Public, ); }; }; 2AFF1B7A1EC2795500495994 /* MPRealTimeTimer.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AFF1B791EC2795500495994 /* MPRealTimeTimer.m */; }; 2AFF1BA71EC289E600495994 /* MPRealTimeTimerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AFF1BA61EC289E600495994 /* MPRealTimeTimerTests.m */; }; 4614B0A5195B6B2200B39F8A /* MPUserInteractionGestureRecognizer.m in Sources */ = {isa = PBXBuildFile; fileRef = 4614B0A3195B6B2200B39F8A /* MPUserInteractionGestureRecognizer.m */; }; - 462D5F7619C128AB00834F28 /* MPInternalUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 4678267819903BF300DB1B61 /* MPInternalUtils.m */; }; 46ECD6E517E7A29500442BCA /* MPAdAlertGestureRecognizer.m in Sources */ = {isa = PBXBuildFile; fileRef = 46ECD6E217E7A29500442BCA /* MPAdAlertGestureRecognizer.m */; }; 46ECD6E817E7A29500442BCA /* MPAdAlertManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 46ECD6E417E7A29500442BCA /* MPAdAlertManager.m */; }; 46EDEE6719832A3B00241385 /* MPNativeAdUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 46EDEE6619832A3B00241385 /* MPNativeAdUtils.m */; }; @@ -436,7 +451,7 @@ 4A5968C218CFE71200D0D1AD /* MPCoreInstanceProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A5968C118CFE71200D0D1AD /* MPCoreInstanceProvider.m */; }; 4A71895718D25FC50056F068 /* NSJSONSerialization+MPAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A71895618D25FC50056F068 /* NSJSONSerialization+MPAdditions.m */; }; 4AAB5337197ED97D00A73C98 /* MPNativeAdSourceQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = 4AAB5336197ED97D00A73C98 /* MPNativeAdSourceQueue.m */; }; - 4AB5CCD019F55596005ABBC1 /* MPLogProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 4AB5CCCF19F55596005ABBC1 /* MPLogProvider.m */; }; + 4AB5CCD019F55596005ABBC1 /* MPLogManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 4AB5CCCF19F55596005ABBC1 /* MPLogManager.m */; }; 4AE42F9D18D8FD2100DE4BF6 /* MPNativeCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 4AE42F9C18D8FD2100DE4BF6 /* MPNativeCache.m */; }; 4AF54B55194A217F0093F714 /* MPMoPubNativeAdAdapter.m in Sources */ = {isa = PBXBuildFile; fileRef = 4AF54B54194A217F0093F714 /* MPMoPubNativeAdAdapter.m */; }; 4AF7F8501A0C194500ABD80D /* MRNativeCommandHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 4AF7F84E1A0C194500ABD80D /* MRNativeCommandHandler.m */; }; @@ -459,7 +474,6 @@ 5758288F1A16F281009C7A85 /* MRController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5758288E1A16F281009C7A85 /* MRController.m */; }; 578D287F1B9A406B002E3905 /* MPNativeAdRendererConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = 578D287C1B9A406B002E3905 /* MPNativeAdRendererConfiguration.m */; }; 578D28881B9A410C002E3905 /* MPStaticNativeAdRendererSettings.m in Sources */ = {isa = PBXBuildFile; fileRef = 578D28861B9A410C002E3905 /* MPStaticNativeAdRendererSettings.m */; }; - 57A1A3801A780A55006A08DA /* MPAdBrowserController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 57A1A37E1A780A55006A08DA /* MPAdBrowserController.xib */; }; 57AC69381BB21FA20053C556 /* MOPUBActivityIndicatorView.m in Sources */ = {isa = PBXBuildFile; fileRef = 57AC69201BB21FA20053C556 /* MOPUBActivityIndicatorView.m */; }; 57AC693E1BB21FA20053C556 /* MOPUBAVPlayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 57AC69221BB21FA20053C556 /* MOPUBAVPlayer.m */; }; 57AC69441BB21FA20053C556 /* MOPUBAVPlayerView.m in Sources */ = {isa = PBXBuildFile; fileRef = 57AC69241BB21FA20053C556 /* MOPUBAVPlayerView.m */; }; @@ -467,7 +481,6 @@ 57AC69501BB21FA20053C556 /* MOPUBNativeVideoAdAdapter.m in Sources */ = {isa = PBXBuildFile; fileRef = 57AC69281BB21FA20053C556 /* MOPUBNativeVideoAdAdapter.m */; }; 57AC69561BB21FA20053C556 /* MOPUBNativeVideoAdConfigValues.m in Sources */ = {isa = PBXBuildFile; fileRef = 57AC692A1BB21FA20053C556 /* MOPUBNativeVideoAdConfigValues.m */; }; 57AC695C1BB21FA20053C556 /* MOPUBNativeVideoCustomEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 57AC692C1BB21FA20053C556 /* MOPUBNativeVideoCustomEvent.m */; }; - 57AC69621BB21FA20053C556 /* MOPUBNativeVideoImpressionAgent.m in Sources */ = {isa = PBXBuildFile; fileRef = 57AC692E1BB21FA20053C556 /* MOPUBNativeVideoImpressionAgent.m */; }; 57AC69681BB21FA20053C556 /* MOPUBPlayerManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 57AC69301BB21FA20053C556 /* MOPUBPlayerManager.m */; }; 57AC696E1BB21FA20053C556 /* MOPUBPlayerView.m in Sources */ = {isa = PBXBuildFile; fileRef = 57AC69321BB21FA20053C556 /* MOPUBPlayerView.m */; }; 57AC69741BB21FA20053C556 /* MOPUBPlayerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 57AC69341BB21FA20053C556 /* MOPUBPlayerViewController.m */; }; @@ -524,7 +537,6 @@ A776A56E1B5EE84A00095706 /* MPVASTResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = A776A56C1B5EE84A00095706 /* MPVASTResponse.m */; }; A778C8A91ABB4ED4003F427D /* MPAPIEndpoints.m in Sources */ = {isa = PBXBuildFile; fileRef = A778C8A71ABB4ED4003F427D /* MPAPIEndpoints.m */; }; A77A791917C3844B00AB0DCA /* MRVideoPlayerManager.m in Sources */ = {isa = PBXBuildFile; fileRef = A77A791817C3844A00AB0DCA /* MRVideoPlayerManager.m */; }; - A77FBEF118C533B400531E8A /* MPTableViewCellImpressionTracker.m in Sources */ = {isa = PBXBuildFile; fileRef = A77FBEE018C533B400531E8A /* MPTableViewCellImpressionTracker.m */; }; A77FBEF318C533B400531E8A /* MPNativeAdError.m in Sources */ = {isa = PBXBuildFile; fileRef = A77FBEE518C533B400531E8A /* MPNativeAdError.m */; }; A77FBEF418C533B400531E8A /* MPNativeAd.m in Sources */ = {isa = PBXBuildFile; fileRef = A77FBEE718C533B400531E8A /* MPNativeAd.m */; }; A77FBEF518C533B400531E8A /* MPNativeAdRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = A77FBEEA18C533B400531E8A /* MPNativeAdRequest.m */; }; @@ -548,7 +560,6 @@ AE515F45171F1B110086B464 /* MPBannerAdManager.m in Sources */ = {isa = PBXBuildFile; fileRef = AEF9D4CA171CA895005AAA5A /* MPBannerAdManager.m */; }; AE515F46171F1B110086B464 /* MPBannerCustomEventAdapter.m in Sources */ = {isa = PBXBuildFile; fileRef = AEF9D4CD171CA895005AAA5A /* MPBannerCustomEventAdapter.m */; }; AE515F47171F1B110086B464 /* MPBaseBannerAdapter.m in Sources */ = {isa = PBXBuildFile; fileRef = AEF9D4CF171CA895005AAA5A /* MPBaseBannerAdapter.m */; }; - AE515F49171F1B110086B464 /* MPAdBrowserController.m in Sources */ = {isa = PBXBuildFile; fileRef = AEF9D4D5171CA895005AAA5A /* MPAdBrowserController.m */; }; AE515F4A171F1B110086B464 /* MPAdConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = AEF9D4D8171CA895005AAA5A /* MPAdConfiguration.m */; }; AE515F4B171F1B110086B464 /* MPAdDestinationDisplayAgent.m in Sources */ = {isa = PBXBuildFile; fileRef = AEF9D4DA171CA895005AAA5A /* MPAdDestinationDisplayAgent.m */; }; AE515F4C171F1B110086B464 /* MPAdServerCommunicator.m in Sources */ = {isa = PBXBuildFile; fileRef = AEF9D4DC171CA895005AAA5A /* MPAdServerCommunicator.m */; }; @@ -569,7 +580,6 @@ AE515F61171F1B110086B464 /* MRCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = AEF9D50E171CA895005AAA5A /* MRCommand.m */; }; AE515F63171F1B110086B464 /* MRProperty.m in Sources */ = {isa = PBXBuildFile; fileRef = AEF9D512171CA895005AAA5A /* MRProperty.m */; }; AE515F64171F1B110086B464 /* NSURL+MPAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = AEF9D516171CA895005AAA5A /* NSURL+MPAdditions.m */; }; - AE515F66171F1B110086B464 /* UIWebView+MPAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = AEF9D51A171CA895005AAA5A /* UIWebView+MPAdditions.m */; }; AE515F67171F1B110086B464 /* MPAnalyticsTracker.m in Sources */ = {isa = PBXBuildFile; fileRef = AEF9D51C171CA895005AAA5A /* MPAnalyticsTracker.m */; }; AE515F68171F1B110086B464 /* MPError.m in Sources */ = {isa = PBXBuildFile; fileRef = AEF9D51E171CA895005AAA5A /* MPError.m */; }; AE515F69171F1B110086B464 /* MPGlobal.m in Sources */ = {isa = PBXBuildFile; fileRef = AEF9D520171CA895005AAA5A /* MPGlobal.m */; }; @@ -577,7 +587,6 @@ AE515F6B171F1B110086B464 /* MPLogging.m in Sources */ = {isa = PBXBuildFile; fileRef = AEF9D524171CA895005AAA5A /* MPLogging.m */; }; AE515F6C171F1B110086B464 /* MPReachability.m in Sources */ = {isa = PBXBuildFile; fileRef = AEF9D526171CA895005AAA5A /* MPReachability.m */; }; AE515F6D171F1B110086B464 /* MPSessionTracker.m in Sources */ = {isa = PBXBuildFile; fileRef = AEF9D528171CA895005AAA5A /* MPSessionTracker.m */; }; - AE515F6E171F1B110086B464 /* MPStoreKitProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = AEF9D52A171CA895005AAA5A /* MPStoreKitProvider.m */; }; AE515F6F171F1B110086B464 /* MPTimer.m in Sources */ = {isa = PBXBuildFile; fileRef = AEF9D52C171CA895005AAA5A /* MPTimer.m */; }; AE515F70171F1B110086B464 /* MPAdConversionTracker.m in Sources */ = {isa = PBXBuildFile; fileRef = AEF9D52F171CA895005AAA5A /* MPAdConversionTracker.m */; }; AE515F71171F1B110086B464 /* MPAdView.m in Sources */ = {isa = PBXBuildFile; fileRef = AEF9D531171CA895005AAA5A /* MPAdView.m */; }; @@ -590,7 +599,6 @@ B23C3B5B1E35709D0003D79E /* linear-tracking-no-event.xml in Resources */ = {isa = PBXBuildFile; fileRef = B23C3B5A1E35709D0003D79E /* linear-tracking-no-event.xml */; }; B23C3B6E1E36900C0003D79E /* XCTestCase+MPAddition.m in Sources */ = {isa = PBXBuildFile; fileRef = B23C3B6D1E36900C0003D79E /* XCTestCase+MPAddition.m */; }; B277838B1CE3BA5C00BAB0B1 /* MPRewardedVideoConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = B27783891CE3BA5C00BAB0B1 /* MPRewardedVideoConnection.m */; }; - B28725E51B9FB5D200C0D61B /* UIButton+MPAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = B28725E11B9FB5D200C0D61B /* UIButton+MPAdditions.m */; }; B28725EB1B9FB5D200C0D61B /* UIColor+MPAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = B28725E31B9FB5D200C0D61B /* UIColor+MPAdditions.m */; }; B294E0681BACDB1E00909531 /* MOPUBNativeVideoAdRendererSettings.m in Sources */ = {isa = PBXBuildFile; fileRef = B294E0661BACDB1E00909531 /* MOPUBNativeVideoAdRendererSettings.m */; }; B2950B3F1FBE5E5B00C40BF5 /* MPBannerCustomEvent+Internal.m in Sources */ = {isa = PBXBuildFile; fileRef = B2950B3D1FBE5E5B00C40BF5 /* MPBannerCustomEvent+Internal.m */; }; @@ -613,6 +621,7 @@ BC08C7831E36AD7C00444F16 /* MPVASTLinearAdTests.m in Sources */ = {isa = PBXBuildFile; fileRef = BC08C7821E36AD7C00444F16 /* MPVASTLinearAdTests.m */; }; BC08C7A51E36BB8200444F16 /* linear-mime-types.xml in Resources */ = {isa = PBXBuildFile; fileRef = BC08C7A41E36BB8200444F16 /* linear-mime-types.xml */; }; BC08C7B31E36C71B00444F16 /* linear-mime-types-all-invalid.xml in Resources */ = {isa = PBXBuildFile; fileRef = BC08C7B21E36C71B00444F16 /* linear-mime-types-all-invalid.xml */; }; + BC098E4D226E3B53001E44A6 /* MPNativeAdRequestTargetingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = BC098E4C226E3B53001E44A6 /* MPNativeAdRequestTargetingTests.m */; }; BC0ADDED207FFBEA000ADEA4 /* MPConsentManager+Testing.m in Sources */ = {isa = PBXBuildFile; fileRef = BC0ADDEC207FFBEA000ADEA4 /* MPConsentManager+Testing.m */; }; BC0ADDF02080023C000ADEA4 /* NSString+MPConsentStatus.h in Headers */ = {isa = PBXBuildFile; fileRef = BC0ADDEE2080023C000ADEA4 /* NSString+MPConsentStatus.h */; }; BC0ADDF12080023C000ADEA4 /* NSString+MPConsentStatus.m in Sources */ = {isa = PBXBuildFile; fileRef = BC0ADDEF2080023C000ADEA4 /* NSString+MPConsentStatus.m */; }; @@ -638,24 +647,26 @@ BC119227207D211B005DF26E /* MPConsentChangedNotification.m in Sources */ = {isa = PBXBuildFile; fileRef = BC119224207D211B005DF26E /* MPConsentChangedNotification.m */; }; BC119228207D211B005DF26E /* MPConsentChangedNotification.m in Sources */ = {isa = PBXBuildFile; fileRef = BC119224207D211B005DF26E /* MPConsentChangedNotification.m */; }; BC11922A207D6D06005DF26E /* MPConsentManagerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = BC119229207D6D06005DF26E /* MPConsentManagerTests.m */; }; - BC12D248206304AE0073388B /* MPAdvancedBiddingManagerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = BC12D247206304AE0073388B /* MPAdvancedBiddingManagerTests.m */; }; - BC12D24B206304FA0073388B /* MPAdvancedBiddingManager+Testing.m in Sources */ = {isa = PBXBuildFile; fileRef = BC12D24A206304FA0073388B /* MPAdvancedBiddingManager+Testing.m */; }; - BC12D24E206305940073388B /* MPStubAdvancedBidder.m in Sources */ = {isa = PBXBuildFile; fileRef = BC12D24D206305940073388B /* MPStubAdvancedBidder.m */; }; + BC12D24B206304FA0073388B /* MPMediationManager+Testing.m in Sources */ = {isa = PBXBuildFile; fileRef = BC12D24A206304FA0073388B /* MPMediationManager+Testing.m */; }; BC181D661ECE4DD6009C752C /* MPAdServerURLBuilderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = BC181D651ECE4DD6009C752C /* MPAdServerURLBuilderTests.m */; }; + BC1920872236F8FC004318D2 /* MPIdentityProviderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = BC1920862236F8FC004318D2 /* MPIdentityProviderTests.m */; }; + BC19208A2236FA83004318D2 /* MPIdentityProvider+Testing.m in Sources */ = {isa = PBXBuildFile; fileRef = BC1920892236FA83004318D2 /* MPIdentityProvider+Testing.m */; }; BC19E33120DC1E7700673D60 /* MPBannerCustomEventAdapter+Testing.m in Sources */ = {isa = PBXBuildFile; fileRef = BC19E33020DC1E7700673D60 /* MPBannerCustomEventAdapter+Testing.m */; }; BC19E33320DC1F9A00673D60 /* MPBannerAdManagerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = BC19E33220DC1F9A00673D60 /* MPBannerAdManagerTests.m */; }; BC19E33620DC203F00673D60 /* MPBannerAdManagerDelegateHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = BC19E33520DC203F00673D60 /* MPBannerAdManagerDelegateHandler.m */; }; BC19E33920DC22D200673D60 /* MPMockBannerCustomEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = BC19E33820DC22D200673D60 /* MPMockBannerCustomEvent.m */; }; BC1A2C61210685CD00A6A773 /* MPBannerAdapterDelegateHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = BC1A2C60210685CD00A6A773 /* MPBannerAdapterDelegateHandler.m */; }; BC1ED25F21471B0500065952 /* MPMockLongLoadNativeCustomEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = BC1ED25E21471B0500065952 /* MPMockLongLoadNativeCustomEvent.m */; }; + BC2448C622B171B0003EBB4B /* MPExtendedHitBoxButton.h in Headers */ = {isa = PBXBuildFile; fileRef = BC2448C422B171B0003EBB4B /* MPExtendedHitBoxButton.h */; }; + BC2448C722B171B0003EBB4B /* MPExtendedHitBoxButton.m in Sources */ = {isa = PBXBuildFile; fileRef = BC2448C522B171B0003EBB4B /* MPExtendedHitBoxButton.m */; }; + BC2448C822B171B0003EBB4B /* MPExtendedHitBoxButton.m in Sources */ = {isa = PBXBuildFile; fileRef = BC2448C522B171B0003EBB4B /* MPExtendedHitBoxButton.m */; }; BC246A6C1E5672AA00CEFA33 /* MPRewardedVideoAdManagerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = BC246A6B1E5672AA00CEFA33 /* MPRewardedVideoAdManagerTests.m */; }; BC246A791E5675CB00CEFA33 /* MPRewardedVideoAdManager+Testing.m in Sources */ = {isa = PBXBuildFile; fileRef = BC246A781E5675CB00CEFA33 /* MPRewardedVideoAdManager+Testing.m */; }; BC246A7C1E56795000CEFA33 /* MPRewardedVideoDelegateHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = BC246A7B1E56795000CEFA33 /* MPRewardedVideoDelegateHandler.m */; }; BC246A7F1E567D1500CEFA33 /* MPMockRewardedVideoAdapter.m in Sources */ = {isa = PBXBuildFile; fileRef = BC246A7E1E567D1500CEFA33 /* MPMockRewardedVideoAdapter.m */; }; - BC2C540A206EF34400171B98 /* MPStubMediatedNetwork.m in Sources */ = {isa = PBXBuildFile; fileRef = BC2C5409206EF34400171B98 /* MPStubMediatedNetwork.m */; }; BC2C5B5A1F5727D40025FFF5 /* MPMockViewabilityAdapterAvid.m in Sources */ = {isa = PBXBuildFile; fileRef = BC2C5B591F5727D40025FFF5 /* MPMockViewabilityAdapterAvid.m */; }; - BC310E8E1F15884A002A6809 /* MPMockAdColonyRewardedVideoCustomEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = BC310E8D1F15884A002A6809 /* MPMockAdColonyRewardedVideoCustomEvent.m */; }; - BC310E9B1F158A7B002A6809 /* MPMockChartboostRewardedVideoCustomEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = BC310E9A1F158A7B002A6809 /* MPMockChartboostRewardedVideoCustomEvent.m */; }; + BC310E8E1F15884A002A6809 /* MPMockAdColonyAdapterConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = BC310E8D1F15884A002A6809 /* MPMockAdColonyAdapterConfiguration.m */; }; + BC310E9B1F158A7B002A6809 /* MPMockChartboostAdapterConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = BC310E9A1F158A7B002A6809 /* MPMockChartboostAdapterConfiguration.m */; }; BC41F72020DAD554004BE29C /* MPMockAdDestinationDisplayAgent.m in Sources */ = {isa = PBXBuildFile; fileRef = BC41F71F20DAD554004BE29C /* MPMockAdDestinationDisplayAgent.m */; }; BC41F72220DAF23C004BE29C /* MPMemoryCacheTests.m in Sources */ = {isa = PBXBuildFile; fileRef = BC41F72120DAF23C004BE29C /* MPMemoryCacheTests.m */; }; BC4370532087C5D6001B86D4 /* MPAdServerKeys.h in Headers */ = {isa = PBXBuildFile; fileRef = BC4370512087C5D6001B86D4 /* MPAdServerKeys.h */; }; @@ -664,19 +675,28 @@ BC4370562087C5D6001B86D4 /* MPAdServerKeys.m in Sources */ = {isa = PBXBuildFile; fileRef = BC4370522087C5D6001B86D4 /* MPAdServerKeys.m */; }; BC437058208A8AC9001B86D4 /* MPBool.h in Headers */ = {isa = PBXBuildFile; fileRef = BC437057208A8AC9001B86D4 /* MPBool.h */; settings = {ATTRIBUTES = (Public, ); }; }; BC4982F921407EF200799273 /* NSErrorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = BC4982F821407EF200799273 /* NSErrorTests.m */; }; - BC4C9EEC1E2991EF006021CB /* MPCountdownTimer.html in Resources */ = {isa = PBXBuildFile; fileRef = BCF0FA8A1DC9512900ADFE4F /* MPCountdownTimer.html */; }; - BC57D5FF1F0ED4240030C365 /* MPRewardedVideoCustomEvent+Caching.m in Sources */ = {isa = PBXBuildFile; fileRef = BC57D5FE1F0ED4240030C365 /* MPRewardedVideoCustomEvent+Caching.m */; }; - BC57D6001F0ED4240030C365 /* MPRewardedVideoCustomEvent+Caching.m in Sources */ = {isa = PBXBuildFile; fileRef = BC57D5FE1F0ED4240030C365 /* MPRewardedVideoCustomEvent+Caching.m */; }; + BC4A4D14217F99ED008A7410 /* MPAdapterConfiguration.h in Headers */ = {isa = PBXBuildFile; fileRef = BC4A4D13217F99ED008A7410 /* MPAdapterConfiguration.h */; settings = {ATTRIBUTES = (Public, ); }; }; + BC4A4D17218125D3008A7410 /* MPMockTapjoyAdapterConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = BC4A4D16218125D3008A7410 /* MPMockTapjoyAdapterConfiguration.m */; }; + BC4A4D1A218239FB008A7410 /* MPMockChartboostRewardedVideoCustomEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = BC4A4D19218239FB008A7410 /* MPMockChartboostRewardedVideoCustomEvent.m */; }; + BC4A4D1D21827287008A7410 /* MPBaseAdapterConfiguration.h in Headers */ = {isa = PBXBuildFile; fileRef = BC4A4D1B21827287008A7410 /* MPBaseAdapterConfiguration.h */; settings = {ATTRIBUTES = (Public, ); }; }; + BC4A4D1E21827287008A7410 /* MPBaseAdapterConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = BC4A4D1C21827287008A7410 /* MPBaseAdapterConfiguration.m */; }; + BC4A4D1F21827287008A7410 /* MPBaseAdapterConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = BC4A4D1C21827287008A7410 /* MPBaseAdapterConfiguration.m */; }; + BC4A4D2021827287008A7410 /* MPBaseAdapterConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = BC4A4D1C21827287008A7410 /* MPBaseAdapterConfiguration.m */; }; BC57D6121F0EF75A0030C365 /* MPStubCustomEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = BC57D6111F0EF75A0030C365 /* MPStubCustomEvent.m */; }; BC57D63C1F10318F0030C365 /* MoPubTests.m in Sources */ = {isa = PBXBuildFile; fileRef = BC57D63B1F10318F0030C365 /* MoPubTests.m */; }; BC6269901ED4B18F00724C4A /* NSString+MPAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = BC62698F1ED4B18F00724C4A /* NSString+MPAdditions.m */; }; - BC64EC5620696F7800CB33A7 /* MPMediationSdkInitializable.h in Headers */ = {isa = PBXBuildFile; fileRef = BC64EC5520696F7800CB33A7 /* MPMediationSdkInitializable.h */; settings = {ATTRIBUTES = (Public, ); }; }; BC64EC592069977E00CB33A7 /* MPMediationManager.h in Headers */ = {isa = PBXBuildFile; fileRef = BC64EC572069977E00CB33A7 /* MPMediationManager.h */; }; BC64EC5A2069977E00CB33A7 /* MPMediationManager.m in Sources */ = {isa = PBXBuildFile; fileRef = BC64EC582069977E00CB33A7 /* MPMediationManager.m */; }; BC64EC5B2069977E00CB33A7 /* MPMediationManager.m in Sources */ = {isa = PBXBuildFile; fileRef = BC64EC582069977E00CB33A7 /* MPMediationManager.m */; }; BC64EC5C2069977E00CB33A7 /* MPMediationManager.m in Sources */ = {isa = PBXBuildFile; fileRef = BC64EC582069977E00CB33A7 /* MPMediationManager.m */; }; BC64EC5D2069977E00CB33A7 /* MPMediationManager.m in Sources */ = {isa = PBXBuildFile; fileRef = BC64EC582069977E00CB33A7 /* MPMediationManager.m */; }; BC64EC5F2069AA4700CB33A7 /* MPMediationManagerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = BC64EC5E2069AA4700CB33A7 /* MPMediationManagerTests.m */; }; + BC68415D22B179A2002B633C /* MPExtendedHitBoxButton.m in Sources */ = {isa = PBXBuildFile; fileRef = BC2448C522B171B0003EBB4B /* MPExtendedHitBoxButton.m */; }; + BC6B9E6622C135D00027F2F9 /* MPEngineInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = BC6B9E6422C135D00027F2F9 /* MPEngineInfo.h */; settings = {ATTRIBUTES = (Public, ); }; }; + BC6B9E6722C135D00027F2F9 /* MPEngineInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = BC6B9E6522C135D00027F2F9 /* MPEngineInfo.m */; }; + BC6B9E6822C135D00027F2F9 /* MPEngineInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = BC6B9E6522C135D00027F2F9 /* MPEngineInfo.m */; }; + BC6B9E6922C135D00027F2F9 /* MPEngineInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = BC6B9E6522C135D00027F2F9 /* MPEngineInfo.m */; }; + BC7003D322679C4F001222E7 /* MPLoggingHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = BC7003D222679C4F001222E7 /* MPLoggingHandler.m */; }; BC756FAC1F34FBA000556299 /* MRController+Testing.m in Sources */ = {isa = PBXBuildFile; fileRef = BC756FAB1F34FBA000556299 /* MRController+Testing.m */; }; BC756FB91F34FC5600556299 /* MPWebView+Testing.m in Sources */ = {isa = PBXBuildFile; fileRef = BC756FB81F34FC5600556299 /* MPWebView+Testing.m */; }; BC789C191F7ABE9C001CE308 /* MPAdConfigurationFactory.m in Sources */ = {isa = PBXBuildFile; fileRef = BC789C171F7ABE9B001CE308 /* MPAdConfigurationFactory.m */; }; @@ -690,6 +710,7 @@ BC7F42892141CE7E007EC273 /* MPAdTargeting.m in Sources */ = {isa = PBXBuildFile; fileRef = BC7F42852141CE7E007EC273 /* MPAdTargeting.m */; }; BC7FF69B20D07BCA003B1842 /* MPHTTPNetworkSession+Testing.m in Sources */ = {isa = PBXBuildFile; fileRef = BC7FF69A20D07BCA003B1842 /* MPHTTPNetworkSession+Testing.m */; }; BC7FF69D20D07C4E003B1842 /* MPHTTPNetworkSessionTests.m in Sources */ = {isa = PBXBuildFile; fileRef = BC7FF69C20D07C4E003B1842 /* MPHTTPNetworkSessionTests.m */; }; + BC8305E6216BCD400097C1BA /* MPMockAdapters.plist in Resources */ = {isa = PBXBuildFile; fileRef = BC8305E5216BCD400097C1BA /* MPMockAdapters.plist */; }; BC86211C1DCBBD5A0012275D /* MPCountdownTimerViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = BC86211B1DCBBD5A0012275D /* MPCountdownTimerViewTests.m */; }; BC8621271DCBBE9F0012275D /* AdSupport.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AEA97CB016EF9E9F0046D464 /* AdSupport.framework */; }; BC86212C1DCBE0180012275D /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AE4341A816F93E8F00B73710 /* AVFoundation.framework */; }; @@ -704,6 +725,7 @@ BC88E7E320769A3D002A3357 /* MPReachabilityManager.m in Sources */ = {isa = PBXBuildFile; fileRef = BC88E7E120769A3D002A3357 /* MPReachabilityManager.m */; }; BC88E7E420769A3D002A3357 /* MPReachabilityManager.m in Sources */ = {isa = PBXBuildFile; fileRef = BC88E7E120769A3D002A3357 /* MPReachabilityManager.m */; }; BC88E7E520769A3D002A3357 /* MPReachabilityManager.m in Sources */ = {isa = PBXBuildFile; fileRef = BC88E7E120769A3D002A3357 /* MPReachabilityManager.m */; }; + BC8CDD65218A12DD006DE606 /* MPMoPubConfigurationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = BC8CDD64218A12DD006DE606 /* MPMoPubConfigurationTests.m */; }; BC926EF520D9753B004ED8F7 /* MPMemoryCache.h in Headers */ = {isa = PBXBuildFile; fileRef = BC926EF320D9753B004ED8F7 /* MPMemoryCache.h */; }; BC926EF620D9753B004ED8F7 /* MPMemoryCache.m in Sources */ = {isa = PBXBuildFile; fileRef = BC926EF420D9753B004ED8F7 /* MPMemoryCache.m */; }; BC926EF720D9753B004ED8F7 /* MPMemoryCache.m in Sources */ = {isa = PBXBuildFile; fileRef = BC926EF420D9753B004ED8F7 /* MPMemoryCache.m */; }; @@ -721,7 +743,9 @@ BC96D55C211CE08600610174 /* NSString+MPAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = BC62698F1ED4B18F00724C4A /* NSString+MPAdditions.m */; }; BC9C160F2036391000348FAD /* MPHTTPNetworkTaskData.h in Headers */ = {isa = PBXBuildFile; fileRef = BCA2EA492023DAC6000F24C0 /* MPHTTPNetworkTaskData.h */; }; BC9C16102036391500348FAD /* MPHTTPNetworkTaskData.m in Sources */ = {isa = PBXBuildFile; fileRef = BCA2EA4A2023DAC8000F24C0 /* MPHTTPNetworkTaskData.m */; }; - BCA00AF11EF47A91006FF762 /* MPInternalUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 4678267819903BF300DB1B61 /* MPInternalUtils.m */; }; + BC9EF9DF216811B3005BEA65 /* MPAdapters.plist in Resources */ = {isa = PBXBuildFile; fileRef = BC9EF9DE216811B3005BEA65 /* MPAdapters.plist */; }; + BC9EF9E0216811B3005BEA65 /* MPAdapters.plist in Resources */ = {isa = PBXBuildFile; fileRef = BC9EF9DE216811B3005BEA65 /* MPAdapters.plist */; }; + BC9EF9E1216811EA005BEA65 /* MPAdapters.plist in Resources */ = {isa = PBXBuildFile; fileRef = BC9EF9DE216811B3005BEA65 /* MPAdapters.plist */; }; BCA00AF31EF47A91006FF762 /* MPBannerAdManager.m in Sources */ = {isa = PBXBuildFile; fileRef = AEF9D4CA171CA895005AAA5A /* MPBannerAdManager.m */; }; BCA00AF41EF47A91006FF762 /* MPBannerCustomEventAdapter.m in Sources */ = {isa = PBXBuildFile; fileRef = AEF9D4CD171CA895005AAA5A /* MPBannerCustomEventAdapter.m */; }; BCA00AF61EF47A91006FF762 /* MRConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 57E078621A1C7A7D00865697 /* MRConstants.m */; }; @@ -729,8 +753,6 @@ BCA00AF81EF47A91006FF762 /* MPBaseBannerAdapter.m in Sources */ = {isa = PBXBuildFile; fileRef = AEF9D4CF171CA895005AAA5A /* MPBaseBannerAdapter.m */; }; BCA00AFA1EF47A91006FF762 /* MPGeolocationProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = A7759F051A1EB19500087E00 /* MPGeolocationProvider.m */; }; BCA00AFB1EF47A91006FF762 /* MOPUBExperimentProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = B2D54B551ED20521004E3C7B /* MOPUBExperimentProvider.m */; }; - BCA00B001EF47A91006FF762 /* UIButton+MPAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = B28725E11B9FB5D200C0D61B /* UIButton+MPAdditions.m */; }; - BCA00B011EF47A91006FF762 /* MPAdBrowserController.m in Sources */ = {isa = PBXBuildFile; fileRef = AEF9D4D5171CA895005AAA5A /* MPAdBrowserController.m */; }; BCA00B021EF47A91006FF762 /* MPAdConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = AEF9D4D8171CA895005AAA5A /* MPAdConfiguration.m */; }; BCA00B051EF47A91006FF762 /* MPAdDestinationDisplayAgent.m in Sources */ = {isa = PBXBuildFile; fileRef = AEF9D4DA171CA895005AAA5A /* MPAdDestinationDisplayAgent.m */; }; BCA00B061EF47A91006FF762 /* MPAdServerCommunicator.m in Sources */ = {isa = PBXBuildFile; fileRef = AEF9D4DC171CA895005AAA5A /* MPAdServerCommunicator.m */; }; @@ -772,7 +794,6 @@ BCA00B4C1EF47A91006FF762 /* MPConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 57B86B5619C78F5D00AD50EE /* MPConstants.m */; }; BCA00B4D1EF47A91006FF762 /* MRProperty.m in Sources */ = {isa = PBXBuildFile; fileRef = AEF9D512171CA895005AAA5A /* MRProperty.m */; }; BCA00B521EF47A91006FF762 /* NSURL+MPAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = AEF9D516171CA895005AAA5A /* NSURL+MPAdditions.m */; }; - BCA00B531EF47A91006FF762 /* UIWebView+MPAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = AEF9D51A171CA895005AAA5A /* UIWebView+MPAdditions.m */; }; BCA00B541EF47A91006FF762 /* MoPub.m in Sources */ = {isa = PBXBuildFile; fileRef = A71DB8121A2FE68300D3B229 /* MoPub.m */; }; BCA00B5A1EF47A91006FF762 /* MPAnalyticsTracker.m in Sources */ = {isa = PBXBuildFile; fileRef = AEF9D51C171CA895005AAA5A /* MPAnalyticsTracker.m */; }; BCA00B5C1EF47A91006FF762 /* MPError.m in Sources */ = {isa = PBXBuildFile; fileRef = AEF9D51E171CA895005AAA5A /* MPError.m */; }; @@ -781,13 +802,12 @@ BCA00B601EF47A91006FF762 /* MPGlobal.m in Sources */ = {isa = PBXBuildFile; fileRef = AEF9D520171CA895005AAA5A /* MPGlobal.m */; }; BCA00B611EF47A91006FF762 /* MPMoPubRewardedPlayableCustomEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = BCDE9EC01DEFA9130034A444 /* MPMoPubRewardedPlayableCustomEvent.m */; }; BCA00B641EF47A91006FF762 /* MRError.m in Sources */ = {isa = PBXBuildFile; fileRef = 57D647FA1A0B4E4400433CFE /* MRError.m */; }; - BCA00B6A1EF47A91006FF762 /* MPLogProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 4AB5CCCF19F55596005ABBC1 /* MPLogProvider.m */; }; + BCA00B6A1EF47A91006FF762 /* MPLogManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 4AB5CCCF19F55596005ABBC1 /* MPLogManager.m */; }; BCA00B6B1EF47A91006FF762 /* MPIdentityProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = AEF9D522171CA895005AAA5A /* MPIdentityProvider.m */; }; BCA00B6C1EF47A91006FF762 /* MPActivityViewControllerHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 875390C31B04073F001F0550 /* MPActivityViewControllerHelper.m */; }; BCA00B6E1EF47A91006FF762 /* MPLogging.m in Sources */ = {isa = PBXBuildFile; fileRef = AEF9D524171CA895005AAA5A /* MPLogging.m */; }; BCA00B701EF47A91006FF762 /* MPReachability.m in Sources */ = {isa = PBXBuildFile; fileRef = AEF9D526171CA895005AAA5A /* MPReachability.m */; }; BCA00B731EF47A91006FF762 /* MPSessionTracker.m in Sources */ = {isa = PBXBuildFile; fileRef = AEF9D528171CA895005AAA5A /* MPSessionTracker.m */; }; - BCA00B751EF47A91006FF762 /* MPStoreKitProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = AEF9D52A171CA895005AAA5A /* MPStoreKitProvider.m */; }; BCA00B781EF47A91006FF762 /* MPTimer.m in Sources */ = {isa = PBXBuildFile; fileRef = AEF9D52C171CA895005AAA5A /* MPTimer.m */; }; BCA00B791EF47A91006FF762 /* MPRewardedVideoCustomEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 57ACE3F61AA7E8D900A05633 /* MPRewardedVideoCustomEvent.m */; }; BCA00B7A1EF47A91006FF762 /* MPAdConversionTracker.m in Sources */ = {isa = PBXBuildFile; fileRef = AEF9D52F171CA895005AAA5A /* MPAdConversionTracker.m */; }; @@ -805,6 +825,10 @@ BCA00B911EF47A91006FF762 /* MPAdAlertManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 46ECD6E417E7A29500442BCA /* MPAdAlertManager.m */; }; BCA2EA4B2023DAC9000F24C0 /* MPHTTPNetworkTaskData.m in Sources */ = {isa = PBXBuildFile; fileRef = BCA2EA4A2023DAC8000F24C0 /* MPHTTPNetworkTaskData.m */; }; BCA2EA4C2023DAD8000F24C0 /* MPHTTPNetworkTaskData.m in Sources */ = {isa = PBXBuildFile; fileRef = BCA2EA4A2023DAC8000F24C0 /* MPHTTPNetworkTaskData.m */; }; + BCA762322149B4B100D55A05 /* MPLogEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = BCA762302149B4B100D55A05 /* MPLogEvent.h */; settings = {ATTRIBUTES = (Public, ); }; }; + BCA762332149B4B100D55A05 /* MPLogEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = BCA762312149B4B100D55A05 /* MPLogEvent.m */; }; + BCA762342149B4B100D55A05 /* MPLogEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = BCA762312149B4B100D55A05 /* MPLogEvent.m */; }; + BCA762352149B4B100D55A05 /* MPLogEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = BCA762312149B4B100D55A05 /* MPLogEvent.m */; }; BCA77A2E1DCBAFAC0049768E /* NSBundle+MPAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = BCA77A2D1DCBAFAC0049768E /* NSBundle+MPAdditions.m */; }; BCAC6F5E1E5CF6F5002B2656 /* MPRewardedVideoTests.m in Sources */ = {isa = PBXBuildFile; fileRef = BCAC6F5D1E5CF6F5002B2656 /* MPRewardedVideoTests.m */; }; BCAC6F6E1E5D0730002B2656 /* MPRewardedVideo+Testing.m in Sources */ = {isa = PBXBuildFile; fileRef = BCAC6F6D1E5D0730002B2656 /* MPRewardedVideo+Testing.m */; }; @@ -816,6 +840,7 @@ BCAD022820CEE695007DC2B2 /* NSError+MPAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = BCAD022620CEE694007DC2B2 /* NSError+MPAdditions.m */; }; BCAD022920CEE695007DC2B2 /* NSError+MPAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = BCAD022620CEE694007DC2B2 /* NSError+MPAdditions.m */; }; BCAD022A20CEE695007DC2B2 /* NSError+MPAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = BCAD022620CEE694007DC2B2 /* NSError+MPAdditions.m */; }; + BCAD76A7214AE1A600A1B067 /* MPBLogger.h in Headers */ = {isa = PBXBuildFile; fileRef = BCAD76A6214AE05B00A1B067 /* MPBLogger.h */; settings = {ATTRIBUTES = (Public, ); }; }; BCAED2541DF6194E00D45480 /* MPMoPubRewardedPlayableCustomEventTests.m in Sources */ = {isa = PBXBuildFile; fileRef = BCAED2531DF6194E00D45480 /* MPMoPubRewardedPlayableCustomEventTests.m */; }; BCAED2611DF628D400D45480 /* MPMoPubRewardedPlayableCustomEvent+Testing.m in Sources */ = {isa = PBXBuildFile; fileRef = BCAED2601DF628D400D45480 /* MPMoPubRewardedPlayableCustomEvent+Testing.m */; }; BCAED2651DF62DB000D45480 /* MPMockMRAIDInterstitialViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = BCAED2641DF62DB000D45480 /* MPMockMRAIDInterstitialViewController.m */; }; @@ -828,17 +853,20 @@ BCC54C2F1ECFB8A300A4FEF0 /* MPNativeAdRequest+Testing.m in Sources */ = {isa = PBXBuildFile; fileRef = BCC54C2E1ECFB8A300A4FEF0 /* MPNativeAdRequest+Testing.m */; }; BCC79EC4204DC28B00F7ABE6 /* MPURLRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = BCC79EC3204DC28B00F7ABE6 /* MPURLRequestTests.m */; }; BCCE7A2B20768922003027BA /* MPReachabilityTests.m in Sources */ = {isa = PBXBuildFile; fileRef = BCCE7A2A20768922003027BA /* MPReachabilityTests.m */; }; + BCCEE891214B04E5003BD130 /* MPConsoleLogger.h in Headers */ = {isa = PBXBuildFile; fileRef = BCCEE88F214B04E5003BD130 /* MPConsoleLogger.h */; }; + BCCEE892214B04E5003BD130 /* MPConsoleLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = BCCEE890214B04E5003BD130 /* MPConsoleLogger.m */; }; + BCCEE893214B04E5003BD130 /* MPConsoleLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = BCCEE890214B04E5003BD130 /* MPConsoleLogger.m */; }; + BCCEE894214B04E5003BD130 /* MPConsoleLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = BCCEE890214B04E5003BD130 /* MPConsoleLogger.m */; }; BCD118872034E01100C03B7D /* MPMoPubConfiguration.h in Headers */ = {isa = PBXBuildFile; fileRef = 2AD9F9E01F8547DF00E0A5F0 /* MPMoPubConfiguration.h */; settings = {ATTRIBUTES = (Public, ); }; }; - BCD118882034E01600C03B7D /* MPAdvancedBidder.h in Headers */ = {isa = PBXBuildFile; fileRef = BC64149E1F84137300D067F6 /* MPAdvancedBidder.h */; settings = {ATTRIBUTES = (Public, ); }; }; BCD118892034E02E00C03B7D /* MPMoPubConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AD9F9D51F8547DE00E0A5F0 /* MPMoPubConfiguration.m */; }; BCD1188A2034E02F00C03B7D /* MPMoPubConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AD9F9D51F8547DE00E0A5F0 /* MPMoPubConfiguration.m */; }; BCD1188B2034E03000C03B7D /* MPMoPubConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AD9F9D51F8547DE00E0A5F0 /* MPMoPubConfiguration.m */; }; - BCD1188C2034E04100C03B7D /* MPAdvancedBiddingManager.h in Headers */ = {isa = PBXBuildFile; fileRef = BCAAD10F1F85696A00047435 /* MPAdvancedBiddingManager.h */; }; - BCD1188D2034E04400C03B7D /* MPAdvancedBiddingManager.m in Sources */ = {isa = PBXBuildFile; fileRef = BCAAD1041F85696700047435 /* MPAdvancedBiddingManager.m */; }; - BCD1188E2034E04500C03B7D /* MPAdvancedBiddingManager.m in Sources */ = {isa = PBXBuildFile; fileRef = BCAAD1041F85696700047435 /* MPAdvancedBiddingManager.m */; }; - BCD1188F2034E04600C03B7D /* MPAdvancedBiddingManager.m in Sources */ = {isa = PBXBuildFile; fileRef = BCAAD1041F85696700047435 /* MPAdvancedBiddingManager.m */; }; BCDE9EC21DEFA9130034A444 /* MPMoPubRewardedPlayableCustomEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = BCDE9EC01DEFA9130034A444 /* MPMoPubRewardedPlayableCustomEvent.m */; }; BCDE9ED71DF0AF970034A444 /* MPAdConfigurationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = BCDE9ED61DF0AF970034A444 /* MPAdConfigurationTests.m */; }; + BCDECB12219B4628002C1E7A /* MPContentBlocker.m in Sources */ = {isa = PBXBuildFile; fileRef = BCDECB10219B4627002C1E7A /* MPContentBlocker.m */; }; + BCDECB13219B4628002C1E7A /* MPContentBlocker.m in Sources */ = {isa = PBXBuildFile; fileRef = BCDECB10219B4627002C1E7A /* MPContentBlocker.m */; }; + BCDECB14219B4628002C1E7A /* MPContentBlocker.m in Sources */ = {isa = PBXBuildFile; fileRef = BCDECB10219B4627002C1E7A /* MPContentBlocker.m */; }; + BCDECB15219B4628002C1E7A /* MPContentBlocker.h in Headers */ = {isa = PBXBuildFile; fileRef = BCDECB11219B4628002C1E7A /* MPContentBlocker.h */; }; BCECF30B2047715E005AF3BD /* MPURLRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = BCECF3092047715E005AF3BD /* MPURLRequest.h */; }; BCECF30C2047715E005AF3BD /* MPURLRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = BCECF30A2047715E005AF3BD /* MPURLRequest.m */; }; BCECF30D2047715E005AF3BD /* MPURLRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = BCECF30A2047715E005AF3BD /* MPURLRequest.m */; }; @@ -847,7 +875,6 @@ BCEE050B2037A42C0076CA86 /* MPHTTPNetworkSession.h in Headers */ = {isa = PBXBuildFile; fileRef = BCEE05082037A4270076CA86 /* MPHTTPNetworkSession.h */; }; BCEE050C2037A4300076CA86 /* MPHTTPNetworkSession.m in Sources */ = {isa = PBXBuildFile; fileRef = BCEE05092037A4280076CA86 /* MPHTTPNetworkSession.m */; }; BCEE050D2037A4310076CA86 /* MPHTTPNetworkSession.m in Sources */ = {isa = PBXBuildFile; fileRef = BCEE05092037A4280076CA86 /* MPHTTPNetworkSession.m */; }; - BCF0FA8C1DC9512900ADFE4F /* MPCountdownTimer.html in Resources */ = {isa = PBXBuildFile; fileRef = BCF0FA8A1DC9512900ADFE4F /* MPCountdownTimer.html */; }; BCF0FA991DC9536B00ADFE4F /* MPCountdownTimerView.m in Sources */ = {isa = PBXBuildFile; fileRef = BCF0FA981DC9536B00ADFE4F /* MPCountdownTimerView.m */; }; BCF2F1B9206317B500DAF66E /* MPAdServerURLBuilder+Testing.m in Sources */ = {isa = PBXBuildFile; fileRef = BCF2F1B8206317B500DAF66E /* MPAdServerURLBuilder+Testing.m */; }; BCF525D62040BF6800E5E6A6 /* MPMediationSettingsProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = BC27343D203F51C900920507 /* MPMediationSettingsProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -856,6 +883,22 @@ BCFE67DA208508D3005E458A /* MPConsentChangedReason.m in Sources */ = {isa = PBXBuildFile; fileRef = BCFE67D8208508D3005E458A /* MPConsentChangedReason.m */; }; BCFE67DB208508D3005E458A /* MPConsentChangedReason.m in Sources */ = {isa = PBXBuildFile; fileRef = BCFE67D8208508D3005E458A /* MPConsentChangedReason.m */; }; BCFE67DC208508D3005E458A /* MPConsentChangedReason.m in Sources */ = {isa = PBXBuildFile; fileRef = BCFE67D8208508D3005E458A /* MPConsentChangedReason.m */; }; + EC0BF276227CB3FD003DB141 /* MoPub+Utility.h in Headers */ = {isa = PBXBuildFile; fileRef = EC0BF273227C9A09003DB141 /* MoPub+Utility.h */; }; + EC0BF277227CB402003DB141 /* MoPub+Utility.m in Sources */ = {isa = PBXBuildFile; fileRef = EC0BF274227C9A09003DB141 /* MoPub+Utility.m */; }; + EC0BF278227CB402003DB141 /* MoPub+Utility.m in Sources */ = {isa = PBXBuildFile; fileRef = EC0BF274227C9A09003DB141 /* MoPub+Utility.m */; }; + EC0BF279227CB403003DB141 /* MoPub+Utility.m in Sources */ = {isa = PBXBuildFile; fileRef = EC0BF274227C9A09003DB141 /* MoPub+Utility.m */; }; + EC481B0323209D84003AA3B9 /* MPWebBrowserUserAgentInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = EC481B0223209D84003AA3B9 /* MPWebBrowserUserAgentInfo.m */; }; + EC481B0523209E65003AA3B9 /* MPWebBrowserUserAgentInfoTests.m in Sources */ = {isa = PBXBuildFile; fileRef = EC481B0423209E65003AA3B9 /* MPWebBrowserUserAgentInfoTests.m */; }; + EC6349332320A04E00FEB2F5 /* MPWebBrowserUserAgentInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = EC481B0223209D84003AA3B9 /* MPWebBrowserUserAgentInfo.m */; }; + EC6349342320A04F00FEB2F5 /* MPWebBrowserUserAgentInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = EC481B0223209D84003AA3B9 /* MPWebBrowserUserAgentInfo.m */; }; + EC87668A22EA849100D4B3D9 /* MPVASTMediaFileTests.m in Sources */ = {isa = PBXBuildFile; fileRef = EC87668922EA849100D4B3D9 /* MPVASTMediaFileTests.m */; }; + EC923EF922FDD38B00ED83EE /* VAST_3.0_linear_ad_comprehensive.xml in Resources */ = {isa = PBXBuildFile; fileRef = EC923EF822FDD38B00ED83EE /* VAST_3.0_linear_ad_comprehensive.xml */; }; + EC923EFB22FDD3A400ED83EE /* MPVASTTrackingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = EC923EFA22FDD3A400ED83EE /* MPVASTTrackingTests.m */; }; + ECA6FF17226F8DD7007626A5 /* MPTimerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = ECA6FF16226F8DD7007626A5 /* MPTimerTests.m */; }; + ECA6FF1A226F9B4C007626A5 /* MPTimer+Testing.m in Sources */ = {isa = PBXBuildFile; fileRef = ECA6FF19226F9B4C007626A5 /* MPTimer+Testing.m */; }; + ECD106E62280FE2600398CA5 /* MRControllerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = ECD106E52280FE2600398CA5 /* MRControllerTests.m */; }; + ECF653C522DD3D0700B7DE1D /* MPDiskLRUCacheTests.m in Sources */ = {isa = PBXBuildFile; fileRef = ECF653C422DD3D0700B7DE1D /* MPDiskLRUCacheTests.m */; }; + ECF653C922DE75C500B7DE1D /* AVKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BC86212A1DCBE00D0012275D /* AVKit.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -892,9 +935,11 @@ /* Begin PBXFileReference section */ 2A2701ED2020FC7E004A72E6 /* MPViewabilityOption.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = MPViewabilityOption.h; path = MoPubSDK/Viewability/MPViewabilityOption.h; sourceTree = SOURCE_ROOT; }; 2A2701EE2020FCDC004A72E6 /* MOPUBDisplayAgentType.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MOPUBDisplayAgentType.h; sourceTree = ""; }; + 2A48DF39229601C1003763C2 /* MPImpressionTrackedNotification.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPImpressionTrackedNotification.h; sourceTree = ""; }; + 2A48DF3A229601C1003763C2 /* MPImpressionTrackedNotification.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPImpressionTrackedNotification.m; sourceTree = ""; }; 2A4A5D211F86DF270082FC4C /* MPAdServerCommunicatorTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPAdServerCommunicatorTests.m; sourceTree = ""; }; - 2A4A5D351F86E02A0082FC4C /* MPAdserverCommunicatorDelegateHandler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPAdserverCommunicatorDelegateHandler.h; sourceTree = ""; }; - 2A4A5D361F86E02A0082FC4C /* MPAdserverCommunicatorDelegateHandler.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPAdserverCommunicatorDelegateHandler.m; sourceTree = ""; }; + 2A4A5D351F86E02A0082FC4C /* MPAdServerCommunicatorDelegateHandler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPAdServerCommunicatorDelegateHandler.h; sourceTree = ""; }; + 2A4A5D361F86E02A0082FC4C /* MPAdServerCommunicatorDelegateHandler.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPAdServerCommunicatorDelegateHandler.m; sourceTree = ""; }; 2A4A5D421F86E2340082FC4C /* MPAdServerCommunicator+Testing.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MPAdServerCommunicator+Testing.h"; sourceTree = ""; }; 2A4A5D431F86E2340082FC4C /* MPAdServerCommunicator+Testing.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "MPAdServerCommunicator+Testing.m"; sourceTree = ""; }; 2A4D35DA211CBF5100BE9377 /* MPCoreInstanceProvider+MRAID.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MPCoreInstanceProvider+MRAID.h"; sourceTree = ""; }; @@ -908,10 +953,16 @@ 2A5C4DB71F6B25F20076C08C /* MPNativeAdConfigValuesTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPNativeAdConfigValuesTests.m; sourceTree = ""; }; 2A5D06DF1FCE19F100645822 /* MPAdImpressionTimer+Testing.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MPAdImpressionTimer+Testing.h"; sourceTree = ""; }; 2A5D06E01FCE19F100645822 /* MPAdImpressionTimer+Testing.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "MPAdImpressionTimer+Testing.m"; sourceTree = ""; }; + 2A60998E225E8E020095890A /* MPMoPubAdPlacer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPMoPubAdPlacer.h; sourceTree = ""; }; 2A6471E52087C74A001D7308 /* MPConsentDialogViewControllerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPConsentDialogViewControllerTests.m; sourceTree = ""; }; 2A6471E72087C775001D7308 /* MPConsentDialogViewControllerDelegateHandler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPConsentDialogViewControllerDelegateHandler.h; sourceTree = ""; }; 2A6471E82087C775001D7308 /* MPConsentDialogViewControllerDelegateHandler.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPConsentDialogViewControllerDelegateHandler.m; sourceTree = ""; }; 2A65B0981D9C9292008E0CAD /* MPWebViewTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPWebViewTests.m; sourceTree = ""; }; + 2A73E336226E43D3001FEE03 /* MPAdViewDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPAdViewDelegate.h; sourceTree = ""; }; + 2A73E338226E44F8001FEE03 /* MPInterstitialAdControllerDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPInterstitialAdControllerDelegate.h; sourceTree = ""; }; + 2A73E33A226E45F4001FEE03 /* MPStreamAdPlacerDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPStreamAdPlacerDelegate.h; sourceTree = ""; }; + 2A73E33C226E46B7001FEE03 /* MPCollectionViewAdPlacerDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPCollectionViewAdPlacerDelegate.h; sourceTree = ""; }; + 2A73E33E226E5851001FEE03 /* MPTableViewAdPlacerDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPTableViewAdPlacerDelegate.h; sourceTree = ""; }; 2A75215B1F70720F0099C33C /* MPMoPubNativeAdAdapterTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPMoPubNativeAdAdapterTests.m; sourceTree = ""; }; 2A7521671F70723E0099C33C /* MPMoPubNativeAdAdapter+Testing.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MPMoPubNativeAdAdapter+Testing.h"; sourceTree = ""; }; 2A7521681F70723E0099C33C /* MPMoPubNativeAdAdapter+Testing.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "MPMoPubNativeAdAdapter+Testing.m"; sourceTree = ""; }; @@ -924,9 +975,26 @@ 2A80EAB71E675CCE00D7FDD9 /* MPViewabilityTracker+Testing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MPViewabilityTracker+Testing.m"; sourceTree = ""; }; 2A80EAE81E6779F500D7FDD9 /* MPWebView+Viewability.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "MPWebView+Viewability.h"; path = "MoPubSDK/Viewability/MPWebView+Viewability.h"; sourceTree = SOURCE_ROOT; }; 2A80EAE91E6779F500D7FDD9 /* MPWebView+Viewability.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "MPWebView+Viewability.m"; path = "MoPubSDK/Viewability/MPWebView+Viewability.m"; sourceTree = SOURCE_ROOT; }; + 2A890DE52303656D00FE683F /* SKStoreProductViewController+MPAdditions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SKStoreProductViewController+MPAdditions.h"; sourceTree = ""; }; + 2A890DE62303656D00FE683F /* SKStoreProductViewController+MPAdditions.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "SKStoreProductViewController+MPAdditions.m"; sourceTree = ""; }; + 2A89F1472236DF2200E03010 /* MPRateLimitConfiguration.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPRateLimitConfiguration.h; sourceTree = ""; }; + 2A89F1482236DF2200E03010 /* MPRateLimitConfiguration.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPRateLimitConfiguration.m; sourceTree = ""; }; + 2A89F14D2236DF3600E03010 /* MPRateLimitManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPRateLimitManager.h; sourceTree = ""; }; + 2A89F14E2236DF3600E03010 /* MPRateLimitManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPRateLimitManager.m; sourceTree = ""; }; + 2A89F1552237136700E03010 /* MPRateLimitManagerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPRateLimitManagerTests.m; sourceTree = ""; }; + 2A89F157223713A100E03010 /* MPRateLimitManager+Testing.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MPRateLimitManager+Testing.h"; sourceTree = ""; }; + 2A89F158223713A100E03010 /* MPRateLimitManager+Testing.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "MPRateLimitManager+Testing.m"; sourceTree = ""; }; + 2A89F15A22371E6500E03010 /* MPRateLimitConfiguration+Testing.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MPRateLimitConfiguration+Testing.h"; sourceTree = ""; }; + 2A89F15B22371E6500E03010 /* MPRateLimitConfiguration+Testing.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "MPRateLimitConfiguration+Testing.m"; sourceTree = ""; }; + 2A89F15D22371ECC00E03010 /* MPRateLimitConfigurationTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPRateLimitConfigurationTests.m; sourceTree = ""; }; 2A922D391E9C4CF900F83923 /* MPInterstitialCustomEventAdapterTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPInterstitialCustomEventAdapterTests.m; sourceTree = ""; }; 2A9AA6BC201FF02B0043FF7E /* MoPubSDKFramework.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = MoPubSDKFramework.modulemap; sourceTree = ""; }; 2A9F8EA62126201B0060E1E7 /* MPVASTModelTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPVASTModelTests.m; sourceTree = ""; }; + 2A9FD2D42269326500F2C33B /* MPNativeAdDelegateHandler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPNativeAdDelegateHandler.h; sourceTree = ""; }; + 2A9FD2D52269326500F2C33B /* MPNativeAdDelegateHandler.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPNativeAdDelegateHandler.m; sourceTree = ""; }; + 2A9FD2D7226935C000F2C33B /* MPNativeAd+Testing.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MPNativeAd+Testing.h"; sourceTree = ""; }; + 2A9FD2D8226935C000F2C33B /* MPNativeAd+Testing.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "MPNativeAd+Testing.m"; sourceTree = ""; }; + 2AA2E2FD225FE0AB00478D5C /* MPImpressionDataTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPImpressionDataTests.m; sourceTree = ""; }; 2AA73B9B1FCF7EDB001FB787 /* MOPUBNativeVideoAdConfigValuesTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MOPUBNativeVideoAdConfigValuesTests.m; sourceTree = ""; }; 2AA73B9D1FCF869D001FB787 /* MOPUBNativeVideoAdAdapterTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MOPUBNativeVideoAdAdapterTests.m; sourceTree = ""; }; 2AA73BA21FCF8C49001FB787 /* MOPUBNativeVideoAdAdapter+Testing.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MOPUBNativeVideoAdAdapter+Testing.h"; sourceTree = ""; }; @@ -941,6 +1009,8 @@ 2AE45D3A1E9D882B00ED5DD2 /* MPInterstitialAdapterDelegateHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPInterstitialAdapterDelegateHandler.m; sourceTree = ""; }; 2AE45D3C1E9D8FFA00ED5DD2 /* MPInterstitialCustomEventAdapter+Testing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MPInterstitialCustomEventAdapter+Testing.h"; sourceTree = ""; }; 2AE45D3D1E9D8FFA00ED5DD2 /* MPInterstitialCustomEventAdapter+Testing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MPInterstitialCustomEventAdapter+Testing.m"; sourceTree = ""; }; + 2AE81D5323187B06002252EF /* MPRealTimeTimer+Testing.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MPRealTimeTimer+Testing.h"; sourceTree = ""; }; + 2AE81D5423187B06002252EF /* MPRealTimeTimer+Testing.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "MPRealTimeTimer+Testing.m"; sourceTree = ""; }; 2AF030C12016723700909F29 /* MoPubSDKFramework.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = MoPubSDKFramework.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 2AF030C42016723700909F29 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 2AF177321E9846A000A600BD /* MPRewardedVideoAdapterTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPRewardedVideoAdapterTests.m; sourceTree = ""; }; @@ -950,14 +1020,15 @@ 2AF177431E984DED00A600BD /* MPRewardedVideoAdapterDelegateHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPRewardedVideoAdapterDelegateHandler.m; sourceTree = ""; }; 2AF21E6421349EB000CC12D8 /* MPURLRequest+Testing.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MPURLRequest+Testing.h"; sourceTree = ""; }; 2AF21E6521349EB000CC12D8 /* MPURLRequest+Testing.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "MPURLRequest+Testing.m"; sourceTree = ""; }; + 2AFEE720225BC22300DD82C8 /* MPMoPubAd.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPMoPubAd.h; sourceTree = ""; }; + 2AFEE721225BC38400DD82C8 /* MPImpressionData.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPImpressionData.h; sourceTree = ""; }; + 2AFEE722225BC38400DD82C8 /* MPImpressionData.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPImpressionData.m; sourceTree = ""; }; 2AFF1B781EC2795500495994 /* MPRealTimeTimer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPRealTimeTimer.h; sourceTree = ""; }; 2AFF1B791EC2795500495994 /* MPRealTimeTimer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPRealTimeTimer.m; sourceTree = ""; }; 2AFF1BA61EC289E600495994 /* MPRealTimeTimerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPRealTimeTimerTests.m; sourceTree = ""; }; 3DD2F5DD1CBE970D003F2D81 /* MPNativeAdRendererConstants.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPNativeAdRendererConstants.h; sourceTree = ""; }; 4614B0A3195B6B2200B39F8A /* MPUserInteractionGestureRecognizer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPUserInteractionGestureRecognizer.m; sourceTree = ""; }; 4614B0A4195B6B2200B39F8A /* MPUserInteractionGestureRecognizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPUserInteractionGestureRecognizer.h; sourceTree = ""; }; - 4678267719903BF300DB1B61 /* MPInternalUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPInternalUtils.h; sourceTree = ""; }; - 4678267819903BF300DB1B61 /* MPInternalUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPInternalUtils.m; sourceTree = ""; }; 468BCD9219996B1A0054A051 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; 468BD68717E8CE4100F8849B /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = System/Library/Frameworks/CFNetwork.framework; sourceTree = SDKROOT; }; 46DD862F18037F8D00E5B8E3 /* CoreMedia.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMedia.framework; path = System/Library/Frameworks/CoreMedia.framework; sourceTree = SDKROOT; }; @@ -977,8 +1048,8 @@ 4AAB5335197ED97D00A73C98 /* MPNativeAdSourceQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPNativeAdSourceQueue.h; sourceTree = ""; }; 4AAB5336197ED97D00A73C98 /* MPNativeAdSourceQueue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPNativeAdSourceQueue.m; sourceTree = ""; }; 4AAB533A197F24BA00A73C98 /* MPNativeAdRequest+MPNativeAdSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MPNativeAdRequest+MPNativeAdSource.h"; sourceTree = ""; }; - 4AB5CCCE19F55596005ABBC1 /* MPLogProvider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPLogProvider.h; sourceTree = ""; }; - 4AB5CCCF19F55596005ABBC1 /* MPLogProvider.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPLogProvider.m; sourceTree = ""; }; + 4AB5CCCE19F55596005ABBC1 /* MPLogManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPLogManager.h; sourceTree = ""; }; + 4AB5CCCF19F55596005ABBC1 /* MPLogManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPLogManager.m; sourceTree = ""; }; 4AB846EC1A71CEE8003C23E6 /* MPForceableOrientationProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPForceableOrientationProtocol.h; sourceTree = ""; }; 4AD1D8F919E4958200705E52 /* MPNativeAdDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPNativeAdDelegate.h; sourceTree = ""; }; 4AE42F9B18D8FD2100DE4BF6 /* MPNativeCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPNativeCache.h; sourceTree = ""; }; @@ -1015,7 +1086,6 @@ 57401A421B7AAEF0000EEA64 /* MPDAAIcon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = MPDAAIcon.png; sourceTree = ""; }; 57401A431B7AAEF0000EEA64 /* MPDAAIcon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "MPDAAIcon@2x.png"; sourceTree = ""; }; 57401A441B7AAEF0000EEA64 /* MPDAAIcon@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "MPDAAIcon@3x.png"; sourceTree = ""; }; - 5755ADCD19C79CAD0012B01F /* MoPub-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MoPub-Bridging-Header.h"; sourceTree = ""; }; 575828811A16F12C009C7A85 /* MRConstants.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MRConstants.h; sourceTree = ""; }; 5758288D1A16F281009C7A85 /* MRController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MRController.h; sourceTree = ""; }; 5758288E1A16F281009C7A85 /* MRController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MRController.m; sourceTree = ""; }; @@ -1025,7 +1095,6 @@ 578D287D1B9A406B002E3905 /* MPNativeAdRendererSettings.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPNativeAdRendererSettings.h; sourceTree = ""; }; 578D28851B9A410C002E3905 /* MPStaticNativeAdRendererSettings.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPStaticNativeAdRendererSettings.h; sourceTree = ""; }; 578D28861B9A410C002E3905 /* MPStaticNativeAdRendererSettings.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPStaticNativeAdRendererSettings.m; sourceTree = ""; }; - 57A1A37E1A780A55006A08DA /* MPAdBrowserController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MPAdBrowserController.xib; sourceTree = ""; }; 57AC691F1BB21FA20053C556 /* MOPUBActivityIndicatorView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MOPUBActivityIndicatorView.h; sourceTree = ""; }; 57AC69201BB21FA20053C556 /* MOPUBActivityIndicatorView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MOPUBActivityIndicatorView.m; sourceTree = ""; }; 57AC69211BB21FA20053C556 /* MOPUBAVPlayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MOPUBAVPlayer.h; sourceTree = ""; }; @@ -1040,8 +1109,6 @@ 57AC692A1BB21FA20053C556 /* MOPUBNativeVideoAdConfigValues.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MOPUBNativeVideoAdConfigValues.m; sourceTree = ""; }; 57AC692B1BB21FA20053C556 /* MOPUBNativeVideoCustomEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MOPUBNativeVideoCustomEvent.h; sourceTree = ""; }; 57AC692C1BB21FA20053C556 /* MOPUBNativeVideoCustomEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MOPUBNativeVideoCustomEvent.m; sourceTree = ""; }; - 57AC692D1BB21FA20053C556 /* MOPUBNativeVideoImpressionAgent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MOPUBNativeVideoImpressionAgent.h; sourceTree = ""; }; - 57AC692E1BB21FA20053C556 /* MOPUBNativeVideoImpressionAgent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MOPUBNativeVideoImpressionAgent.m; sourceTree = ""; }; 57AC692F1BB21FA20053C556 /* MOPUBPlayerManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MOPUBPlayerManager.h; sourceTree = ""; }; 57AC69301BB21FA20053C556 /* MOPUBPlayerManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MOPUBPlayerManager.m; sourceTree = ""; }; 57AC69311BB21FA20053C556 /* MOPUBPlayerView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MOPUBPlayerView.h; sourceTree = ""; }; @@ -1129,8 +1196,6 @@ A778C8A71ABB4ED4003F427D /* MPAPIEndpoints.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAPIEndpoints.m; sourceTree = ""; }; A77A791717C3844A00AB0DCA /* MRVideoPlayerManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MRVideoPlayerManager.h; sourceTree = ""; }; A77A791817C3844A00AB0DCA /* MRVideoPlayerManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MRVideoPlayerManager.m; sourceTree = ""; }; - A77FBEDF18C533B400531E8A /* MPTableViewCellImpressionTracker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPTableViewCellImpressionTracker.h; sourceTree = ""; }; - A77FBEE018C533B400531E8A /* MPTableViewCellImpressionTracker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPTableViewCellImpressionTracker.m; sourceTree = ""; }; A77FBEE118C533B400531E8A /* MPNativeAd+Internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "MPNativeAd+Internal.h"; path = "../MPNativeAd+Internal.h"; sourceTree = ""; }; A77FBEE418C533B400531E8A /* MPNativeAdError.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPNativeAdError.h; sourceTree = ""; }; A77FBEE518C533B400531E8A /* MPNativeAdError.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPNativeAdError.m; sourceTree = ""; }; @@ -1208,8 +1273,6 @@ AEF9D4CE171CA895005AAA5A /* MPBaseBannerAdapter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPBaseBannerAdapter.h; sourceTree = ""; }; AEF9D4CF171CA895005AAA5A /* MPBaseBannerAdapter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPBaseBannerAdapter.m; sourceTree = ""; }; AEF9D4D2171CA895005AAA5A /* MPPrivateBannerCustomEventDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPPrivateBannerCustomEventDelegate.h; sourceTree = ""; }; - AEF9D4D4171CA895005AAA5A /* MPAdBrowserController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAdBrowserController.h; sourceTree = ""; }; - AEF9D4D5171CA895005AAA5A /* MPAdBrowserController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAdBrowserController.m; sourceTree = ""; }; AEF9D4D7171CA895005AAA5A /* MPAdConfiguration.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAdConfiguration.h; sourceTree = ""; }; AEF9D4D8171CA895005AAA5A /* MPAdConfiguration.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAdConfiguration.m; sourceTree = ""; }; AEF9D4D9171CA895005AAA5A /* MPAdDestinationDisplayAgent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAdDestinationDisplayAgent.h; sourceTree = ""; }; @@ -1253,8 +1316,6 @@ AEF9D512171CA895005AAA5A /* MRProperty.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MRProperty.m; sourceTree = ""; }; AEF9D515171CA895005AAA5A /* NSURL+MPAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSURL+MPAdditions.h"; sourceTree = ""; }; AEF9D516171CA895005AAA5A /* NSURL+MPAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSURL+MPAdditions.m"; sourceTree = ""; }; - AEF9D519171CA895005AAA5A /* UIWebView+MPAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIWebView+MPAdditions.h"; sourceTree = ""; }; - AEF9D51A171CA895005AAA5A /* UIWebView+MPAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIWebView+MPAdditions.m"; sourceTree = ""; }; AEF9D51B171CA895005AAA5A /* MPAnalyticsTracker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAnalyticsTracker.h; sourceTree = ""; }; AEF9D51C171CA895005AAA5A /* MPAnalyticsTracker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAnalyticsTracker.m; sourceTree = ""; }; AEF9D51D171CA895005AAA5A /* MPError.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPError.h; sourceTree = ""; }; @@ -1269,8 +1330,6 @@ AEF9D526171CA895005AAA5A /* MPReachability.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPReachability.m; sourceTree = ""; }; AEF9D527171CA895005AAA5A /* MPSessionTracker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPSessionTracker.h; sourceTree = ""; }; AEF9D528171CA895005AAA5A /* MPSessionTracker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPSessionTracker.m; sourceTree = ""; }; - AEF9D529171CA895005AAA5A /* MPStoreKitProvider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPStoreKitProvider.h; sourceTree = ""; }; - AEF9D52A171CA895005AAA5A /* MPStoreKitProvider.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPStoreKitProvider.m; sourceTree = ""; }; AEF9D52B171CA895005AAA5A /* MPTimer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPTimer.h; sourceTree = ""; }; AEF9D52C171CA895005AAA5A /* MPTimer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPTimer.m; sourceTree = ""; }; AEF9D52E171CA895005AAA5A /* MPAdConversionTracker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAdConversionTracker.h; sourceTree = ""; }; @@ -1307,8 +1366,6 @@ B2745AA41EEB195300A86A5B /* MOPUBExperimentProvider+Testing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MOPUBExperimentProvider+Testing.h"; sourceTree = ""; }; B27783881CE3BA5C00BAB0B1 /* MPRewardedVideoConnection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPRewardedVideoConnection.h; sourceTree = ""; }; B27783891CE3BA5C00BAB0B1 /* MPRewardedVideoConnection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPRewardedVideoConnection.m; sourceTree = ""; }; - B28725E01B9FB5D200C0D61B /* UIButton+MPAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIButton+MPAdditions.h"; sourceTree = ""; }; - B28725E11B9FB5D200C0D61B /* UIButton+MPAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIButton+MPAdditions.m"; sourceTree = ""; }; B28725E21B9FB5D200C0D61B /* UIColor+MPAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIColor+MPAdditions.h"; sourceTree = ""; }; B28725E31B9FB5D200C0D61B /* UIColor+MPAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIColor+MPAdditions.m"; sourceTree = ""; }; B294E0651BACDB1E00909531 /* MOPUBNativeVideoAdRendererSettings.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MOPUBNativeVideoAdRendererSettings.h; sourceTree = ""; }; @@ -1341,6 +1398,7 @@ BC08C7821E36AD7C00444F16 /* MPVASTLinearAdTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPVASTLinearAdTests.m; sourceTree = ""; }; BC08C7A41E36BB8200444F16 /* linear-mime-types.xml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = "linear-mime-types.xml"; sourceTree = ""; }; BC08C7B21E36C71B00444F16 /* linear-mime-types-all-invalid.xml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = "linear-mime-types-all-invalid.xml"; sourceTree = ""; }; + BC098E4C226E3B53001E44A6 /* MPNativeAdRequestTargetingTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPNativeAdRequestTargetingTests.m; sourceTree = ""; }; BC0ADDEB207FFBEA000ADEA4 /* MPConsentManager+Testing.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MPConsentManager+Testing.h"; sourceTree = ""; }; BC0ADDEC207FFBEA000ADEA4 /* MPConsentManager+Testing.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "MPConsentManager+Testing.m"; sourceTree = ""; }; BC0ADDEE2080023C000ADEA4 /* NSString+MPConsentStatus.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSString+MPConsentStatus.h"; sourceTree = ""; }; @@ -1363,12 +1421,12 @@ BC119223207D211B005DF26E /* MPConsentChangedNotification.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPConsentChangedNotification.h; sourceTree = ""; }; BC119224207D211B005DF26E /* MPConsentChangedNotification.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPConsentChangedNotification.m; sourceTree = ""; }; BC119229207D6D06005DF26E /* MPConsentManagerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPConsentManagerTests.m; sourceTree = ""; }; - BC12D247206304AE0073388B /* MPAdvancedBiddingManagerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPAdvancedBiddingManagerTests.m; sourceTree = ""; }; - BC12D249206304FA0073388B /* MPAdvancedBiddingManager+Testing.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MPAdvancedBiddingManager+Testing.h"; sourceTree = ""; }; - BC12D24A206304FA0073388B /* MPAdvancedBiddingManager+Testing.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "MPAdvancedBiddingManager+Testing.m"; sourceTree = ""; }; - BC12D24C206305940073388B /* MPStubAdvancedBidder.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPStubAdvancedBidder.h; sourceTree = ""; }; - BC12D24D206305940073388B /* MPStubAdvancedBidder.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPStubAdvancedBidder.m; sourceTree = ""; }; + BC12D249206304FA0073388B /* MPMediationManager+Testing.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MPMediationManager+Testing.h"; sourceTree = ""; }; + BC12D24A206304FA0073388B /* MPMediationManager+Testing.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "MPMediationManager+Testing.m"; sourceTree = ""; }; BC181D651ECE4DD6009C752C /* MPAdServerURLBuilderTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAdServerURLBuilderTests.m; sourceTree = ""; }; + BC1920862236F8FC004318D2 /* MPIdentityProviderTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPIdentityProviderTests.m; sourceTree = ""; }; + BC1920882236FA83004318D2 /* MPIdentityProvider+Testing.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MPIdentityProvider+Testing.h"; sourceTree = ""; }; + BC1920892236FA83004318D2 /* MPIdentityProvider+Testing.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "MPIdentityProvider+Testing.m"; sourceTree = ""; }; BC19E33020DC1E7700673D60 /* MPBannerCustomEventAdapter+Testing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MPBannerCustomEventAdapter+Testing.m"; sourceTree = ""; }; BC19E33220DC1F9A00673D60 /* MPBannerAdManagerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPBannerAdManagerTests.m; sourceTree = ""; }; BC19E33420DC203F00673D60 /* MPBannerAdManagerDelegateHandler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPBannerAdManagerDelegateHandler.h; sourceTree = ""; }; @@ -1380,6 +1438,8 @@ BC1A914F1F95687D00D4895B /* MPViewabilityAdapter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MPViewabilityAdapter.h; path = MoPubSDK/Viewability/MPViewabilityAdapter.h; sourceTree = SOURCE_ROOT; }; BC1ED25D21471B0500065952 /* MPMockLongLoadNativeCustomEvent.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPMockLongLoadNativeCustomEvent.h; sourceTree = ""; }; BC1ED25E21471B0500065952 /* MPMockLongLoadNativeCustomEvent.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPMockLongLoadNativeCustomEvent.m; sourceTree = ""; }; + BC2448C422B171B0003EBB4B /* MPExtendedHitBoxButton.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPExtendedHitBoxButton.h; sourceTree = ""; }; + BC2448C522B171B0003EBB4B /* MPExtendedHitBoxButton.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPExtendedHitBoxButton.m; sourceTree = ""; }; BC246A6B1E5672AA00CEFA33 /* MPRewardedVideoAdManagerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPRewardedVideoAdManagerTests.m; sourceTree = ""; }; BC246A771E5675CB00CEFA33 /* MPRewardedVideoAdManager+Testing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MPRewardedVideoAdManager+Testing.h"; sourceTree = ""; }; BC246A781E5675CB00CEFA33 /* MPRewardedVideoAdManager+Testing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MPRewardedVideoAdManager+Testing.m"; sourceTree = ""; }; @@ -1388,14 +1448,12 @@ BC246A7D1E567D1500CEFA33 /* MPMockRewardedVideoAdapter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPMockRewardedVideoAdapter.h; sourceTree = ""; }; BC246A7E1E567D1500CEFA33 /* MPMockRewardedVideoAdapter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPMockRewardedVideoAdapter.m; sourceTree = ""; }; BC27343D203F51C900920507 /* MPMediationSettingsProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPMediationSettingsProtocol.h; sourceTree = ""; }; - BC2C5408206EF34400171B98 /* MPStubMediatedNetwork.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPStubMediatedNetwork.h; sourceTree = ""; }; - BC2C5409206EF34400171B98 /* MPStubMediatedNetwork.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPStubMediatedNetwork.m; sourceTree = ""; }; BC2C5B581F5727D40025FFF5 /* MPMockViewabilityAdapterAvid.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPMockViewabilityAdapterAvid.h; sourceTree = ""; }; BC2C5B591F5727D40025FFF5 /* MPMockViewabilityAdapterAvid.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPMockViewabilityAdapterAvid.m; sourceTree = ""; }; - BC310E8C1F15884A002A6809 /* MPMockAdColonyRewardedVideoCustomEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPMockAdColonyRewardedVideoCustomEvent.h; sourceTree = ""; }; - BC310E8D1F15884A002A6809 /* MPMockAdColonyRewardedVideoCustomEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPMockAdColonyRewardedVideoCustomEvent.m; sourceTree = ""; }; - BC310E991F158A7B002A6809 /* MPMockChartboostRewardedVideoCustomEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPMockChartboostRewardedVideoCustomEvent.h; sourceTree = ""; }; - BC310E9A1F158A7B002A6809 /* MPMockChartboostRewardedVideoCustomEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPMockChartboostRewardedVideoCustomEvent.m; sourceTree = ""; }; + BC310E8C1F15884A002A6809 /* MPMockAdColonyAdapterConfiguration.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPMockAdColonyAdapterConfiguration.h; sourceTree = ""; }; + BC310E8D1F15884A002A6809 /* MPMockAdColonyAdapterConfiguration.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPMockAdColonyAdapterConfiguration.m; sourceTree = ""; }; + BC310E991F158A7B002A6809 /* MPMockChartboostAdapterConfiguration.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPMockChartboostAdapterConfiguration.h; sourceTree = ""; }; + BC310E9A1F158A7B002A6809 /* MPMockChartboostAdapterConfiguration.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPMockChartboostAdapterConfiguration.m; sourceTree = ""; }; BC41F71E20DAD554004BE29C /* MPMockAdDestinationDisplayAgent.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPMockAdDestinationDisplayAgent.h; sourceTree = ""; }; BC41F71F20DAD554004BE29C /* MPMockAdDestinationDisplayAgent.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPMockAdDestinationDisplayAgent.m; sourceTree = ""; }; BC41F72120DAF23C004BE29C /* MPMemoryCacheTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPMemoryCacheTests.m; sourceTree = ""; }; @@ -1403,25 +1461,32 @@ BC4370522087C5D6001B86D4 /* MPAdServerKeys.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPAdServerKeys.m; sourceTree = ""; }; BC437057208A8AC9001B86D4 /* MPBool.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPBool.h; sourceTree = ""; }; BC4982F821407EF200799273 /* NSErrorTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NSErrorTests.m; sourceTree = ""; }; - BC57D5FD1F0ED4240030C365 /* MPRewardedVideoCustomEvent+Caching.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MPRewardedVideoCustomEvent+Caching.h"; sourceTree = ""; }; - BC57D5FE1F0ED4240030C365 /* MPRewardedVideoCustomEvent+Caching.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MPRewardedVideoCustomEvent+Caching.m"; sourceTree = ""; }; + BC4A4D13217F99ED008A7410 /* MPAdapterConfiguration.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPAdapterConfiguration.h; sourceTree = ""; }; + BC4A4D15218125D3008A7410 /* MPMockTapjoyAdapterConfiguration.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPMockTapjoyAdapterConfiguration.h; sourceTree = ""; }; + BC4A4D16218125D3008A7410 /* MPMockTapjoyAdapterConfiguration.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPMockTapjoyAdapterConfiguration.m; sourceTree = ""; }; + BC4A4D18218239FB008A7410 /* MPMockChartboostRewardedVideoCustomEvent.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPMockChartboostRewardedVideoCustomEvent.h; sourceTree = ""; }; + BC4A4D19218239FB008A7410 /* MPMockChartboostRewardedVideoCustomEvent.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPMockChartboostRewardedVideoCustomEvent.m; sourceTree = ""; }; + BC4A4D1B21827287008A7410 /* MPBaseAdapterConfiguration.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPBaseAdapterConfiguration.h; sourceTree = ""; }; + BC4A4D1C21827287008A7410 /* MPBaseAdapterConfiguration.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPBaseAdapterConfiguration.m; sourceTree = ""; }; BC57D6101F0EF75A0030C365 /* MPStubCustomEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPStubCustomEvent.h; sourceTree = ""; }; BC57D6111F0EF75A0030C365 /* MPStubCustomEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPStubCustomEvent.m; sourceTree = ""; }; BC57D63B1F10318F0030C365 /* MoPubTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MoPubTests.m; sourceTree = ""; }; BC62698E1ED4B18F00724C4A /* NSString+MPAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+MPAdditions.h"; sourceTree = ""; }; BC62698F1ED4B18F00724C4A /* NSString+MPAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+MPAdditions.m"; sourceTree = ""; }; - BC64149E1F84137300D067F6 /* MPAdvancedBidder.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPAdvancedBidder.h; sourceTree = ""; }; - BC64EC5520696F7800CB33A7 /* MPMediationSdkInitializable.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPMediationSdkInitializable.h; sourceTree = ""; }; BC64EC572069977E00CB33A7 /* MPMediationManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPMediationManager.h; sourceTree = ""; }; BC64EC582069977E00CB33A7 /* MPMediationManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPMediationManager.m; sourceTree = ""; }; BC64EC5E2069AA4700CB33A7 /* MPMediationManagerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPMediationManagerTests.m; sourceTree = ""; }; + BC6B9E6422C135D00027F2F9 /* MPEngineInfo.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPEngineInfo.h; sourceTree = ""; }; + BC6B9E6522C135D00027F2F9 /* MPEngineInfo.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPEngineInfo.m; sourceTree = ""; }; + BC7003D122679C4F001222E7 /* MPLoggingHandler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPLoggingHandler.h; sourceTree = ""; }; + BC7003D222679C4F001222E7 /* MPLoggingHandler.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPLoggingHandler.m; sourceTree = ""; }; BC756FAA1F34FBA000556299 /* MRController+Testing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MRController+Testing.h"; sourceTree = ""; }; BC756FAB1F34FBA000556299 /* MRController+Testing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MRController+Testing.m"; sourceTree = ""; }; BC756FB71F34FC5600556299 /* MPWebView+Testing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MPWebView+Testing.h"; sourceTree = ""; }; BC756FB81F34FC5600556299 /* MPWebView+Testing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MPWebView+Testing.m"; sourceTree = ""; }; BC789C171F7ABE9B001CE308 /* MPAdConfigurationFactory.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAdConfigurationFactory.m; sourceTree = ""; }; BC789C181F7ABE9B001CE308 /* MPAdConfigurationFactory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAdConfigurationFactory.h; sourceTree = ""; }; - BC79EC231F9810BF00FFC893 /* MPLogLevel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPLogLevel.h; sourceTree = ""; }; + BC79EC231F9810BF00FFC893 /* MPBLogLevel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPBLogLevel.h; sourceTree = ""; }; BC7F0F181ECF9BF800BB087E /* MPAdViewTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAdViewTests.m; sourceTree = ""; }; BC7F0F1A1ECF9D6400BB087E /* MPAdView+Testing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MPAdView+Testing.h"; sourceTree = ""; }; BC7F0F1B1ECF9D6400BB087E /* MPAdView+Testing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MPAdView+Testing.m"; sourceTree = ""; }; @@ -1434,22 +1499,25 @@ BC7FF69920D07BCA003B1842 /* MPHTTPNetworkSession+Testing.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MPHTTPNetworkSession+Testing.h"; sourceTree = ""; }; BC7FF69A20D07BCA003B1842 /* MPHTTPNetworkSession+Testing.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "MPHTTPNetworkSession+Testing.m"; sourceTree = ""; }; BC7FF69C20D07C4E003B1842 /* MPHTTPNetworkSessionTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPHTTPNetworkSessionTests.m; sourceTree = ""; }; + BC8305E5216BCD400097C1BA /* MPMockAdapters.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = MPMockAdapters.plist; sourceTree = ""; }; BC86211B1DCBBD5A0012275D /* MPCountdownTimerViewTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPCountdownTimerViewTests.m; sourceTree = ""; }; BC86212A1DCBE00D0012275D /* AVKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVKit.framework; path = System/Library/Frameworks/AVKit.framework; sourceTree = SDKROOT; }; BC86212F1DCBE0590012275D /* CoreMotion.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMotion.framework; path = System/Library/Frameworks/CoreMotion.framework; sourceTree = SDKROOT; }; BC88E7E020769A3D002A3357 /* MPReachabilityManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPReachabilityManager.h; sourceTree = ""; }; BC88E7E120769A3D002A3357 /* MPReachabilityManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPReachabilityManager.m; sourceTree = ""; }; + BC8CDD64218A12DD006DE606 /* MPMoPubConfigurationTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPMoPubConfigurationTests.m; sourceTree = ""; }; BC926EF320D9753B004ED8F7 /* MPMemoryCache.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPMemoryCache.h; sourceTree = ""; }; BC926EF420D9753B004ED8F7 /* MPMemoryCache.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPMemoryCache.m; sourceTree = ""; }; BC94CC8720FF97FE00FB018A /* MPURL.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPURL.h; sourceTree = ""; }; BC94CC8820FF97FE00FB018A /* MPURL.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPURL.m; sourceTree = ""; }; + BC9EF9DE216811B3005BEA65 /* MPAdapters.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = MPAdapters.plist; sourceTree = ""; }; BCA00B971EF47A91006FF762 /* libMoPubSDK-ExcludeNative.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libMoPubSDK-ExcludeNative.a"; sourceTree = BUILT_PRODUCTS_DIR; }; BCA2EA492023DAC6000F24C0 /* MPHTTPNetworkTaskData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPHTTPNetworkTaskData.h; sourceTree = ""; }; BCA2EA4A2023DAC8000F24C0 /* MPHTTPNetworkTaskData.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPHTTPNetworkTaskData.m; sourceTree = ""; }; + BCA762302149B4B100D55A05 /* MPLogEvent.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPLogEvent.h; sourceTree = ""; }; + BCA762312149B4B100D55A05 /* MPLogEvent.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPLogEvent.m; sourceTree = ""; }; BCA77A2C1DCBAFAC0049768E /* NSBundle+MPAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSBundle+MPAdditions.h"; sourceTree = ""; }; BCA77A2D1DCBAFAC0049768E /* NSBundle+MPAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSBundle+MPAdditions.m"; sourceTree = ""; }; - BCAAD1041F85696700047435 /* MPAdvancedBiddingManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAdvancedBiddingManager.m; sourceTree = ""; }; - BCAAD10F1F85696A00047435 /* MPAdvancedBiddingManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAdvancedBiddingManager.h; sourceTree = ""; }; BCAC6F5D1E5CF6F5002B2656 /* MPRewardedVideoTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPRewardedVideoTests.m; sourceTree = ""; }; BCAC6F6C1E5D0730002B2656 /* MPRewardedVideo+Testing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MPRewardedVideo+Testing.h"; sourceTree = ""; }; BCAC6F6D1E5D0730002B2656 /* MPRewardedVideo+Testing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MPRewardedVideo+Testing.m"; sourceTree = ""; }; @@ -1457,6 +1525,7 @@ BCAD021A20CB388C007DC2B2 /* NSDate+MPAdditions.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSDate+MPAdditions.m"; sourceTree = ""; }; BCAD022520CEE694007DC2B2 /* NSError+MPAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSError+MPAdditions.h"; sourceTree = ""; }; BCAD022620CEE694007DC2B2 /* NSError+MPAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSError+MPAdditions.m"; sourceTree = ""; }; + BCAD76A6214AE05B00A1B067 /* MPBLogger.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPBLogger.h; sourceTree = ""; }; BCAED2531DF6194E00D45480 /* MPMoPubRewardedPlayableCustomEventTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPMoPubRewardedPlayableCustomEventTests.m; sourceTree = ""; }; BCAED25F1DF628D400D45480 /* MPMoPubRewardedPlayableCustomEvent+Testing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MPMoPubRewardedPlayableCustomEvent+Testing.h"; sourceTree = ""; }; BCAED2601DF628D400D45480 /* MPMoPubRewardedPlayableCustomEvent+Testing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MPMoPubRewardedPlayableCustomEvent+Testing.m"; sourceTree = ""; }; @@ -1476,14 +1545,17 @@ BCC54C2E1ECFB8A300A4FEF0 /* MPNativeAdRequest+Testing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MPNativeAdRequest+Testing.m"; sourceTree = ""; }; BCC79EC3204DC28B00F7ABE6 /* MPURLRequestTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPURLRequestTests.m; sourceTree = ""; }; BCCE7A2A20768922003027BA /* MPReachabilityTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPReachabilityTests.m; sourceTree = ""; }; + BCCEE88F214B04E5003BD130 /* MPConsoleLogger.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPConsoleLogger.h; sourceTree = ""; }; + BCCEE890214B04E5003BD130 /* MPConsoleLogger.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPConsoleLogger.m; sourceTree = ""; }; BCDE9EBF1DEFA9130034A444 /* MPMoPubRewardedPlayableCustomEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPMoPubRewardedPlayableCustomEvent.h; sourceTree = ""; }; BCDE9EC01DEFA9130034A444 /* MPMoPubRewardedPlayableCustomEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPMoPubRewardedPlayableCustomEvent.m; sourceTree = ""; }; BCDE9ED61DF0AF970034A444 /* MPAdConfigurationTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAdConfigurationTests.m; sourceTree = ""; }; + BCDECB10219B4627002C1E7A /* MPContentBlocker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPContentBlocker.m; sourceTree = ""; }; + BCDECB11219B4628002C1E7A /* MPContentBlocker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPContentBlocker.h; sourceTree = ""; }; BCECF3092047715E005AF3BD /* MPURLRequest.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPURLRequest.h; sourceTree = ""; }; BCECF30A2047715E005AF3BD /* MPURLRequest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPURLRequest.m; sourceTree = ""; }; BCEE05082037A4270076CA86 /* MPHTTPNetworkSession.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPHTTPNetworkSession.h; sourceTree = ""; }; BCEE05092037A4280076CA86 /* MPHTTPNetworkSession.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPHTTPNetworkSession.m; sourceTree = ""; }; - BCF0FA8A1DC9512900ADFE4F /* MPCountdownTimer.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = MPCountdownTimer.html; sourceTree = ""; }; BCF0FA971DC9536B00ADFE4F /* MPCountdownTimerView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPCountdownTimerView.h; sourceTree = ""; }; BCF0FA981DC9536B00ADFE4F /* MPCountdownTimerView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPCountdownTimerView.m; sourceTree = ""; }; BCF2F1B7206317B500DAF66E /* MPAdServerURLBuilder+Testing.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MPAdServerURLBuilder+Testing.h"; sourceTree = ""; }; @@ -1492,6 +1564,21 @@ BCF9BDB92119FA7800A2F557 /* NSURLSessionTask+Testing.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSURLSessionTask+Testing.m"; sourceTree = ""; }; BCFE67D7208508D3005E458A /* MPConsentChangedReason.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPConsentChangedReason.h; sourceTree = ""; }; BCFE67D8208508D3005E458A /* MPConsentChangedReason.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPConsentChangedReason.m; sourceTree = ""; }; + EC0BF273227C9A09003DB141 /* MoPub+Utility.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MoPub+Utility.h"; sourceTree = ""; }; + EC0BF274227C9A09003DB141 /* MoPub+Utility.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "MoPub+Utility.m"; sourceTree = ""; }; + EC109EE0226E881F00079B57 /* MPCountdownTimerView+Testing.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MPCountdownTimerView+Testing.h"; sourceTree = ""; }; + EC481B0123209D84003AA3B9 /* MPWebBrowserUserAgentInfo.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPWebBrowserUserAgentInfo.h; sourceTree = ""; }; + EC481B0223209D84003AA3B9 /* MPWebBrowserUserAgentInfo.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPWebBrowserUserAgentInfo.m; sourceTree = ""; }; + EC481B0423209E65003AA3B9 /* MPWebBrowserUserAgentInfoTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPWebBrowserUserAgentInfoTests.m; sourceTree = ""; }; + EC87668922EA849100D4B3D9 /* MPVASTMediaFileTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPVASTMediaFileTests.m; sourceTree = ""; }; + EC87668C22EB6E8100D4B3D9 /* MPMediaFileCache.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPMediaFileCache.h; sourceTree = ""; }; + EC923EF822FDD38B00ED83EE /* VAST_3.0_linear_ad_comprehensive.xml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = VAST_3.0_linear_ad_comprehensive.xml; sourceTree = ""; }; + EC923EFA22FDD3A400ED83EE /* MPVASTTrackingTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPVASTTrackingTests.m; sourceTree = ""; }; + ECA6FF16226F8DD7007626A5 /* MPTimerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPTimerTests.m; sourceTree = ""; }; + ECA6FF18226F9B4C007626A5 /* MPTimer+Testing.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MPTimer+Testing.h"; sourceTree = ""; }; + ECA6FF19226F9B4C007626A5 /* MPTimer+Testing.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "MPTimer+Testing.m"; sourceTree = ""; }; + ECD106E52280FE2600398CA5 /* MRControllerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MRControllerTests.m; sourceTree = ""; }; + ECF653C422DD3D0700B7DE1D /* MPDiskLRUCacheTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPDiskLRUCacheTests.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -1499,6 +1586,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + ECF653C922DE75C500B7DE1D /* AVKit.framework in Frameworks */, B2D54B621ED2058E004E3C7B /* SafariServices.framework in Frameworks */, BC8621371DCBE0EC0012275D /* MediaPlayer.framework in Frameworks */, BC8621361DCBE0DA0012275D /* SystemConfiguration.framework in Frameworks */, @@ -1580,7 +1668,11 @@ BCAED2621DF62CED00D45480 /* Categories */, BCAED27E1DF73E2A00D45480 /* Delegate Handlers */, B23C3B501E353F950003D79E /* Fixtures */, + ECD106E92280FE3700398CA5 /* Internal */, 2AAA8DFD1D95C77B006962E8 /* Info.plist */, + EC481B0423209E65003AA3B9 /* MPWebBrowserUserAgentInfoTests.m */, + ECF653C422DD3D0700B7DE1D /* MPDiskLRUCacheTests.m */, + BC7003D422679C72001222E7 /* Logging */, BCAED2661DF62E4200D45480 /* Mocks */, B2D54B661ED20C95004E3C7B /* MOPUBExperimentProviderTests.m */, 2AA73B9D1FCF869D001FB787 /* MOPUBNativeVideoAdAdapterTests.m */, @@ -1592,7 +1684,6 @@ B2C423B61FA3B5FE00D8E164 /* MPAdImpressionTimerTests.m */, 2A4A5D211F86DF270082FC4C /* MPAdServerCommunicatorTests.m */, BC181D651ECE4DD6009C752C /* MPAdServerURLBuilderTests.m */, - BC12D247206304AE0073388B /* MPAdvancedBiddingManagerTests.m */, BC7F0F181ECF9BF800BB087E /* MPAdViewTests.m */, BC19E33220DC1F9A00673D60 /* MPBannerAdManagerTests.m */, B2C423BA1FA7A38500D8E164 /* MPBannerCustomEventAdapterTests.m */, @@ -1602,21 +1693,28 @@ B2FE8FAF206ABFF500593089 /* MPDictionaryAdditionTests.m */, B2FC80CC20993CF200D43EE7 /* MPGeolocationProviderTest.m */, BC7FF69C20D07C4E003B1842 /* MPHTTPNetworkSessionTests.m */, + BC1920862236F8FC004318D2 /* MPIdentityProviderTests.m */, + 2AA2E2FD225FE0AB00478D5C /* MPImpressionDataTests.m */, BCC54C231ECFACE200A4FEF0 /* MPInterstitialAdControllerTests.m */, BC0BE6A620D4349200DB0D2C /* MPInterstitialAdManagerTests.m */, 2A922D391E9C4CF900F83923 /* MPInterstitialCustomEventAdapterTests.m */, BC64EC5E2069AA4700CB33A7 /* MPMediationManagerTests.m */, BC41F72120DAF23C004BE29C /* MPMemoryCacheTests.m */, + BC8CDD64218A12DD006DE606 /* MPMoPubConfigurationTests.m */, 2A75215B1F70720F0099C33C /* MPMoPubNativeAdAdapterTests.m */, BCAED2531DF6194E00D45480 /* MPMoPubRewardedPlayableCustomEventTests.m */, 2A5C4DB71F6B25F20076C08C /* MPNativeAdConfigValuesTests.m */, + BC098E4C226E3B53001E44A6 /* MPNativeAdRequestTargetingTests.m */, BCC54C2B1ECFB81000A4FEF0 /* MPNativeAdRequestTests.m */, + 2A89F15D22371ECC00E03010 /* MPRateLimitConfigurationTests.m */, + 2A89F1552237136700E03010 /* MPRateLimitManagerTests.m */, BCCE7A2A20768922003027BA /* MPReachabilityTests.m */, 2AFF1BA61EC289E600495994 /* MPRealTimeTimerTests.m */, 2AF177321E9846A000A600BD /* MPRewardedVideoAdapterTests.m */, BC246A6B1E5672AA00CEFA33 /* MPRewardedVideoAdManagerTests.m */, BC031AE3211CF93B00E4715B /* MPRewardedVideoRewardTests.m */, BCAC6F5D1E5CF6F5002B2656 /* MPRewardedVideoTests.m */, + ECA6FF16226F8DD7007626A5 /* MPTimerTests.m */, BCC79EC3204DC28B00F7ABE6 /* MPURLRequestTests.m */, B2D54B681ED20FA1004E3C7B /* MPURLResolverTests.m */, BC08C7821E36AD7C00444F16 /* MPVASTLinearAdTests.m */, @@ -1683,8 +1781,6 @@ 57AC692A1BB21FA20053C556 /* MOPUBNativeVideoAdConfigValues.m */, 57AC692B1BB21FA20053C556 /* MOPUBNativeVideoCustomEvent.h */, 57AC692C1BB21FA20053C556 /* MOPUBNativeVideoCustomEvent.m */, - 57AC692D1BB21FA20053C556 /* MOPUBNativeVideoImpressionAgent.h */, - 57AC692E1BB21FA20053C556 /* MOPUBNativeVideoImpressionAgent.m */, 57AC692F1BB21FA20053C556 /* MOPUBPlayerManager.h */, 57AC69301BB21FA20053C556 /* MOPUBPlayerManager.m */, 57AC69311BB21FA20053C556 /* MOPUBPlayerView.h */, @@ -1705,8 +1801,6 @@ 57ACE3F41AA7E8D900A05633 /* MPRewardedVideo.m */, 57ACE3F51AA7E8D900A05633 /* MPRewardedVideoCustomEvent.h */, 57ACE3F61AA7E8D900A05633 /* MPRewardedVideoCustomEvent.m */, - BC57D5FD1F0ED4240030C365 /* MPRewardedVideoCustomEvent+Caching.h */, - BC57D5FE1F0ED4240030C365 /* MPRewardedVideoCustomEvent+Caching.m */, 57ACE3F71AA7E8D900A05633 /* MPRewardedVideoError.h */, 57ACE3F81AA7E8D900A05633 /* MPRewardedVideoError.m */, 57ACE3F91AA7E8D900A05633 /* MPRewardedVideoReward.h */, @@ -1794,6 +1888,7 @@ A7A9AC0D1974578E00C26A7E /* MPClientAdPositioning.m */, A7A1CDD61974783D0082A6FA /* MPCollectionViewAdPlacer.h */, A7A1CDD71974783D0082A6FA /* MPCollectionViewAdPlacer.m */, + 2A73E33C226E46B7001FEE03 /* MPCollectionViewAdPlacerDelegate.h */, A77FBEE618C533B400531E8A /* MPNativeAd.h */, A77FBEE718C533B400531E8A /* MPNativeAd.m */, 4AF54B40194A21340093F714 /* MPNativeAdAdapter.h */, @@ -1814,6 +1909,7 @@ A72F90EC19B7CA0400A5601B /* MPServerAdPositioning.m */, A7A1CDD11974782E0082A6FA /* MPTableViewAdPlacer.h */, A7A1CDD21974782E0082A6FA /* MPTableViewAdPlacer.m */, + 2A73E33E226E5851001FEE03 /* MPTableViewAdPlacerDelegate.h */, ); path = NativeAds; sourceTree = ""; @@ -1836,10 +1932,10 @@ 571FA43A193FC4D300A36583 /* MPMoPubNativeCustomEvent.m */, 57AC6AB11BB30F860053C556 /* MPNativeAd+Internal.h */, 57E6B87F1BA25B4B001FE403 /* MPNativeAd+Internal.m */, - 2A501D3D1F68AD5100806177 /* MPNativeAdConfigValues+Internal.h */, - 2A501D3E1F68AD5100806177 /* MPNativeAdConfigValues+Internal.m */, 2A501D2C1F68ABDE00806177 /* MPNativeAdConfigValues.h */, 2A501D2D1F68ABDE00806177 /* MPNativeAdConfigValues.m */, + 2A501D3D1F68AD5100806177 /* MPNativeAdConfigValues+Internal.h */, + 2A501D3E1F68AD5100806177 /* MPNativeAdConfigValues+Internal.m */, A7A1CDDB197478E20082A6FA /* MPNativeAdData.h */, A7A1CDDC197478E20082A6FA /* MPNativeAdData.m */, 3DD2F5DD1CBE970D003F2D81 /* MPNativeAdRendererConstants.h */, @@ -1864,10 +1960,9 @@ A7A1CDE61974904A0082A6FA /* MPStreamAdPlacementData.m */, A7A1CDC219745F0E0082A6FA /* MPStreamAdPlacer.h */, A7A1CDC319745F0E0082A6FA /* MPStreamAdPlacer.m */, + 2A73E33A226E45F4001FEE03 /* MPStreamAdPlacerDelegate.h */, 573A82EE1B8E488400ED4067 /* MPTableViewAdPlacerCell.h */, 573A82EF1B8E488400ED4067 /* MPTableViewAdPlacerCell.m */, - A77FBEDF18C533B400531E8A /* MPTableViewCellImpressionTracker.h */, - A77FBEE018C533B400531E8A /* MPTableViewCellImpressionTracker.m */, ); path = Internal; sourceTree = ""; @@ -1946,20 +2041,25 @@ isa = PBXGroup; children = ( AEF9D4C7171CA895005AAA5A /* Internal */, - 5755ADCD19C79CAD0012B01F /* MoPub-Bridging-Header.h */, + BCCEE888214AF8A9003BD130 /* Logging */, A71DB8111A2FE68300D3B229 /* MoPub.h */, A71DB8121A2FE68300D3B229 /* MoPub.m */, 2A2701EE2020FCDC004A72E6 /* MOPUBDisplayAgentType.h */, B2D54B541ED20521004E3C7B /* MOPUBExperimentProvider.h */, B2D54B551ED20521004E3C7B /* MOPUBExperimentProvider.m */, + BC4A4D13217F99ED008A7410 /* MPAdapterConfiguration.h */, AEF9D52E171CA895005AAA5A /* MPAdConversionTracker.h */, AEF9D52F171CA895005AAA5A /* MPAdConversionTracker.m */, - BC64149E1F84137300D067F6 /* MPAdvancedBidder.h */, + BC7F42842141CE7E007EC273 /* MPAdTargeting.h */, + BC7F42852141CE7E007EC273 /* MPAdTargeting.m */, AEF9D530171CA895005AAA5A /* MPAdView.h */, AEF9D531171CA895005AAA5A /* MPAdView.m */, + 2A73E336226E43D3001FEE03 /* MPAdViewDelegate.h */, AEF9D532171CA895005AAA5A /* MPBannerCustomEvent.h */, AEF9D533171CA895005AAA5A /* MPBannerCustomEvent.m */, AEF9D534171CA895005AAA5A /* MPBannerCustomEventDelegate.h */, + BC4A4D1B21827287008A7410 /* MPBaseAdapterConfiguration.h */, + BC4A4D1C21827287008A7410 /* MPBaseAdapterConfiguration.m */, BC437057208A8AC9001B86D4 /* MPBool.h */, BC119223207D211B005DF26E /* MPConsentChangedNotification.h */, BC119224207D211B005DF26E /* MPConsentChangedNotification.m */, @@ -1969,18 +2069,23 @@ BC119219207BDD45005DF26E /* MPConsentStatus.h */, AEF9D535171CA895005AAA5A /* MPConstants.h */, 57B86B5619C78F5D00AD50EE /* MPConstants.m */, + BC6B9E6422C135D00027F2F9 /* MPEngineInfo.h */, + BC6B9E6522C135D00027F2F9 /* MPEngineInfo.m */, + 2AFEE721225BC38400DD82C8 /* MPImpressionData.h */, + 2AFEE722225BC38400DD82C8 /* MPImpressionData.m */, + 2A48DF39229601C1003763C2 /* MPImpressionTrackedNotification.h */, + 2A48DF3A229601C1003763C2 /* MPImpressionTrackedNotification.m */, AEF9D536171CA895005AAA5A /* MPInterstitialAdController.h */, AEF9D537171CA895005AAA5A /* MPInterstitialAdController.m */, + 2A73E338226E44F8001FEE03 /* MPInterstitialAdControllerDelegate.h */, AEF9D538171CA895005AAA5A /* MPInterstitialCustomEvent.h */, AEF9D539171CA895005AAA5A /* MPInterstitialCustomEvent.m */, AEF9D53A171CA895005AAA5A /* MPInterstitialCustomEventDelegate.h */, - BC79EC231F9810BF00FFC893 /* MPLogLevel.h */, - BC64EC5520696F7800CB33A7 /* MPMediationSdkInitializable.h */, BC27343D203F51C900920507 /* MPMediationSettingsProtocol.h */, + 2AFEE720225BC22300DD82C8 /* MPMoPubAd.h */, + 2A60998E225E8E020095890A /* MPMoPubAdPlacer.h */, 2AD9F9E01F8547DF00E0A5F0 /* MPMoPubConfiguration.h */, 2AD9F9D51F8547DE00E0A5F0 /* MPMoPubConfiguration.m */, - BC7F42842141CE7E007EC273 /* MPAdTargeting.h */, - BC7F42852141CE7E007EC273 /* MPAdTargeting.m */, A77FBED818C533B400531E8A /* NativeAds */, B27114991B8549ED0066CE82 /* NativeVideo */, AEF9D53B171CA895005AAA5A /* Resources */, @@ -1997,32 +2102,38 @@ AEF9D4D3171CA895005AAA5A /* Common */, AEF9D4E6171CA895005AAA5A /* HTML */, AEF9D4F1171CA895005AAA5A /* Interstitials */, - BCAAD10F1F85696A00047435 /* MPAdvancedBiddingManager.h */, - BCAAD1041F85696700047435 /* MPAdvancedBiddingManager.m */, + BC4370512087C5D6001B86D4 /* MPAdServerKeys.h */, + BC4370522087C5D6001B86D4 /* MPAdServerKeys.m */, + BC0ADDF420810B38000ADEA4 /* MPConsentDialogViewController.h */, + BC0ADDF520810B3A000ADEA4 /* MPConsentDialogViewController.m */, + BC11921B207BE949005DF26E /* MPConsentManager.h */, + BC11921C207BE949005DF26E /* MPConsentManager.m */, 4A5968C018CFE71200D0D1AD /* MPCoreInstanceProvider.h */, 4A5968C118CFE71200D0D1AD /* MPCoreInstanceProvider.m */, 2A4D35DA211CBF5100BE9377 /* MPCoreInstanceProvider+MRAID.h */, 2A4D35DB211CBF5200BE9377 /* MPCoreInstanceProvider+MRAID.m */, + BC2448C422B171B0003EBB4B /* MPExtendedHitBoxButton.h */, + BC2448C522B171B0003EBB4B /* MPExtendedHitBoxButton.m */, BCEE05082037A4270076CA86 /* MPHTTPNetworkSession.h */, BCEE05092037A4280076CA86 /* MPHTTPNetworkSession.m */, BCA2EA492023DAC6000F24C0 /* MPHTTPNetworkTaskData.h */, BCA2EA4A2023DAC8000F24C0 /* MPHTTPNetworkTaskData.m */, - BC88E7E020769A3D002A3357 /* MPReachabilityManager.h */, - BC88E7E120769A3D002A3357 /* MPReachabilityManager.m */, - BCECF3092047715E005AF3BD /* MPURLRequest.h */, - BCECF30A2047715E005AF3BD /* MPURLRequest.m */, - BC94CC8720FF97FE00FB018A /* MPURL.h */, - BC94CC8820FF97FE00FB018A /* MPURL.m */, BC64EC572069977E00CB33A7 /* MPMediationManager.h */, BC64EC582069977E00CB33A7 /* MPMediationManager.m */, - BC11921B207BE949005DF26E /* MPConsentManager.h */, - BC11921C207BE949005DF26E /* MPConsentManager.m */, - BC0ADDF420810B38000ADEA4 /* MPConsentDialogViewController.h */, - BC0ADDF520810B3A000ADEA4 /* MPConsentDialogViewController.m */, - BC4370512087C5D6001B86D4 /* MPAdServerKeys.h */, - BC4370522087C5D6001B86D4 /* MPAdServerKeys.m */, BC926EF320D9753B004ED8F7 /* MPMemoryCache.h */, BC926EF420D9753B004ED8F7 /* MPMemoryCache.m */, + 2A89F1472236DF2200E03010 /* MPRateLimitConfiguration.h */, + 2A89F1482236DF2200E03010 /* MPRateLimitConfiguration.m */, + 2A89F14D2236DF3600E03010 /* MPRateLimitManager.h */, + 2A89F14E2236DF3600E03010 /* MPRateLimitManager.m */, + BC88E7E020769A3D002A3357 /* MPReachabilityManager.h */, + BC88E7E120769A3D002A3357 /* MPReachabilityManager.m */, + BC94CC8720FF97FE00FB018A /* MPURL.h */, + BC94CC8820FF97FE00FB018A /* MPURL.m */, + BCECF3092047715E005AF3BD /* MPURLRequest.h */, + BCECF30A2047715E005AF3BD /* MPURLRequest.m */, + EC481B0123209D84003AA3B9 /* MPWebBrowserUserAgentInfo.h */, + EC481B0223209D84003AA3B9 /* MPWebBrowserUserAgentInfo.m */, AEF9D500171CA895005AAA5A /* MRAID */, AEF9D513171CA895005AAA5A /* Utility */, A776A5121B5DDE6200095706 /* VAST */, @@ -2055,9 +2166,6 @@ 875390C31B04073F001F0550 /* MPActivityViewControllerHelper.m */, 875390C41B04073F001F0550 /* MPActivityViewControllerHelper+TweetShare.h */, 875390C51B04073F001F0550 /* MPActivityViewControllerHelper+TweetShare.m */, - AEF9D4D4171CA895005AAA5A /* MPAdBrowserController.h */, - AEF9D4D5171CA895005AAA5A /* MPAdBrowserController.m */, - 57A1A37E1A780A55006A08DA /* MPAdBrowserController.xib */, AEF9D4D7171CA895005AAA5A /* MPAdConfiguration.h */, AEF9D4D8171CA895005AAA5A /* MPAdConfiguration.m */, AEF9D4D9171CA895005AAA5A /* MPAdDestinationDisplayAgent.h */, @@ -2099,6 +2207,8 @@ children = ( AEF9D4E9171CA895005AAA5A /* MPAdWebViewAgent.h */, AEF9D4EA171CA895005AAA5A /* MPAdWebViewAgent.m */, + BCDECB11219B4628002C1E7A /* MPContentBlocker.h */, + BCDECB10219B4627002C1E7A /* MPContentBlocker.m */, AEF9D4EB171CA895005AAA5A /* MPHTMLBannerCustomEvent.h */, AEF9D4EC171CA895005AAA5A /* MPHTMLBannerCustomEvent.m */, AEF9D4ED171CA895005AAA5A /* MPHTMLInterstitialCustomEvent.h */, @@ -2166,8 +2276,7 @@ AEF9D513171CA895005AAA5A /* Utility */ = { isa = PBXGroup; children = ( - 4AB5CCCE19F55596005ABBC1 /* MPLogProvider.h */, - 4AB5CCCF19F55596005ABBC1 /* MPLogProvider.m */, + EC87668B22EB6E6B00D4B3D9 /* Protocols */, AEF9D514171CA895005AAA5A /* Categories */, AEF9D51B171CA895005AAA5A /* MPAnalyticsTracker.h */, AEF9D51C171CA895005AAA5A /* MPAnalyticsTracker.m */, @@ -2177,20 +2286,14 @@ AEF9D520171CA895005AAA5A /* MPGlobal.m */, AEF9D521171CA895005AAA5A /* MPIdentityProvider.h */, AEF9D522171CA895005AAA5A /* MPIdentityProvider.m */, - AEF9D523171CA895005AAA5A /* MPLogging.h */, - AEF9D524171CA895005AAA5A /* MPLogging.m */, AEF9D525171CA895005AAA5A /* MPReachability.h */, AEF9D526171CA895005AAA5A /* MPReachability.m */, AEF9D527171CA895005AAA5A /* MPSessionTracker.h */, AEF9D528171CA895005AAA5A /* MPSessionTracker.m */, - AEF9D529171CA895005AAA5A /* MPStoreKitProvider.h */, - AEF9D52A171CA895005AAA5A /* MPStoreKitProvider.m */, AEF9D52B171CA895005AAA5A /* MPTimer.h */, AEF9D52C171CA895005AAA5A /* MPTimer.m */, 4614B0A4195B6B2200B39F8A /* MPUserInteractionGestureRecognizer.h */, 4614B0A3195B6B2200B39F8A /* MPUserInteractionGestureRecognizer.m */, - 4678267719903BF300DB1B61 /* MPInternalUtils.h */, - 4678267819903BF300DB1B61 /* MPInternalUtils.m */, A7759F041A1EB19500087E00 /* MPGeolocationProvider.h */, A7759F051A1EB19500087E00 /* MPGeolocationProvider.m */, ); @@ -2200,6 +2303,8 @@ AEF9D514171CA895005AAA5A /* Categories */ = { isa = PBXGroup; children = ( + EC0BF273227C9A09003DB141 /* MoPub+Utility.h */, + EC0BF274227C9A09003DB141 /* MoPub+Utility.m */, BCA77A2C1DCBAFAC0049768E /* NSBundle+MPAdditions.h */, BCA77A2D1DCBAFAC0049768E /* NSBundle+MPAdditions.m */, BCAD021920CB388C007DC2B2 /* NSDate+MPAdditions.h */, @@ -2220,14 +2325,12 @@ BC0ADDEF2080023C000ADEA4 /* NSString+MPConsentStatus.m */, AEF9D515171CA895005AAA5A /* NSURL+MPAdditions.h */, AEF9D516171CA895005AAA5A /* NSURL+MPAdditions.m */, - B28725E01B9FB5D200C0D61B /* UIButton+MPAdditions.h */, - B28725E11B9FB5D200C0D61B /* UIButton+MPAdditions.m */, + 2A890DE52303656D00FE683F /* SKStoreProductViewController+MPAdditions.h */, + 2A890DE62303656D00FE683F /* SKStoreProductViewController+MPAdditions.m */, B28725E21B9FB5D200C0D61B /* UIColor+MPAdditions.h */, B28725E31B9FB5D200C0D61B /* UIColor+MPAdditions.m */, 57AC697F1BB21FC30053C556 /* UIView+MPAdditions.h */, 57AC69801BB21FC30053C556 /* UIView+MPAdditions.m */, - AEF9D519171CA895005AAA5A /* UIWebView+MPAdditions.h */, - AEF9D51A171CA895005AAA5A /* UIWebView+MPAdditions.m */, ); path = Categories; sourceTree = ""; @@ -2254,7 +2357,7 @@ 57401A421B7AAEF0000EEA64 /* MPDAAIcon.png */, 57401A431B7AAEF0000EEA64 /* MPDAAIcon@2x.png */, 57401A441B7AAEF0000EEA64 /* MPDAAIcon@3x.png */, - BCF0FA8A1DC9512900ADFE4F /* MPCountdownTimer.html */, + BC9EF9DE216811B3005BEA65 /* MPAdapters.plist */, ); path = Resources; sourceTree = ""; @@ -2270,10 +2373,11 @@ B23C3B511E353FC20003D79E /* VastXML */ = { isa = PBXGroup; children = ( + BC08C7B21E36C71B00444F16 /* linear-mime-types-all-invalid.xml */, + BC08C7A41E36BB8200444F16 /* linear-mime-types.xml */, B23C3B5A1E35709D0003D79E /* linear-tracking-no-event.xml */, B23C3B521E353FD20003D79E /* linear-tracking.xml */, - BC08C7A41E36BB8200444F16 /* linear-mime-types.xml */, - BC08C7B21E36C71B00444F16 /* linear-mime-types-all-invalid.xml */, + EC923EF822FDD38B00ED83EE /* VAST_3.0_linear_ad_comprehensive.xml */, ); path = VastXML; sourceTree = ""; @@ -2290,19 +2394,35 @@ path = NativeVideo; sourceTree = ""; }; + BC31026C21598E070007FA69 /* Internal */ = { + isa = PBXGroup; + children = ( + 4AB5CCCE19F55596005ABBC1 /* MPLogManager.h */, + 4AB5CCCF19F55596005ABBC1 /* MPLogManager.m */, + BCCEE88F214B04E5003BD130 /* MPConsoleLogger.h */, + BCCEE890214B04E5003BD130 /* MPConsoleLogger.m */, + ); + path = Internal; + sourceTree = ""; + }; BC57D6131F0EF75E0030C365 /* Stubs */ = { isa = PBXGroup; children = ( BC57D6101F0EF75A0030C365 /* MPStubCustomEvent.h */, BC57D6111F0EF75A0030C365 /* MPStubCustomEvent.m */, - BC12D24C206305940073388B /* MPStubAdvancedBidder.h */, - BC12D24D206305940073388B /* MPStubAdvancedBidder.m */, - BC2C5408206EF34400171B98 /* MPStubMediatedNetwork.h */, - BC2C5409206EF34400171B98 /* MPStubMediatedNetwork.m */, ); name = Stubs; sourceTree = ""; }; + BC7003D422679C72001222E7 /* Logging */ = { + isa = PBXGroup; + children = ( + BC7003D122679C4F001222E7 /* MPLoggingHandler.h */, + BC7003D222679C4F001222E7 /* MPLoggingHandler.m */, + ); + name = Logging; + sourceTree = ""; + }; BCAED2621DF62CED00D45480 /* Categories */ = { isa = PBXGroup; children = ( @@ -2319,8 +2439,6 @@ 2A4A5D431F86E2340082FC4C /* MPAdServerCommunicator+Testing.m */, BCF2F1B7206317B500DAF66E /* MPAdServerURLBuilder+Testing.h */, BCF2F1B8206317B500DAF66E /* MPAdServerURLBuilder+Testing.m */, - BC12D249206304FA0073388B /* MPAdvancedBiddingManager+Testing.h */, - BC12D24A206304FA0073388B /* MPAdvancedBiddingManager+Testing.m */, BC7F0F1A1ECF9D6400BB087E /* MPAdView+Testing.h */, BC7F0F1B1ECF9D6400BB087E /* MPAdView+Testing.m */, BC7F0F201ECF9EEA00BB087E /* MPBannerAdManager+Testing.h */, @@ -2331,26 +2449,41 @@ BC0ADDEC207FFBEA000ADEA4 /* MPConsentManager+Testing.m */, 2AB6301A1E9C2D0C00B0DDC7 /* MPConstants+Testing.h */, 2AB6301B1E9C2D0C00B0DDC7 /* MPConstants+Testing.m */, + EC109EE0226E881F00079B57 /* MPCountdownTimerView+Testing.h */, BC7FF69920D07BCA003B1842 /* MPHTTPNetworkSession+Testing.h */, BC7FF69A20D07BCA003B1842 /* MPHTTPNetworkSession+Testing.m */, + BC1920882236FA83004318D2 /* MPIdentityProvider+Testing.h */, + BC1920892236FA83004318D2 /* MPIdentityProvider+Testing.m */, BCC54C281ECFAF9E00A4FEF0 /* MPInterstitialAdController+Testing.h */, BCC54C291ECFAF9E00A4FEF0 /* MPInterstitialAdController+Testing.m */, BCC54C251ECFAF2800A4FEF0 /* MPInterstitialAdManager+Testing.h */, BCC54C261ECFAF2800A4FEF0 /* MPInterstitialAdManager+Testing.m */, 2AE45D3C1E9D8FFA00ED5DD2 /* MPInterstitialCustomEventAdapter+Testing.h */, 2AE45D3D1E9D8FFA00ED5DD2 /* MPInterstitialCustomEventAdapter+Testing.m */, + BC12D249206304FA0073388B /* MPMediationManager+Testing.h */, + BC12D24A206304FA0073388B /* MPMediationManager+Testing.m */, 2A7521671F70723E0099C33C /* MPMoPubNativeAdAdapter+Testing.h */, 2A7521681F70723E0099C33C /* MPMoPubNativeAdAdapter+Testing.m */, BCAED25F1DF628D400D45480 /* MPMoPubRewardedPlayableCustomEvent+Testing.h */, BCAED2601DF628D400D45480 /* MPMoPubRewardedPlayableCustomEvent+Testing.m */, BCC54C2D1ECFB8A300A4FEF0 /* MPNativeAdRequest+Testing.h */, BCC54C2E1ECFB8A300A4FEF0 /* MPNativeAdRequest+Testing.m */, + 2A9FD2D7226935C000F2C33B /* MPNativeAd+Testing.h */, + 2A9FD2D8226935C000F2C33B /* MPNativeAd+Testing.m */, + 2A89F15A22371E6500E03010 /* MPRateLimitConfiguration+Testing.h */, + 2A89F15B22371E6500E03010 /* MPRateLimitConfiguration+Testing.m */, + 2A89F157223713A100E03010 /* MPRateLimitManager+Testing.h */, + 2A89F158223713A100E03010 /* MPRateLimitManager+Testing.m */, + 2AE81D5323187B06002252EF /* MPRealTimeTimer+Testing.h */, + 2AE81D5423187B06002252EF /* MPRealTimeTimer+Testing.m */, BCAC6F6C1E5D0730002B2656 /* MPRewardedVideo+Testing.h */, BCAC6F6D1E5D0730002B2656 /* MPRewardedVideo+Testing.m */, 2AF1773F1E984AD700A600BD /* MPRewardedVideoAdapter+Testing.h */, 2AF177401E984AD700A600BD /* MPRewardedVideoAdapter+Testing.m */, BC246A771E5675CB00CEFA33 /* MPRewardedVideoAdManager+Testing.h */, BC246A781E5675CB00CEFA33 /* MPRewardedVideoAdManager+Testing.m */, + ECA6FF18226F9B4C007626A5 /* MPTimer+Testing.h */, + ECA6FF19226F9B4C007626A5 /* MPTimer+Testing.m */, 2AF21E6421349EB000CC12D8 /* MPURLRequest+Testing.h */, 2AF21E6521349EB000CC12D8 /* MPURLRequest+Testing.m */, 2A80EAB61E675CCE00D7FDD9 /* MPViewabilityTracker+Testing.h */, @@ -2372,16 +2505,19 @@ BCAED2661DF62E4200D45480 /* Mocks */ = { isa = PBXGroup; children = ( - BC310E8C1F15884A002A6809 /* MPMockAdColonyRewardedVideoCustomEvent.h */, - BC310E8D1F15884A002A6809 /* MPMockAdColonyRewardedVideoCustomEvent.m */, + BC8305E5216BCD400097C1BA /* MPMockAdapters.plist */, + BC310E8C1F15884A002A6809 /* MPMockAdColonyAdapterConfiguration.h */, + BC310E8D1F15884A002A6809 /* MPMockAdColonyAdapterConfiguration.m */, BC41F71E20DAD554004BE29C /* MPMockAdDestinationDisplayAgent.h */, BC41F71F20DAD554004BE29C /* MPMockAdDestinationDisplayAgent.m */, BC7F0F1D1ECF9E5100BB087E /* MPMockAdServerCommunicator.h */, BC7F0F1E1ECF9E5100BB087E /* MPMockAdServerCommunicator.m */, BC19E33720DC22D200673D60 /* MPMockBannerCustomEvent.h */, BC19E33820DC22D200673D60 /* MPMockBannerCustomEvent.m */, - BC310E991F158A7B002A6809 /* MPMockChartboostRewardedVideoCustomEvent.h */, - BC310E9A1F158A7B002A6809 /* MPMockChartboostRewardedVideoCustomEvent.m */, + BC310E991F158A7B002A6809 /* MPMockChartboostAdapterConfiguration.h */, + BC310E9A1F158A7B002A6809 /* MPMockChartboostAdapterConfiguration.m */, + BC4A4D18218239FB008A7410 /* MPMockChartboostRewardedVideoCustomEvent.h */, + BC4A4D19218239FB008A7410 /* MPMockChartboostRewardedVideoCustomEvent.m */, BC0BE6AB20D438D300DB0D2C /* MPMockInterstitialCustomEvent.h */, BC0BE6AC20D438D300DB0D2C /* MPMockInterstitialCustomEvent.m */, BCAED2631DF62DB000D45480 /* MPMockMRAIDInterstitialViewController.h */, @@ -2394,6 +2530,8 @@ BC246A7E1E567D1500CEFA33 /* MPMockRewardedVideoAdapter.m */, BC0BE6AE20D43B9B00DB0D2C /* MPMockRewardedVideoCustomEvent.h */, BC0BE6AF20D43B9B00DB0D2C /* MPMockRewardedVideoCustomEvent.m */, + BC4A4D15218125D3008A7410 /* MPMockTapjoyAdapterConfiguration.h */, + BC4A4D16218125D3008A7410 /* MPMockTapjoyAdapterConfiguration.m */, BC2C5B581F5727D40025FFF5 /* MPMockViewabilityAdapterAvid.h */, BC2C5B591F5727D40025FFF5 /* MPMockViewabilityAdapterAvid.m */, ); @@ -2403,8 +2541,8 @@ BCAED27E1DF73E2A00D45480 /* Delegate Handlers */ = { isa = PBXGroup; children = ( - 2A4A5D351F86E02A0082FC4C /* MPAdserverCommunicatorDelegateHandler.h */, - 2A4A5D361F86E02A0082FC4C /* MPAdserverCommunicatorDelegateHandler.m */, + 2A4A5D351F86E02A0082FC4C /* MPAdServerCommunicatorDelegateHandler.h */, + 2A4A5D361F86E02A0082FC4C /* MPAdServerCommunicatorDelegateHandler.m */, BC1A2C5F210685CD00A6A773 /* MPBannerAdapterDelegateHandler.h */, BC1A2C60210685CD00A6A773 /* MPBannerAdapterDelegateHandler.m */, BC19E33420DC203F00673D60 /* MPBannerAdManagerDelegateHandler.h */, @@ -2415,6 +2553,8 @@ 2AE45D3A1E9D882B00ED5DD2 /* MPInterstitialAdapterDelegateHandler.m */, BC0BE6A820D434DA00DB0D2C /* MPInterstitialAdManagerDelegateHandler.h */, BC0BE6A920D434DA00DB0D2C /* MPInterstitialAdManagerDelegateHandler.m */, + 2A9FD2D42269326500F2C33B /* MPNativeAdDelegateHandler.h */, + 2A9FD2D52269326500F2C33B /* MPNativeAdDelegateHandler.m */, BCAED2711DF63ED800D45480 /* MPPrivateRewardedVideoCustomEventDelegateHandler.h */, BCAED2721DF63ED800D45480 /* MPPrivateRewardedVideoCustomEventDelegateHandler.m */, 2AF177421E984DED00A600BD /* MPRewardedVideoAdapterDelegateHandler.h */, @@ -2425,6 +2565,54 @@ name = "Delegate Handlers"; sourceTree = ""; }; + BCCEE888214AF8A9003BD130 /* Logging */ = { + isa = PBXGroup; + children = ( + BC31026C21598E070007FA69 /* Internal */, + BCA762302149B4B100D55A05 /* MPLogEvent.h */, + BCA762312149B4B100D55A05 /* MPLogEvent.m */, + BCAD76A6214AE05B00A1B067 /* MPBLogger.h */, + AEF9D523171CA895005AAA5A /* MPLogging.h */, + AEF9D524171CA895005AAA5A /* MPLogging.m */, + BC79EC231F9810BF00FFC893 /* MPBLogLevel.h */, + ); + path = Logging; + sourceTree = ""; + }; + EC87668822EA847200D4B3D9 /* VAST */ = { + isa = PBXGroup; + children = ( + EC87668922EA849100D4B3D9 /* MPVASTMediaFileTests.m */, + EC923EFA22FDD3A400ED83EE /* MPVASTTrackingTests.m */, + ); + name = VAST; + sourceTree = ""; + }; + EC87668B22EB6E6B00D4B3D9 /* Protocols */ = { + isa = PBXGroup; + children = ( + EC87668C22EB6E8100D4B3D9 /* MPMediaFileCache.h */, + ); + path = Protocols; + sourceTree = ""; + }; + ECD106E92280FE3700398CA5 /* Internal */ = { + isa = PBXGroup; + children = ( + ECD106EA2280FE4400398CA5 /* MRAID */, + EC87668822EA847200D4B3D9 /* VAST */, + ); + name = Internal; + sourceTree = ""; + }; + ECD106EA2280FE4400398CA5 /* MRAID */ = { + isa = PBXGroup; + children = ( + ECD106E52280FE2600398CA5 /* MRControllerTests.m */, + ); + name = MRAID; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -2432,14 +2620,15 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + 2A73E33B226E45F7001FEE03 /* MPStreamAdPlacerDelegate.h in Headers */, 2AF030C92016731800909F29 /* MoPub.h in Headers */, 2AF031CD2016B74400909F29 /* MOPUBNativeVideoAdRendererSettings.h in Headers */, 2A4D35DC211CBF5300BE9377 /* MPCoreInstanceProvider+MRAID.h in Headers */, + 2A89F14F2236DF3600E03010 /* MPRateLimitManager.h in Headers */, 2AF031CE2016B74400909F29 /* MOPUBNativeVideoAdRenderer.h in Headers */, 2AF031CF2016B74400909F29 /* MPRewardedVideo.h in Headers */, BCEE050B2037A42C0076CA86 /* MPHTTPNetworkSession.h in Headers */, 2AF031D12016B74400909F29 /* MPRewardedVideoCustomEvent.h in Headers */, - 2AF031D22016B74400909F29 /* MPRewardedVideoCustomEvent+Caching.h in Headers */, 2AF031D32016B74400909F29 /* MPRewardedVideoError.h in Headers */, 2AF031D42016B74400909F29 /* MPRewardedVideoReward.h in Headers */, 2AF031D62016B74400909F29 /* MPStaticNativeAdRendererSettings.h in Headers */, @@ -2470,12 +2659,12 @@ 2AF031ED2016B74400909F29 /* MPBannerCustomEvent.h in Headers */, 2AF031EE2016B74400909F29 /* MPBannerCustomEventDelegate.h in Headers */, 2AF031EF2016B74400909F29 /* MPConstants.h in Headers */, + BC4A4D14217F99ED008A7410 /* MPAdapterConfiguration.h in Headers */, 2AF031F02016B74400909F29 /* MPInterstitialAdController.h in Headers */, 2AF031F12016B74400909F29 /* MPInterstitialCustomEvent.h in Headers */, 2AF031F22016B74400909F29 /* MPInterstitialCustomEventDelegate.h in Headers */, - 2AF031F32016B74400909F29 /* MoPub-Bridging-Header.h in Headers */, BCAD022720CEE695007DC2B2 /* NSError+MPAdditions.h in Headers */, - 2AF031F42016B74400909F29 /* MPLogLevel.h in Headers */, + 2AF031F42016B74400909F29 /* MPBLogLevel.h in Headers */, 2AF032892016B78800909F29 /* MPViewabilityAdapter.h in Headers */, 2AF031F52016B78800909F29 /* MOPUBExperimentProvider.h in Headers */, 2AF031F62016B78800909F29 /* MOPUBActivityIndicatorView.h in Headers */, @@ -2485,16 +2674,15 @@ 2AF031FA2016B78800909F29 /* MOPUBNativeVideoAdAdapter.h in Headers */, 2AF031FB2016B78800909F29 /* MOPUBNativeVideoAdConfigValues.h in Headers */, 2AF031FC2016B78800909F29 /* MOPUBNativeVideoCustomEvent.h in Headers */, - 2AF031FD2016B78800909F29 /* MOPUBNativeVideoImpressionAgent.h in Headers */, BCECF30B2047715E005AF3BD /* MPURLRequest.h in Headers */, 2AF031FE2016B78800909F29 /* MOPUBPlayerManager.h in Headers */, 2AF031FF2016B78800909F29 /* MOPUBPlayerView.h in Headers */, 2AF032002016B78800909F29 /* MOPUBPlayerViewController.h in Headers */, 2AF032012016B78800909F29 /* MOPUBReplayView.h in Headers */, - BC64EC5620696F7800CB33A7 /* MPMediationSdkInitializable.h in Headers */, 2AF032022016B78800909F29 /* MPRewardedVideoConnection.h in Headers */, 2AF032032016B78800909F29 /* MPMoPubRewardedVideoCustomEvent.h in Headers */, 2AF032042016B78800909F29 /* MPMoPubRewardedPlayableCustomEvent.h in Headers */, + 2A73E33D226E46B7001FEE03 /* MPCollectionViewAdPlacerDelegate.h in Headers */, 2AF032052016B78800909F29 /* MPPrivateRewardedVideoCustomEventDelegate.h in Headers */, BC0ADDFB20810B3C000ADEA4 /* MPConsentError.h in Headers */, 2AF032062016B78800909F29 /* MPRewardedVideoAdapter.h in Headers */, @@ -2508,7 +2696,6 @@ BCD118872034E01100C03B7D /* MPMoPubConfiguration.h in Headers */, 2AF0320D2016B78800909F29 /* MPImageDownloadQueue.h in Headers */, BC96D55B211CE07C00610174 /* NSMutableArray+MPAdditions.h in Headers */, - BCD118882034E01600C03B7D /* MPAdvancedBidder.h in Headers */, 2AF0320E2016B78800909F29 /* MPMoPubNativeAdAdapter.h in Headers */, 2AF0320F2016B78800909F29 /* MPMoPubNativeCustomEvent.h in Headers */, 2AF032112016B78800909F29 /* MPNativeAdConfigValues+Internal.h in Headers */, @@ -2528,7 +2715,7 @@ 2AF0321E2016B78800909F29 /* MPStreamAdPlacementData.h in Headers */, 2AF0321F2016B78800909F29 /* MPStreamAdPlacer.h in Headers */, 2AF032202016B78800909F29 /* MPTableViewAdPlacerCell.h in Headers */, - 2AF032212016B78800909F29 /* MPTableViewCellImpressionTracker.h in Headers */, + BCDECB15219B4628002C1E7A /* MPContentBlocker.h in Headers */, BC4370532087C5D6001B86D4 /* MPAdServerKeys.h in Headers */, 2AF032292016B78800909F29 /* MPAdAlertGestureRecognizer.h in Headers */, 2AF0322A2016B78800909F29 /* MPAdAlertManager.h in Headers */, @@ -2536,7 +2723,6 @@ BCAD021B20CB388C007DC2B2 /* NSDate+MPAdditions.h in Headers */, 2AF0322B2016B78800909F29 /* MPActivityViewControllerHelper.h in Headers */, 2AF0322C2016B78800909F29 /* MPActivityViewControllerHelper+TweetShare.h in Headers */, - 2AF0322D2016B78800909F29 /* MPAdBrowserController.h in Headers */, 2AF0322E2016B78800909F29 /* MPAdConfiguration.h in Headers */, 2AF0322F2016B78800909F29 /* MPAdDestinationDisplayAgent.h in Headers */, 2AF032302016B78800909F29 /* MPAdImpressionTimer.h in Headers */, @@ -2556,6 +2742,7 @@ 2AF032402016B78800909F29 /* MPBannerCustomEvent+Internal.h in Headers */, 2AF032412016B78800909F29 /* MPBannerAdManager.h in Headers */, 2AF032422016B78800909F29 /* MPBannerAdManagerDelegate.h in Headers */, + EC0BF276227CB3FD003DB141 /* MoPub+Utility.h in Headers */, BCFE67D9208508D3005E458A /* MPConsentChangedReason.h in Headers */, BC96D558211CE02400610174 /* NSDictionary+MPAdditions.h in Headers */, 2AF032432016B78800909F29 /* MPBannerCustomEventAdapter.h in Headers */, @@ -2581,17 +2768,21 @@ 2AF032542016B78800909F29 /* MRNativeCommandHandler.h in Headers */, 2AF032552016B78800909F29 /* MPMRAIDBannerCustomEvent.h in Headers */, 2AF032562016B78800909F29 /* MPMRAIDInterstitialCustomEvent.h in Headers */, + BC4A4D1D21827287008A7410 /* MPBaseAdapterConfiguration.h in Headers */, 2AF032572016B78800909F29 /* MPMRAIDInterstitialViewController.h in Headers */, 2AF032582016B78800909F29 /* MRBundleManager.h in Headers */, 2AF032592016B78800909F29 /* MRController.h in Headers */, 2AF0325A2016B78800909F29 /* MRVideoPlayerManager.h in Headers */, + BCAD76A7214AE1A600A1B067 /* MPBLogger.h in Headers */, + BC6B9E6622C135D00027F2F9 /* MPEngineInfo.h in Headers */, BC437058208A8AC9001B86D4 /* MPBool.h in Headers */, 2AF0325B2016B78800909F29 /* MRCommand.h in Headers */, 2AF0325C2016B78800909F29 /* MRProperty.h in Headers */, 2AF0325D2016B78800909F29 /* MRBridge.h in Headers */, + 2A890DE72303656D00FE683F /* SKStoreProductViewController+MPAdditions.h in Headers */, 2AF0325E2016B78800909F29 /* MRError.h in Headers */, BC926EF520D9753B004ED8F7 /* MPMemoryCache.h in Headers */, - 2AF0325F2016B78800909F29 /* MPLogProvider.h in Headers */, + 2AF0325F2016B78800909F29 /* MPLogManager.h in Headers */, 2AF032602016B78800909F29 /* NSBundle+MPAdditions.h in Headers */, 2AF032612016B78800909F29 /* NSHTTPURLResponse+MPAdditions.h in Headers */, BC11921A207BDD45005DF26E /* MPConsentStatus.h in Headers */, @@ -2599,10 +2790,8 @@ 2A2701F02020FD71004A72E6 /* MOPUBDisplayAgentType.h in Headers */, 2AF032632016B78800909F29 /* NSString+MPAdditions.h in Headers */, 2AF032642016B78800909F29 /* NSURL+MPAdditions.h in Headers */, - 2AF032652016B78800909F29 /* UIButton+MPAdditions.h in Headers */, 2AF032662016B78800909F29 /* UIColor+MPAdditions.h in Headers */, 2AF032672016B78800909F29 /* UIView+MPAdditions.h in Headers */, - 2AF032682016B78800909F29 /* UIWebView+MPAdditions.h in Headers */, 2AF032692016B78800909F29 /* MPAnalyticsTracker.h in Headers */, 2AF0326A2016B78800909F29 /* MPError.h in Headers */, 2AF0326B2016B78800909F29 /* MPGlobal.h in Headers */, @@ -2611,34 +2800,42 @@ 2AF0326D2016B78800909F29 /* MPLogging.h in Headers */, 2AF0326E2016B78800909F29 /* MPReachability.h in Headers */, 2AF0326F2016B78800909F29 /* MPSessionTracker.h in Headers */, - 2AF032702016B78800909F29 /* MPStoreKitProvider.h in Headers */, 2AF032712016B78800909F29 /* MPTimer.h in Headers */, 2AF032722016B78800909F29 /* MPUserInteractionGestureRecognizer.h in Headers */, - 2AF032732016B78800909F29 /* MPInternalUtils.h in Headers */, 2AF032742016B78800909F29 /* MPGeolocationProvider.h in Headers */, 2AF032752016B78800909F29 /* MPVASTAd.h in Headers */, + 2A73E33F226E5851001FEE03 /* MPTableViewAdPlacerDelegate.h in Headers */, 2AF032762016B78800909F29 /* MPVASTCompanionAd.h in Headers */, - BCD1188C2034E04100C03B7D /* MPAdvancedBiddingManager.h in Headers */, 2AF032772016B78800909F29 /* MPVASTCreative.h in Headers */, 2AF032782016B78800909F29 /* MPVASTDurationOffset.h in Headers */, + 2A89F1492236DF2200E03010 /* MPRateLimitConfiguration.h in Headers */, 2A2701EF2020FD68004A72E6 /* MPViewabilityOption.h in Headers */, 2AF032792016B78800909F29 /* MPVASTIndustryIcon.h in Headers */, 2AF0327A2016B78800909F29 /* MPVASTInline.h in Headers */, 2AF0327B2016B78800909F29 /* MPVASTLinearAd.h in Headers */, 2AF0327C2016B78800909F29 /* MPVASTMacroProcessor.h in Headers */, + BC2448C622B171B0003EBB4B /* MPExtendedHitBoxButton.h in Headers */, + BCCEE891214B04E5003BD130 /* MPConsoleLogger.h in Headers */, 2AF0327D2016B78800909F29 /* MPVASTManager.h in Headers */, + BCA762322149B4B100D55A05 /* MPLogEvent.h in Headers */, 2AF0327E2016B78800909F29 /* MPVASTMediaFile.h in Headers */, 2AF0327F2016B78800909F29 /* MPVASTModel.h in Headers */, 2AF032802016B78800909F29 /* MPVASTResource.h in Headers */, 2AF032812016B78800909F29 /* MPVASTResponse.h in Headers */, BC119225207D211B005DF26E /* MPConsentChangedNotification.h in Headers */, 2AF032822016B78800909F29 /* MPVASTStringUtilities.h in Headers */, + 2A60998F225E8E020095890A /* MPMoPubAdPlacer.h in Headers */, 2AF032832016B78800909F29 /* MPVASTTrackingEvent.h in Headers */, 2AF032842016B78800909F29 /* MPVASTWrapper.h in Headers */, + 2A73E337226E43D3001FEE03 /* MPAdViewDelegate.h in Headers */, 2AF032852016B78800909F29 /* MPVASTTracking.h in Headers */, + 2A48DF3B229601C1003763C2 /* MPImpressionTrackedNotification.h in Headers */, 2AF032862016B78800909F29 /* MPCoreInstanceProvider.h in Headers */, + 2A73E339226E44F8001FEE03 /* MPInterstitialAdControllerDelegate.h in Headers */, 2AF0328A2016B78800909F29 /* MPViewabilityTracker.h in Headers */, 2AF0328B2016B78800909F29 /* MPWebView+Viewability.h in Headers */, + 2AFEE727225C130B00DD82C8 /* MPMoPubAd.h in Headers */, + 2AFEE723225BC38400DD82C8 /* MPImpressionData.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2740,7 +2937,7 @@ AEF1F32016EF9AD700273462 /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0930; + LastUpgradeCheck = 1020; ORGANIZATIONNAME = MoPub; TargetAttributes = { 2AAA8DF81D95C77B006962E8 = { @@ -2760,10 +2957,11 @@ }; buildConfigurationList = AEF1F32316EF9AD700273462 /* Build configuration list for PBXProject "MoPubSDK" */; compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; + developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, + Base, ); mainGroup = AEF1F31F16EF9AD700273462; productRefGroup = AEF1F32916EF9AD700273462 /* Products */; @@ -2784,10 +2982,12 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + BC9EF9E1216811EA005BEA65 /* MPAdapters.plist in Resources */, + BC8305E6216BCD400097C1BA /* MPMockAdapters.plist in Resources */, 2AA73B9F1FCF8ACC001FB787 /* MPDAAIcon.png in Resources */, B23C3B5B1E35709D0003D79E /* linear-tracking-no-event.xml in Resources */, + EC923EF922FDD38B00ED83EE /* VAST_3.0_linear_ad_comprehensive.xml in Resources */, 2AA73BA11FCF8AD6001FB787 /* MPDAAIcon@3x.png in Resources */, - BC4C9EEC1E2991EF006021CB /* MPCountdownTimer.html in Resources */, 2AA73BA01FCF8AD0001FB787 /* MPDAAIcon@2x.png in Resources */, BC08C7A51E36BB8200444F16 /* linear-mime-types.xml in Resources */, BC08C7B31E36C71B00444F16 /* linear-mime-types-all-invalid.xml in Resources */, @@ -2818,8 +3018,7 @@ 2A711FED202267B6007A2412 /* MPDAAIcon.png in Resources */, 2A711FEE202267B6007A2412 /* MPDAAIcon@2x.png in Resources */, 2A711FEF202267B6007A2412 /* MPDAAIcon@3x.png in Resources */, - 2A711FF0202267B6007A2412 /* MPCountdownTimer.html in Resources */, - 2A711FDC20226737007A2412 /* MPAdBrowserController.xib in Resources */, + BC9EF9E0216811B3005BEA65 /* MPAdapters.plist in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2841,12 +3040,11 @@ 5724084E1A68677A009271B8 /* MRAID.bundle in Resources */, 57401A481B7AAEF0000EEA64 /* MPDAAIcon@2x.png in Resources */, 5724084D1A686777009271B8 /* MPCloseButtonX.png in Resources */, - 57A1A3801A780A55006A08DA /* MPAdBrowserController.xib in Resources */, 57E20E811BCC63A400B51C8C /* MPMutedBtn.png in Resources */, 57E20E841BCC63A400B51C8C /* MPPlayBtn.png in Resources */, - BCF0FA8C1DC9512900ADFE4F /* MPCountdownTimer.html in Resources */, 5724084C1A686770009271B8 /* MPCloseButtonX@2x.png in Resources */, 57E20E871BCC63A400B51C8C /* MPUnmutedBtn.png in Resources */, + BC9EF9DF216811B3005BEA65 /* MPAdapters.plist in Resources */, 57E20E7E1BCC639F00B51C8C /* MPCloseBtn.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -2878,7 +3076,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = ". \"${SRCROOT}/Scripts/mraid_build.sh\"\ncopyMRAIDToResources \"${SRCROOT}\""; + shellScript = ". \"${SRCROOT}/Scripts/mraid_build.sh\"\ncopyMRAIDToResources \"${SRCROOT}\"\n"; }; /* End PBXShellScriptBuildPhase section */ @@ -2888,64 +3086,81 @@ buildActionMask = 2147483647; files = ( 2AF177411E984AD700A600BD /* MPRewardedVideoAdapter+Testing.m in Sources */, + EC923EFB22FDD3A400ED83EE /* MPVASTTrackingTests.m in Sources */, BC11922A207D6D06005DF26E /* MPConsentManagerTests.m in Sources */, + 2AE81D5523187B06002252EF /* MPRealTimeTimer+Testing.m in Sources */, + ECA6FF17226F8DD7007626A5 /* MPTimerTests.m in Sources */, BCC79EC4204DC28B00F7ABE6 /* MPURLRequestTests.m in Sources */, + BC19208A2236FA83004318D2 /* MPIdentityProvider+Testing.m in Sources */, BCF9BDBA2119FA7800A2F557 /* NSURLSessionTask+Testing.m in Sources */, + BC4A4D17218125D3008A7410 /* MPMockTapjoyAdapterConfiguration.m in Sources */, BC41F72220DAF23C004BE29C /* MPMemoryCacheTests.m in Sources */, + 2A89F159223713A100E03010 /* MPRateLimitManager+Testing.m in Sources */, 2AFF1BA71EC289E600495994 /* MPRealTimeTimerTests.m in Sources */, 2A922D3A1E9C4CF900F83923 /* MPInterstitialCustomEventAdapterTests.m in Sources */, BC7FF69B20D07BCA003B1842 /* MPHTTPNetworkSession+Testing.m in Sources */, + BC7003D322679C4F001222E7 /* MPLoggingHandler.m in Sources */, BCAED2731DF63ED800D45480 /* MPPrivateRewardedVideoCustomEventDelegateHandler.m in Sources */, BCDE9ED71DF0AF970034A444 /* MPAdConfigurationTests.m in Sources */, 2AF177331E9846A000A600BD /* MPRewardedVideoAdapterTests.m in Sources */, BC41F72020DAD554004BE29C /* MPMockAdDestinationDisplayAgent.m in Sources */, - BC12D24B206304FA0073388B /* MPAdvancedBiddingManager+Testing.m in Sources */, + BC098E4D226E3B53001E44A6 /* MPNativeAdRequestTargetingTests.m in Sources */, + BC12D24B206304FA0073388B /* MPMediationManager+Testing.m in Sources */, BC86211C1DCBBD5A0012275D /* MPCountdownTimerViewTests.m in Sources */, + EC481B0523209E65003AA3B9 /* MPWebBrowserUserAgentInfoTests.m in Sources */, BCC54C241ECFACE200A4FEF0 /* MPInterstitialAdControllerTests.m in Sources */, BC7F0F1F1ECF9E5100BB087E /* MPMockAdServerCommunicator.m in Sources */, BC7F0F221ECF9EEA00BB087E /* MPBannerAdManager+Testing.m in Sources */, BCC54C2F1ECFB8A300A4FEF0 /* MPNativeAdRequest+Testing.m in Sources */, + ECF653C522DD3D0700B7DE1D /* MPDiskLRUCacheTests.m in Sources */, 2AB6301C1E9C2D0C00B0DDC7 /* MPConstants+Testing.m in Sources */, BCAC6F6E1E5D0730002B2656 /* MPRewardedVideo+Testing.m in Sources */, B23C3B6E1E36900C0003D79E /* XCTestCase+MPAddition.m in Sources */, - BC2C540A206EF34400171B98 /* MPStubMediatedNetwork.m in Sources */, BCCE7A2B20768922003027BA /* MPReachabilityTests.m in Sources */, BC246A7F1E567D1500CEFA33 /* MPMockRewardedVideoAdapter.m in Sources */, - 2A4A5D371F86E02A0082FC4C /* MPAdserverCommunicatorDelegateHandler.m in Sources */, + 2A4A5D371F86E02A0082FC4C /* MPAdServerCommunicatorDelegateHandler.m in Sources */, BC031AE4211CF93B00E4715B /* MPRewardedVideoRewardTests.m in Sources */, BC246A791E5675CB00CEFA33 /* MPRewardedVideoAdManager+Testing.m in Sources */, B23C3B4F1E353DD90003D79E /* MPVideoConfigTests.m in Sources */, + ECD106E62280FE2600398CA5 /* MRControllerTests.m in Sources */, BCAED2651DF62DB000D45480 /* MPMockMRAIDInterstitialViewController.m in Sources */, 2A6471E62087C74A001D7308 /* MPConsentDialogViewControllerTests.m in Sources */, BC1ED25F21471B0500065952 /* MPMockLongLoadNativeCustomEvent.m in Sources */, + 2A89F15C22371E6500E03010 /* MPRateLimitConfiguration+Testing.m in Sources */, + 2A89F15E22371ECC00E03010 /* MPRateLimitConfigurationTests.m in Sources */, BC19E33620DC203F00673D60 /* MPBannerAdManagerDelegateHandler.m in Sources */, BCB43DCE1E5F932000A95E22 /* NSURLComponents+Testing.m in Sources */, 2A75215C1F70720F0099C33C /* MPMoPubNativeAdAdapterTests.m in Sources */, 2A65B0991D9C9292008E0CAD /* MPWebViewTests.m in Sources */, + ECA6FF1A226F9B4C007626A5 /* MPTimer+Testing.m in Sources */, BC7F0F191ECF9BF800BB087E /* MPAdViewTests.m in Sources */, BC08C7831E36AD7C00444F16 /* MPVASTLinearAdTests.m in Sources */, + 2A9FD2D9226935C000F2C33B /* MPNativeAd+Testing.m in Sources */, BC0BE6A720D4349200DB0D2C /* MPInterstitialAdManagerTests.m in Sources */, BC19E33120DC1E7700673D60 /* MPBannerCustomEventAdapter+Testing.m in Sources */, B2C423BB1FA7A38500D8E164 /* MPBannerCustomEventAdapterTests.m in Sources */, 2AA73B9E1FCF869D001FB787 /* MOPUBNativeVideoAdAdapterTests.m in Sources */, + 2AA2E2FE225FE0AB00478D5C /* MPImpressionDataTests.m in Sources */, + EC87668A22EA849100D4B3D9 /* MPVASTMediaFileTests.m in Sources */, 2A5C4DB81F6B25F20076C08C /* MPNativeAdConfigValuesTests.m in Sources */, 2AE45D3E1E9D8FFA00ED5DD2 /* MPInterstitialCustomEventAdapter+Testing.m in Sources */, BC7F0F1C1ECF9D6400BB087E /* MPAdView+Testing.m in Sources */, 2A7521691F70723E0099C33C /* MPMoPubNativeAdAdapter+Testing.m in Sources */, + BC8CDD65218A12DD006DE606 /* MPMoPubConfigurationTests.m in Sources */, 2AF177441E984DED00A600BD /* MPRewardedVideoAdapterDelegateHandler.m in Sources */, 2AF21E6621349EB000CC12D8 /* MPURLRequest+Testing.m in Sources */, B2D54B671ED20C95004E3C7B /* MOPUBExperimentProviderTests.m in Sources */, - BC12D24E206305940073388B /* MPStubAdvancedBidder.m in Sources */, BC2C5B5A1F5727D40025FFF5 /* MPMockViewabilityAdapterAvid.m in Sources */, BC7FF69D20D07C4E003B1842 /* MPHTTPNetworkSessionTests.m in Sources */, + 2A9FD2D62269326500F2C33B /* MPNativeAdDelegateHandler.m in Sources */, 2AA73B9C1FCF7EDB001FB787 /* MOPUBNativeVideoAdConfigValuesTests.m in Sources */, 2A6471E92087C775001D7308 /* MPConsentDialogViewControllerDelegateHandler.m in Sources */, BC756FB91F34FC5600556299 /* MPWebView+Testing.m in Sources */, 2A9F8EA72126201B0060E1E7 /* MPVASTModelTests.m in Sources */, 2A7F96FA1E6646DC00114565 /* MPViewabilityTrackerTests.m in Sources */, BC0BE6B320D4699100DB0D2C /* MPMockNativeCustomEvent.m in Sources */, + BC1920872236F8FC004318D2 /* MPIdentityProviderTests.m in Sources */, BC1A2C61210685CD00A6A773 /* MPBannerAdapterDelegateHandler.m in Sources */, - BC12D248206304AE0073388B /* MPAdvancedBiddingManagerTests.m in Sources */, BC0ADDED207FFBEA000ADEA4 /* MPConsentManager+Testing.m in Sources */, 2A80EAB81E675CCE00D7FDD9 /* MPViewabilityTracker+Testing.m in Sources */, BCC54C2A1ECFAF9E00A4FEF0 /* MPInterstitialAdController+Testing.m in Sources */, @@ -2962,6 +3177,7 @@ BCAED2541DF6194E00D45480 /* MPMoPubRewardedPlayableCustomEventTests.m in Sources */, B2D54B691ED20FA1004E3C7B /* MPURLResolverTests.m in Sources */, BCC54C2C1ECFB81000A4FEF0 /* MPNativeAdRequestTests.m in Sources */, + 2A89F1562237136700E03010 /* MPRateLimitManagerTests.m in Sources */, BC246A7C1E56795000CEFA33 /* MPRewardedVideoDelegateHandler.m in Sources */, 2AE45D3B1E9D882B00ED5DD2 /* MPInterstitialAdapterDelegateHandler.m in Sources */, BCAED2611DF628D400D45480 /* MPMoPubRewardedPlayableCustomEvent+Testing.m in Sources */, @@ -2969,16 +3185,17 @@ BC246A6C1E5672AA00CEFA33 /* MPRewardedVideoAdManagerTests.m in Sources */, BC181D661ECE4DD6009C752C /* MPAdServerURLBuilderTests.m in Sources */, BCAC6F5E1E5CF6F5002B2656 /* MPRewardedVideoTests.m in Sources */, - BC310E9B1F158A7B002A6809 /* MPMockChartboostRewardedVideoCustomEvent.m in Sources */, + BC310E9B1F158A7B002A6809 /* MPMockChartboostAdapterConfiguration.m in Sources */, 2A5D06E11FCE19F100645822 /* MPAdImpressionTimer+Testing.m in Sources */, BC19E33320DC1F9A00673D60 /* MPBannerAdManagerTests.m in Sources */, BC57D63C1F10318F0030C365 /* MoPubTests.m in Sources */, - BC310E8E1F15884A002A6809 /* MPMockAdColonyRewardedVideoCustomEvent.m in Sources */, + BC310E8E1F15884A002A6809 /* MPMockAdColonyAdapterConfiguration.m in Sources */, BC0BE6AA20D434DA00DB0D2C /* MPInterstitialAdManagerDelegateHandler.m in Sources */, BC06FF9720447D08003CF04C /* MoPub+Testing.m in Sources */, BC19E33920DC22D200673D60 /* MPMockBannerCustomEvent.m in Sources */, 2A4A5D221F86DF270082FC4C /* MPAdServerCommunicatorTests.m in Sources */, BC756FAC1F34FBA000556299 /* MRController+Testing.m in Sources */, + BC4A4D1A218239FB008A7410 /* MPMockChartboostRewardedVideoCustomEvent.m in Sources */, B2FE8FB0206ABFF500593089 /* MPDictionaryAdditionTests.m in Sources */, B2D54B651ED20B80004E3C7B /* MPAdConfiguration+Testing.m in Sources */, BC0BE6B020D43B9B00DB0D2C /* MPMockRewardedVideoCustomEvent.m in Sources */, @@ -2997,7 +3214,6 @@ 2A27021020214502004A72E6 /* MOPUBNativeVideoAdAdapter.m in Sources */, 2A27021120214502004A72E6 /* MOPUBNativeVideoAdConfigValues.m in Sources */, 2A27021220214502004A72E6 /* MOPUBNativeVideoCustomEvent.m in Sources */, - 2A27021320214502004A72E6 /* MOPUBNativeVideoImpressionAgent.m in Sources */, 2A27021420214502004A72E6 /* MOPUBPlayerManager.m in Sources */, 2A27021520214502004A72E6 /* MOPUBPlayerView.m in Sources */, 2A27021620214502004A72E6 /* MOPUBPlayerViewController.m in Sources */, @@ -3009,9 +3225,10 @@ 2A27021C20214502004A72E6 /* MPMoPubRewardedPlayableCustomEvent.m in Sources */, 2A27021D20214502004A72E6 /* MPRewardedVideoAdapter.m in Sources */, 2A27021E20214502004A72E6 /* MPRewardedVideoAdManager.m in Sources */, + BC4A4D2021827287008A7410 /* MPBaseAdapterConfiguration.m in Sources */, + 2AFEE726225BC38400DD82C8 /* MPImpressionData.m in Sources */, 2A27021F20214502004A72E6 /* MPRewardedVideo.m in Sources */, 2A27022120214502004A72E6 /* MPRewardedVideoCustomEvent.m in Sources */, - 2A27022220214502004A72E6 /* MPRewardedVideoCustomEvent+Caching.m in Sources */, 2A27022320214502004A72E6 /* MPRewardedVideoError.m in Sources */, 2A27022420214502004A72E6 /* MPRewardedVideoReward.m in Sources */, 2A27022520214502004A72E6 /* MPAdPlacerInvocation.m in Sources */, @@ -3020,6 +3237,7 @@ 2A27022820214502004A72E6 /* MPImageDownloadQueue.m in Sources */, 2A27022920214502004A72E6 /* MPMoPubNativeAdAdapter.m in Sources */, 2A27022A20214502004A72E6 /* MPMoPubNativeCustomEvent.m in Sources */, + 2A89F1522236DF3600E03010 /* MPRateLimitManager.m in Sources */, 2A27022B20214502004A72E6 /* MPNativeAd+Internal.m in Sources */, 2A27022C20214502004A72E6 /* MPNativeAdConfigValues+Internal.m in Sources */, 2A27022D20214502004A72E6 /* MPNativeAdConfigValues.m in Sources */, @@ -3038,10 +3256,11 @@ BC64EC5D2069977E00CB33A7 /* MPMediationManager.m in Sources */, 2A27023820214502004A72E6 /* MPStreamAdPlacer.m in Sources */, 2A27023920214502004A72E6 /* MPTableViewAdPlacerCell.m in Sources */, - 2A27023A20214502004A72E6 /* MPTableViewCellImpressionTracker.m in Sources */, + BC2448C822B171B0003EBB4B /* MPExtendedHitBoxButton.m in Sources */, 2A27023B20214502004A72E6 /* MPStaticNativeAdRendererSettings.m in Sources */, 2A27023C20214502004A72E6 /* MPNativeAdRendererConfiguration.m in Sources */, 2A27023D20214502004A72E6 /* MPStaticNativeAdRenderer.m in Sources */, + BCCEE894214B04E5003BD130 /* MPConsoleLogger.m in Sources */, BC0ADDF32080023C000ADEA4 /* NSString+MPConsentStatus.m in Sources */, 2A27023E20214502004A72E6 /* MPAdPositioning.m in Sources */, 2A27023F20214502004A72E6 /* MPNativeAdRenderingImageLoader.m in Sources */, @@ -3055,6 +3274,7 @@ 2A27024520214502004A72E6 /* MPNativeAdRequest.m in Sources */, 2A27024620214502004A72E6 /* MPNativeAdRequestTargeting.m in Sources */, 2A27024720214502004A72E6 /* MPNativeCustomEvent.m in Sources */, + 2A48DF3E229601C1003763C2 /* MPImpressionTrackedNotification.m in Sources */, BC926EF820D9753B004ED8F7 /* MPMemoryCache.m in Sources */, BC88E7E520769A3D002A3357 /* MPReachabilityManager.m in Sources */, BCAD021E20CB388D007DC2B2 /* NSDate+MPAdditions.m in Sources */, @@ -3065,10 +3285,11 @@ 2A27025120214502004A72E6 /* MPAdAlertGestureRecognizer.m in Sources */, 2A27025220214502004A72E6 /* MPAdAlertManager.m in Sources */, 2A27025320214502004A72E6 /* MPActivityViewControllerHelper.m in Sources */, + BCA762352149B4B100D55A05 /* MPLogEvent.m in Sources */, 2A27025420214502004A72E6 /* MPActivityViewControllerHelper+TweetShare.m in Sources */, - 2A27025520214502004A72E6 /* MPAdBrowserController.m in Sources */, 2A27025620214502004A72E6 /* MPAdConfiguration.m in Sources */, 2A27025720214502004A72E6 /* MPAdDestinationDisplayAgent.m in Sources */, + EC0BF279227CB403003DB141 /* MoPub+Utility.m in Sources */, 2A27025820214502004A72E6 /* MPAdImpressionTimer.m in Sources */, 2A27025920214502004A72E6 /* MPAdServerCommunicator.m in Sources */, 2A27025A20214502004A72E6 /* MPAdServerURLBuilder.m in Sources */, @@ -3086,6 +3307,7 @@ 2A27026720214502004A72E6 /* MPBannerCustomEvent+Internal.m in Sources */, 2A27026820214502004A72E6 /* MPBannerAdManager.m in Sources */, 2A27026920214502004A72E6 /* MPBannerCustomEventAdapter.m in Sources */, + 2A89F14C2236DF2200E03010 /* MPRateLimitConfiguration.m in Sources */, 2A27026A20214502004A72E6 /* MPBaseBannerAdapter.m in Sources */, 2A27026B20214502004A72E6 /* MPBaseInterstitialAdapter.m in Sources */, 2A27026C20214502004A72E6 /* MPInterstitialAdManager.m in Sources */, @@ -3104,7 +3326,6 @@ 2A27027720214502004A72E6 /* MPMRAIDBannerCustomEvent.m in Sources */, 2A27027820214502004A72E6 /* MPMRAIDInterstitialCustomEvent.m in Sources */, 2A27027920214502004A72E6 /* MPMRAIDInterstitialViewController.m in Sources */, - BCD1188F2034E04600C03B7D /* MPAdvancedBiddingManager.m in Sources */, 2A27027A20214502004A72E6 /* MRBundleManager.m in Sources */, 2A27027B20214502004A72E6 /* MRController.m in Sources */, 2A27027C20214502004A72E6 /* MRVideoPlayerManager.m in Sources */, @@ -3115,7 +3336,7 @@ 2A27028020214502004A72E6 /* MRError.m in Sources */, BC0ADDFA20810B3C000ADEA4 /* MPConsentDialogViewController.m in Sources */, BCECF30E2047715E005AF3BD /* MPURLRequest.m in Sources */, - 2A27028120214502004A72E6 /* MPLogProvider.m in Sources */, + 2A27028120214502004A72E6 /* MPLogManager.m in Sources */, 2A27028220214502004A72E6 /* NSBundle+MPAdditions.m in Sources */, BC119220207BE949005DF26E /* MPConsentManager.m in Sources */, 2A27028320214502004A72E6 /* NSHTTPURLResponse+MPAdditions.m in Sources */, @@ -3123,10 +3344,8 @@ 2A27028420214502004A72E6 /* NSJSONSerialization+MPAdditions.m in Sources */, 2A27028520214502004A72E6 /* NSString+MPAdditions.m in Sources */, 2A27028620214502004A72E6 /* NSURL+MPAdditions.m in Sources */, - 2A27028720214502004A72E6 /* UIButton+MPAdditions.m in Sources */, 2A27028820214502004A72E6 /* UIColor+MPAdditions.m in Sources */, 2A27028920214502004A72E6 /* UIView+MPAdditions.m in Sources */, - 2A27028A20214502004A72E6 /* UIWebView+MPAdditions.m in Sources */, 2A27028B20214502004A72E6 /* MPAnalyticsTracker.m in Sources */, 2A27028C20214502004A72E6 /* MPError.m in Sources */, 2A27028D20214502004A72E6 /* MPGlobal.m in Sources */, @@ -3135,10 +3354,8 @@ 2A27029020214502004A72E6 /* MPReachability.m in Sources */, BC94CC8C20FF97FE00FB018A /* MPURL.m in Sources */, 2A27029120214502004A72E6 /* MPSessionTracker.m in Sources */, - 2A27029220214502004A72E6 /* MPStoreKitProvider.m in Sources */, 2A27029320214502004A72E6 /* MPTimer.m in Sources */, 2A27029420214502004A72E6 /* MPUserInteractionGestureRecognizer.m in Sources */, - 2A27029520214502004A72E6 /* MPInternalUtils.m in Sources */, 2A27029620214502004A72E6 /* MPGeolocationProvider.m in Sources */, 2A27029720214502004A72E6 /* MPVASTAd.m in Sources */, 2A27029820214502004A72E6 /* MPVASTCompanionAd.m in Sources */, @@ -3148,6 +3365,7 @@ 2A27029C20214502004A72E6 /* MPVASTInline.m in Sources */, 2A27029D20214502004A72E6 /* MPVASTLinearAd.m in Sources */, 2A27029E20214502004A72E6 /* MPVASTMacroProcessor.m in Sources */, + 2A890DEA2303656D00FE683F /* SKStoreProductViewController+MPAdditions.m in Sources */, 2A27029F20214502004A72E6 /* MPVASTManager.m in Sources */, 2A2702A020214502004A72E6 /* MPVASTMediaFile.m in Sources */, 2A2702A120214502004A72E6 /* MPVASTModel.m in Sources */, @@ -3156,11 +3374,14 @@ 2A2702A420214502004A72E6 /* MPVASTStringUtilities.m in Sources */, 2A2702A520214502004A72E6 /* MPVASTTrackingEvent.m in Sources */, 2A2702A620214502004A72E6 /* MPVASTWrapper.m in Sources */, + BCDECB14219B4628002C1E7A /* MPContentBlocker.m in Sources */, 2A2702A720214502004A72E6 /* MPVASTTracking.m in Sources */, + EC6349332320A04E00FEB2F5 /* MPWebBrowserUserAgentInfo.m in Sources */, 2A2702A820214502004A72E6 /* MPCoreInstanceProvider.m in Sources */, 2A2702AA20214502004A72E6 /* MPViewabilityTracker.m in Sources */, 2A2702AB20214502004A72E6 /* MPWebView+Viewability.m in Sources */, 2A2702AC20214502004A72E6 /* MPAdConversionTracker.m in Sources */, + BC6B9E6922C135D00027F2F9 /* MPEngineInfo.m in Sources */, 2A2702AD20214502004A72E6 /* MPAdView.m in Sources */, 2A2702AE20214502004A72E6 /* MPBannerCustomEvent.m in Sources */, 2A2702AF20214502004A72E6 /* MPConstants.m in Sources */, @@ -3183,7 +3404,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 462D5F7619C128AB00834F28 /* MPInternalUtils.m in Sources */, A77FBEF318C533B400531E8A /* MPNativeAdError.m in Sources */, AE515F45171F1B110086B464 /* MPBannerAdManager.m in Sources */, AE515F46171F1B110086B464 /* MPBannerCustomEventAdapter.m in Sources */, @@ -3198,11 +3418,10 @@ 573A82F71B8E488400ED4067 /* MPNativeView.m in Sources */, A776A5561B5DDFD800095706 /* MPVASTCompanionAd.m in Sources */, BC6269901ED4B18F00724C4A /* NSString+MPAdditions.m in Sources */, - A77FBEF118C533B400531E8A /* MPTableViewCellImpressionTracker.m in Sources */, - B28725E51B9FB5D200C0D61B /* UIButton+MPAdditions.m in Sources */, - AE515F49171F1B110086B464 /* MPAdBrowserController.m in Sources */, AE515F4A171F1B110086B464 /* MPAdConfiguration.m in Sources */, A7A1CDDD197478E20082A6FA /* MPNativeAdData.m in Sources */, + BC4A4D1E21827287008A7410 /* MPBaseAdapterConfiguration.m in Sources */, + 2AFEE724225BC38400DD82C8 /* MPImpressionData.m in Sources */, A776A54E1B5DDFCA00095706 /* MPVASTCreative.m in Sources */, AE515F4B171F1B110086B464 /* MPAdDestinationDisplayAgent.m in Sources */, AE515F4C171F1B110086B464 /* MPAdServerCommunicator.m in Sources */, @@ -3213,6 +3432,7 @@ 57AC69381BB21FA20053C556 /* MOPUBActivityIndicatorView.m in Sources */, A7A1CDC419745F0E0082A6FA /* MPStreamAdPlacer.m in Sources */, AE515F4D171F1B110086B464 /* MPAdServerURLBuilder.m in Sources */, + 2A89F1502236DF3600E03010 /* MPRateLimitManager.m in Sources */, 2A7F96DC1E66411700114565 /* MPViewabilityTracker.m in Sources */, A7A1CDD81974783D0082A6FA /* MPCollectionViewAdPlacer.m in Sources */, 2AFF1B7A1EC2795500495994 /* MPRealTimeTimer.m in Sources */, @@ -3232,10 +3452,12 @@ AE515F52171F1B110086B464 /* MPAdWebViewAgent.m in Sources */, 57AC693E1BB21FA20053C556 /* MOPUBAVPlayer.m in Sources */, 57AC697A1BB21FA20053C556 /* MOPUBReplayView.m in Sources */, + BC2448C722B171B0003EBB4B /* MPExtendedHitBoxButton.m in Sources */, AE515F53171F1B110086B464 /* MPHTMLBannerCustomEvent.m in Sources */, 2A80EAEA1E6779F500D7FDD9 /* MPWebView+Viewability.m in Sources */, BCAD022820CEE695007DC2B2 /* NSError+MPAdditions.m in Sources */, BC0ADDF12080023C000ADEA4 /* NSString+MPConsentStatus.m in Sources */, + BCCEE892214B04E5003BD130 /* MPConsoleLogger.m in Sources */, AE515F54171F1B110086B464 /* MPHTMLInterstitialCustomEvent.m in Sources */, 2A4D35DD211CBF5300BE9377 /* MPCoreInstanceProvider+MRAID.m in Sources */, A72F90ED19B7CA0400A5601B /* MPServerAdPositioning.m in Sources */, @@ -3249,6 +3471,7 @@ BCA2EA4B2023DAC9000F24C0 /* MPHTTPNetworkTaskData.m in Sources */, A7A1CDD31974782E0082A6FA /* MPTableViewAdPlacer.m in Sources */, AE515F57171F1B110086B464 /* MPInterstitialAdManager.m in Sources */, + 2A48DF3C229601C1003763C2 /* MPImpressionTrackedNotification.m in Sources */, A7220FF11B6C2D750099DCF6 /* MPVASTResource.m in Sources */, 57EAA4DD1A1F3BE300923851 /* MRExpandModalViewController.m in Sources */, BC88E7E320769A3D002A3357 /* MPReachabilityManager.m in Sources */, @@ -3259,15 +3482,15 @@ AE515F58171F1B110086B464 /* MPInterstitialCustomEventAdapter.m in Sources */, A7A1CDE71974904A0082A6FA /* MPStreamAdPlacementData.m in Sources */, A714FE641B699587000EAEC4 /* MPVASTStringUtilities.m in Sources */, - 57AC69621BB21FA20053C556 /* MOPUBNativeVideoImpressionAgent.m in Sources */, + BCA762332149B4B100D55A05 /* MPLogEvent.m in Sources */, AE515F59171F1B110086B464 /* MPInterstitialViewController.m in Sources */, 57AC698A1BB21FFC0053C556 /* MPNativeAdRendererImageHandler.m in Sources */, A776A5361B5DDF9900095706 /* MPVASTWrapper.m in Sources */, + EC0BF277227CB402003DB141 /* MoPub+Utility.m in Sources */, 57ACE3FD1AA7E8D900A05633 /* MPRewardedVideoAdapter.m in Sources */, A77FBF0418C5343E00531E8A /* MPImageDownloadQueue.m in Sources */, 5758288F1A16F281009C7A85 /* MRController.m in Sources */, 57ACE4091AA7E8D900A05633 /* MPRewardedVideo.m in Sources */, - BC57D5FF1F0ED4240030C365 /* MPRewardedVideoCustomEvent+Caching.m in Sources */, 578D287F1B9A406B002E3905 /* MPNativeAdRendererConfiguration.m in Sources */, AE515F5C171F1B110086B464 /* MPMRAIDBannerCustomEvent.m in Sources */, B277838B1CE3BA5C00BAB0B1 /* MPRewardedVideoConnection.m in Sources */, @@ -3280,6 +3503,7 @@ 2A7FC6AD1D8CA33000165D41 /* MPWebView.m in Sources */, A77FBF0318C5343E00531E8A /* MPDiskLRUCache.m in Sources */, 4AE42F9D18D8FD2100DE4BF6 /* MPNativeCache.m in Sources */, + 2A89F14A2236DF2200E03010 /* MPRateLimitConfiguration.m in Sources */, 578D28881B9A410C002E3905 /* MPStaticNativeAdRendererSettings.m in Sources */, A7A4229E1B603CB400024A3A /* MPVASTTrackingEvent.m in Sources */, AE515F61171F1B110086B464 /* MRCommand.m in Sources */, @@ -3296,7 +3520,6 @@ A72210331B792C6F0099DCF6 /* MPVideoConfig.m in Sources */, 4AF54B55194A217F0093F714 /* MPMoPubNativeAdAdapter.m in Sources */, AE515F64171F1B110086B464 /* NSURL+MPAdditions.m in Sources */, - AE515F66171F1B110086B464 /* UIWebView+MPAdditions.m in Sources */, A71DB8131A2FE68300D3B229 /* MoPub.m in Sources */, BCD118892034E02E00C03B7D /* MPMoPubConfiguration.m in Sources */, 2A501D2E1F68ABDE00806177 /* MPNativeAdConfigValues.m in Sources */, @@ -3304,9 +3527,9 @@ A72F913A19C0D4C700A5601B /* MPNativePositionResponseDeserializer.m in Sources */, 571FA43B193FC4D300A36583 /* MPMoPubNativeCustomEvent.m in Sources */, AE515F67171F1B110086B464 /* MPAnalyticsTracker.m in Sources */, - BCD1188D2034E04400C03B7D /* MPAdvancedBiddingManager.m in Sources */, AE515F68171F1B110086B464 /* MPError.m in Sources */, A7BB73E41B34D79B00B3161B /* MPEnhancedDeeplinkRequest.m in Sources */, + 2A890DE82303656D00FE683F /* SKStoreProductViewController+MPAdditions.m in Sources */, BC0ADDF820810B3C000ADEA4 /* MPConsentDialogViewController.m in Sources */, 57ACE4151AA7E8D900A05633 /* MPRewardedVideoError.m in Sources */, 570F754E19820ADB00466F6F /* MPAdPlacerInvocation.m in Sources */, @@ -3324,7 +3547,7 @@ A776A5161B5DDE7E00095706 /* MPVASTAd.m in Sources */, A72F90DB19B7B1FF00A5601B /* MPNativePositionSource.m in Sources */, 573A82D91B8E487300ED4067 /* MPStaticNativeAdRenderer.m in Sources */, - 4AB5CCD019F55596005ABBC1 /* MPLogProvider.m in Sources */, + 4AB5CCD019F55596005ABBC1 /* MPLogManager.m in Sources */, AE515F6A171F1B110086B464 /* MPIdentityProvider.m in Sources */, 875390C61B04073F001F0550 /* MPActivityViewControllerHelper.m in Sources */, 2A501D3F1F68AD5100806177 /* MPNativeAdConfigValues+Internal.m in Sources */, @@ -3332,7 +3555,6 @@ AE515F6C171F1B110086B464 /* MPReachability.m in Sources */, 57AC695C1BB21FA20053C556 /* MOPUBNativeVideoCustomEvent.m in Sources */, AE515F6D171F1B110086B464 /* MPSessionTracker.m in Sources */, - AE515F6E171F1B110086B464 /* MPStoreKitProvider.m in Sources */, A776A50B1B5DDE0A00095706 /* MPXMLParser.m in Sources */, A72210671B79705D0099DCF6 /* MPVASTMacroProcessor.m in Sources */, AE515F6F171F1B110086B464 /* MPTimer.m in Sources */, @@ -3350,11 +3572,14 @@ B2950B3F1FBE5E5B00C40BF5 /* MPBannerCustomEvent+Internal.m in Sources */, B2FE8FAB2069B28900593089 /* NSDictionary+MPAdditions.m in Sources */, AE515F74171F1B110086B464 /* MPInterstitialCustomEvent.m in Sources */, + BCDECB12219B4628002C1E7A /* MPContentBlocker.m in Sources */, AE14FA4C1774D95D00ABF744 /* MPLastResortDelegate.m in Sources */, + EC481B0323209D84003AA3B9 /* MPWebBrowserUserAgentInfo.m in Sources */, 57AC69561BB21FA20053C556 /* MOPUBNativeVideoAdConfigValues.m in Sources */, AEAFC85F1798615C007F5911 /* MRBundleManager.m in Sources */, A7A422981B603CB400024A3A /* MPVASTModel.m in Sources */, A77FBEF418C533B400531E8A /* MPNativeAd.m in Sources */, + BC6B9E6722C135D00027F2F9 /* MPEngineInfo.m in Sources */, 2A5275B91FCE1FCF00FF39D5 /* MPAdImpressionTimer.m in Sources */, BCF0FA991DC9536B00ADFE4F /* MPCountdownTimerView.m in Sources */, A714FEB41B6AB09A000EAEC4 /* MPVASTManager.m in Sources */, @@ -3369,7 +3594,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - BCA00AF11EF47A91006FF762 /* MPInternalUtils.m in Sources */, + BC68415D22B179A2002B633C /* MPExtendedHitBoxButton.m in Sources */, BCA00AF31EF47A91006FF762 /* MPBannerAdManager.m in Sources */, BCA00AF41EF47A91006FF762 /* MPBannerCustomEventAdapter.m in Sources */, BCA00AF61EF47A91006FF762 /* MRConstants.m in Sources */, @@ -3380,11 +3605,10 @@ BCAD021D20CB388D007DC2B2 /* NSDate+MPAdditions.m in Sources */, BCA00AFA1EF47A91006FF762 /* MPGeolocationProvider.m in Sources */, 2A501D401F68AD5100806177 /* MPNativeAdConfigValues+Internal.m in Sources */, + BC6B9E6822C135D00027F2F9 /* MPEngineInfo.m in Sources */, BCAD022920CEE695007DC2B2 /* NSError+MPAdditions.m in Sources */, BCA00AFB1EF47A91006FF762 /* MOPUBExperimentProvider.m in Sources */, BC119227207D211B005DF26E /* MPConsentChangedNotification.m in Sources */, - BCA00B001EF47A91006FF762 /* UIButton+MPAdditions.m in Sources */, - BCA00B011EF47A91006FF762 /* MPAdBrowserController.m in Sources */, BCA00B021EF47A91006FF762 /* MPAdConfiguration.m in Sources */, BCA00B051EF47A91006FF762 /* MPAdDestinationDisplayAgent.m in Sources */, BCA00B061EF47A91006FF762 /* MPAdServerCommunicator.m in Sources */, @@ -3398,6 +3622,7 @@ BCA00B121EF47A91006FF762 /* NSBundle+MPAdditions.m in Sources */, BCA00B131EF47A91006FF762 /* MRNativeCommandHandler.m in Sources */, BCA2EA4C2023DAD8000F24C0 /* MPHTTPNetworkTaskData.m in Sources */, + 2A89F14B2236DF2200E03010 /* MPRateLimitConfiguration.m in Sources */, BCA00B151EF47A91006FF762 /* MPProgressOverlayView.m in Sources */, BCA00B161EF47A91006FF762 /* MPURLResolver.m in Sources */, BCA00B181EF47A91006FF762 /* MPRewardedVideoAdManager.m in Sources */, @@ -3409,6 +3634,7 @@ BCA00B221EF47A91006FF762 /* MPHTMLInterstitialViewController.m in Sources */, BC11921F207BE949005DF26E /* MPConsentManager.m in Sources */, BCA00B231EF47A91006FF762 /* MPRewardedVideoReward.m in Sources */, + BCA762342149B4B100D55A05 /* MPLogEvent.m in Sources */, BCA00B241EF47A91006FF762 /* MPBaseInterstitialAdapter.m in Sources */, BC4370552087C5D6001B86D4 /* MPAdServerKeys.m in Sources */, BCA00B271EF47A91006FF762 /* MPInterstitialAdManager.m in Sources */, @@ -3422,6 +3648,7 @@ BCA00B391EF47A91006FF762 /* MPMRAIDBannerCustomEvent.m in Sources */, BCA00B3A1EF47A91006FF762 /* MPRewardedVideoConnection.m in Sources */, BC96D557211CDFEB00610174 /* NSDictionary+MPAdditions.m in Sources */, + BC4A4D1F21827287008A7410 /* MPBaseAdapterConfiguration.m in Sources */, BC96D559211CE07900610174 /* NSMutableArray+MPAdditions.m in Sources */, BCA00B3C1EF47A91006FF762 /* MPMRAIDInterstitialCustomEvent.m in Sources */, BCA00B3E1EF47A91006FF762 /* MPUserInteractionGestureRecognizer.m in Sources */, @@ -3432,31 +3659,34 @@ BCA00B471EF47A91006FF762 /* MPClosableView.m in Sources */, BCA00B491EF47A91006FF762 /* NSJSONSerialization+MPAdditions.m in Sources */, BCA00B4A1EF47A91006FF762 /* MPActivityViewControllerHelper+TweetShare.m in Sources */, - BCD1188E2034E04500C03B7D /* MPAdvancedBiddingManager.m in Sources */, BCA00B4C1EF47A91006FF762 /* MPConstants.m in Sources */, + 2A48DF3D229601C1003763C2 /* MPImpressionTrackedNotification.m in Sources */, BCA00B4D1EF47A91006FF762 /* MRProperty.m in Sources */, BCA00B521EF47A91006FF762 /* NSURL+MPAdditions.m in Sources */, BCFE67DB208508D3005E458A /* MPConsentChangedReason.m in Sources */, - BCA00B531EF47A91006FF762 /* UIWebView+MPAdditions.m in Sources */, + BCCEE893214B04E5003BD130 /* MPConsoleLogger.m in Sources */, BCA00B541EF47A91006FF762 /* MoPub.m in Sources */, + EC0BF278227CB402003DB141 /* MoPub+Utility.m in Sources */, BC0ADDF22080023C000ADEA4 /* NSString+MPConsentStatus.m in Sources */, + BCDECB13219B4628002C1E7A /* MPContentBlocker.m in Sources */, BCA00B5A1EF47A91006FF762 /* MPAnalyticsTracker.m in Sources */, BCA00B5C1EF47A91006FF762 /* MPError.m in Sources */, BCA00B5D1EF47A91006FF762 /* MPEnhancedDeeplinkRequest.m in Sources */, + 2A89F1512236DF3600E03010 /* MPRateLimitManager.m in Sources */, BCA00B5E1EF47A91006FF762 /* MPRewardedVideoError.m in Sources */, BCA00B601EF47A91006FF762 /* MPGlobal.m in Sources */, BCEE050C2037A4300076CA86 /* MPHTTPNetworkSession.m in Sources */, BCA00B611EF47A91006FF762 /* MPMoPubRewardedPlayableCustomEvent.m in Sources */, + EC6349342320A04F00FEB2F5 /* MPWebBrowserUserAgentInfo.m in Sources */, BCA00B641EF47A91006FF762 /* MRError.m in Sources */, BC926EF720D9753B004ED8F7 /* MPMemoryCache.m in Sources */, - BCA00B6A1EF47A91006FF762 /* MPLogProvider.m in Sources */, + BCA00B6A1EF47A91006FF762 /* MPLogManager.m in Sources */, BC7F42882141CE7E007EC273 /* MPAdTargeting.m in Sources */, BCA00B6B1EF47A91006FF762 /* MPIdentityProvider.m in Sources */, BCA00B6C1EF47A91006FF762 /* MPActivityViewControllerHelper.m in Sources */, BCA00B6E1EF47A91006FF762 /* MPLogging.m in Sources */, BCA00B701EF47A91006FF762 /* MPReachability.m in Sources */, BCA00B731EF47A91006FF762 /* MPSessionTracker.m in Sources */, - BCA00B751EF47A91006FF762 /* MPStoreKitProvider.m in Sources */, BCA00B781EF47A91006FF762 /* MPTimer.m in Sources */, 2AA9C5471F14461A006629C6 /* MPViewabilityTracker.m in Sources */, BCA00B791EF47A91006FF762 /* MPRewardedVideoCustomEvent.m in Sources */, @@ -3470,14 +3700,15 @@ BCA00B801EF47A91006FF762 /* MPURLActionInfo.m in Sources */, BCA00B841EF47A91006FF762 /* MPInterstitialAdController.m in Sources */, BCA00B861EF47A91006FF762 /* MPInterstitialCustomEvent.m in Sources */, + 2A890DE92303656D00FE683F /* SKStoreProductViewController+MPAdditions.m in Sources */, BCA00B871EF47A91006FF762 /* MPLastResortDelegate.m in Sources */, BCA00B891EF47A91006FF762 /* MRBundleManager.m in Sources */, BCA00B8C1EF47A91006FF762 /* MPCountdownTimerView.m in Sources */, 2AA9C5521F14462E006629C6 /* MPWebView+Viewability.m in Sources */, BCA00B8E1EF47A91006FF762 /* MRVideoPlayerManager.m in Sources */, - BC57D6001F0ED4240030C365 /* MPRewardedVideoCustomEvent+Caching.m in Sources */, BCA00B8F1EF47A91006FF762 /* UIView+MPAdditions.m in Sources */, BCA00B901EF47A91006FF762 /* MPAdAlertGestureRecognizer.m in Sources */, + 2AFEE725225BC38400DD82C8 /* MPImpressionData.m in Sources */, BCA00B911EF47A91006FF762 /* MPAdAlertManager.m in Sources */, BC96D55C211CE08600610174 /* NSString+MPAdditions.m in Sources */, ); @@ -3507,7 +3738,7 @@ CLANG_WARN_SUSPICIOUS_MOVES = YES; CLANG_WARN_UNREACHABLE_CODE = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 5.4.0; + CURRENT_PROJECT_VERSION = 5.9.0; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 4S7XS533V3; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -3519,7 +3750,6 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; INFOPLIST_FILE = MoPubSDKTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MTL_ENABLE_DEBUG_INFO = YES; OTHER_LDFLAGS = ( @@ -3529,7 +3759,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = com.mopub.MoPubSDKTests; PRODUCT_NAME = "$(TARGET_NAME)"; - VALID_ARCHS = "arm64 armv7 armv7s i386"; + VALID_ARCHS = "$(ARCHS_STANDARD)"; VERSIONING_SYSTEM = "apple-generic"; }; name = Debug; @@ -3548,7 +3778,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 5.4.0; + CURRENT_PROJECT_VERSION = 5.9.0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 4S7XS533V3; ENABLE_NS_ASSERTIONS = NO; @@ -3560,7 +3790,6 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; INFOPLIST_FILE = MoPubSDKTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MTL_ENABLE_DEBUG_INFO = NO; OTHER_LDFLAGS = ( @@ -3570,7 +3799,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = com.mopub.MoPubSDKTests; PRODUCT_NAME = "$(TARGET_NAME)"; - VALID_ARCHS = "arm64 armv7 armv7s i386"; + VALID_ARCHS = "$(ARCHS_STANDARD)"; VERSIONING_SYSTEM = "apple-generic"; }; name = Release; @@ -3578,6 +3807,8 @@ 2AF030C62016723700909F29 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + BITCODE_GENERATION_MODE = bitcode; + BUILD_LIBRARY_FOR_DISTRIBUTION = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -3589,12 +3820,12 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 5.4.0; + CURRENT_PROJECT_VERSION = 5.9.0; DEBUG_INFORMATION_FORMAT = dwarf; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 4S7XS533V3; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 5.4.0; + DYLIB_CURRENT_VERSION = 5.9.0; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_PREPROCESSOR_DEFINITIONS = ( @@ -3606,15 +3837,15 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; INFOPLIST_FILE = MoPubSDKFramework/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MODULEMAP_FILE = MoPubSDKFramework/MoPubSDKFramework.modulemap; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = NO; PRODUCT_BUNDLE_IDENTIFIER = com.mopub.MoPubSDKFramework; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = "1,2"; - VALID_ARCHS = "$(ARCHS_STANDARD) x86_64 i386"; + VALID_ARCHS = "$(ARCHS_STANDARD) armv7s x86_64 i386"; VERSIONING_SYSTEM = "apple-generic"; }; name = Debug; @@ -3622,6 +3853,8 @@ 2AF030C72016723700909F29 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + BITCODE_GENERATION_MODE = bitcode; + BUILD_LIBRARY_FOR_DISTRIBUTION = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -3634,12 +3867,12 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 5.4.0; + CURRENT_PROJECT_VERSION = 5.9.0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 4S7XS533V3; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 5.4.0; + DYLIB_CURRENT_VERSION = 5.9.0; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_NS_ASSERTIONS = NO; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -3651,15 +3884,15 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; INFOPLIST_FILE = MoPubSDKFramework/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MODULEMAP_FILE = MoPubSDKFramework/MoPubSDKFramework.modulemap; MTL_ENABLE_DEBUG_INFO = NO; ONLY_ACTIVE_ARCH = NO; PRODUCT_BUNDLE_IDENTIFIER = com.mopub.MoPubSDKFramework; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = "1,2"; - VALID_ARCHS = "$(ARCHS_STANDARD) x86_64 i386"; + VALID_ARCHS = "$(ARCHS_STANDARD) armv7s"; VERSIONING_SYSTEM = "apple-generic"; }; name = Release; @@ -3672,7 +3905,7 @@ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNREACHABLE_CODE = YES; - CURRENT_PROJECT_VERSION = 5.4.0; + CURRENT_PROJECT_VERSION = 5.9.0; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_GENERATE_DEBUGGING_SYMBOLS = NO; GCC_PREPROCESSOR_DEFINITIONS = ( @@ -3706,7 +3939,7 @@ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNREACHABLE_CODE = YES; - CURRENT_PROJECT_VERSION = 5.4.0; + CURRENT_PROJECT_VERSION = 5.9.0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -3733,7 +3966,7 @@ AE515F9A171F1B110086B464 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - CURRENT_PROJECT_VERSION = 5.4.0; + CURRENT_PROJECT_VERSION = 5.9.0; DSTROOT = /tmp/MoPubSDK.dst; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -3753,7 +3986,7 @@ AE515F9B171F1B110086B464 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - CURRENT_PROJECT_VERSION = 5.4.0; + CURRENT_PROJECT_VERSION = 5.9.0; DSTROOT = /tmp/MoPubSDK.dst; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -3773,6 +4006,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_OBJC_ARC = YES; @@ -3811,9 +4045,9 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; ONLY_ACTIVE_ARCH = YES; - OTHER_CFLAGS = "-fembed-bitcode-marker"; + OTHER_CFLAGS = "-fembed-bitcode"; OTHER_LDFLAGS = ( "-ObjC", "$(inherited)", @@ -3826,6 +4060,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_OBJC_ARC = YES; @@ -3856,7 +4091,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; OTHER_CFLAGS = "-fembed-bitcode"; OTHER_LDFLAGS = ( "-ObjC", @@ -3870,7 +4105,7 @@ BCA00B951EF47A91006FF762 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - CURRENT_PROJECT_VERSION = 5.4.0; + CURRENT_PROJECT_VERSION = 5.9.0; DSTROOT = /tmp/MoPubSDK.dst; GCC_PRECOMPILE_PREFIX_HEADER = NO; GCC_TREAT_WARNINGS_AS_ERRORS = YES; @@ -3887,7 +4122,7 @@ BCA00B961EF47A91006FF762 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - CURRENT_PROJECT_VERSION = 5.4.0; + CURRENT_PROJECT_VERSION = 5.9.0; DSTROOT = /tmp/MoPubSDK.dst; GCC_PRECOMPILE_PREFIX_HEADER = NO; GCC_TREAT_WARNINGS_AS_ERRORS = YES; diff --git a/MoPubSDK.xcodeproj/xcshareddata/xcschemes/MoPubResources.xcscheme b/MoPubSDK.xcodeproj/xcshareddata/xcschemes/MoPubResources.xcscheme index 5ab6df379..af7bf2103 100644 --- a/MoPubSDK.xcodeproj/xcshareddata/xcschemes/MoPubResources.xcscheme +++ b/MoPubSDK.xcodeproj/xcshareddata/xcschemes/MoPubResources.xcscheme @@ -1,6 +1,6 @@ + + + + + + + + diff --git a/MoPubSDK.xcodeproj/xcshareddata/xcschemes/MoPubSDKFramework.xcscheme b/MoPubSDK.xcodeproj/xcshareddata/xcschemes/MoPubSDKFramework.xcscheme index 5d2291f6b..54cec0384 100644 --- a/MoPubSDK.xcodeproj/xcshareddata/xcschemes/MoPubSDKFramework.xcscheme +++ b/MoPubSDK.xcodeproj/xcshareddata/xcschemes/MoPubSDKFramework.xcscheme @@ -1,7 +1,7 @@ + LastUpgradeVersion = "1020" + version = "1.3"> @@ -76,23 +76,5 @@ - - - - - - - - - - diff --git a/MoPubSDK.xcodeproj/xcshareddata/xcschemes/MoPubSDKTests.xcscheme b/MoPubSDK.xcodeproj/xcshareddata/xcschemes/MoPubSDKTests.xcscheme index a06fb70da..67984ba5d 100644 --- a/MoPubSDK.xcodeproj/xcshareddata/xcschemes/MoPubSDKTests.xcscheme +++ b/MoPubSDK.xcodeproj/xcshareddata/xcschemes/MoPubSDKTests.xcscheme @@ -1,6 +1,6 @@ + shouldUseLaunchSchemeArgsEnv = "YES" + codeCoverageEnabled = "YES"> + + + + - - - - - - - - @property (nonatomic, weak) id delegate; +@property (nonatomic, readonly) BOOL isMraidAd; + +@property (nonatomic, readonly) Class customEventClass; +@property (nonatomic, readonly) NSString* dspCreativeId; +@property (nonatomic, readonly) NSString* lineItemId; - (id)initWithDelegate:(id)delegate; @@ -25,5 +30,6 @@ - (void)stopAutomaticallyRefreshingContents; - (void)startAutomaticallyRefreshingContents; - (void)rotateToOrientation:(UIInterfaceOrientation)orientation; +- (NSString *)getDspCreativeId; @end diff --git a/MoPubSDK/Internal/Banners/MPBannerAdManager.m b/MoPubSDK/Internal/Banners/MPBannerAdManager.m index 0e648611e..c4fbc99f5 100644 --- a/MoPubSDK/Internal/Banners/MPBannerAdManager.m +++ b/MoPubSDK/Internal/Banners/MPBannerAdManager.m @@ -1,7 +1,7 @@ // // MPBannerAdManager.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -30,6 +30,7 @@ @interface MPBannerAdManager () @property (nonatomic, strong) MPAdTargeting *targeting; @property (nonatomic, strong) NSMutableArray *remainingConfigurations; @property (nonatomic, strong) MPTimer *refreshTimer; +@property (nonatomic, strong) NSURL *mostRecentlyLoadedURL; // ADF-4286: avoid infinite ad reloads @property (nonatomic, assign) BOOL adActionInProgress; @property (nonatomic, assign) BOOL automaticallyRefreshesContents; @property (nonatomic, assign) BOOL hasRequestedAtLeastOneAd; @@ -45,14 +46,6 @@ - (void)refreshTimerDidFire; @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]; @@ -97,14 +90,30 @@ - (BOOL)loading return self.communicator.loading || self.requestingAdapter; } +- (Class)customEventClass +{ + return self.requestingConfiguration.customEventClass; +} + +- (NSString*)dspCreativeId +{ + return self.requestingConfiguration.dspCreativeId; +} + +- (NSString*)lineItemId { + return self.requestingConfiguration.lineItemId; +} + - (void)loadAdWithTargeting:(MPAdTargeting *)targeting { + MPLogAdEvent(MPLogEvent.adLoadAttempt, self.delegate.adUnitId); + 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]); + MPLogEvent([MPLogEvent error:NSError.adAlreadyLoading message:nil]); return; } @@ -136,6 +145,13 @@ - (void)pauseRefreshTimer } } +- (void)resumeRefreshTimer +{ + if ([self.refreshTimer isValid]) { + [self.refreshTimer resume]; + } +} + - (void)stopAutomaticallyRefreshingContents { self.automaticallyRefreshesContents = NO; @@ -165,10 +181,9 @@ - (void)loadAdWithURL:(NSURL *)URL [self.communicator cancel]; - URL = (URL) ? URL : [MPAdServerURLBuilder URLWithAdUnitID:[self.delegate adUnitId] - keywords:self.targeting.keywords - userDataKeywords:self.targeting.userDataKeywords - location:self.targeting.location]; + URL = (URL) ? URL : [MPAdServerURLBuilder URLWithAdUnitID:[self.delegate adUnitId] targeting:self.targeting]; + + self.mostRecentlyLoadedURL = URL; [self.communicator loadURL:URL]; } @@ -180,6 +195,11 @@ - (void)rotateToOrientation:(UIInterfaceOrientation)orientation [self.onscreenAdapter rotateToOrientation:orientation]; } +- (BOOL)isMraidAd +{ + return self.requestingConfiguration.isMraidAd; +} + #pragma mark - Internal - (void)scheduleRefreshTimer @@ -187,11 +207,11 @@ - (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]; + if (self.automaticallyRefreshesContents && timeInterval > 0) { + self.refreshTimer = [MPTimer timerWithTimeInterval: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); } @@ -199,8 +219,11 @@ - (void)scheduleRefreshTimer - (void)refreshTimerDidFire { - if (!self.loading && self.automaticallyRefreshesContents) { - [self loadAdWithTargeting:self.targeting]; + [self.delegate managerRefreshAd:self.requestingAdapterAdContentView]; + if (!self.loading) { + // Instead of reusing the existing `MPAdTargeting` that is potentially outdated, ask the + // delegate to provide the `MPAdTargeting` so that it's the latest. + [self loadAdWithTargeting:self.delegate.adTargeting]; } } @@ -213,26 +236,15 @@ - (BOOL)shouldScheduleTimerOnImpressionDisplay { - (void)fetchAdWithConfiguration:(MPAdConfiguration *)configuration { MPLogInfo(@"Banner ad view is fetching ad network type: %@", configuration.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]]; + [self didFailToLoadAdapterWithError:[NSError errorWithCode:MOPUBErrorAdUnitWarmingUp]]; return; } if ([configuration.networkType isEqualToString:kAdTypeClear]) { MPLogInfo(kMPClearErrorLogFormatWithAdUnitID, self.delegate.adUnitId); - [self didFailToLoadAdapterWithError:[MOPUBError errorWithCode:MOPUBErrorNoInventory]]; + [self didFailToLoadAdapterWithError:[NSError errorWithCode:MOPUBErrorNoInventory]]; return; } @@ -249,6 +261,7 @@ - (void)fetchAdWithConfiguration:(MPAdConfiguration *)configuration { return; } + [self.delegate bannerWillStartAttemptForAdManager:self]; [self.requestingAdapter _getAdWithConfiguration:configuration targeting:self.targeting containerSize:self.delegate.containerSize]; } @@ -262,7 +275,7 @@ - (void)communicatorDidReceiveAdConfigurations:(NSArray *)c // There are no configurations to try. Consider this a clear response by the server. if (self.remainingConfigurations.count == 0 && self.requestingConfiguration == nil) { MPLogInfo(kMPClearErrorLogFormatWithAdUnitID, self.delegate.adUnitId); - [self didFailToLoadAdapterWithError:[MOPUBError errorWithCode:MOPUBErrorNoInventory]]; + [self didFailToLoadAdapterWithError:[NSError errorWithCode:MOPUBErrorNoInventory]]; return; } @@ -276,10 +289,16 @@ - (void)communicatorDidFailWithError:(NSError *)error - (void)didFailToLoadAdapterWithError:(NSError *)error { - [self.delegate managerDidFailToLoadAd]; + [self.delegate managerDidFailToLoadAdWithError:error]; [self scheduleRefreshTimer]; +} + +- (MPAdType)adTypeForAdServerCommunicator:(MPAdServerCommunicator *)adServerCommunicator { + return MPAdTypeInline; +} - MPLogError(@"Banner view (%@) failed. Error: %@", [self.delegate adUnitId], error); +- (NSString *)adUnitIDForAdServerCommunicator:(MPAdServerCommunicator *)adServerCommunicator { + return [self.delegate adUnitId]; } #pragma mark - @@ -335,6 +354,7 @@ - (void)presentRequestingAdapter - (void)adapter:(MPBaseBannerAdapter *)adapter didFinishLoadingAd:(UIView *)ad { + [self.delegate bannerDidSucceedAttemptForAdManager:self]; if (self.requestingAdapter == adapter) { self.remainingConfigurations = nil; self.requestingAdapterAdContentView = ad; @@ -343,12 +363,14 @@ - (void)adapter:(MPBaseBannerAdapter *)adapter didFinishLoadingAd:(UIView *)ad NSTimeInterval duration = NSDate.now.timeIntervalSince1970 - self.adapterLoadStartTimestamp; [self.communicator sendAfterLoadUrlWithConfiguration:self.requestingConfiguration adapterLoadDuration:duration adapterLoadResult:MPAfterLoadResultAdLoaded]; + MPLogAdEvent(MPLogEvent.adDidLoad, self.delegate.banner.adUnitId); [self presentRequestingAdapter]; } } - (void)adapter:(MPBaseBannerAdapter *)adapter didFailToLoadAdWithError:(NSError *)error { + [self.delegate bannerDidFailAttemptForAdManager:self error:error]; // Record the end of the adapter load and send off the fire and forget after-load-url tracker // with the appropriate error code result. NSTimeInterval duration = NSDate.now.timeIntervalSince1970 - self.adapterLoadStartTimestamp; @@ -362,13 +384,15 @@ - (void)adapter:(MPBaseBannerAdapter *)adapter didFailToLoadAdWithError:(NSError [self fetchAdWithConfiguration:self.requestingConfiguration]; } // No more configurations to try. Send new request to Ads server to get more Ads. - else if (self.requestingConfiguration.nextURL != nil) { + else if (self.requestingConfiguration.nextURL != nil + && [self.requestingConfiguration.nextURL isEqual:self.mostRecentlyLoadedURL] == false) { [self loadAdWithURL:self.requestingConfiguration.nextURL]; } // No more configurations to try and no more pages to load. else { - MPLogInfo(kMPClearErrorLogFormatWithAdUnitID, self.delegate.adUnitId); - [self didFailToLoadAdapterWithError:[MOPUBError errorWithCode:MOPUBErrorNoInventory]]; + NSError * clearResponseError = [NSError errorWithCode:MOPUBErrorNoInventory localizedDescription:[NSString stringWithFormat:kMPClearErrorLogFormatWithAdUnitID, self.delegate.banner.adUnitId]]; + MPLogAdEvent([MPLogEvent adFailedToLoadWithError:clearResponseError], self.delegate.banner.adUnitId); + [self didFailToLoadAdapterWithError:clearResponseError]; } } @@ -391,16 +415,21 @@ - (void)adapter:(MPBaseBannerAdapter *)adapter didFailToLoadAdWithError:(NSError } } -- (void)adapter:(MPBaseBannerAdapter *)adapter didTrackImpressionForAd:(UIView *)ad { +- (void)adapterDidTrackImpressionForAd:(MPBaseBannerAdapter *)adapter { if (self.onscreenAdapter == adapter && [self shouldScheduleTimerOnImpressionDisplay]) { [self scheduleRefreshTimer]; } + + [self.delegate impressionDidFireWithImpressionData:self.requestingConfiguration.impressionData]; } - (void)userActionWillBeginForAdapter:(MPBaseBannerAdapter *)adapter { if (self.onscreenAdapter == adapter) { self.adActionInProgress = YES; + + MPLogAdEvent(MPLogEvent.adTapped, self.delegate.banner.adUnitId); + MPLogAdEvent(MPLogEvent.adWillPresentModal, self.delegate.banner.adUnitId); [self.delegate userActionWillBegin]; } } @@ -408,7 +437,9 @@ - (void)userActionWillBeginForAdapter:(MPBaseBannerAdapter *)adapter - (void)userActionDidFinishForAdapter:(MPBaseBannerAdapter *)adapter { if (self.onscreenAdapter == adapter) { + MPLogAdEvent(MPLogEvent.adDidDismissModal, self.delegate.banner.adUnitId); [self.delegate userActionDidFinish]; + self.adActionInProgress = NO; [self presentRequestingAdapter]; } @@ -417,10 +448,30 @@ - (void)userActionDidFinishForAdapter:(MPBaseBannerAdapter *)adapter - (void)userWillLeaveApplicationFromAdapter:(MPBaseBannerAdapter *)adapter { if (self.onscreenAdapter == adapter) { + MPLogAdEvent(MPLogEvent.adTapped, self.delegate.banner.adUnitId); + MPLogAdEvent(MPLogEvent.adWillLeaveApplication, self.delegate.banner.adUnitId); [self.delegate userWillLeaveApplication]; } } +- (void)adWillExpandForAdapter:(MPBaseBannerAdapter *)adapter +{ + // While the banner ad is in an expanded state, the refresh timer should be paused + // since the user is interacting with the ad experience. + [self pauseRefreshTimer]; +} + +- (void)adDidCollapseForAdapter:(MPBaseBannerAdapter *)adapter +{ + // Once the banner ad is collapsed back into its default state, the refresh timer + // should be resumed to queue up the next ad. + [self resumeRefreshTimer]; +} + +- (NSString *)getDspCreativeId { + return [_requestingConfiguration dspCreativeId]; +} + @end diff --git a/MoPubSDK/Internal/Banners/MPBannerAdManagerDelegate.h b/MoPubSDK/Internal/Banners/MPBannerAdManagerDelegate.h index 1744f4ed0..8142034c9 100644 --- a/MoPubSDK/Internal/Banners/MPBannerAdManagerDelegate.h +++ b/MoPubSDK/Internal/Banners/MPBannerAdManagerDelegate.h @@ -1,7 +1,7 @@ // // MPBannerAdManagerDelegate.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -9,6 +9,8 @@ #import @class MPAdView; +@class MPAdTargeting; + @protocol MPAdViewDelegate; @protocol MPBannerAdManagerDelegate @@ -20,12 +22,23 @@ - (CGSize)containerSize; - (UIViewController *)viewControllerForPresentingModalView; +/** + * The latest ad targeting information for ad refresh and other scenarios. + */ +- (MPAdTargeting *)adTargeting; + - (void)invalidateContentView; +- (void)bannerWillStartAttemptForAdManager:(MPBannerAdManager *)manager; +- (void)bannerDidSucceedAttemptForAdManager:(MPBannerAdManager *)manager; +- (void)bannerDidFailAttemptForAdManager:(MPBannerAdManager *)manager error:(NSError*)error; + - (void)managerDidLoadAd:(UIView *)ad; -- (void)managerDidFailToLoadAd; +- (void)managerDidFailToLoadAdWithError:(NSError *)error; - (void)userActionWillBegin; - (void)userActionDidFinish; - (void)userWillLeaveApplication; +- (void)impressionDidFireWithImpressionData:(MPImpressionData *)impressionData; +- (void)managerRefreshAd:(UIView *)ad; @end diff --git a/MoPubSDK/Internal/Banners/MPBannerCustomEvent+Internal.h b/MoPubSDK/Internal/Banners/MPBannerCustomEvent+Internal.h index d2d21bde5..bfa65ad72 100644 --- a/MoPubSDK/Internal/Banners/MPBannerCustomEvent+Internal.h +++ b/MoPubSDK/Internal/Banners/MPBannerCustomEvent+Internal.h @@ -1,7 +1,7 @@ // // MPBannerCustomEvent+Internal.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/Internal/Banners/MPBannerCustomEvent+Internal.m b/MoPubSDK/Internal/Banners/MPBannerCustomEvent+Internal.m index ccc6f079b..fa7e2ac9f 100644 --- a/MoPubSDK/Internal/Banners/MPBannerCustomEvent+Internal.m +++ b/MoPubSDK/Internal/Banners/MPBannerCustomEvent+Internal.m @@ -1,7 +1,7 @@ // // MPBannerCustomEvent+Internal.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/Internal/Banners/MPBannerCustomEventAdapter.h b/MoPubSDK/Internal/Banners/MPBannerCustomEventAdapter.h index 1f4c08f79..27abf0f88 100644 --- a/MoPubSDK/Internal/Banners/MPBannerCustomEventAdapter.h +++ b/MoPubSDK/Internal/Banners/MPBannerCustomEventAdapter.h @@ -1,7 +1,7 @@ // // MPBannerCustomEventAdapter.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -11,9 +11,11 @@ #import "MPPrivateBannerCustomEventDelegate.h" @class MPBannerCustomEvent; +@class WBBannerProxy; @interface MPBannerCustomEventAdapter : MPBaseBannerAdapter - (instancetype)initWithConfiguration:(MPAdConfiguration *)configuration delegate:(id)delegate; +- (instancetype)withBannerProxy:(WBBannerProxy *)proxy; @end diff --git a/MoPubSDK/Internal/Banners/MPBannerCustomEventAdapter.m b/MoPubSDK/Internal/Banners/MPBannerCustomEventAdapter.m index eefefc11e..ea2dbb4a9 100644 --- a/MoPubSDK/Internal/Banners/MPBannerCustomEventAdapter.m +++ b/MoPubSDK/Internal/Banners/MPBannerCustomEventAdapter.m @@ -1,7 +1,7 @@ // // MPBannerCustomEventAdapter.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -12,6 +12,7 @@ #import "MPAdTargeting.h" #import "MPBannerCustomEvent.h" #import "MPCoreInstanceProvider.h" +#import "MPError.h" #import "MPLogging.h" #import "MPAdImpressionTimer.h" #import "MPBannerCustomEvent+Internal.h" @@ -31,6 +32,8 @@ - (void)trackClickOnce; @implementation MPBannerCustomEventAdapter +WBBannerProxy *bannerProxy; + - (instancetype)initWithConfiguration:(MPAdConfiguration *)configuration delegate:(id)delegate { if (!configuration.customEventClass) { @@ -39,6 +42,11 @@ - (instancetype)initWithConfiguration:(MPAdConfiguration *)configuration delegat return [self initWithDelegate:delegate]; } +- (instancetype)withBannerProxy:(WBBannerProxy *)proxy { + bannerProxy = proxy; + return self; +} + - (void)unregisterDelegate { if ([self.bannerCustomEvent respondsToSelector:@selector(invalidate)]) { @@ -63,8 +71,9 @@ - (void)getAdWithConfiguration:(MPAdConfiguration *)configuration targeting:(MPA MPBannerCustomEvent *customEvent = [[configuration.customEventClass alloc] init]; if (![customEvent isKindOfClass:[MPBannerCustomEvent class]]) { - MPLogError(@"**** Custom Event Class: %@ does not extend MPBannerCustomEvent ****", NSStringFromClass(configuration.customEventClass)); - [self.delegate adapter:self didFailToLoadAdWithError:nil]; + NSError * error = [NSError customEventClass:configuration.customEventClass doesNotInheritFrom:MPBannerCustomEvent.class]; + MPLogEvent([MPLogEvent error:error message:nil]); + [self.delegate adapter:self didFailToLoadAdWithError:error]; return; } @@ -72,6 +81,7 @@ - (void)getAdWithConfiguration:(MPAdConfiguration *)configuration targeting:(MPA self.bannerCustomEvent = customEvent; self.bannerCustomEvent.delegate = self; self.bannerCustomEvent.localExtras = targeting.localExtras; + [self.bannerCustomEvent requestAdWithSize:size customEventInfo:configuration.customEventClassData adMarkup:configuration.advancedBidPayload]; } @@ -186,6 +196,23 @@ - (void)trackClickOnce } } +- (void)bannerCustomEventWillExpandAd:(MPBannerCustomEvent *)event +{ + [self.delegate adWillExpandForAdapter:self]; +} + +- (void)bannerCustomEventDidCollapseAd:(MPBannerCustomEvent *)event +{ + [self.delegate adDidCollapseForAdapter:self]; +} + +- (void)trackImpression { + [super trackImpression]; + + // Notify delegate that an impression tracker was fired + [self.delegate adapterDidTrackImpressionForAd:self]; +} + #pragma mark - MPAdImpressionTimerDelegate - (void)adViewWillLogImpression:(UIView *)adView @@ -196,9 +223,6 @@ - (void)adViewWillLogImpression:(UIView *)adView [self.bannerCustomEvent trackImpressionsIncludedInMarkup]; // Start viewability tracking [self.bannerCustomEvent startViewabilityTracker]; - - // Notify delegate that an impression tracker was fired - [self.delegate adapter:self didTrackImpressionForAd:adView]; } @end diff --git a/MoPubSDK/Internal/Banners/MPBaseBannerAdapter.h b/MoPubSDK/Internal/Banners/MPBaseBannerAdapter.h index 59c0513b9..bfc0cd165 100644 --- a/MoPubSDK/Internal/Banners/MPBaseBannerAdapter.h +++ b/MoPubSDK/Internal/Banners/MPBaseBannerAdapter.h @@ -1,7 +1,7 @@ // // MPBaseBannerAdapter.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -81,6 +81,12 @@ /** * Fires when the impression tracker has been sent. */ -- (void)adapter:(MPBaseBannerAdapter *)adapter didTrackImpressionForAd:(UIView *)ad; +- (void)adapterDidTrackImpressionForAd:(MPBaseBannerAdapter *)adapter; + +/** + * Fires when the banner ad is expanding/resizing and collapsing. + */ +- (void)adWillExpandForAdapter:(MPBaseBannerAdapter *)adapter; +- (void)adDidCollapseForAdapter:(MPBaseBannerAdapter *)adapter; @end diff --git a/MoPubSDK/Internal/Banners/MPBaseBannerAdapter.m b/MoPubSDK/Internal/Banners/MPBaseBannerAdapter.m index 1128543a4..a500a3d24 100644 --- a/MoPubSDK/Internal/Banners/MPBaseBannerAdapter.m +++ b/MoPubSDK/Internal/Banners/MPBaseBannerAdapter.m @@ -1,7 +1,7 @@ // // MPBaseBannerAdapter.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -80,18 +80,17 @@ - (void)startTimeoutTimer self.configuration.adTimeoutInterval : BANNER_TIMEOUT_INTERVAL; if (timeInterval > 0) { - self.timeoutTimer = [[MPCoreInstanceProvider sharedProvider] buildMPTimerWithTimeInterval:timeInterval - target:self - selector:@selector(timeout) - repeats:NO]; - + self.timeoutTimer = [MPTimer timerWithTimeInterval:timeInterval + target:self + selector:@selector(timeout) + repeats:NO]; [self.timeoutTimer scheduleNow]; } } - (void)timeout { - NSError * error = [MOPUBError errorWithCode:MOPUBErrorAdRequestTimedOut + NSError * error = [NSError errorWithCode:MOPUBErrorAdRequestTimedOut localizedDescription:@"Banner ad request timed out"]; [self.delegate adapter:self didFailToLoadAdWithError:error]; } @@ -101,8 +100,6 @@ - (void)timeout - (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 diff --git a/MoPubSDK/Internal/Banners/MPPrivateBannerCustomEventDelegate.h b/MoPubSDK/Internal/Banners/MPPrivateBannerCustomEventDelegate.h index a2d548f82..668ff5a8f 100644 --- a/MoPubSDK/Internal/Banners/MPPrivateBannerCustomEventDelegate.h +++ b/MoPubSDK/Internal/Banners/MPPrivateBannerCustomEventDelegate.h @@ -1,7 +1,7 @@ // // MPPrivateBannerCustomEventDelegate.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/Internal/Common/AdAlerts/MPAdAlertGestureRecognizer.h b/MoPubSDK/Internal/Common/AdAlerts/MPAdAlertGestureRecognizer.h index eb64f431d..427c64498 100644 --- a/MoPubSDK/Internal/Common/AdAlerts/MPAdAlertGestureRecognizer.h +++ b/MoPubSDK/Internal/Common/AdAlerts/MPAdAlertGestureRecognizer.h @@ -1,33 +1,26 @@ // // MPAdAlertGestureRecognizer.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // -#import - -extern NSInteger const kMPAdAlertGestureMaxAllowedYAxisMovement; +/** + To trigger this gesture recognizer, the user needs to swipe from left to right, and then right to + left at least four times in the target view, while keeping the finger in a straight horizontal line. + See documentation here: + https://developers.mopub.com/publishers/tools/creative-flagging-tool/#report-a-creative + */ -typedef enum -{ - MPAdAlertGestureRecognizerState_ZigRight1, - MPAdAlertGestureRecognizerState_ZagLeft2, - MPAdAlertGestureRecognizerState_Recognized -} MPAdAlertGestureRecognizerState; +#import @interface MPAdAlertGestureRecognizer : UIGestureRecognizer -// default is 4 -@property (nonatomic, assign) NSInteger numZigZagsForRecognition; - -// default is 100 +/** + After adding this gesture recognizer to a new view, set minimum tracking distance base on the view size. + 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/MoPubSDK/Internal/Common/AdAlerts/MPAdAlertGestureRecognizer.m b/MoPubSDK/Internal/Common/AdAlerts/MPAdAlertGestureRecognizer.m index 339bb1247..c642e937d 100644 --- a/MoPubSDK/Internal/Common/AdAlerts/MPAdAlertGestureRecognizer.m +++ b/MoPubSDK/Internal/Common/AdAlerts/MPAdAlertGestureRecognizer.m @@ -1,7 +1,7 @@ // // MPAdAlertGestureRecognizer.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -14,6 +14,12 @@ #define kDefaultMinTrackedDistance 100 #define kDefaultNumZigZagsForRecognition 4 +typedef NS_ENUM(NSUInteger, MPAdAlertGestureRecognizerState) { + MPAdAlertGestureRecognizerState_ZigRight1, + MPAdAlertGestureRecognizerState_ZagLeft2, + MPAdAlertGestureRecognizerState_Recognized +}; + NSInteger const kMPAdAlertGestureMaxAllowedYAxisMovement = 50; @interface MPAdAlertGestureRecognizer () @@ -23,18 +29,12 @@ @interface MPAdAlertGestureRecognizer () @property (nonatomic, assign) CGPoint startingPoint; @property (nonatomic, assign) BOOL thresholdReached; @property (nonatomic, assign) NSInteger curNumZigZags; +@property (nonatomic, assign) NSInteger numZigZagsForRecognition; // default is 4 @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]; diff --git a/MoPubSDK/Internal/Common/AdAlerts/MPAdAlertManager.h b/MoPubSDK/Internal/Common/AdAlerts/MPAdAlertManager.h index ffc686474..a0373ea34 100644 --- a/MoPubSDK/Internal/Common/AdAlerts/MPAdAlertManager.h +++ b/MoPubSDK/Internal/Common/AdAlerts/MPAdAlertManager.h @@ -1,7 +1,7 @@ // // MPAdAlertManager.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/Internal/Common/AdAlerts/MPAdAlertManager.m b/MoPubSDK/Internal/Common/AdAlerts/MPAdAlertManager.m index b517f972b..954edf54c 100644 --- a/MoPubSDK/Internal/Common/AdAlerts/MPAdAlertManager.m +++ b/MoPubSDK/Internal/Common/AdAlerts/MPAdAlertManager.m @@ -1,7 +1,7 @@ // // MPAdAlertManager.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -9,10 +9,11 @@ #import "MPAdAlertManager.h" #import "MPAdConfiguration.h" #import "MPAdAlertGestureRecognizer.h" -#import "MPLogging.h" -#import "MPIdentityProvider.h" +#import "MPConstants.h" #import "MPCoreInstanceProvider.h" +#import "MPIdentityProvider.h" #import "MPLastResortDelegate.h" +#import "MPLogging.h" #import #import @@ -30,14 +31,12 @@ @interface MPAdAlertManager () -#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/MoPubSDK/Internal/Common/MPAdBrowserController.m b/MoPubSDK/Internal/Common/MPAdBrowserController.m deleted file mode 100644 index f00a3123c..000000000 --- a/MoPubSDK/Internal/Common/MPAdBrowserController.m +++ /dev/null @@ -1,345 +0,0 @@ -// -// MPAdBrowserController.m -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import "MPAdBrowserController.h" -#import "MPLogging.h" -#import "MPAdConfiguration.h" -#import "MPAPIEndpoints.h" -#import "NSBundle+MPAdditions.h" -#import "MPURLRequest.h" - -static NSString * const kAdBrowserControllerNibName = @"MPAdBrowserController"; - -@interface MPAdBrowserController () - -@property (weak, nonatomic) IBOutlet UINavigationBar *navigationBar; -@property (weak, nonatomic) IBOutlet NSLayoutConstraint *navigationBarYConstraint; - -@property (weak, nonatomic) IBOutlet UIToolbar *browserControlToolbar; -@property (weak, nonatomic) IBOutlet NSLayoutConstraint *browserControlToolbarBottomConstraint; - -@property (weak, nonatomic) IBOutlet NSLayoutConstraint *webViewTopConstraint; -@property (weak, nonatomic) IBOutlet NSLayoutConstraint *webViewLeadingConstraint; -@property (weak, nonatomic) IBOutlet NSLayoutConstraint *webViewTrailingConstraint; - -@property (nonatomic, strong) UIActionSheet *actionSheet; -@property (nonatomic, strong) NSString *HTMLString; -@property (nonatomic, assign) int webViewLoadCount; - -- (void)dismissActionSheet; - -@end - -@implementation MPAdBrowserController - -#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.spinner = [[UIActivityIndicatorView alloc] initWithFrame:CGRectZero]; - [self.spinner sizeToFit]; - self.spinner.hidesWhenStopped = YES; - - self.webViewLoadCount = 0; - } - 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; - - // If iOS 11, set up autolayout constraints so that the toolbar and web view stay within the safe area - // Note: The web view has to be constrained to the safe area on top for the notch in Portait and leading/trailing - // for the notch in Landscape. Only the bottom of the toolbar needs to be constrained because Apple will move - // the buttons into the safe area automatically in Landscape, and otherwise it's preferable for the toolbar to - // stretch the length of the unsafe area as well. - if (@available(iOS 11, *)) { - // Disable the old constraints - self.navigationBarYConstraint.active = NO; - self.browserControlToolbarBottomConstraint.active = NO; - self.webViewTopConstraint.active = NO; - self.webViewLeadingConstraint.active = NO; - self.webViewTrailingConstraint.active = NO; - - // Set new constraints based on the safe area layout guide - self.navigationBarYConstraint = [self.navigationBar.bottomAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor]; // put nav bar just above safe area - self.browserControlToolbarBottomConstraint = [self.browserControlToolbar.bottomAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.bottomAnchor]; - self.webViewTopConstraint = [self.webView.topAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor]; - self.webViewLeadingConstraint = [self.webView.leadingAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.leadingAnchor]; - self.webViewTrailingConstraint = [self.webView.trailingAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.trailingAnchor]; - - // Enable the new constraints - [NSLayoutConstraint activateConstraints:@[ - self.navigationBarYConstraint, - self.browserControlToolbarBottomConstraint, - self.webViewTopConstraint, - self.webViewLeadingConstraint, - self.webViewTrailingConstraint, - ]]; - } - - // Set web view background color to white so scrolling at extremes won't have a gray background - self.webView.backgroundColor = [UIColor whiteColor]; -} - -- (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]; - - 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:[MPURLRequest 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]; - } -} - -- (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; -} - -@end diff --git a/MoPubSDK/Internal/Common/MPAdBrowserController.xib b/MoPubSDK/Internal/Common/MPAdBrowserController.xib deleted file mode 100644 index f79f27b03..000000000 --- a/MoPubSDK/Internal/Common/MPAdBrowserController.xib +++ /dev/null @@ -1,97 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/MoPubSDK/Internal/Common/MPAdConfiguration.h b/MoPubSDK/Internal/Common/MPAdConfiguration.h index a5acbad09..15dd0699f 100644 --- a/MoPubSDK/Internal/Common/MPAdConfiguration.h +++ b/MoPubSDK/Internal/Common/MPAdConfiguration.h @@ -1,22 +1,21 @@ // // MPAdConfiguration.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // #import #import "MPGlobal.h" +#import "MPImpressionData.h" @class MPRewardedVideoReward; -enum { - MPAdTypeUnknown = -1, - MPAdTypeBanner = 0, - MPAdTypeInterstitial = 1 +typedef NS_ENUM(NSUInteger, MPAdType) { + MPAdTypeInline, + MPAdTypeFullscreen }; -typedef NSUInteger MPAdType; typedef NS_ENUM(NSUInteger, MPAfterLoadResult) { MPAfterLoadResultMissingAdapter, @@ -32,6 +31,7 @@ extern NSString * const kCreativeIdMetadataKey; extern NSString * const kCustomEventClassNameMetadataKey; extern NSString * const kCustomEventClassDataMetadataKey; extern NSString * const kNextUrlMetadataKey; +extern NSString * const kFormatMetadataKey; extern NSString * const kBeforeLoadUrlMetadataKey; extern NSString * const kAfterLoadUrlMetadataKey; extern NSString * const kAfterLoadSuccessUrlMetadataKey; @@ -53,6 +53,7 @@ extern NSString * const kRewardedVideoCompletionUrlMetadataKey; extern NSString * const kRewardedCurrenciesMetadataKey; extern NSString * const kRewardedPlayableDurationMetadataKey; extern NSString * const kRewardedPlayableRewardOnClickMetadataKey; +extern NSString * const kImpressionDataMetadataKey; extern NSString * const kInterstitialAdTypeMetadataKey; extern NSString * const kOrientationTypeMetadataKey; @@ -75,6 +76,7 @@ extern NSString * const kBannerImpressionMinPixelMetadataKey; @property (nonatomic, assign) MPAdType adType; @property (nonatomic, assign) BOOL adUnitWarmingUp; +@property (nonatomic, readonly) BOOL isMraidAd; @property (nonatomic, copy) NSString *networkType; // If this flag is YES, it implies that we've reached the end of the waterfall for the request // and there is no need to hit ad server again. @@ -92,6 +94,7 @@ extern NSString * const kBannerImpressionMinPixelMetadataKey; @property (nonatomic, strong) NSDictionary *customEventClassData; @property (nonatomic, assign) MPInterstitialOrientationType orientationType; @property (nonatomic, copy) NSString *dspCreativeId; +@property (nonatomic, copy) NSString *lineItemId; @property (nonatomic, assign) BOOL precacheRequired; @property (nonatomic, assign) BOOL isVastVideoPlayer; @property (nonatomic, strong) NSDate *creationTimestamp; @@ -110,13 +113,23 @@ extern NSString * const kBannerImpressionMinPixelMetadataKey; @property (nonatomic, assign) NSTimeInterval rewardedPlayableDuration; @property (nonatomic, assign) BOOL rewardedPlayableShouldRewardOnClick; @property (nonatomic, copy) NSString *advancedBidPayload; +@property (nonatomic, strong) MPImpressionData *impressionData; +@property (nonatomic, assign) BOOL isVASTClickabilityExperimentEnabled; + +/** + Unified ad unit format in its raw string representation. + */ +@property (nonatomic, copy) NSString *format; // viewable impression tracking experiment @property (nonatomic) NSTimeInterval impressionMinVisibleTimeInSec; @property (nonatomic) CGFloat impressionMinVisiblePixels; @property (nonatomic) BOOL visibleImpressionTrackingEnabled; -- (id)initWithMetadata:(NSDictionary *)metadata data:(NSData *)data; +- (instancetype)initWithMetadata:(NSDictionary *)metadata data:(NSData *)data adType:(MPAdType)adType; + +// Default @c init is unavailable +- (instancetype)init NS_UNAVAILABLE; - (BOOL)hasPreferredSize; - (NSString *)adResponseHTMLString; diff --git a/MoPubSDK/Internal/Common/MPAdConfiguration.m b/MoPubSDK/Internal/Common/MPAdConfiguration.m index 2af4e215b..6335dabd9 100644 --- a/MoPubSDK/Internal/Common/MPAdConfiguration.m +++ b/MoPubSDK/Internal/Common/MPAdConfiguration.m @@ -1,22 +1,30 @@ // // MPAdConfiguration.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // -#import "MPAdConfiguration.h" - #import "MOPUBExperimentProvider.h" +#import "MOPUBNativeVideoCustomEvent.h" +#import "MPAdConfiguration.h" #import "MPAdServerKeys.h" #import "MPConstants.h" +#import "MPHTMLBannerCustomEvent.h" +#import "MPHTMLInterstitialCustomEvent.h" #import "MPLogging.h" +#import "MPMoPubNativeCustomEvent.h" +#import "MPMoPubRewardedPlayableCustomEvent.h" +#import "MPMoPubRewardedVideoCustomEvent.h" +#import "MPMRAIDBannerCustomEvent.h" +#import "MPMRAIDInterstitialCustomEvent.h" #import "MPRewardedVideoReward.h" +#import "MPVASTTracking.h" #import "MPViewabilityTracker.h" +#import "NSDictionary+MPAdditions.h" #import "NSJSONSerialization+MPAdditions.h" #import "NSString+MPAdditions.h" -#import "NSDictionary+MPAdditions.h" #if MP_HAS_NATIVE_PACKAGE #import "MPVASTTrackingEvent.h" @@ -26,6 +34,7 @@ #define AFTER_LOAD_DURATION_MACRO @"%%LOAD_DURATION_MS%%" #define AFTER_LOAD_RESULT_MACRO @"%%LOAD_RESULT%%" +NSString * const kAdLineItemIdKey = @"x-adgroupid"; NSString * const kAdTypeMetadataKey = @"x-adtype"; NSString * const kAdUnitWarmingUpMetadataKey = @"x-warmup"; NSString * const kClickthroughMetadataKey = @"x-clickthrough"; @@ -33,6 +42,7 @@ NSString * const kCustomEventClassNameMetadataKey = @"x-custom-event-class-name"; NSString * const kCustomEventClassDataMetadataKey = @"x-custom-event-class-data"; NSString * const kNextUrlMetadataKey = @"x-next-url"; +NSString * const kFormatMetadataKey = @"adunit-format"; NSString * const kBeforeLoadUrlMetadataKey = @"x-before-load-url"; NSString * const kAfterLoadUrlMetadataKey = @"x-after-load-url"; NSString * const kAfterLoadSuccessUrlMetadataKey = @"x-after-load-success-url"; @@ -46,8 +56,9 @@ NSString * const kAdTimeoutMetadataKey = @"x-ad-timeout-ms"; NSString * const kWidthMetadataKey = @"x-width"; NSString * const kDspCreativeIdKey = @"x-dspcreativeid"; -NSString * const kPrecacheRequiredKey = @"x-precacheRequired"; +NSString * const kPrecacheRequiredKey = @"x-precacherequired"; NSString * const kIsVastVideoPlayerKey = @"x-vastvideoplayer"; +NSString * const kImpressionDataMetadataKey = @"impdata"; NSString * const kInterstitialAdTypeMetadataKey = @"x-fulladtype"; NSString * const kOrientationTypeMetadataKey = @"x-orientation"; @@ -97,205 +108,240 @@ // advanced bidding NSString * const kAdvancedBiddingMarkupMetadataKey = @"adm"; +// clickability experiment +NSString * const kVASTClickabilityExperimentKey = @"vast-click-enabled"; + @interface MPAdConfiguration () @property (nonatomic, copy) NSString *adResponseHTMLString; @property (nonatomic, strong, readwrite) NSArray *availableRewards; @property (nonatomic) MOPUBDisplayAgentType clickthroughExperimentBrowserAgent; +@property (nonatomic, strong) MOPUBExperimentProvider *experimentProvider; @property (nonatomic, copy) NSArray *afterLoadUrlsWithMacros; @property (nonatomic, copy) NSArray *afterLoadSuccessUrlsWithMacros; @property (nonatomic, copy) NSArray *afterLoadFailureUrlsWithMacros; -- (MPAdType)adTypeFromMetadata:(NSDictionary *)metadata; -- (NSString *)networkTypeFromMetadata:(NSDictionary *)metadata; -- (NSTimeInterval)refreshIntervalFromMetadata:(NSDictionary *)metadata; -- (NSDictionary *)dictionaryFromMetadata:(NSDictionary *)metadata forKey:(NSString *)key; -- (NSURL *)URLFromMetadata:(NSDictionary *)metadata forKey:(NSString *)key; -- (NSArray *)URLsFromMetadata:(NSDictionary *)metadata forKey:(NSString *)key; -- (Class)setUpCustomEventClassFromMetadata:(NSDictionary *)metadata; - @end //////////////////////////////////////////////////////////////////////////////////////////////////// @implementation MPAdConfiguration -- (id)initWithMetadata:(NSDictionary *)metadata data:(NSData *)data +- (instancetype)initWithMetadata:(NSDictionary *)metadata data:(NSData *)data adType:(MPAdType)adType { self = [super init]; if (self) { - self.adResponseData = data; + [self commonInitWithMetadata:metadata + data:data + adType:adType + experimentProvider:MOPUBExperimentProvider.sharedInstance]; + } + return self; +} + +/** + This common init enables unit testing with an `MOPUBExperimentProvider` instance that is not a singleton. + */ +- (void)commonInitWithMetadata:(NSDictionary *)metadata + data:(NSData *)data + adType:(MPAdType)adType + experimentProvider:(MOPUBExperimentProvider *)experimentProvider +{ + self.adResponseData = data; + + self.adType = adType; + self.adUnitWarmingUp = [metadata mp_boolForKey:kAdUnitWarmingUpMetadataKey]; - self.adType = [self adTypeFromMetadata:metadata]; - self.adUnitWarmingUp = [metadata mp_boolForKey:kAdUnitWarmingUpMetadataKey]; + self.networkType = [self networkTypeFromMetadata:metadata]; + self.networkType = self.networkType ? self.networkType : @""; - self.networkType = [self networkTypeFromMetadata:metadata]; - self.networkType = self.networkType ? self.networkType : @""; + self.preferredSize = CGSizeMake([metadata mp_floatForKey:kWidthMetadataKey], + [metadata mp_floatForKey:kHeightMetadataKey]); - self.preferredSize = CGSizeMake([metadata mp_floatForKey:kWidthMetadataKey], - [metadata mp_floatForKey:kHeightMetadataKey]); + self.clickTrackingURL = [self URLFromMetadata:metadata + forKey:kClickthroughMetadataKey]; + self.nextURL = [self URLFromMetadata:metadata + forKey:kNextUrlMetadataKey]; + self.format = [metadata objectForKey:kFormatMetadataKey]; + self.beforeLoadURL = [self URLFromMetadata:metadata forKey:kBeforeLoadUrlMetadataKey]; + self.afterLoadUrlsWithMacros = [self URLStringsFromMetadata:metadata forKey:kAfterLoadUrlMetadataKey]; + self.afterLoadSuccessUrlsWithMacros = [self URLStringsFromMetadata:metadata forKey:kAfterLoadSuccessUrlMetadataKey]; + self.afterLoadFailureUrlsWithMacros = [self URLStringsFromMetadata:metadata forKey:kAfterLoadFailureUrlMetadataKey]; - self.clickTrackingURL = [self URLFromMetadata:metadata - forKey:kClickthroughMetadataKey]; - self.nextURL = [self URLFromMetadata:metadata - forKey:kNextUrlMetadataKey]; - self.beforeLoadURL = [self URLFromMetadata:metadata forKey:kBeforeLoadUrlMetadataKey]; - self.afterLoadUrlsWithMacros = [self URLStringsFromMetadata:metadata forKey:kAfterLoadUrlMetadataKey]; - self.afterLoadSuccessUrlsWithMacros = [self URLStringsFromMetadata:metadata forKey:kAfterLoadSuccessUrlMetadataKey]; - self.afterLoadFailureUrlsWithMacros = [self URLStringsFromMetadata:metadata forKey:kAfterLoadFailureUrlMetadataKey]; + self.refreshInterval = [self refreshIntervalFromMetadata:metadata]; + self.adTimeoutInterval = [self timeIntervalFromMsmetadata:metadata forKey:kAdTimeoutMetadataKey]; - self.refreshInterval = [self refreshIntervalFromMetadata:metadata]; - self.adTimeoutInterval = [self timeIntervalFromMsmetadata:metadata forKey:kAdTimeoutMetadataKey]; + self.nativeSDKParameters = [self dictionaryFromMetadata:metadata + forKey:kNativeSDKParametersMetadataKey]; - self.nativeSDKParameters = [self dictionaryFromMetadata:metadata - forKey:kNativeSDKParametersMetadataKey]; + self.orientationType = [self orientationTypeFromMetadata:metadata]; - self.orientationType = [self orientationTypeFromMetadata:metadata]; + self.customEventClass = [self setUpCustomEventClassFromMetadata:metadata]; - self.customEventClass = [self setUpCustomEventClassFromMetadata:metadata]; + self.customEventClassData = [self customEventClassDataFromMetadata:metadata]; - self.customEventClassData = [self customEventClassDataFromMetadata:metadata]; + self.dspCreativeId = [metadata objectForKey:kDspCreativeIdKey]; - self.dspCreativeId = [metadata objectForKey:kDspCreativeIdKey]; + self.precacheRequired = [metadata mp_boolForKey:kPrecacheRequiredKey]; - self.precacheRequired = [metadata mp_boolForKey:kPrecacheRequiredKey]; + self.isVastVideoPlayer = [metadata mp_boolForKey:kIsVastVideoPlayerKey]; - self.isVastVideoPlayer = [metadata mp_boolForKey:kIsVastVideoPlayerKey]; + self.creationTimestamp = [NSDate date]; - self.creationTimestamp = [NSDate date]; + self.creativeId = [metadata objectForKey:kCreativeIdMetadataKey]; - self.creativeId = [metadata objectForKey:kCreativeIdMetadataKey]; + self.lineItemId = [metadata objectForKey:kAdLineItemIdKey]; - self.metadataAdType = [metadata objectForKey:kAdTypeMetadataKey]; + self.metadataAdType = [metadata objectForKey:kAdTypeMetadataKey]; - self.nativeVideoPlayVisiblePercent = [self percentFromMetadata:metadata forKey:kNativeVideoPlayVisiblePercentMetadataKey]; + self.nativeVideoPlayVisiblePercent = [self percentFromMetadata:metadata forKey:kNativeVideoPlayVisiblePercentMetadataKey]; - self.nativeVideoPauseVisiblePercent = [self percentFromMetadata:metadata forKey:kNativeVideoPauseVisiblePercentMetadataKey]; + self.nativeVideoPauseVisiblePercent = [self percentFromMetadata:metadata forKey:kNativeVideoPauseVisiblePercentMetadataKey]; - self.nativeImpressionMinVisiblePixels = [[self adAmountFromMetadata:metadata key:kNativeImpressionMinVisiblePixelsMetadataKey] floatValue]; + self.nativeImpressionMinVisiblePixels = [[self adAmountFromMetadata:metadata key:kNativeImpressionMinVisiblePixelsMetadataKey] floatValue]; - self.nativeImpressionMinVisiblePercent = [self percentFromMetadata:metadata forKey:kNativeImpressionMinVisiblePercentMetadataKey]; + self.nativeImpressionMinVisiblePercent = [self percentFromMetadata:metadata forKey:kNativeImpressionMinVisiblePercentMetadataKey]; - self.nativeImpressionMinVisibleTimeInterval = [self timeIntervalFromMsmetadata:metadata forKey:kNativeImpressionVisibleMsMetadataKey]; + self.nativeImpressionMinVisibleTimeInterval = [self timeIntervalFromMsmetadata:metadata forKey:kNativeImpressionVisibleMsMetadataKey]; - self.nativeVideoMaxBufferingTime = [self timeIntervalFromMsmetadata:metadata forKey:kNativeVideoMaxBufferingTimeMsMetadataKey]; + self.nativeVideoMaxBufferingTime = [self timeIntervalFromMsmetadata:metadata forKey:kNativeVideoMaxBufferingTimeMsMetadataKey]; #if MP_HAS_NATIVE_PACKAGE - self.nativeVideoTrackers = [self nativeVideoTrackersFromMetadata:metadata key:kNativeVideoTrackersMetadataKey]; + self.nativeVideoTrackers = [self nativeVideoTrackersFromMetadata:metadata key:kNativeVideoTrackersMetadataKey]; #endif - self.impressionMinVisibleTimeInSec = [self timeIntervalFromMsmetadata:metadata forKey:kBannerImpressionVisableMsMetadataKey]; - self.impressionMinVisiblePixels = [[self adAmountFromMetadata:metadata key:kBannerImpressionMinPixelMetadataKey] floatValue]; - - // Organize impression tracking URLs - NSArray * URLs = [self URLsFromMetadata:metadata forKey:kImpressionTrackersMetadataKey]; - // Check to see if the array actually contains URLs - if (URLs.count > 0) { - self.impressionTrackingURLs = URLs; - } else { - // If the array does not contain URLs, take the old `x-imptracker` URL and save that into an array instead. - self.impressionTrackingURLs = [self URLsFromMetadata:metadata forKey:kImpressionTrackerMetadataKey]; - } + self.impressionMinVisibleTimeInSec = [self timeIntervalFromMsmetadata:metadata forKey:kBannerImpressionVisableMsMetadataKey]; + self.impressionMinVisiblePixels = [[self adAmountFromMetadata:metadata key:kBannerImpressionMinPixelMetadataKey] floatValue]; - // rewarded video + self.impressionData = [self impressionDataFromMetadata:metadata]; + self.isVASTClickabilityExperimentEnabled = [metadata mp_boolForKey:kVASTClickabilityExperimentKey defaultValue:NO]; - // Attempt to parse the multiple currency Metadata first since this will take - // precedence over the older single currency approach. - self.availableRewards = [self parseAvailableRewardsFromMetadata:metadata]; - 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 - // metadata. - else { - NSString *currencyName = [metadata objectForKey:kRewardedVideoCurrencyNameMetadataKey] ?: kMPRewardedVideoRewardCurrencyTypeUnspecified; + // Organize impression tracking URLs + NSArray * URLs = [self URLsFromMetadata:metadata forKey:kImpressionTrackersMetadataKey]; + // Check to see if the array actually contains URLs + if (URLs.count > 0) { + self.impressionTrackingURLs = URLs; + } else { + // If the array does not contain URLs, take the old `x-imptracker` URL and save that into an array instead. + self.impressionTrackingURLs = [self URLsFromMetadata:metadata forKey:kImpressionTrackerMetadataKey]; + } - NSNumber *currencyAmount = [self adAmountFromMetadata:metadata key:kRewardedVideoCurrencyAmountMetadataKey]; - if (currencyAmount.integerValue <= 0) { - currencyAmount = @(kMPRewardedVideoRewardCurrencyAmountUnspecified); - } + // rewarded video - MPRewardedVideoReward * reward = [[MPRewardedVideoReward alloc] initWithCurrencyType:currencyName amount:currencyAmount]; - self.availableRewards = [NSArray arrayWithObject:reward]; - self.selectedReward = reward; + // Attempt to parse the multiple currency Metadata first since this will take + // precedence over the older single currency approach. + self.availableRewards = [self parseAvailableRewardsFromMetadata:metadata]; + 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 + // metadata. + else { + NSString *currencyName = [metadata objectForKey:kRewardedVideoCurrencyNameMetadataKey] ?: kMPRewardedVideoRewardCurrencyTypeUnspecified; + + NSNumber *currencyAmount = [self adAmountFromMetadata:metadata key:kRewardedVideoCurrencyAmountMetadataKey]; + if (currencyAmount.integerValue <= 0) { + currencyAmount = @(kMPRewardedVideoRewardCurrencyAmountUnspecified); } - self.rewardedVideoCompletionUrl = [metadata objectForKey:kRewardedVideoCompletionUrlMetadataKey]; + MPRewardedVideoReward * reward = [[MPRewardedVideoReward alloc] initWithCurrencyType:currencyName amount:currencyAmount]; + self.availableRewards = [NSArray arrayWithObject:reward]; + self.selectedReward = reward; + } - // rewarded playables - self.rewardedPlayableDuration = [self timeIntervalFromMetadata:metadata forKey:kRewardedPlayableDurationMetadataKey]; - self.rewardedPlayableShouldRewardOnClick = [[metadata objectForKey:kRewardedPlayableRewardOnClickMetadataKey] boolValue]; + self.rewardedVideoCompletionUrl = [metadata objectForKey:kRewardedVideoCompletionUrlMetadataKey]; - // clickthrough experiment - self.clickthroughExperimentBrowserAgent = [self clickthroughExperimentVariantFromMetadata:metadata forKey:kClickthroughExperimentBrowserAgent]; - [MOPUBExperimentProvider setDisplayAgentFromAdServer:self.clickthroughExperimentBrowserAgent]; + // rewarded playables + self.rewardedPlayableDuration = [self timeIntervalFromMetadata:metadata forKey:kRewardedPlayableDurationMetadataKey]; + self.rewardedPlayableShouldRewardOnClick = [[metadata objectForKey:kRewardedPlayableRewardOnClickMetadataKey] boolValue]; - // viewability - NSInteger disabledViewabilityValue = [metadata mp_integerForKey:kViewabilityDisableMetadataKey]; + // clickthrough experiment + self.clickthroughExperimentBrowserAgent = [self clickthroughExperimentVariantFromMetadata:metadata forKey:kClickthroughExperimentBrowserAgent]; + self.experimentProvider = experimentProvider; + [self.experimentProvider setDisplayAgentFromAdServer:self.clickthroughExperimentBrowserAgent]; - if (disabledViewabilityValue != 0 && - disabledViewabilityValue >= MPViewabilityOptionNone && - disabledViewabilityValue <= MPViewabilityOptionAll) { - MPViewabilityOption vendorsToDisable = (MPViewabilityOption)disabledViewabilityValue; - [MPViewabilityTracker disableViewability:vendorsToDisable]; - } + // viewability + NSInteger disabledViewabilityValue = [metadata mp_integerForKey:kViewabilityDisableMetadataKey]; - // advanced bidding - self.advancedBidPayload = [metadata objectForKey:kAdvancedBiddingMarkupMetadataKey]; + if (disabledViewabilityValue != 0 && + disabledViewabilityValue >= MPViewabilityOptionNone && + disabledViewabilityValue <= MPViewabilityOptionAll) { + MPViewabilityOption vendorsToDisable = (MPViewabilityOption)disabledViewabilityValue; + [MPViewabilityTracker disableViewability:vendorsToDisable]; } - return self; + + // advanced bidding + self.advancedBidPayload = [metadata objectForKey:kAdvancedBiddingMarkupMetadataKey]; } +/** + Provided the metadata of an ad, return the class of corresponding custome event. + */ - (Class)setUpCustomEventClassFromMetadata:(NSDictionary *)metadata { - NSString *customEventClassName = [metadata objectForKey:kCustomEventClassNameMetadataKey]; - - 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]; + NSDictionary *customEventTable; + switch (self.adType) { + case MPAdTypeInline: { + customEventTable = @{@"admob_native": @"MPGoogleAdMobBannerCustomEvent", // optional class + @"html": NSStringFromClass([MPHTMLBannerCustomEvent class]), + @"mraid": NSStringFromClass([MPMRAIDBannerCustomEvent class]), + @"json_video": NSStringFromClass([MOPUBNativeVideoCustomEvent class]), + @"json": NSStringFromClass([MPMoPubNativeCustomEvent class])}; + break; + } + case MPAdTypeFullscreen: { + customEventTable = @{@"admob_full": @"MPGoogleAdMobInterstitialCustomEvent", // optional class + @"html": NSStringFromClass([MPHTMLInterstitialCustomEvent class]), + @"mraid": NSStringFromClass([MPMRAIDInterstitialCustomEvent class]), + @"rewarded_video": NSStringFromClass([MPMoPubRewardedVideoCustomEvent class]), + @"rewarded_playable": NSStringFromClass([MPMoPubRewardedPlayableCustomEvent class])}; + break; + } } - Class customEventClass = NSClassFromString(customEventClassName); + NSString *customEventClassName = metadata[kCustomEventClassNameMetadataKey]; + if (customEventTable[self.networkType]) { + customEventClassName = customEventTable[self.networkType]; + } + Class customEventClass = NSClassFromString(customEventClassName); if (customEventClassName && !customEventClass) { - MPLogWarn(@"Could not find custom event class named %@", customEventClassName); + MPLogInfo(@"Could not find custom event class named %@", customEventClassName); } return customEventClass; } - - - (NSDictionary *)customEventClassDataFromMetadata:(NSDictionary *)metadata { + // Parse out custom event data if its present NSDictionary *result = [self dictionaryFromMetadata:metadata forKey:kCustomEventClassDataMetadataKey]; - if (!result) { + if (result != nil) { + // Inject the unified ad unit format into the custom data so that + // all adapters (including mediated ones) can differentiate between + // banner and medium rectangle formats. + // The key `adunit_format` is used to denote the format, which is the same as the + // key for impression level revenue data since they represent the same information. + NSString *format = [metadata objectForKey:kFormatMetadataKey]; + if (format.length > 0) { + NSMutableDictionary *dictionary = [result mutableCopy]; + dictionary[kImpressionDataAdUnitFormatKey] = format; + result = dictionary; + } + } + // No custom event data found; this is probably a native ad payload. + else { result = [self dictionaryFromMetadata:metadata forKey:kNativeSDKParametersMetadataKey]; } return result; @@ -363,6 +409,11 @@ - (NSString *)adResponseHTMLString return urls.count > 0 ? urls : nil; } +- (BOOL)isMraidAd +{ + return [self.metadataAdType isEqualToString:kAdTypeMraid]; +} + #pragma mark - Private - (NSArray *)concatenateBaseUrlArray:(NSArray *)baseArray withConditionalArray:(NSArray *)conditionalArray { @@ -381,22 +432,6 @@ - (NSArray *)concatenateBaseUrlArray:(NSArray *)baseArray withConditionalArray:( return [baseArray arrayByAddingObjectsFromArray:conditionalArray]; } -- (MPAdType)adTypeFromMetadata:(NSDictionary *)metadata -{ - NSString *adTypeString = [metadata objectForKey:kAdTypeMetadataKey]; - - if ([adTypeString isEqualToString:@"interstitial"] || [adTypeString isEqualToString:@"rewarded_video"] || [adTypeString isEqualToString:@"rewarded_playable"]) { - return MPAdTypeInterstitial; - } else if (adTypeString && - [metadata objectForKey:kOrientationTypeMetadataKey]) { - return MPAdTypeInterstitial; - } else if (adTypeString) { - return MPAdTypeBanner; - } else { - return MPAdTypeUnknown; - } -} - - (NSString *)networkTypeFromMetadata:(NSDictionary *)metadata { NSString *adTypeString = [metadata objectForKey:kAdTypeMetadataKey]; @@ -549,8 +584,14 @@ - (NSDictionary *)nativeVideoTrackersFromMetadata:(NSDictionary *)metadata key:( NSMutableDictionary *videoTrackerDict = [NSMutableDictionary new]; NSArray *events = dictFromMetadata[kNativeVideoTrackerEventsMetadataKey]; NSArray *urls = dictFromMetadata[kNativeVideoTrackerUrlsMetadataKey]; - NSSet *supportedEvents = [NSSet setWithObjects:MPVASTTrackingEventTypeStart, MPVASTTrackingEventTypeFirstQuartile, MPVASTTrackingEventTypeMidpoint, MPVASTTrackingEventTypeThirdQuartile, MPVASTTrackingEventTypeComplete, nil]; - for (NSString *event in events) { + NSSet *supportedEvents = [NSSet setWithObjects: + MPVideoEventStart, + MPVideoEventFirstQuartile, + MPVideoEventMidpoint, + MPVideoEventThirdQuartile, + MPVideoEventComplete, + nil]; + for (MPVideoEvent event in events) { if (![supportedEvents containsObject:event]) { continue; } @@ -562,7 +603,7 @@ - (NSDictionary *)nativeVideoTrackersFromMetadata:(NSDictionary *)metadata key:( return videoTrackerDict; } -- (void)setVideoTrackers:(NSMutableDictionary *)videoTrackerDict event:(NSString *)event urls:(NSArray *)urls { +- (void)setVideoTrackers:(NSMutableDictionary *)videoTrackerDict event:(MPVideoEvent)event urls:(NSArray *)urls { NSMutableArray *trackers = [NSMutableArray new]; for (NSString *url in urls) { if ([url rangeOfString:kNativeVideoTrackerUrlMacro].location != NSNotFound) { @@ -591,7 +632,7 @@ - (NSArray *)parseAvailableRewardsFromMetadata:(NSDictionary *)metadata { // This is an error. NSArray * rewards = [currencies objectForKey:@"rewards"]; if (rewards.count == 0) { - MPLogError(@"No available rewards found."); + MPLogDebug(@"No available rewards found."); return nil; } @@ -626,4 +667,15 @@ - (BOOL)visibleImpressionTrackingEnabled return YES; } +- (MPImpressionData *)impressionDataFromMetadata:(NSDictionary *)metadata +{ + NSDictionary * impressionDataDictionary = metadata[kImpressionDataMetadataKey]; + if (impressionDataDictionary == nil) { + return nil; + } + + MPImpressionData * impressionData = [[MPImpressionData alloc] initWithDictionary:impressionDataDictionary]; + return impressionData; +} + @end diff --git a/MoPubSDK/Internal/Common/MPAdDestinationDisplayAgent.h b/MoPubSDK/Internal/Common/MPAdDestinationDisplayAgent.h index 234369b15..25e300993 100644 --- a/MoPubSDK/Internal/Common/MPAdDestinationDisplayAgent.h +++ b/MoPubSDK/Internal/Common/MPAdDestinationDisplayAgent.h @@ -1,7 +1,7 @@ // // MPAdDestinationDisplayAgent.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -10,21 +10,17 @@ #import "MPActivityViewControllerHelper+TweetShare.h" #import "MPURLResolver.h" #import "MPProgressOverlayView.h" -#import "MPAdBrowserController.h" -#import "MPStoreKitProvider.h" #import "MOPUBDisplayAgentType.h" @protocol MPAdDestinationDisplayAgentDelegate; @interface MPAdDestinationDisplayAgent : NSObject @property (nonatomic, weak) id delegate; + (MPAdDestinationDisplayAgent *)agentWithDelegate:(id)delegate; -+ (BOOL)shouldUseSafariViewController; ++ (BOOL)shouldDisplayContentInApp; - (void)displayDestinationForURL:(NSURL *)URL; - (void)cancel; diff --git a/MoPubSDK/Internal/Common/MPAdDestinationDisplayAgent.m b/MoPubSDK/Internal/Common/MPAdDestinationDisplayAgent.m index ebe259752..f3a22d8af 100644 --- a/MoPubSDK/Internal/Common/MPAdDestinationDisplayAgent.m +++ b/MoPubSDK/Internal/Common/MPAdDestinationDisplayAgent.m @@ -1,7 +1,7 @@ // // MPAdDestinationDisplayAgent.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -14,13 +14,15 @@ #import "MPCoreInstanceProvider.h" #import "MPAnalyticsTracker.h" #import "MOPUBExperimentProvider.h" +#import "MoPub+Utility.h" +#import "SKStoreProductViewController+MPAdditions.h" #import static NSString * const kDisplayAgentErrorDomain = @"com.mopub.displayagent"; //////////////////////////////////////////////////////////////////////////////////////////////////// -@interface MPAdDestinationDisplayAgent () +@interface MPAdDestinationDisplayAgent () @property (nonatomic, strong) MPURLResolver *resolver; @property (nonatomic, strong) MPURLResolver *enhancedDeeplinkFallbackResolver; @@ -28,35 +30,23 @@ @interface MPAdDestinationDisplayAgent () @property (nonatomic, assign) BOOL isLoadingDestination; @property (nonatomic) MOPUBDisplayAgentType displayAgentType; @property (nonatomic, strong) SKStoreProductViewController *storeKitController; - -@property (nonatomic, strong) MPAdBrowserController *browserController; @property (nonatomic, strong) SFSafariViewController *safariController; -@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]; - agent.displayAgentType = [MOPUBExperimentProvider displayAgentType]; + agent.displayAgentType = MOPUBExperimentProvider.sharedInstance.displayAgentType; return agent; } @@ -71,9 +61,6 @@ - (void)dealloc // 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]; - - self.browserController.delegate = nil; - } - (void)dismissAllModalContent @@ -81,6 +68,20 @@ - (void)dismissAllModalContent [self.overlayView hide]; } ++ (BOOL)shouldDisplayContentInApp +{ + switch (MOPUBExperimentProvider.sharedInstance.displayAgentType) { + case MOPUBDisplayAgentTypeInApp: +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + case MOPUBDisplayAgentTypeSafariViewController: +#pragma clang diagnostic pop + return YES; + case MOPUBDisplayAgentTypeNativeSafari: + return NO; + } +} + - (void)displayDestinationForURL:(NSURL *)URL { if (self.isLoadingDestination) return; @@ -164,20 +165,21 @@ - (BOOL)handleSuggestedURLAction:(MPURLActionInfo *)actionInfo isResolvingEnhanc - (void)handleEnhancedDeeplinkRequest:(MPEnhancedDeeplinkRequest *)request { - BOOL didOpenSuccessfully = [[UIApplication sharedApplication] openURL:request.primaryURL]; - if (didOpenSuccessfully) { - [self hideOverlay]; - [self.delegate displayAgentWillLeaveApplication]; - [self completeDestinationLoading]; - [[MPAnalyticsTracker sharedTracker] sendTrackingRequestForURLs:request.primaryTrackingURLs]; - } else if (request.fallbackURL) { - [self handleEnhancedDeeplinkFallbackForRequest:request]; - } else { - [self openURLInApplication:request.originalURL]; - } + [MoPub openURL:request.primaryURL options:@{} completion:^(BOOL didOpenURLSuccessfully) { + if (didOpenURLSuccessfully) { + [self hideOverlay]; + [self.delegate displayAgentWillLeaveApplication]; + [self completeDestinationLoading]; + [[MPAnalyticsTracker sharedTracker] sendTrackingRequestForURLs:request.primaryTrackingURLs]; + } else if (request.fallbackURL) { + [self handleEnhancedDeeplinkFallbackForRequest:request]; + } else { + [self openURLInApplication:request.originalURL]; + } + }]; } -- (void)handleEnhancedDeeplinkFallbackForRequest:(MPEnhancedDeeplinkRequest *)request; +- (void)handleEnhancedDeeplinkFallbackForRequest:(MPEnhancedDeeplinkRequest *)request { __weak __typeof__(self) weakSelf = self; [self.enhancedDeeplinkFallbackResolver cancel]; @@ -202,22 +204,18 @@ - (void)handleEnhancedDeeplinkFallbackForRequest:(MPEnhancedDeeplinkRequest *)re - (void)showWebViewWithHTMLString:(NSString *)HTMLString baseURL:(NSURL *)URL actionType:(MPURLActionType)actionType { switch (self.displayAgentType) { case MOPUBDisplayAgentTypeInApp: +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" case MOPUBDisplayAgentTypeSafariViewController: - if ([MPAdDestinationDisplayAgent shouldUseSafariViewController]) { - if (@available(iOS 9.0, *)) { - self.safariController = [[SFSafariViewController alloc] initWithURL:URL]; - self.safariController.delegate = self; - } - } else { - if (actionType == MPURLActionTypeOpenInWebView) { - self.browserController = [[MPAdBrowserController alloc] initWithURL:URL - HTMLString:HTMLString - delegate:self]; - } else { - self.browserController = [[MPAdBrowserController alloc] initWithURL:URL - delegate:self]; - } - } +#pragma clang diagnostic pop + self.safariController = ({ + SFSafariViewController * controller = [[SFSafariViewController alloc] initWithURL:URL]; + controller.delegate = self; + controller.modalTransitionStyle = UIModalTransitionStyleCrossDissolve; + controller.modalPresentationStyle = UIModalPresentationFullScreen; + controller; + }); + [self showAdBrowserController]; break; case MOPUBDisplayAgentTypeNativeSafari: @@ -228,37 +226,31 @@ - (void)showWebViewWithHTMLString:(NSString *)HTMLString baseURL:(NSURL *)URL ac - (void)showAdBrowserController { [self hideOverlay]; - - UIViewController *browserViewController = self.safariController ? self.safariController : self.browserController; - - browserViewController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve; - [[self.delegate viewControllerForPresentingModalView] presentViewController:browserViewController + [[self.delegate viewControllerForPresentingModalView] presentViewController:self.safariController animated:MP_ANIMATED completion:nil]; } -- (void)showStoreKitProductWithParameter:(NSString *)parameter fallbackURL:(NSURL *)URL +- (void)showStoreKitProductWithParameters:(NSDictionary *)parameters fallbackURL:(NSURL *)URL { - if ([MPStoreKitProvider deviceHasStoreKit]) { - [self presentStoreKitControllerWithItemIdentifier:parameter fallbackURL:URL]; - } else { + if (!SKStoreProductViewController.canUseStoreProductViewController) { [self openURLInApplication:URL]; + return; } + + [self presentStoreKitControllerWithProductParameters:parameters fallbackURL: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) { + [MoPub openURL:URL options:@{} completion:^(BOOL didOpenURLSuccessfully) { + if (didOpenURLSuccessfully) { [self.delegate displayAgentWillLeaveApplication]; } [self completeDestinationLoading]; - } + }]; } - (BOOL)openShareURL:(NSURL *)URL @@ -269,28 +261,11 @@ - (BOOL)openShareURL:(NSURL *)URL case MPMoPubShareHostCommandTweet: return [self.activityViewControllerHelper presentActivityViewControllerWithTweetShareURL:URL]; default: - MPLogWarn(@"MPAdDestinationDisplayAgent - unsupported Share URL: %@", [URL absoluteString]); + MPLogInfo(@"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]; @@ -303,20 +278,18 @@ - (void)completeDestinationLoading [self.delegate displayAgentDidDismissModal]; } -- (void)presentStoreKitControllerWithItemIdentifier:(NSString *)identifier fallbackURL:(NSURL *)URL +- (void)presentStoreKitControllerWithProductParameters:(NSDictionary *)parameters fallbackURL:(NSURL *)URL { - self.storeKitController = [MPStoreKitProvider buildController]; + self.storeKitController = [[SKStoreProductViewController alloc] init]; + self.storeKitController.modalPresentationStyle = UIModalPresentationFullScreen; 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]; } -#pragma mark - +#pragma mark - - (void)productViewControllerDidFinish:(SKStoreProductViewController *)viewController { @@ -324,23 +297,6 @@ - (void)productViewControllerDidFinish:(SKStoreProductViewController *)viewContr [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)safariViewControllerDidFinish:(SFSafariViewController *)controller { @@ -364,16 +320,6 @@ - (void)hideModalAndNotifyDelegate }]; } -+ (BOOL)shouldUseSafariViewController -{ - MOPUBDisplayAgentType displayAgentType = [MOPUBExperimentProvider displayAgentType]; - if (@available(iOS 9.0, *)) { - return (displayAgentType == MOPUBDisplayAgentTypeSafariViewController); - } - - return NO; -} - - (void)hideOverlay { [self.overlayView hide]; @@ -404,9 +350,12 @@ - (void)showStoreKitWithAction:(MPURLActionInfo *)actionInfo { switch (self.displayAgentType) { case MOPUBDisplayAgentTypeInApp: +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" case MOPUBDisplayAgentTypeSafariViewController: // It doesn't make sense to open store kit in SafariViewController so storeKitController is used here. - [self showStoreKitProductWithParameter:actionInfo.iTunesItemIdentifier - fallbackURL:actionInfo.iTunesStoreFallbackURL]; +#pragma clang diagnostic pop + [self showStoreKitProductWithParameters:actionInfo.iTunesStoreParameters + fallbackURL:actionInfo.iTunesStoreFallbackURL]; break; case MOPUBDisplayAgentTypeNativeSafari: [self openURLInApplication:actionInfo.iTunesStoreFallbackURL]; diff --git a/MoPubSDK/Internal/Common/MPAdImpressionTimer.h b/MoPubSDK/Internal/Common/MPAdImpressionTimer.h index 6861d6ff0..901ca0f0e 100644 --- a/MoPubSDK/Internal/Common/MPAdImpressionTimer.h +++ b/MoPubSDK/Internal/Common/MPAdImpressionTimer.h @@ -1,7 +1,7 @@ // // MPAdImpressionTimer.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/Internal/Common/MPAdImpressionTimer.m b/MoPubSDK/Internal/Common/MPAdImpressionTimer.m index f004fbe72..9af007c27 100644 --- a/MoPubSDK/Internal/Common/MPAdImpressionTimer.m +++ b/MoPubSDK/Internal/Common/MPAdImpressionTimer.m @@ -1,7 +1,7 @@ // // MPAdImpressionTimer.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -31,8 +31,11 @@ @implementation MPAdImpressionTimer - (instancetype)initWithRequiredSecondsForImpression:(NSTimeInterval)requiredSecondsForImpression requiredViewVisibilityPixels:(CGFloat)visibilityPixels { if (self = [super init]) { - _viewVisibilityTimer = [MPTimer timerWithTimeInterval:kImpressionTimerInterval target:self selector:@selector(tick:) repeats:YES]; - _viewVisibilityTimer.runLoopMode = NSRunLoopCommonModes; + _viewVisibilityTimer = [MPTimer timerWithTimeInterval:kImpressionTimerInterval + target:self + selector:@selector(tick:) + repeats:YES + runLoopMode:NSRunLoopCommonModes]; _requiredSecondsForImpression = requiredSecondsForImpression; _pixelsRequiredForViewVisibility = visibilityPixels; _firstVisibilityTimestamp = kFirstVisibilityTimestampNone; @@ -47,8 +50,11 @@ - (instancetype)initWithRequiredSecondsForImpression:(NSTimeInterval)requiredSec // Set `pixelsRequiredForViewVisibility` to a default invalid value so that we know to use the percent directly instead. _pixelsRequiredForViewVisibility = kDefaultPixelCountWhenUsingPercentage; - _viewVisibilityTimer = [MPTimer timerWithTimeInterval:kImpressionTimerInterval target:self selector:@selector(tick:) repeats:YES]; - _viewVisibilityTimer.runLoopMode = NSRunLoopCommonModes; + _viewVisibilityTimer = [MPTimer timerWithTimeInterval:kImpressionTimerInterval + target:self + selector:@selector(tick:) + repeats:YES + runLoopMode:NSRunLoopCommonModes]; _requiredSecondsForImpression = requiredSecondsForImpression; _percentageRequiredForViewVisibility = visibilityPercentage; _firstVisibilityTimestamp = kFirstVisibilityTimestampNone; @@ -67,12 +73,12 @@ - (void)dealloc - (void)startTrackingView:(UIView *)view { if (!view) { - MPLogError(@"Cannot track empty view"); + MPLogInfo(@"Cannot track empty view"); return; } - if (self.viewVisibilityTimer.isScheduled) { - MPLogWarn(@"viewVisibilityTimer is already started."); + if (self.viewVisibilityTimer.isCountdownActive) { + MPLogInfo(@"viewVisibilityTimer is already started."); return; } @@ -86,7 +92,7 @@ - (void)tick:(MPTimer *)timer { CGFloat adViewArea = CGRectGetWidth(self.adView.bounds) * CGRectGetHeight(self.adView.bounds); if (adViewArea == 0) { - MPLogError(@"ad view area cannot be 0"); + MPLogInfo(@"ad view area cannot be 0"); return; } diff --git a/MoPubSDK/Internal/Common/MPAdServerCommunicator.h b/MoPubSDK/Internal/Common/MPAdServerCommunicator.h index 63f1950f2..bfaba1ceb 100644 --- a/MoPubSDK/Internal/Common/MPAdServerCommunicator.h +++ b/MoPubSDK/Internal/Common/MPAdServerCommunicator.h @@ -1,7 +1,7 @@ // // MPAdServerCommunicator.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -40,4 +40,6 @@ - (void)communicatorDidReceiveAdConfigurations:(NSArray *)configurations; - (void)communicatorDidFailWithError:(NSError *)error; +- (MPAdType)adTypeForAdServerCommunicator:(MPAdServerCommunicator *)adServerCommunicator; +- (NSString *)adUnitIDForAdServerCommunicator:(MPAdServerCommunicator *)adServerCommunicator; @end diff --git a/MoPubSDK/Internal/Common/MPAdServerCommunicator.m b/MoPubSDK/Internal/Common/MPAdServerCommunicator.m index b9d39f04a..0491b9d36 100644 --- a/MoPubSDK/Internal/Common/MPAdServerCommunicator.m +++ b/MoPubSDK/Internal/Common/MPAdServerCommunicator.m @@ -1,7 +1,7 @@ // // MPAdServerCommunicator.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -17,6 +17,7 @@ #import "MPError.h" #import "MPHTTPNetworkSession.h" #import "MPLogging.h" +#import "MPRateLimitManager.h" #import "MPURLRequest.h" // Multiple response JSON fields @@ -33,6 +34,8 @@ @interface MPAdServerCommunicator () @property (nonatomic, strong) NSDictionary *responseHeaders; @property (nonatomic) NSArray *topLevelJsonKeys; +@property (nonatomic, readonly) BOOL isRateLimited; + @end @interface MPAdServerCommunicator (Consent) @@ -61,7 +64,7 @@ - (id)initWithDelegate:(id)delegate self = [super init]; if (self) { _delegate = delegate; - _topLevelJsonKeys = @[kNextUrlMetadataKey]; + _topLevelJsonKeys = @[kNextUrlMetadataKey, kFormatMetadataKey]; } return self; } @@ -75,6 +78,11 @@ - (void)dealloc - (void)loadURL:(NSURL *)URL { + if (self.isRateLimited) { + [self didFailWithError:[NSError tooManyRequests]]; + return; + } + [self cancel]; // Delete any cookies previous creatives have set before starting the load @@ -88,22 +96,15 @@ - (void)loadURL:(NSURL *)URL // Generate request MPURLRequest * request = [[MPURLRequest alloc] initWithURL:URL]; - MPLogInfo(@"Loading ad with MoPub server URL: %@", request); + MPLogEvent([MPLogEvent adRequestedWithRequest:request]); __weak __typeof__(self) weakSelf = self; self.task = [MPHTTPNetworkSession startTaskWithHttpRequest:request responseHandler:^(NSData * data, NSHTTPURLResponse * response) { // Capture strong self for the duration of this block. __typeof__(self) strongSelf = weakSelf; - // Status code indicates an error. - if (response.statusCode >= 400) { - [strongSelf didFailWithError:[strongSelf errorForStatusCode:response.statusCode]]; - return; - } - // Handle the response. [strongSelf didFinishLoadingWithData:data]; - } errorHandler:^(NSError * error) { // Capture strong self for the duration of this block. __typeof__(self) strongSelf = weakSelf; @@ -127,9 +128,9 @@ - (void)sendBeforeLoadUrlWithConfiguration:(MPAdConfiguration *)configuration if (configuration.beforeLoadURL != nil) { MPURLRequest * request = [MPURLRequest requestWithURL:configuration.beforeLoadURL]; [MPHTTPNetworkSession startTaskWithHttpRequest:request responseHandler:^(NSData * _Nonnull data, NSHTTPURLResponse * _Nonnull response) { - MPLogTrace(@"Sucessfully sent before load URL"); + MPLogDebug(@"Successfully sent before load URL"); } errorHandler:^(NSError * _Nonnull error) { - MPLogWarn(@"Failed to send before load URL"); + MPLogInfo(@"Failed to send before load URL"); }]; } } @@ -143,20 +144,20 @@ - (void)sendAfterLoadUrlWithConfiguration:(MPAdConfiguration *)configuration for (NSURL * afterLoadUrl in afterLoadUrls) { MPURLRequest * request = [MPURLRequest requestWithURL:afterLoadUrl]; [MPHTTPNetworkSession startTaskWithHttpRequest:request responseHandler:^(NSData * _Nonnull data, NSHTTPURLResponse * _Nonnull response) { - // no-op + MPLogDebug(@"Successfully sent after load URL: %@", afterLoadUrl); } errorHandler:^(NSError * _Nonnull error) { MPLogDebug(@"Failed to send after load URL: %@", afterLoadUrl); }]; } } -- (void)failLoadForSDKInit { - NSString * errorString = @"Ad prevented from loading. Error: Ad requested before initializing MoPub SDK. The MoPub SDK requires initializeSdkWithConfiguration:completion: to be called on MoPub.sharedInstance before attempting to load ads. Please update your integration."; - MPLogError(errorString); +- (BOOL)isRateLimited { + return [[MPRateLimitManager sharedInstance] isRateLimitedForAdUnitId:[self.delegate adUnitIDForAdServerCommunicator:self]]; +} - NSError *error = [NSError errorWithDomain:kMOPUBErrorDomain - code:MOPUBErrorSDKNotInitialized - userInfo:@{ NSLocalizedDescriptionKey : errorString }]; +- (void)failLoadForSDKInit { + NSError *error = [NSError adLoadFailedBecauseSdkNotInitialized]; + MPLogEvent([MPLogEvent error:error message:nil]); [self didFailWithError:error]; } @@ -169,6 +170,13 @@ - (void)didFailWithError:(NSError *)error { } - (void)didFinishLoadingWithData:(NSData *)data { + // In the event that the @c adUnitIdUsedForConsent from @c MPConsentManager is @c nil or malformed, + // we should populate it with this known good adunit ID. This is to cover any edge case where the + // publisher manages to initialize with no adunit ID or a malformed adunit ID. + // It is known good since this is the success callback from the ad request. + NSString * adunitID = [self.delegate adUnitIDForAdServerCommunicator:self]; + [MPConsentManager.sharedManager setAdUnitIdUsedForConsent:adunitID isKnownGood:YES]; + // Headers from the original HTTP response are intentionally ignored as laid out // by the Client Side Waterfall design doc. // @@ -190,25 +198,28 @@ - (void)didFinishLoadingWithData:(NSData *)data { NSError * error = nil; NSDictionary * json = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error]; if (error) { - MPLogError(@"Failed to parse ad response JSON: %@", error.localizedDescription); - self.loading = NO; - [self.delegate communicatorDidFailWithError:error]; + NSError * parseError = [NSError adResponseFailedToParseWithError:error]; + MPLogEvent([MPLogEvent error:parseError message:nil]); + [self didFailWithError:parseError]; return; } - // Handle consent overrides and strip them out of the top level JSON response. - json = [self handleConsentOverrides:json]; + MPLogEvent([MPLogEvent adRequestReceivedResponse:json]); + + // Handle ad server overrides and strip them out of the top level JSON response. + json = [self handleAdResponseOverrides:json]; + // Add top level json attributes to each ad server response so MPAdConfiguration contains + // all attributes for an ad response. NSArray *responses = [self getFlattenJsonResponses:json keys:self.topLevelJsonKeys]; if (responses == nil) { - MPLogError(@"No ad responses"); - self.loading = NO; - [self.delegate communicatorDidFailWithError:[MOPUBError errorWithCode:MOPUBErrorUnableToParseJSONAdResponse]]; + NSError * noResponsesError = [NSError adResponsesNotFound]; + MPLogEvent([MPLogEvent error:noResponsesError message:nil]); + [self didFailWithError:noResponsesError]; return; } - MPLogInfo(@"There are %ld ad responses", responses.count); - + // Attempt to parse each ad response JSON into its corresponding MPAdConfiguration object. NSMutableArray * configurations = [NSMutableArray arrayWithCapacity:responses.count]; for (NSDictionary * responseJson in responses) { // The `metadata` field is required and must contain at least one entry. The `content` field is optional. @@ -216,19 +227,25 @@ - (void)didFinishLoadingWithData:(NSData *)data { NSDictionary * metadata = responseJson[kAdResonsesMetadataKey]; NSData * content = [responseJson[kAdResonsesContentKey] dataUsingEncoding:NSUTF8StringEncoding]; if (metadata == nil || (metadata != nil && metadata.count == 0)) { - MPLogError(@"The metadata field is either non-existent or empty"); + MPLogInfo(@"The metadata field is either non-existent or empty"); continue; } - MPAdConfiguration * configuration = [[MPAdConfiguration alloc] initWithMetadata:metadata data:content]; + MPAdConfiguration * configuration = [[MPAdConfiguration alloc] initWithMetadata:metadata data:content adType:[self.delegate adTypeForAdServerCommunicator:self]]; if (configuration != nil) { [configurations addObject:configuration]; - } - else { + } else { MPLogInfo(@"Failed to generate configuration from\nmetadata:\n%@\nbody:\n%@", metadata, responseJson[kAdResonsesContentKey]); } } + // Set up rate limiting (has no effect if backoffMs is 0) + NSInteger backoffMs = [json[kBackoffMsKey] integerValue]; + NSString * backoffReason = json[kBackoffReasonKey]; + [[MPRateLimitManager sharedInstance] setRateLimitTimerWithAdUnitId:[self.delegate adUnitIDForAdServerCommunicator:self] + milliseconds:backoffMs + reason:backoffReason]; + self.loading = NO; [self.delegate communicatorDidReceiveAdConfigurations:configurations]; } @@ -245,8 +262,9 @@ - (NSArray *)getFlattenJsonResponses:(NSDictionary *)json keys:(NSArray *)keys NSMutableArray *flattenResponses = [NSMutableArray new]; for (NSDictionary *response in responses) { NSMutableDictionary *flattenResponse = [response mutableCopy]; + flattenResponse[kAdResonsesMetadataKey] = [response[kAdResonsesMetadataKey] mutableCopy]; + for (NSString *key in keys) { - flattenResponse[kAdResonsesMetadataKey] = [response[kAdResonsesMetadataKey] mutableCopy]; flattenResponse[kAdResonsesMetadataKey][key] = json[key]; } [flattenResponses addObject:flattenResponse]; @@ -254,6 +272,29 @@ - (NSArray *)getFlattenJsonResponses:(NSDictionary *)json keys:(NSArray *)keys return flattenResponses; } +// Process any top level json attributes that trigger state changes within the SDK. +/** + Handles all server-side overrides, and strips them out of the response JSON + so that they are not propagated to the rest of the responses. + @param serverResponseJson Top-level JSON response from the server + @return Top-level JSON response stripped of all override fields + */ +- (NSDictionary *)handleAdResponseOverrides:(NSDictionary *)serverResponseJson { + // Handle Consent + NSMutableDictionary * json = [[self handleConsentOverrides:serverResponseJson] mutableCopy]; + + // Handle the enabling of debug logging. + NSNumber * debugLoggingEnabled = json[kEnableDebugLogging]; + if (debugLoggingEnabled != nil && [debugLoggingEnabled boolValue]) { + MPLogInfo(@"Debug logging enabled"); + MPLogging.consoleLogLevel = MPBLogLevelDebug; + + json[kEnableDebugLogging] = nil; + } + + return json; +} + #pragma mark - Internal - (NSError *)errorForStatusCode:(NSInteger)statusCode diff --git a/MoPubSDK/Internal/Common/MPAdServerURLBuilder.h b/MoPubSDK/Internal/Common/MPAdServerURLBuilder.h index 1275f002e..f90e3cb4a 100644 --- a/MoPubSDK/Internal/Common/MPAdServerURLBuilder.h +++ b/MoPubSDK/Internal/Common/MPAdServerURLBuilder.h @@ -1,18 +1,25 @@ // // MPAdServerURLBuilder.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // #import +#import "MPAdTargeting.h" +#import "MPEngineInfo.h" #import "MPURL.h" @class CLLocation; @interface MPAdServerURLBuilder : NSObject +/** + Stores the information of the engine used to render the MoPub SDK. + */ +@property (class, nonatomic, strong) MPEngineInfo * engineInformation; + /** * Returns an NSURL object given an endpoint and a dictionary of query parameters/values */ @@ -23,21 +30,15 @@ @interface MPAdServerURLBuilder (Ad) + (MPURL *)URLWithAdUnitID:(NSString *)adUnitID - keywords:(NSString *)keywords - userDataKeywords:(NSString *)userDataKeywords - location:(CLLocation *)location; + targeting:(MPAdTargeting *)targeting; + (MPURL *)URLWithAdUnitID:(NSString *)adUnitID - keywords:(NSString *)keywords - userDataKeywords:(NSString *)userDataKeywords - location:(CLLocation *)location + targeting:(MPAdTargeting *)targeting desiredAssets:(NSArray *)assets viewability:(BOOL)viewability; + (MPURL *)URLWithAdUnitID:(NSString *)adUnitID - keywords:(NSString *)keywords - userDataKeywords:(NSString *)userDataKeywords - location:(CLLocation *)location + targeting:(MPAdTargeting *)targeting desiredAssets:(NSArray *)assets adSequence:(NSInteger)adSequence viewability:(BOOL)viewability; diff --git a/MoPubSDK/Internal/Common/MPAdServerURLBuilder.m b/MoPubSDK/Internal/Common/MPAdServerURLBuilder.m index 1d9c25384..f96f8c3b1 100644 --- a/MoPubSDK/Internal/Common/MPAdServerURLBuilder.m +++ b/MoPubSDK/Internal/Common/MPAdServerURLBuilder.m @@ -1,7 +1,7 @@ // // MPAdServerURLBuilder.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -10,19 +10,22 @@ #import -#import "MPAdvancedBiddingManager.h" #import "MPAdServerKeys.h" +#import "MPAPIEndpoints.h" +#import "MPConsentManager.h" #import "MPConstants.h" +#import "MPCoreInstanceProvider+MRAID.h" +#import "MPError.h" #import "MPGeolocationProvider.h" #import "MPGlobal.h" #import "MPIdentityProvider.h" -#import "MPCoreInstanceProvider+MRAID.h" +#import "MPLogging.h" +#import "MPMediationManager.h" +#import "MPRateLimitManager.h" #import "MPReachabilityManager.h" -#import "MPAPIEndpoints.h" #import "MPViewabilityTracker.h" #import "NSString+MPAdditions.h" #import "NSString+MPConsentStatus.h" -#import "MPConsentManager.h" static NSString * const kMoPubInterfaceOrientationPortrait = @"p"; static NSString * const kMoPubInterfaceOrientationLandscape = @"l"; @@ -60,6 +63,20 @@ + (NSMutableDictionary *)baseParametersDictionaryWithIDFAUsingIDFAForConsent:(BO @implementation MPAdServerURLBuilder +#pragma mark - Static Properties + +static MPEngineInfo * _engineInfo = nil; + ++ (MPEngineInfo *)engineInformation { + return _engineInfo; +} + ++ (void)setEngineInformation:(MPEngineInfo *)engineInformation { + _engineInfo = engineInformation; +} + +#pragma mark - URL Building + + (MPURL *)URLWithEndpointPath:(NSString *)endpointPath postData:(NSDictionary *)parameters { // Build the full URL string NSURLComponents * components = [MPAPIEndpoints baseURLComponentsWithPath:endpointPath]; @@ -79,6 +96,10 @@ + (NSMutableDictionary *)baseParametersDictionaryWithIDParameter:(NSString *)idP // REQUIRED: SDK Version queryParameters[kSDKVersionKey] = MP_SDK_VERSION; + // REQUIRED: SDK Engine Information + queryParameters[kSDKEngineNameKey] = [self engineNameValue]; + queryParameters[kSDKEngineVersionKey] = [self engineVersionValue]; + // REQUIRED: Application Version queryParameters[kApplicationVersionKey] = [self applicationVersion]; @@ -97,6 +118,11 @@ + (NSMutableDictionary *)baseParametersDictionaryWithIDParameter:(NSString *)idP queryParameters[kDoNotTrackIdKey] = [MPIdentityProvider advertisingTrackingEnabled] ? nil : @"1"; queryParameters[kBundleKey] = [[NSBundle mainBundle] bundleIdentifier]; + // REQUIRED: MoPub ID + // After user consented IDFA access, UDID uses IDFA and thus different from MoPub ID. + // Otherwise, UDID is the same as MoPub ID. + queryParameters[kMoPubIDKey] = [MPIdentityProvider unobfuscatedMoPubIdentifier]; + // OPTIONAL: Consented versions queryParameters[kConsentedPrivacyPolicyVersionKey] = manager.consentedPrivacyPolicyVersion; queryParameters[kConsentedVendorListVersionKey] = manager.consentedVendorListVersion; @@ -129,56 +155,47 @@ + (NSString *)applicationVersion { return gApplicationVersion; } ++ (NSString *)engineNameValue { + return self.engineInformation.name; +} + ++ (NSString *)engineVersionValue { + return self.engineInformation.version; +} + @end @implementation MPAdServerURLBuilder (Ad) + (MPURL *)URLWithAdUnitID:(NSString *)adUnitID - keywords:(NSString *)keywords - userDataKeywords:(NSString *)userDataKeywords - location:(CLLocation *)location + targeting:(MPAdTargeting *)targeting { return [self URLWithAdUnitID:adUnitID - keywords:keywords - userDataKeywords:userDataKeywords - location:location + targeting:targeting desiredAssets:nil viewability:YES]; } + (MPURL *)URLWithAdUnitID:(NSString *)adUnitID - keywords:(NSString *)keywords - userDataKeywords:(NSString *)userDataKeywords - location:(CLLocation *)location + targeting:(MPAdTargeting *)targeting desiredAssets:(NSArray *)assets viewability:(BOOL)viewability { return [self URLWithAdUnitID:adUnitID - keywords:keywords - userDataKeywords:userDataKeywords - location:location + targeting:targeting desiredAssets:assets adSequence:kAdSequenceNone viewability:viewability]; } + (MPURL *)URLWithAdUnitID:(NSString *)adUnitID - keywords:(NSString *)keywords - userDataKeywords:(NSString *)userDataKeywords - location:(CLLocation *)location + targeting:(MPAdTargeting *)targeting desiredAssets:(NSArray *)assets adSequence:(NSInteger)adSequence viewability:(BOOL)viewability { - // In the event that the `adUnitIdUsedForConsent` from `MPConsentManager` is still `nil`, - // we should populate it with this `adUnitId`. This is to cover the edge case where the - // publisher does not explcitily initialize the SDK via `initializeSdkWithConfiguration:completion:`. - if (adUnitID != nil && MPConsentManager.sharedManager.adUnitIdUsedForConsent == nil) { - MPConsentManager.sharedManager.adUnitIdUsedForConsent = adUnitID; - } - NSMutableDictionary * queryParams = [self baseParametersDictionaryWithIDFAUsingIDFAForConsent:NO withIDParameter:adUnitID]; @@ -196,21 +213,26 @@ + (MPURL *)URLWithAdUnitID:(NSString *)adUnitID queryParams[kAdSequenceKey] = [self adSequenceValue:adSequence]; queryParams[kScreenResolutionWidthKey] = [self physicalScreenResolutionWidthValue]; queryParams[kScreenResolutionHeightKey] = [self physicalScreenResolutionHeightValue]; + queryParams[kCreativeSafeWidthKey] = [self creativeSafeWidthValue:targeting.creativeSafeSize]; + queryParams[kCreativeSafeHeightKey] = [self creativeSafeHeightValue:targeting.creativeSafeSize]; queryParams[kAppTransportSecurityStatusKey] = [self appTransportSecurityStatusValue]; - queryParams[kKeywordsKey] = [self keywordsValue:keywords]; - queryParams[kUserDataKeywordsKey] = [self userDataKeywordsValue:userDataKeywords]; + queryParams[kKeywordsKey] = [self keywordsValue:targeting.keywords]; + queryParams[kUserDataKeywordsKey] = [self userDataKeywordsValue:targeting.userDataKeywords]; queryParams[kViewabilityStatusKey] = [self viewabilityStatusValue:viewability]; queryParams[kAdvancedBiddingKey] = [self advancedBiddingValue]; - [queryParams addEntriesFromDictionary:[self locationInformationDictionary:location]]; + queryParams[kBackoffMsKey] = [self backoffMillisecondsValueForAdUnitID:adUnitID]; + queryParams[kBackoffReasonKey] = [[MPRateLimitManager sharedInstance] lastRateLimitReasonForAdUnitId:adUnitID]; + [queryParams addEntriesFromDictionary:[self locationInformationDictionary:targeting.location]]; return [self URLWithEndpointPath:MOPUB_API_PATH_AD_REQUEST postData:queryParams]; } + (NSString *)orientationValue { - UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation; - return UIInterfaceOrientationIsPortrait(orientation) ? - kMoPubInterfaceOrientationPortrait : kMoPubInterfaceOrientationLandscape; + // Starting with iOS8, the orientation of the device is taken into account when + // requesting the key window's bounds. + CGRect appBounds = [UIApplication sharedApplication].keyWindow.bounds; + return appBounds.size.width > appBounds.size.height ? kMoPubInterfaceOrientationLandscape : kMoPubInterfaceOrientationPortrait; } + (NSString *)scaleFactorValue @@ -294,6 +316,18 @@ + (NSString *)physicalScreenResolutionHeightValue return [NSString stringWithFormat:@"%.0f", MPScreenResolution().height]; } ++ (NSString *)creativeSafeWidthValue:(CGSize)creativeSafeSize +{ + CGFloat scale = MPDeviceScaleFactor(); + return [NSString stringWithFormat:@"%.0f", creativeSafeSize.width * scale]; +} + ++ (NSString *)creativeSafeHeightValue:(CGSize)creativeSafeSize +{ + CGFloat scale = MPDeviceScaleFactor(); + return [NSString stringWithFormat:@"%.0f", creativeSafeSize.height * scale]; +} + + (NSString *)appTransportSecurityStatusValue { return [NSString stringWithFormat:@"%@", @([[MPCoreInstanceProvider sharedProvider] appTransportSecuritySettings])]; @@ -323,22 +357,36 @@ + (NSString *)viewabilityStatusValue:(BOOL)isViewabilityEnabled { } + (NSString *)advancedBiddingValue { - // Opted out of advanced bidding, no query parameter should be sent. - if (![MPAdvancedBiddingManager sharedManager].advancedBiddingEnabled) { + // Retrieve the tokens + NSDictionary * tokens = MPMediationManager.sharedManager.advancedBiddingTokens; + if (tokens == nil) { return nil; } - // No JSON at this point means that no advanced bidders were initialized. - NSString * tokens = MPAdvancedBiddingManager.sharedManager.bidderTokensJson; - if (tokens == nil) { + // Serialize the JSON dictionary into a JSON string. + NSError * error = nil; + NSData * jsonData = [NSJSONSerialization dataWithJSONObject:tokens options:0 error:&error]; + if (jsonData == nil) { + NSError * jsonError = [NSError serializationOfJson:tokens failedWithError:error]; + MPLogEvent([MPLogEvent error:jsonError message:nil]); return nil; } - return tokens; + return [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; +} + ++ (NSString *)backoffMillisecondsValueForAdUnitID:(NSString *)adUnitID { + NSUInteger lastRateLimitWaitTimeMilliseconds = [[MPRateLimitManager sharedInstance] lastRateLimitMillisecondsForAdUnitId:adUnitID]; + return lastRateLimitWaitTimeMilliseconds > 0 ? [NSString stringWithFormat:@"%@", @(lastRateLimitWaitTimeMilliseconds)] : nil; +} + ++ (NSDictionary *)adapterInformation { + return MPMediationManager.sharedManager.adRequestPayload; } + (NSDictionary *)locationInformationDictionary:(CLLocation *)location { - if (![MPConsentManager.sharedManager canCollectPersonalInfo] || !location) { + // Not allowed to collect location because it is PII + if (![MPConsentManager.sharedManager canCollectPersonalInfo]) { return @{}; } @@ -347,6 +395,7 @@ + (NSDictionary *)locationInformationDictionary:(CLLocation *)location { CLLocation *bestLocation = location; CLLocation *locationFromProvider = [[[MPCoreInstanceProvider sharedProvider] sharedMPGeolocationProvider] lastKnownLocation]; + // Location determined by CoreLocation is given priority over the Publisher-specified location. if (locationFromProvider) { bestLocation = locationFromProvider; } @@ -374,7 +423,7 @@ + (NSDictionary *)locationInformationDictionary:(CLLocation *)location { @implementation MPAdServerURLBuilder (Open) -+ (NSURL *)conversionTrackingURLForAppID:(NSString *)appID { ++ (MPURL *)conversionTrackingURLForAppID:(NSString *)appID { return [self openEndpointURLWithIDParameter:appID isSessionTracking:NO]; } diff --git a/MoPubSDK/Internal/Common/MPClosableView.h b/MoPubSDK/Internal/Common/MPClosableView.h index 37cff36d3..e440c7700 100644 --- a/MoPubSDK/Internal/Common/MPClosableView.h +++ b/MoPubSDK/Internal/Common/MPClosableView.h @@ -1,13 +1,15 @@ // // MPClosableView.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // #import +extern const CGSize kCloseRegionSize; + @class MPWebView; enum { diff --git a/MoPubSDK/Internal/Common/MPClosableView.m b/MoPubSDK/Internal/Common/MPClosableView.m index da4b83b20..7f9c643d8 100644 --- a/MoPubSDK/Internal/Common/MPClosableView.m +++ b/MoPubSDK/Internal/Common/MPClosableView.m @@ -1,7 +1,7 @@ // // MPClosableView.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -11,40 +11,49 @@ #import "MPUserInteractionGestureRecognizer.h" #import "MPWebView.h" -static CGFloat kCloseRegionWidth = 50.0f; -static CGFloat kCloseRegionHeight = 50.0f; +/** + Per MRAID spec https://www.iab.com/wp-content/uploads/2015/08/IAB_MRAID_v2_FINAL.pdf, page 31, the + close event region should be 50x50 expandable and interstitial ads. On page 34, the 50x50 size applies + to resized ads as well. + */ +const CGSize kCloseRegionSize = {.width = 50, .height = 50}; + static NSString *const kExpandableCloseButtonImageName = @"MPCloseButtonX.png"; -CGRect MPClosableViewCustomCloseButtonFrame(CGSize size, MPClosableViewCloseButtonLocation closeButtonLocation) +/** + Provided the ad size and close button location, returns the frame of the close button. + Note: The provided ad size is assumed to be at least 50x50, otherwise the return value is undefined. + @param adSize The size of the ad + @param closeButtonLocation The location of the close button + */ +CGRect MPClosableViewCustomCloseButtonFrame(CGSize adSize, MPClosableViewCloseButtonLocation closeButtonLocation) { - CGFloat width = size.width; - CGFloat height = size.height; - CGRect closeButtonFrame = CGRectMake(0.0f, 0.0f, kCloseRegionWidth, kCloseRegionHeight); + CGRect closeButtonFrame = CGRectMake(0.0f, 0.0f, kCloseRegionSize.width, kCloseRegionSize.height); switch (closeButtonLocation) { case MPClosableViewCloseButtonLocationTopRight: - closeButtonFrame.origin = CGPointMake(width-kCloseRegionWidth, 0.0f); + closeButtonFrame.origin = CGPointMake(adSize.width-kCloseRegionSize.width, 0.0f); break; case MPClosableViewCloseButtonLocationTopLeft: closeButtonFrame.origin = CGPointMake(0.0f, 0.0f); break; case MPClosableViewCloseButtonLocationTopCenter: - closeButtonFrame.origin = CGPointMake((width-kCloseRegionWidth) / 2.0f, 0.0f); + closeButtonFrame.origin = CGPointMake((adSize.width-kCloseRegionSize.width) / 2.0f, 0.0f); break; case MPClosableViewCloseButtonLocationBottomRight: - closeButtonFrame.origin = CGPointMake(width-kCloseRegionWidth, height-kCloseRegionHeight); + closeButtonFrame.origin = CGPointMake(adSize.width-kCloseRegionSize.width, adSize.height-kCloseRegionSize.height); break; case MPClosableViewCloseButtonLocationBottomLeft: - closeButtonFrame.origin = CGPointMake(0.0f, height-kCloseRegionHeight); + closeButtonFrame.origin = CGPointMake(0.0f, adSize.height-kCloseRegionSize.height); break; case MPClosableViewCloseButtonLocationBottomCenter: - closeButtonFrame.origin = CGPointMake((width-kCloseRegionWidth) / 2.0f, height-kCloseRegionHeight); + closeButtonFrame.origin = CGPointMake((adSize.width-kCloseRegionSize.width) / 2.0f, adSize.height-kCloseRegionSize.height); break; case MPClosableViewCloseButtonLocationCenter: - closeButtonFrame.origin = CGPointMake((width-kCloseRegionWidth) / 2.0f, (height-kCloseRegionHeight) / 2.0f); + closeButtonFrame.origin = CGPointMake((adSize.width-kCloseRegionSize.width) / 2.0f, (adSize.height-kCloseRegionSize.height) / 2.0f); break; - default: - closeButtonFrame.origin = CGPointMake(width-kCloseRegionWidth, 0.0f); + default: // top right + closeButtonFrame.origin = CGPointMake(adSize.width-kCloseRegionSize.width, 0.0f); break; } @@ -110,12 +119,13 @@ - (void)dealloc - (void)layoutSubviews { + [super layoutSubviews]; if (@available(iOS 11, *)) { self.closeButton.translatesAutoresizingMaskIntoConstraints = NO; NSMutableArray *constraints = [NSMutableArray arrayWithObjects: - [self.closeButton.widthAnchor constraintEqualToConstant:kCloseRegionWidth], - [self.closeButton.heightAnchor constraintEqualToConstant:kCloseRegionHeight], + [self.closeButton.widthAnchor constraintEqualToConstant:kCloseRegionSize.width], + [self.closeButton.heightAnchor constraintEqualToConstant:kCloseRegionSize.height], nil]; switch (self.closeButtonLocation) { @@ -207,7 +217,7 @@ - (void)handleInteraction:(UIGestureRecognizer *)gestureRecognizer #pragma mark - -- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer; +- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer { return YES; } diff --git a/MoPubSDK/Internal/Common/MPCountdownTimerView.h b/MoPubSDK/Internal/Common/MPCountdownTimerView.h index 2003465fe..68cd6e831 100644 --- a/MoPubSDK/Internal/Common/MPCountdownTimerView.h +++ b/MoPubSDK/Internal/Common/MPCountdownTimerView.h @@ -1,59 +1,46 @@ // // MPCountdownTimerView.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // #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; +NS_ASSUME_NONNULL_BEGIN /** - * Flag indicating if the timer is currently paused. + * A view that will display a countdown timer and invoke a completion block once the timer has elapsed. + * After the countdown starts, the countdown is paused automatically when the app becomes inactive, + * and the countdown resumes when the app becomes active again (by listening to @c UIApplicationWillResignActiveNotification + * and @c UIApplicationDidBecomeActiveNotification notifications). This view has an intrinsic size, so + * do not add width and height constaints to it. This is a square view. */ -@property (nonatomic, readonly) BOOL isPaused; +@interface MPCountdownTimerView : UIView /** * 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. + * @param completion Completion block that is fired after the timer elapses or is stopped. * @returns An initialized timer if successful; otherwise nil. */ -- (instancetype)initWithFrame:(CGRect)frame duration:(NSTimeInterval)seconds; +- (instancetype)initWithDuration:(NSTimeInterval)seconds timerCompletion:(void(^)(BOOL hasElapsed))completion; /** * 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; +- (void)start; /** - * Stops the timer and optionally invokes the completion block from `startWithTimerCompletion:`. - * If the timer hasn't started, calling this method will do nothing. + * Stops the timer and optionally invokes the completion block. If the timer hasn't started, calling + * this method will do nothing. + * + * @param shouldSignalCompletion If YES, then invoke the completion. Do not invoke the completion otherwise. */ - (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 + +NS_ASSUME_NONNULL_END diff --git a/MoPubSDK/Internal/Common/MPCountdownTimerView.m b/MoPubSDK/Internal/Common/MPCountdownTimerView.m index 9b82d9d4b..bf7f43476 100644 --- a/MoPubSDK/Internal/Common/MPCountdownTimerView.m +++ b/MoPubSDK/Internal/Common/MPCountdownTimerView.m @@ -1,7 +1,7 @@ // // MPCountdownTimerView.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -9,50 +9,85 @@ #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; +static NSTimeInterval const kCountdownTimerInterval = 0.05; // internal timer firing frequency in seconds +static CGFloat const kTimerStartAngle = -M_PI * 0.5; // 12 o'clock position +static CGFloat const kOneCycle = M_PI * 2; +static CGFloat const kRingWidth = 3; +static CGFloat const kRingRadius = 16; +static CGFloat const kRingPadding = 8; +static NSString * const kAnimationKey = @"Timer"; -@interface MPCountdownTimerView() -@property (nonatomic, assign, readwrite) BOOL isPaused; +@interface MPCountdownTimerView() @property (nonatomic, copy) void(^completionBlock)(BOOL); -@property (nonatomic, assign) NSTimeInterval currentSeconds; -@property (nonatomic, strong) MPTimer * timer; -@property (nonatomic, strong) UIWebView * timerView; +@property (nonatomic, assign) NSTimeInterval remainingSeconds; +@property (nonatomic, strong) MPTimer * timer; // timer instantiation is deferred to `start` +@property (nonatomic, strong) CAShapeLayer * backgroundRingLayer; +@property (nonatomic, strong) CAShapeLayer * animatingRingLayer; +@property (nonatomic, strong) UILabel * countdownLabel; +@property (nonatomic, strong) NSNotificationCenter *notificationCenter; + @end @implementation MPCountdownTimerView -#pragma mark - Initialization +#pragma mark - Life Cycle -- (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."); +- (instancetype)initWithDuration:(NSTimeInterval)seconds timerCompletion:(void(^)(BOOL hasElapsed))completion { + if (self = [super initWithFrame:CGRectZero]) { + if (seconds <= 0) { + MPLogDebug(@"Attempted to initialize MPCountdownTimerView with a non-positive 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; + _completionBlock = completion; + _remainingSeconds = seconds; + _timer = nil; // timer instantiation is deferred to `start` + _notificationCenter = [NSNotificationCenter defaultCenter]; + + CGPoint ringCenter = CGPointMake([MPCountdownTimerView intrinsicContentDimension] / 2, + [MPCountdownTimerView intrinsicContentDimension] / 2); + + // the actual animation is the reverse of this path + UIBezierPath * circularPath = [UIBezierPath bezierPathWithArcCenter:ringCenter + radius:kRingRadius + startAngle:kTimerStartAngle + kOneCycle + endAngle:kTimerStartAngle + clockwise:false]; + _backgroundRingLayer = ({ + CAShapeLayer * layer = [CAShapeLayer new]; + layer.fillColor = UIColor.clearColor.CGColor; + layer.lineWidth = kRingWidth; + layer.path = [circularPath CGPath]; + layer.strokeColor = [UIColor.whiteColor colorWithAlphaComponent:0.5].CGColor; + layer; }); - self.userInteractionEnabled = NO; - [self addSubview:_timerView]; - [_timerView loadHTMLString:self.timerHtml baseURL:self.timerBaseUrl]; + _animatingRingLayer = ({ + CAShapeLayer * layer = [CAShapeLayer new]; + layer.fillColor = UIColor.clearColor.CGColor; + layer.lineWidth = kRingWidth; + layer.path = [circularPath CGPath]; + layer.strokeColor = UIColor.whiteColor.CGColor; + layer; + }); + + _countdownLabel = ({ + UILabel * label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, [MPCountdownTimerView intrinsicContentDimension], [MPCountdownTimerView intrinsicContentDimension])]; + label.center = ringCenter; + label.font = [UIFont systemFontOfSize:12 weight:UIFontWeightBold]; + label.text = [NSString stringWithFormat:@"%.0f", ceil(seconds)]; + label.textAlignment = NSTextAlignmentCenter; + label.textColor = UIColor.whiteColor; + label; + }); + + [self.layer addSublayer:_backgroundRingLayer]; + [self.layer addSublayer:_animatingRingLayer]; + [self addSubview:_countdownLabel]; + + self.userInteractionEnabled = NO; } return self; @@ -65,24 +100,50 @@ - (void)dealloc { [self stopAndSignalCompletion:NO]; } +#pragma mark - Dimension + ++ (CGFloat)intrinsicContentDimension { + return (kRingRadius + kRingPadding) * 2; +} + +- (CGSize)intrinsicContentSize +{ + return CGSizeMake([MPCountdownTimerView intrinsicContentDimension], + [MPCountdownTimerView intrinsicContentDimension]); +} + #pragma mark - Timer -- (BOOL)isActive { - return (self.timer != nil); +- (BOOL)hasStarted { + return self.timer != nil; } -- (void)startWithTimerCompletion:(void(^)(BOOL hasElapsed))completion { - if (self.isActive) { +- (void)start { + if (self.hasStarted) { + MPLogDebug(@"MPCountdownTimerView cannot start again since it has started"); 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. + // Observer app state for automatic pausing and resuming + [self.notificationCenter addObserver:self + selector:@selector(pause) + name:UIApplicationDidEnterBackgroundNotification + object:nil]; + [self.notificationCenter addObserver:self + selector:@selector(resume) + name:UIApplicationWillEnterForegroundNotification + object:nil]; + + // This animation is the ring disappearing clockwise from full (12 o'clock) to empty. + CABasicAnimation * animation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"]; + animation.fromValue = @1; + animation.toValue = @0; + animation.duration = self.remainingSeconds; + animation.fillMode = kCAFillModeForwards; // for keeping the completed animation + animation.removedOnCompletion = NO; // for keeping the completed animation + [self.animatingRingLayer addAnimation:animation forKey:kAnimationKey]; + + // Fire the timer self.timer = [MPTimer timerWithTimeInterval:kCountdownTimerInterval target:self selector:@selector(onTimerUpdate:) @@ -93,19 +154,20 @@ - (void)startWithTimerCompletion:(void(^)(BOOL hasElapsed))completion { } - (void)stopAndSignalCompletion:(BOOL)shouldSignalCompletion { - if (!self.isActive) { + if (!self.hasStarted) { + MPLogDebug(@"MPCountdownTimerView cannot stop since it has not started yet"); return; } - // Invalidate and clear the timer to stop it completely. + // Invalidate and clear the timer to stop it completely. Intentionally not setting `timer` to nil + // so that the computed var `hasStarted` is still `YES` after the timer stops. [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); + BOOL hasElapsed = (self.remainingSeconds <= 0); self.completionBlock(hasElapsed); MPLogInfo(@"MPCountdownTimerView completion block notified"); @@ -117,80 +179,61 @@ - (void)stopAndSignalCompletion:(BOOL)shouldSignalCompletion { } - (void)pause { - if (!self.isActive) { + if (!self.hasStarted) { + MPLogDebug(@"MPCountdownTimerView cannot pause since it has not started yet"); return; } - self.isPaused = [self.timer pause]; - if (self.isPaused) { - MPLogInfo(@"MPCountdownTimerView paused"); + if (!self.timer.isCountdownActive) { + MPLogInfo(@"MPCountdownTimerView is already paused"); + return; // avoid wrong animation timing } -} + [self.timer pause]; -- (void)resume { - if (!self.isActive) { - return; - } + // See documentation for pausing and resuming animation: + // https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CoreAnimation_guide/AdvancedAnimationTricks/AdvancedAnimationTricks.html + CFTimeInterval pausedTime = [self.animatingRingLayer convertTime:CACurrentMediaTime() fromLayer:nil]; + self.animatingRingLayer.speed = 0.0; + self.animatingRingLayer.timeOffset = pausedTime; - if ([self.timer resume]) { - self.isPaused = NO; - MPLogInfo(@"MPCountdownTimerView resumed"); - } + MPLogInfo(@"MPCountdownTimerView paused"); } -#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); - } +- (void)resume { + if (!self.hasStarted) { + MPLogDebug(@"MPCountdownTimerView cannot resume since it has not started yet"); + return; } - 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]; + if (self.timer.isCountdownActive) { + MPLogInfo(@"MPCountdownTimerView is already running"); + return; // avoid wrong animation timing } - - return baseUrl; + [self.timer resume]; + + // See documentation for pausing and resuming animation: + // https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CoreAnimation_guide/AdvancedAnimationTricks/AdvancedAnimationTricks.html + CFTimeInterval pausedTime = [self.animatingRingLayer timeOffset]; + self.animatingRingLayer.speed = 1.0; + self.animatingRingLayer.timeOffset = 0.0; + self.animatingRingLayer.beginTime = 0.0; + CFTimeInterval timeSincePause = [self.animatingRingLayer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime; + self.animatingRingLayer.beginTime = timeSincePause; + + MPLogInfo(@"MPCountdownTimerView resumed"); } #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]; + self.remainingSeconds -= kCountdownTimerInterval; + self.countdownLabel.text = [NSString stringWithFormat:@"%.0f", ceil(self.remainingSeconds)]; // Stop the timer and notify the completion block. - if (self.currentSeconds <= 0) { + if (self.remainingSeconds <= 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/MoPubSDK/Internal/Common/MPEnhancedDeeplinkRequest.h b/MoPubSDK/Internal/Common/MPEnhancedDeeplinkRequest.h index 1b1153b63..8065ce0a3 100644 --- a/MoPubSDK/Internal/Common/MPEnhancedDeeplinkRequest.h +++ b/MoPubSDK/Internal/Common/MPEnhancedDeeplinkRequest.h @@ -1,7 +1,7 @@ // // MPEnhancedDeeplinkRequest.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/Internal/Common/MPEnhancedDeeplinkRequest.m b/MoPubSDK/Internal/Common/MPEnhancedDeeplinkRequest.m index 1566d610c..8f69b7d61 100644 --- a/MoPubSDK/Internal/Common/MPEnhancedDeeplinkRequest.m +++ b/MoPubSDK/Internal/Common/MPEnhancedDeeplinkRequest.m @@ -1,7 +1,7 @@ // // MPEnhancedDeeplinkRequest.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/Internal/Common/MPLastResortDelegate.h b/MoPubSDK/Internal/Common/MPLastResortDelegate.h index 844861299..5967dd122 100644 --- a/MoPubSDK/Internal/Common/MPLastResortDelegate.h +++ b/MoPubSDK/Internal/Common/MPLastResortDelegate.h @@ -1,7 +1,7 @@ // // MPLastResortDelegate.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/Internal/Common/MPLastResortDelegate.m b/MoPubSDK/Internal/Common/MPLastResortDelegate.m index 14061a84e..c4161c22b 100644 --- a/MoPubSDK/Internal/Common/MPLastResortDelegate.m +++ b/MoPubSDK/Internal/Common/MPLastResortDelegate.m @@ -1,7 +1,7 @@ // // MPLastResortDelegate.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/Internal/Common/MPProgressOverlayView.h b/MoPubSDK/Internal/Common/MPProgressOverlayView.h index 66ecbe217..f9c624e7d 100644 --- a/MoPubSDK/Internal/Common/MPProgressOverlayView.h +++ b/MoPubSDK/Internal/Common/MPProgressOverlayView.h @@ -1,7 +1,7 @@ // // MPProgressOverlayView.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -10,30 +10,44 @@ @protocol MPProgressOverlayViewDelegate; -@interface MPProgressOverlayView : UIView { - id __weak _delegate; - UIView *_outerContainer; - UIView *_innerContainer; - UIActivityIndicatorView *_activityIndicator; - UIButton *_closeButton; - CGPoint _closeButtonPortraitCenter; -} - +/** + Progress overlay meant for display over the key window of the application. + */ +@interface MPProgressOverlayView : UIView +/** + Optional delegate to listen for progress overlay events. + */ @property (nonatomic, weak) id delegate; -@property (nonatomic, strong) UIButton *closeButton; -- (id)initWithDelegate:(id)delegate; +/** + Initializes the progress overlay with an optional delegate. + @param delegate Optional delegate to listen for progress overlay events. + @return A progress overlay instance. + */ +- (instancetype)initWithDelegate:(id)delegate; + +/** + Shows the progress overlay over the key window. + */ - (void)show; -- (void)hide; +/** + Removes the progress overlay from the key window. + */ +- (void)hide; @end -//////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - MPProgressOverlayViewDelegate @protocol MPProgressOverlayViewDelegate - @optional +/** + Cancel button pressed. + */ - (void)overlayCancelButtonPressed; -- (void)overlayDidAppear; +/** + Progress overlay completed animating on screen. + */ +- (void)overlayDidAppear; @end diff --git a/MoPubSDK/Internal/Common/MPProgressOverlayView.m b/MoPubSDK/Internal/Common/MPProgressOverlayView.m index 1a9ae0447..19bb87318 100644 --- a/MoPubSDK/Internal/Common/MPProgressOverlayView.m +++ b/MoPubSDK/Internal/Common/MPProgressOverlayView.m @@ -1,324 +1,206 @@ // // MPProgressOverlayView.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // +#import #import "MPProgressOverlayView.h" #import "MPGlobal.h" -#import "MPLogging.h" -#import + +// Constants +#define kProgressOverlayAlpha 0.6f +#define kProgressOverlayAnimationDuration 0.2f +#define kProgressOverlayBorderWidth 1.0f +#define kProgressOverlayCloseButtonDelay 4.0f +#define kProgressOverlayCornerRadius 8.0f +#define kProgressOverlayShadowOffset 2.0f +#define kProgressOverlayShadowOpacity 0.8f +#define kProgressOverlayShadowRadius 8.0f +#define kProgressOverlaySide 60.0f 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; - +@property (nonatomic, strong) UIActivityIndicatorView * activityIndicator; +@property (nonatomic, strong) UIButton * closeButton; +@property (nonatomic, strong) UIView * progressContainer; @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; +#pragma mark - Life Cycle -- (id)initWithDelegate:(id)delegate -{ - self = [self initWithFrame:MPKeyWindow().bounds]; - if (self) { +- (instancetype)initWithDelegate:(id)delegate { + if (self = [self initWithFrame:MPKeyWindow().bounds]) { self.delegate = delegate; } return self; } -- (id)initWithFrame:(CGRect)frame -{ - self = [super initWithFrame:frame]; - if (self) { +- (instancetype)initWithFrame:(CGRect)frame { + if (self = [super initWithFrame:frame]) { self.alpha = 0.0; self.opaque = NO; + self.translatesAutoresizingMaskIntoConstraints = 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; + _closeButton = ({ + UIButton * button = [UIButton buttonWithType:UIButtonTypeCustom]; + button.alpha = 0.0; // The close button will be animated onscreen when needed + button.hidden = YES; // Set to hidden to participate in autoresizing, but not capture user input + + [button addTarget:self action:@selector(closeButtonPressed) forControlEvents:UIControlEventTouchUpInside]; + + UIImage * image = [UIImage imageNamed:MPResourcePathForResource(kCloseButtonXImageName)]; + [button setImage:image forState:UIControlStateNormal]; + + [button sizeToFit]; + button; + }); [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 container which provides a semi-opaque background for + // the activity indicator to render. + _progressContainer = ({ + CGRect frame = CGRectIntegral(CGRectMake(0, 0, kProgressOverlaySide, kProgressOverlaySide)); + UIView * container = [[UIView alloc] initWithFrame:frame]; + + container.alpha = kProgressOverlayAlpha; + container.backgroundColor = [UIColor whiteColor]; + container.opaque = NO; + container.layer.cornerRadius = kProgressOverlayCornerRadius; + container.layer.shadowColor = [UIColor blackColor].CGColor; + container.layer.shadowOffset = CGSizeMake(0.0f, kProgressOverlayShadowRadius - kProgressOverlayShadowOffset); + container.layer.shadowOpacity = kProgressOverlayShadowOpacity; + container.layer.shadowRadius = kProgressOverlayShadowRadius; + container.translatesAutoresizingMaskIntoConstraints = NO; + + // Container interior. + CGFloat innerContainerSide = kProgressOverlaySide - (2.0f * kProgressOverlayBorderWidth); + CGRect innerFrame = CGRectIntegral(CGRectMake(0, 0, innerContainerSide, innerContainerSide)); + UIView * innerContainer = [[UIView alloc] initWithFrame:innerFrame]; + innerContainer.backgroundColor = [UIColor blackColor]; + innerContainer.layer.cornerRadius = kProgressOverlayCornerRadius - kProgressOverlayBorderWidth; + innerContainer.opaque = NO; + innerContainer.translatesAutoresizingMaskIntoConstraints = NO; + + // Center the interior container + [container addSubview:innerContainer]; + innerContainer.center = container.center; + + container; + }); + [self addSubview:_progressContainer]; // Progress indicator. - - _activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle: - UIActivityIndicatorViewStyleWhiteLarge]; - [_activityIndicator sizeToFit]; - [_activityIndicator startAnimating]; - _activityIndicator.center = self.center; - _activityIndicator.frame = CGRectIntegral(_activityIndicator.frame); + _activityIndicator = ({ + UIActivityIndicatorView * indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge]; + [indicator sizeToFit]; + [indicator startAnimating]; + indicator; + }); [self addSubview:_activityIndicator]; - [self registerForDeviceOrientationNotifications]; + // Needs initial layout + [self setNeedsLayout]; + + // Listen for device rotation notifications + [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(deviceOrientationDidChange:) name:UIDeviceOrientationDidChangeNotification object:nil]; } 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)dealloc { + // Unregister self from notifications + [NSNotificationCenter.defaultCenter removeObserver:self]; } -- (void)hide -{ - [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(enableCloseButton) object:nil]; +#pragma mark - Layout - 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]; - } -} +- (void)layoutSubviews { + [super layoutSubviews]; -#pragma mark - Drawing and Layout + // This layout is always full screen for the Application frame inclusive + // of the safe area. + CGRect appFrame = MPApplicationFrame(YES); -- (void)drawRect:(CGRect)rect -{ - CGContextRef context = UIGraphicsGetCurrentContext(); + // Update the size of this view to always match that of the key window. + self.frame = MPKeyWindow().bounds; - CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + // Close button should be in the upper right corner. + self.closeButton.frame = CGRectMake(appFrame.origin.x + appFrame.size.width - self.closeButton.frame.size.width, + appFrame.origin.y, + self.closeButton.frame.size.width, + self.closeButton.frame.size.height); - 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}; + // Progress indicator container should be centered. + self.progressContainer.center = self.center; - CGFunctionRef shadingFunction = CGFunctionCreate((__bridge void *)(self), 1, input_value_range, 4, - output_value_range, &callbacks); + // Progress indicator should be centered. + self.activityIndicator.center = self.center; +} - 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; +#pragma mark - Public Methods - CGShadingRef shading = CGShadingCreateRadial(colorSpace, startPoint, startRadius, endPoint, - endRadius, shadingFunction, - YES, // extend shading beyond starting circle - YES); // extend shading beyond ending circle - CGContextDrawShading(context, shading); +- (void)show { + // Add self to the key window + UIWindow * keyWindow = MPKeyWindow(); + [keyWindow addSubview:self]; - CGShadingRelease(shading); - CGFunctionRelease(shadingFunction); - CGColorSpaceRelease(colorSpace); -} + // Re-layout needed. + [self setNeedsLayout]; -#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); -} + // Animate self on screen + [UIView animateWithDuration:kProgressOverlayAnimationDuration animations:^{ + self.alpha = 1.0; + } completion:^(BOOL finished) { + if ([self.delegate respondsToSelector:@selector(overlayDidAppear)]) { + [self.delegate overlayDidAppear]; + } + }]; -- (void)layoutSubviews -{ - [self updateCloseButtonPosition]; + // Show the close button after kProgressOverlayCloseButtonDelay delay to allow + // the user an out if progress gets stuck. + [self performSelector:@selector(enableCloseButton) withObject:nil afterDelay:kProgressOverlayCloseButtonDelay]; } -- (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; -} +- (void)hide { + // Cancel any pending enabling of the close button + [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(enableCloseButton) object:nil]; -#pragma mark - Internal + // Hide the close button and make it non-user interactable immediately + self.closeButton.alpha = 0.0f; + self.closeButton.hidden = YES; -- (void)registerForDeviceOrientationNotifications -{ - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(deviceOrientationDidChange:) - name:UIDeviceOrientationDidChangeNotification - object:nil]; + // Animate removal of self from the key window + [UIView animateWithDuration:kProgressOverlayAnimationDuration animations:^{ + self.alpha = 0.0; + } completion:^(BOOL finished) { + [self removeFromSuperview]; + }]; } -- (void)unregisterForDeviceOrientationNotifications -{ - [[NSNotificationCenter defaultCenter] removeObserver:self - name:UIDeviceOrientationDidChangeNotification - object:nil]; -} +#pragma mark - Notification Handlers -- (void)deviceOrientationDidChange:(NSNotification *)notification -{ - [self setTransformForCurrentOrientationAnimated:YES]; +- (void)deviceOrientationDidChange:(NSNotification *)notification { [self setNeedsLayout]; } -- (void)enableCloseButton -{ - _closeButton.hidden = NO; - - [UIView beginAnimations:nil context:nil]; - _closeButton.alpha = 1.0; - [UIView commitAnimations]; -} +#pragma mark - Close Button -- (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)enableCloseButton { + self.closeButton.hidden = NO; + [UIView animateWithDuration:kProgressOverlayAnimationDuration animations:^{ + self.closeButton.alpha = 1.0f; + }]; } -- (void)setTransformForAllSubviews:(CGAffineTransform)transform -{ - for (UIView *view in self.subviews) { - view.transform = transform; +- (void)closeButtonPressed { + if ([self.delegate respondsToSelector:@selector(overlayCancelButtonPressed)]) { + [self.delegate overlayCancelButtonPressed]; } } diff --git a/MoPubSDK/Internal/Common/MPRealTimeTimer.h b/MoPubSDK/Internal/Common/MPRealTimeTimer.h index a985b4361..832071fda 100644 --- a/MoPubSDK/Internal/Common/MPRealTimeTimer.h +++ b/MoPubSDK/Internal/Common/MPRealTimeTimer.h @@ -1,7 +1,7 @@ // // MPRealTimeTimer.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/Internal/Common/MPRealTimeTimer.m b/MoPubSDK/Internal/Common/MPRealTimeTimer.m index 70d701a60..6731fe4e1 100644 --- a/MoPubSDK/Internal/Common/MPRealTimeTimer.m +++ b/MoPubSDK/Internal/Common/MPRealTimeTimer.m @@ -1,7 +1,7 @@ // // MPRealTimeTimer.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -68,8 +68,8 @@ - (void)setTimerWithCurrentTimeInterval { self.timer = [MPTimer timerWithTimeInterval:self.currentTimeInterval target:self selector:@selector(fire) - repeats:NO]; - self.timer.runLoopMode = NSRunLoopCommonModes; + repeats:NO + runLoopMode:NSRunLoopCommonModes]; [self.timer scheduleNow]; if (!self.fireDate) { self.fireDate = [NSDate dateWithTimeIntervalSinceNow:self.currentTimeInterval]; @@ -82,6 +82,11 @@ - (void)didEnterBackground { } - (void)willEnterForeground { + // skip resetting the timer if it's already set (i.e., in the iOS 13 new-window case) + if ([self.timer isValid]) { + return; + } + // check if date has passed and fire if needed NSComparisonResult result = [[NSDate date] compare:self.fireDate]; if (result == NSOrderedSame || result == NSOrderedDescending) { diff --git a/MoPubSDK/Internal/Common/MPURLActionInfo.h b/MoPubSDK/Internal/Common/MPURLActionInfo.h index 6b72441c0..5df4db18c 100644 --- a/MoPubSDK/Internal/Common/MPURLActionInfo.h +++ b/MoPubSDK/Internal/Common/MPURLActionInfo.h @@ -1,7 +1,7 @@ // // MPURLActionInfo.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -28,7 +28,7 @@ typedef NS_ENUM(NSUInteger, MPURLActionType) { @property (nonatomic, readonly) MPURLActionType actionType; @property (nonatomic, readonly, copy) NSURL *originalURL; -@property (nonatomic, readonly, copy) NSString *iTunesItemIdentifier; +@property (nonatomic, readonly, strong) NSDictionary *iTunesStoreParameters; @property (nonatomic, readonly, copy) NSURL *iTunesStoreFallbackURL; @property (nonatomic, readonly, copy) NSURL *safariDestinationURL; @property (nonatomic, readonly, copy) NSString *HTTPResponseString; @@ -37,7 +37,7 @@ typedef NS_ENUM(NSUInteger, MPURLActionType) { @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 iTunesStoreParameters:(NSDictionary *)parameters iTunesStoreFallbackURL:(NSURL *)fallbackURL; + (instancetype)infoWithURL:(NSURL *)URL safariDestinationURL:(NSURL *)safariDestinationURL; + (instancetype)infoWithURL:(NSURL *)URL HTTPResponseString:(NSString *)responseString webViewBaseURL:(NSURL *)baseURL; + (instancetype)infoWithURL:(NSURL *)URL webViewBaseURL:(NSURL *)baseURL; diff --git a/MoPubSDK/Internal/Common/MPURLActionInfo.m b/MoPubSDK/Internal/Common/MPURLActionInfo.m index 00d3b3fdb..6dea319a4 100644 --- a/MoPubSDK/Internal/Common/MPURLActionInfo.m +++ b/MoPubSDK/Internal/Common/MPURLActionInfo.m @@ -1,7 +1,7 @@ // // MPURLActionInfo.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -12,7 +12,7 @@ @interface MPURLActionInfo () @property (nonatomic, readwrite) MPURLActionType actionType; @property (nonatomic, readwrite, copy) NSURL *originalURL; -@property (nonatomic, readwrite, copy) NSString *iTunesItemIdentifier; +@property (nonatomic, readwrite, strong) NSDictionary *iTunesStoreParameters; @property (nonatomic, readwrite, copy) NSURL *iTunesStoreFallbackURL; @property (nonatomic, readwrite, copy) NSURL *safariDestinationURL; @property (nonatomic, readwrite, copy) NSString *HTTPResponseString; @@ -27,12 +27,12 @@ @interface MPURLActionInfo () @implementation MPURLActionInfo -+ (instancetype)infoWithURL:(NSURL *)URL iTunesItemIdentifier:(NSString *)identifier iTunesStoreFallbackURL:(NSURL *)fallbackURL ++ (instancetype)infoWithURL:(NSURL *)URL iTunesStoreParameters:(NSDictionary *)parameters iTunesStoreFallbackURL:(NSURL *)fallbackURL { MPURLActionInfo *info = [[[self class] alloc] init]; info.actionType = MPURLActionTypeStoreKit; info.originalURL = URL; - info.iTunesItemIdentifier = identifier; + info.iTunesStoreParameters = parameters; info.iTunesStoreFallbackURL = fallbackURL; return info; } diff --git a/MoPubSDK/Internal/Common/MPURLResolver.h b/MoPubSDK/Internal/Common/MPURLResolver.h index f487f8f02..a6f921e33 100644 --- a/MoPubSDK/Internal/Common/MPURLResolver.h +++ b/MoPubSDK/Internal/Common/MPURLResolver.h @@ -1,7 +1,7 @@ // // MPURLResolver.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/Internal/Common/MPURLResolver.m b/MoPubSDK/Internal/Common/MPURLResolver.m index 4cf2cfc59..1341cf96e 100644 --- a/MoPubSDK/Internal/Common/MPURLResolver.m +++ b/MoPubSDK/Internal/Common/MPURLResolver.m @@ -1,11 +1,12 @@ // // MPURLResolver.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // +#import #import #import "MPURLResolver.h" #import "MPHTTPNetworkSession.h" @@ -32,7 +33,6 @@ @interface MPURLResolver () @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; @@ -67,7 +67,7 @@ - (void)start if (info) { [self safeInvokeAndNilCompletionBlock:info error:nil]; - } else if ([self shouldEnableClickthroughExperiment]) { + } else if ([self shouldOpenWithInAppWebBrowser]) { info = [MPURLActionInfo infoWithURL:self.originalURL webViewBaseURL:self.currentURL]; [self safeInvokeAndNilCompletionBlock:info error:nil]; } else if (error) { @@ -154,8 +154,9 @@ - (MPURLActionInfo *)actionInfoFromURL:(NSURL *)URL error:(NSError **)error; return nil; } - if ([self storeItemIdentifierForURL:URL]) { - actionInfo = [MPURLActionInfo infoWithURL:self.originalURL iTunesItemIdentifier:[self storeItemIdentifierForURL:URL] iTunesStoreFallbackURL:URL]; + NSDictionary * storeKitParameters = [self appStoreProductParametersForURL:URL]; + if (storeKitParameters != nil) { + actionInfo = [MPURLActionInfo infoWithURL:self.originalURL iTunesStoreParameters:storeKitParameters iTunesStoreFallbackURL:URL]; } else if ([self URLHasDeeplinkPlusScheme:URL]) { MPEnhancedDeeplinkRequest *request = [[MPEnhancedDeeplinkRequest alloc] initWithURL:URL]; if (request) { @@ -212,28 +213,113 @@ - (BOOL)URLPointsToAMap:(NSURL *)URL return [URL.host hasSuffix:@"maps.google.com"] || [URL.host hasSuffix:@"maps.apple.com"]; } +- (BOOL)URLIsAppleScheme:(NSURL *)URL +{ + // Definitely not an Apple URL scheme. + if (![URL.host hasSuffix:@".apple.com"]) { + return NO; + } + + // Constant set of supported Apple Store subdomains that will be loaded into + // SKStoreProductViewController. This is lazily initialized and limited to the + // scope of this method. + static NSSet * supportedStoreSubdomains = nil; + if (supportedStoreSubdomains == nil) { + supportedStoreSubdomains = [NSSet setWithArray:@[@"apps", @"books", @"itunes", @"music"]]; + } + + // Assumes that the Apple Store sub domains are of the format store-type.apple.com + // At this point we are guaranteed at least 3 components from the previous ".apple.com" + // check. + NSArray * hostComponents = [URL.host componentsSeparatedByString:@"."]; + NSString * subdomain = hostComponents[0]; + + return [supportedStoreSubdomains containsObject:subdomain]; +} + #pragma mark Extracting StoreItem Identifiers -- (NSString *)storeItemIdentifierForURL:(NSURL *)URL +/** + Attempt to parse an Apple store URL into a dictionary of @c SKStoreProductParameter items. This will fast fail + if the URL is not a valid Apple store URL scheme. + @param URL Apple store URL to attempt to parse. + @return A dictionary with at least the required @c SKStoreProductParameterITunesItemIdentifier as an entry; otherwise @c nil + */ +- (NSDictionary *)appStoreProductParametersForURL:(NSURL *)URL { - NSString *itemIdentifier = nil; - if ([URL.host hasSuffix:@"itunes.apple.com"]) { - NSString *lastPathComponent = [[URL path] lastPathComponent]; + // Definitely not an Apple URL scheme. Don't bother to parse. + if (![self URLIsAppleScheme:URL]) { + return nil; + } + + // Failed to parse out the URL into its components. Likely to be an invalid URL. + NSURLComponents * urlComponents = [NSURLComponents componentsWithURL:URL resolvingAgainstBaseURL:YES]; + if (urlComponents == nil) { + return nil; + } + + // Attempt to parse out the item identifier. + NSString * itemIdentifier = ({ + NSString * lastPathComponent = URL.path.lastPathComponent; + NSString * itemIdFromQueryParameter = [URL.mp_queryAsDictionary objectForKey:@"id"]; + NSString * parsedIdentifier = nil; + + // Old style iTunes item identifiers are prefixed with "id". + // Example: https://apps.apple.com/.../id923917775 if ([lastPathComponent hasPrefix:@"id"]) { - itemIdentifier = [lastPathComponent substringFromIndex:2]; - } else { - itemIdentifier = [URL.mp_queryAsDictionary objectForKey:@"id"]; + parsedIdentifier = [lastPathComponent substringFromIndex:2]; + } + // Look for the item identifier as a query parameter in the URL. + // Example: https://itunes.apple.com/...?id=923917775 + else if (itemIdFromQueryParameter != nil) { + parsedIdentifier = itemIdFromQueryParameter; + } + // Newer style Apple Store identifiers are just the last path component. + // Example: https://music.apple.com/.../1451047660 + else { + parsedIdentifier = lastPathComponent; } - } else if ([URL.host hasSuffix:@"phobos.apple.com"]) { - itemIdentifier = [URL.mp_queryAsDictionary objectForKey:@"id"]; + + // Check that the parsed item identifier doesn't exist or contains invalid characters. + NSCharacterSet * nonIntegers = [[NSCharacterSet decimalDigitCharacterSet] invertedSet]; + if (parsedIdentifier.length > 0 && [parsedIdentifier rangeOfCharacterFromSet:nonIntegers].location != NSNotFound) { + parsedIdentifier = nil; + } + + parsedIdentifier; + }); + + // Item identifier is a required field. If it doesn't exist, there is no point + // in continuing to parse the URL. + if (itemIdentifier.length == 0) { + return nil; } - NSCharacterSet *nonIntegers = [[NSCharacterSet decimalDigitCharacterSet] invertedSet]; - if (itemIdentifier && itemIdentifier.length > 0 && [itemIdentifier rangeOfCharacterFromSet:nonIntegers].location == NSNotFound) { - return itemIdentifier; + // Attempt parsing for the following StoreKit product keys: + // SKStoreProductParameterITunesItemIdentifier (required) + // SKStoreProductParameterProductIdentifier (not supported) + // SKStoreProductParameterAdvertisingPartnerToken (not supported) + // SKStoreProductParameterAffiliateToken (optional) + // SKStoreProductParameterCampaignToken (optional) + // SKStoreProductParameterProviderToken (not supported) + // + // Query parameter parsing according to: + // https://affiliate.itunes.apple.com/resources/documentation/basic_affiliate_link_guidelines_for_the_phg_network/ + NSMutableDictionary * parameters = [NSMutableDictionary dictionaryWithCapacity:3]; + parameters[SKStoreProductParameterITunesItemIdentifier] = itemIdentifier; + + for (NSURLQueryItem * queryParameter in urlComponents.queryItems) { + // OPTIONAL: Attempt parsing of SKStoreProductParameterAffiliateToken + if ([queryParameter.name isEqualToString:@"at"]) { + parameters[SKStoreProductParameterAffiliateToken] = queryParameter.value; + } + // OPTIONAL: Attempt parsing of SKStoreProductParameterCampaignToken + else if ([queryParameter.name isEqualToString:@"ct"]) { + parameters[SKStoreProductParameterCampaignToken] = queryParameter.value; + } } - return nil; + return parameters; } #pragma mark - Identifying URLs to open in Safari @@ -257,7 +343,7 @@ - (NSStringEncoding)stringEncodingFromContentType:(NSString *)contentType NSStringEncoding encoding = NSUTF8StringEncoding; if (![contentType length]) { - MPLogWarn(@"Attempting to set string encoding from nil %@", kMoPubHTTPHeaderContentType); + MPLogInfo(@"Attempting to set string encoding from nil %@", kMoPubHTTPHeaderContentType); return encoding; } @@ -281,19 +367,19 @@ - (NSStringEncoding)stringEncodingFromContentType:(NSString *)contentType return encoding; } -#pragma mark - Check if it's necessary to include a URL in the clickthrough experiment. +#pragma mark - Check if it's necessary to handle the clickthrough URL outside of a web browser // There are two types of clickthrough URL sources: from webviews and from non-web views. // The ones from webviews start with (https|http)://ads.mopub.com/m/aclk -// For webviews, in order for a URL to be included in the clickthrough experiment, redirect URL scheme needs to be http/https. - -- (BOOL)shouldEnableClickthroughExperiment +// For webviews, in order for a URL to be processed in a web browser, the redirect URL scheme needs to be http/https. +- (BOOL)shouldOpenWithInAppWebBrowser { if (!self.currentURL) { return NO; } - // If redirect URL isn't http/https, do not include it in the clickthrough experiment. - if (![self URLIsHTTPOrHTTPS:self.currentURL]) { + // If redirect URL isn't http/https, do not open it in a browser. It is likely a deep link + // or an Apple Store scheme that will need special parsing. + if (![self URLIsHTTPOrHTTPS:self.currentURL] || [self URLIsAppleScheme:self.currentURL]) { return NO; } @@ -301,18 +387,21 @@ - (BOOL)shouldEnableClickthroughExperiment if ([self.currentURL.host isEqualToString:kWebviewClickthroughHost] && [self.currentURL.path isEqualToString:kWebviewClickthroughPath]) { + // Extract the redirect URL from the clickthrough. NSString *redirectURLStr = [self.currentURL mp_queryParameterForKey:kRedirectURLQueryStringKey]; - if (!redirectURLStr || ![self URLIsHTTPOrHTTPS:[NSURL URLWithString:redirectURLStr]]) { + NSURL *redirectUrl = [NSURL URLWithString:redirectURLStr]; + + // There is a redirect URL. We need to determine if the redirect also needs additional + // handling. In the event that no redirect URL is present, normal processing will occur. + if (redirectUrl != nil && (![self URLIsHTTPOrHTTPS:redirectUrl] || [self URLIsAppleScheme:redirectUrl])) { return NO; } } - // Check experiment variant is in test group. - if ([MPAdDestinationDisplayAgent shouldUseSafariViewController] || - [MOPUBExperimentProvider displayAgentType] == MOPUBDisplayAgentTypeNativeSafari) { - return YES; - } - return NO; + // ADF-4215: If this trailing return value should be changed, check whether App Store redirection + // links will end up showing the App Store UI in app (expected) or escaping the app to open the + // native iOS App Store (unexpected). + return [MPAdDestinationDisplayAgent shouldDisplayContentInApp]; } @end diff --git a/MoPubSDK/Internal/Common/MPVideoConfig.h b/MoPubSDK/Internal/Common/MPVideoConfig.h index 3149ffd5b..df7d4c155 100644 --- a/MoPubSDK/Internal/Common/MPVideoConfig.h +++ b/MoPubSDK/Internal/Common/MPVideoConfig.h @@ -1,7 +1,7 @@ // // MPVideoConfig.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -11,40 +11,19 @@ @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; +/** + Ad response typically contains multiple video files of different resolutions and bit-rates, and the + best one is picked when the ad is loaded (not when receiving the ad response). + */ +@property (nonatomic, readonly) NSArray *mediaFiles; -/** @name Viewability */ - -@property (nonatomic, readonly) NSTimeInterval minimumViewabilityTimeInterval; -@property (nonatomic, readonly) double minimumFractionOfVideoVisible; -@property (nonatomic, readonly) NSURL *viewabilityTrackingURL; +@property (nonatomic, readonly) NSURL *clickThroughURL; - (instancetype)initWithVASTResponse:(MPVASTResponse *)response additionalTrackers:(NSDictionary *)additionalTrackers; +/** + Take a @c MPVideoEvent string for the key, and return an array of @c MPVASTTrackingEvent. + */ +- (NSArray *)trackingEventsForKey:(MPVideoEvent)key; + @end diff --git a/MoPubSDK/Internal/Common/MPVideoConfig.m b/MoPubSDK/Internal/Common/MPVideoConfig.m index 963c0a774..ee893e0d5 100644 --- a/MoPubSDK/Internal/Common/MPVideoConfig.m +++ b/MoPubSDK/Internal/Common/MPVideoConfig.m @@ -1,7 +1,7 @@ // // MPVideoConfig.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -9,69 +9,43 @@ #import "MPVideoConfig.h" #import "MPLogging.h" #import "MPVASTStringUtilities.h" +#import "MPVASTTracking.h" +/** + This is a private data object that represents an ad candidate for display. + */ @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; +@property (nonatomic, strong) MPVASTLinearAd *linearAd; +@property (nonatomic, strong) NSArray *errorURLs; +@property (nonatomic, strong) NSArray *impressionURLs; @end -//////////////////////////////////////////////////////////////////////////////////////////////////// - @implementation MPVideoPlaybackCandidate - -@end +@end // this data object should have empty implementation //////////////////////////////////////////////////////////////////////////////////////////////////// -@interface MPVideoConfig () +@interface MPVASTLinearAd (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; +@property (nonatomic, strong) NSArray *clickTrackingURLs; +@property (nonatomic, strong) NSArray *customClickURLs; +@property (nonatomic, strong) NSArray *industryIcons; +@property (nonatomic, strong) NSDictionary *trackingEvents; @end //////////////////////////////////////////////////////////////////////////////////////////////////// -@interface MPVASTLinearAd (MPVideoConfig) - -@property (nonatomic, readwrite) NSArray *clickTrackingURLs; -@property (nonatomic, readwrite) NSArray *customClickURLs; -@property (nonatomic, readwrite) NSArray *industryIcons; -@property (nonatomic, readwrite) NSDictionary *trackingEvents; - +@interface MPVideoConfig () +@property (nonatomic, strong) NSDictionary *> *trackingEventTable; @end -//////////////////////////////////////////////////////////////////////////////////////////////////// - @implementation MPVideoConfig +#pragma mark - Public + - (instancetype)initWithVASTResponse:(MPVASTResponse *)response additionalTrackers:(NSDictionary *)additionalTrackers { self = [super init]; @@ -81,52 +55,65 @@ - (instancetype)initWithVASTResponse:(MPVASTResponse *)response additionalTracke return self; } +- (NSArray *)trackingEventsForKey:(MPVideoEvent)key { + return self.trackingEventTable[key]; +} + +#pragma mark - Private + - (void)commonInit:(MPVASTResponse *)response additionalTrackers:(NSDictionary *)additionalTrackers { - NSArray *candidates = [self playbackCandidatesFromVASTResponse:response]; + NSArray *candidates = [self playbackCandidatesFromVASTResponse:response]; if (candidates.count == 0) { return; } MPVideoPlaybackCandidate *candidate = candidates[0]; - MPVASTMediaFile *mediaFile = candidate.linearAd.highestBitrateMediaFile; - _mediaURL = mediaFile.URL; + _mediaFiles = candidate.linearAd.mediaFiles; _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; + + // set up the tracking event table + NSMutableDictionary *> *table + = [NSMutableDictionary dictionaryWithDictionary:candidate.linearAd.trackingEvents]; + for (MPVideoEvent name in @[MPVideoEventStart, + MPVideoEventFirstQuartile, + MPVideoEventMidpoint, + MPVideoEventThirdQuartile, + MPVideoEventComplete]) { + table[name] = [self mergeTrackersOfName:name + originalTrackers:table + additionalTrackers:additionalTrackers]; + } + + NSMutableDictionary *> *eventVsURLs = [NSMutableDictionary new]; + if (candidate.linearAd.clickTrackingURLs.count > 0) { + eventVsURLs[MPVideoEventClick] = candidate.linearAd.clickTrackingURLs; + } + if (candidate.errorURLs.count > 0) { + eventVsURLs[MPVideoEventError] = candidate.errorURLs; + } + if (candidate.impressionURLs.count > 0) { + eventVsURLs[MPVideoEventImpression] = candidate.impressionURLs; + } + + for (MPVideoEvent event in eventVsURLs.allKeys) { + NSMutableArray *trackingEvents = [NSMutableArray new]; + for (NSURL *url in eventVsURLs[event]) { + [trackingEvents addObject:[[MPVASTTrackingEvent alloc] initWithEventType:event + url:url + progressOffset:nil]]; + } + table[event] = trackingEvents; + } + + self.trackingEventTable = [NSDictionary dictionaryWithDictionary:table]; } -- (NSArray *)playbackCandidatesFromVASTResponse:(MPVASTResponse *)response +- (NSArray *)playbackCandidatesFromVASTResponse:(MPVASTResponse *)response { - NSMutableArray *candidates = [NSMutableArray array]; + NSMutableArray *candidates = [NSMutableArray array]; for (MPVASTAd *ad in response.ads) { if (ad.inlineAd) { @@ -138,26 +125,11 @@ - (NSArray *)playbackCandidatesFromVASTResponse:(MPVASTResponse *)response 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]; + NSArray *candidatesFromWrapper = [self playbackCandidatesFromVASTResponse:ad.wrapper.wrappedVASTResponse]; // Merge any wrapper-level tracking URLs into each of the candidates. for (MPVideoPlaybackCandidate *candidate in candidatesFromWrapper) { @@ -263,18 +235,18 @@ - (id)firstObjectForKey:(NSString *)key inDictionary:(NSDictionary *)dictionary } } -- (NSArray *)trackersByMergingOriginalTrackers:(NSDictionary *)originalTrackers additionalTrackers:(NSDictionary *)additionalTrackers name:(NSString *)trackerName -{ - if (![originalTrackers[trackerName] isKindOfClass:[NSArray class]]) { - return additionalTrackers[trackerName]; +- (NSArray *)mergeTrackersOfName:(NSString *)trackerName + originalTrackers:(NSDictionary *> *)originalTrackers + additionalTrackers:(NSDictionary *> *)additionalTrackers { + NSArray *original = originalTrackers[trackerName]; + NSArray *additional = additionalTrackers[trackerName]; + if (original == nil || [original isKindOfClass:[NSArray class]] == false) { + original = @[]; } - if (![additionalTrackers[trackerName] isKindOfClass:[NSArray class]]) { - return originalTrackers[trackerName]; + if ([additional isKindOfClass:[NSArray class]] == false) { + return original; } - NSMutableArray *mergedTrackers = [NSMutableArray new]; - [mergedTrackers addObjectsFromArray:originalTrackers[trackerName]]; - [mergedTrackers addObjectsFromArray:additionalTrackers[trackerName]]; - return mergedTrackers; + return [original arrayByAddingObjectsFromArray:additional]; } - (NSDictionary *)dictionaryByMergingTrackingDictionaries:(NSArray *)dictionaries @@ -289,7 +261,7 @@ - (NSDictionary *)dictionaryByMergingTrackingDictionaries:(NSArray *)dictionarie [mergedDictionary[key] addObjectsFromArray:dictionary[key]]; } else { - MPLogError(@"TrackingEvents dictionary expected an array object for key '%@' " + MPLogInfo(@"TrackingEvents dictionary expected an array object for key '%@' " @"but got an instance of %@ instead.", key, NSStringFromClass([dictionary[key] class])); } diff --git a/MoPubSDK/Internal/Common/MPXMLParser.h b/MoPubSDK/Internal/Common/MPXMLParser.h index 0b7cd100d..69f100744 100644 --- a/MoPubSDK/Internal/Common/MPXMLParser.h +++ b/MoPubSDK/Internal/Common/MPXMLParser.h @@ -1,7 +1,7 @@ // // MPXMLParser.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/Internal/Common/MPXMLParser.m b/MoPubSDK/Internal/Common/MPXMLParser.m index 59e527dcb..44205385d 100644 --- a/MoPubSDK/Internal/Common/MPXMLParser.m +++ b/MoPubSDK/Internal/Common/MPXMLParser.m @@ -1,7 +1,7 @@ // // MPXMLParser.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/Internal/HTML/MPAdWebViewAgent.h b/MoPubSDK/Internal/HTML/MPAdWebViewAgent.h index d686c1c16..cb1e9ce95 100644 --- a/MoPubSDK/Internal/HTML/MPAdWebViewAgent.h +++ b/MoPubSDK/Internal/HTML/MPAdWebViewAgent.h @@ -1,7 +1,7 @@ // // MPAdWebViewAgent.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -31,9 +31,7 @@ typedef NSUInteger MPAdWebViewEvent; - (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; diff --git a/MoPubSDK/Internal/HTML/MPAdWebViewAgent.m b/MoPubSDK/Internal/HTML/MPAdWebViewAgent.m index 3adc5fdb4..db082f394 100644 --- a/MoPubSDK/Internal/HTML/MPAdWebViewAgent.m +++ b/MoPubSDK/Internal/HTML/MPAdWebViewAgent.m @@ -1,24 +1,23 @@ // // MPAdWebViewAgent.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // +#import #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 "MPCoreInstanceProvider.h" #import "MPUserInteractionGestureRecognizer.h" #import "NSJSONSerialization+MPAdditions.h" #import "NSURL+MPAdditions.h" -#import "MPInternalUtils.h" #import "MPAPIEndPoints.h" #import "MoPub.h" #import "MPViewabilityTracker.h" @@ -28,8 +27,6 @@ #define NSFoundationVersionNumber_iOS_6_1 993.00 #endif -#define MPOffscreenWebViewNeedsRenderingWorkaround() (floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_6_1) - @interface MPAdWebViewAgent () @property (nonatomic, strong) MPAdConfiguration *configuration; @@ -40,25 +37,17 @@ @interface MPAdWebViewAgent () @property (nonatomic, strong) MPUserInteractionGestureRecognizer *userInteractionRecognizer; @property (nonatomic, assign) CGRect frame; @property (nonatomic, strong, readwrite) MPViewabilityTracker *viewabilityTracker; +@property (nonatomic, assign) BOOL didFireClickImpression; - (void)performActionForMoPubSpecificURL:(NSURL *)URL; -- (BOOL)shouldIntercept:(NSURL *)URL navigationType:(UIWebViewNavigationType)navigationType; +- (BOOL)shouldIntercept:(NSURL *)URL navigationType:(WKNavigationType)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; +- (id)initWithAdWebViewFrame:(CGRect)frame delegate:(id)delegate { self = [super init]; if (self) { @@ -67,6 +56,7 @@ - (id)initWithAdWebViewFrame:(CGRect)frame delegate:(id -- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer; +- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer { return YES; } @@ -132,7 +122,7 @@ - (void)loadConfiguration:(MPAdConfiguration *)configuration // 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.adType != MPAdTypeFullscreen) { if ([configuration hasPreferredSize]) { CGRect frame = self.view.frame; frame.size.width = configuration.preferredSize.width; @@ -142,7 +132,6 @@ - (void)loadConfiguration:(MPAdConfiguration *)configuration } [self.view mp_setScrollable:NO]; - [self.view disableJavaScriptDialogs]; // Initialize viewability trackers before loading self.view [self init3rdPartyViewabilityTrackers]; @@ -219,8 +208,9 @@ - (MPAdConfiguration *)adConfiguration #pragma mark - -- (BOOL)webView:(MPWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request - navigationType:(UIWebViewNavigationType)navigationType +- (BOOL)webView:(MPWebView *)webView +shouldStartLoadWithRequest:(NSURLRequest *)request + navigationType:(WKNavigationType)navigationType { if (!self.shouldHandleRequests) { return NO; @@ -248,7 +238,7 @@ - (BOOL)webView:(MPWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *) - (void)webViewDidStartLoad:(MPWebView *)webView { - [self.view disableJavaScriptDialogs]; + // no op } #pragma mark - MoPub-specific URL handlers @@ -267,19 +257,17 @@ - (void)performActionForMoPubSpecificURL:(NSURL *)URL [self.delegate adDidFailToLoadAd:self.view]; break; default: - MPLogWarn(@"MPAdWebView - unsupported MoPub URL: %@", [URL absoluteString]); + MPLogInfo(@"MPAdWebView - unsupported MoPub URL: %@", [URL absoluteString]); break; } } #pragma mark - URL Interception -- (BOOL)shouldIntercept:(NSURL *)URL navigationType:(UIWebViewNavigationType)navigationType +- (BOOL)shouldIntercept:(NSURL *)URL navigationType:(WKNavigationType)navigationType { - if ([URL mp_hasTelephoneScheme] || [URL mp_hasTelephonePromptScheme]) { + if (navigationType == WKNavigationTypeLinkActivated) { return YES; - } else if (navigationType == UIWebViewNavigationTypeLinkClicked) { - return YES; - } else if (navigationType == UIWebViewNavigationTypeOther && self.userInteractedWithWebView) { + } else if (navigationType == WKNavigationTypeOther && self.userInteractedWithWebView) { return YES; } else { return NO; @@ -289,7 +277,9 @@ - (BOOL)shouldIntercept:(NSURL *)URL navigationType:(UIWebViewNavigationType)nav - (void)interceptURL:(NSURL *)URL { NSURL *redirectedURL = URL; - if (self.configuration.clickTrackingURL) { + if (self.configuration.clickTrackingURL && !self.didFireClickImpression) { + self.didFireClickImpression = YES; // fire click impression only once + NSString *path = [NSString stringWithFormat:@"%@&r=%@", self.configuration.clickTrackingURL.absoluteString, [[URL absoluteString] mp_URLEncodedString]]; @@ -319,7 +309,7 @@ - (BOOL)shouldStartViewabilityDuringInitialization - (BOOL)isInterstitialAd { - return (self.configuration.adType == MPAdTypeInterstitial); + return (self.configuration.adType == MPAdTypeFullscreen); } - (void)initAdAlertManager @@ -331,49 +321,4 @@ - (void)initAdAlertManager [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: 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/MoPubSDK/Internal/HTML/MPContentBlocker.h b/MoPubSDK/Internal/HTML/MPContentBlocker.h new file mode 100644 index 000000000..e713461c9 --- /dev/null +++ b/MoPubSDK/Internal/HTML/MPContentBlocker.h @@ -0,0 +1,20 @@ +// +// MPContentBlocker.h +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface MPContentBlocker : NSObject +/** + Blocked resources for use with @c WKContentRuleListStore. + */ +@property (class, nonatomic, readonly, nullable) NSString * blockedResourcesList; +@end + +NS_ASSUME_NONNULL_END diff --git a/MoPubSDK/Internal/HTML/MPContentBlocker.m b/MoPubSDK/Internal/HTML/MPContentBlocker.m new file mode 100644 index 000000000..0741685b4 --- /dev/null +++ b/MoPubSDK/Internal/HTML/MPContentBlocker.m @@ -0,0 +1,79 @@ +// +// MPContentBlocker.m +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import "MPContentBlocker.h" +#import "MPAPIEndpoints.h" + +@interface MPContentBlocker() +@property (class, nonatomic, readonly) NSArray * blockedResources; +@end + +@implementation MPContentBlocker + +#pragma mark - Lazy Initialized Properties + +/** + Current list of blocked resources. + */ ++ (NSArray *)blockedResources { + static NSSet * sBlockedResources = nil; + NSString * blockedURLString = [NSString stringWithFormat:@"http.?://%@/mraid.js", MPAPIEndpoints.baseHostname]; + + if (sBlockedResources == nil) { + sBlockedResources = [NSSet setWithObject:blockedURLString]; + } else if (![sBlockedResources containsObject:blockedURLString]) { + sBlockedResources = [sBlockedResources setByAddingObject:blockedURLString]; + } + + return [sBlockedResources allObjects]; +} + +/** + Generates a JSON block pattern from the URL resource. + */ ++ (NSDictionary *)blockPatternFromResource:(NSString *)resource { + if (resource == nil) { + return nil; + } + + // See https://developer.apple.com/documentation/safariservices/creating_a_content_blocker?language=objc + // for the specifics of the content blocking JSON structure. + return @{ @"action": @{ @"type": @"block" }, + @"trigger": @{ @"url-filter": resource } }; +} + ++ (NSString *)blockedResourcesList { + static NSString * sBlockedResourcesList = nil; + static NSInteger sBlockedResourcesListCount = 0; + + // Update the blocked resources string if: + // - the string @c sBlockedResourcesList has not been initialized + // - the count for @c blockedResources (stored in @c sBlockedResourcesListCount) has changed + if (sBlockedResourcesList == nil || sBlockedResourcesListCount != MPContentBlocker.blockedResources.count) { + // Update present blocked resources count to new value + sBlockedResourcesListCount = MPContentBlocker.blockedResources.count; + + // Aggregate all resource patterns to block into a single JSON structure. + NSMutableArray * patterns = [NSMutableArray arrayWithCapacity:MPContentBlocker.blockedResources.count]; + [MPContentBlocker.blockedResources enumerateObjectsUsingBlock:^(NSString * resource, NSUInteger idx, BOOL * _Nonnull stop) { + NSDictionary * blockPattern = [MPContentBlocker blockPatternFromResource:resource]; + if (blockPattern != nil) { + [patterns addObject:blockPattern]; + } + }]; + + // Generate a JSON string. + NSError * error = nil; + NSData * jsonData = [NSJSONSerialization dataWithJSONObject:patterns options:0 error:&error]; + sBlockedResourcesList = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; + } + + return sBlockedResourcesList; +} + +@end diff --git a/MoPubSDK/Internal/HTML/MPHTMLBannerCustomEvent.h b/MoPubSDK/Internal/HTML/MPHTMLBannerCustomEvent.h index 993b6ff9c..5de2985d0 100644 --- a/MoPubSDK/Internal/HTML/MPHTMLBannerCustomEvent.h +++ b/MoPubSDK/Internal/HTML/MPHTMLBannerCustomEvent.h @@ -1,7 +1,7 @@ // // MPHTMLBannerCustomEvent.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -12,9 +12,6 @@ @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/MoPubSDK/Internal/HTML/MPHTMLBannerCustomEvent.m b/MoPubSDK/Internal/HTML/MPHTMLBannerCustomEvent.m index c174b0bb8..1ea76cc1f 100644 --- a/MoPubSDK/Internal/HTML/MPHTMLBannerCustomEvent.m +++ b/MoPubSDK/Internal/HTML/MPHTMLBannerCustomEvent.m @@ -1,13 +1,14 @@ // // MPHTMLBannerCustomEvent.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // #import "MPHTMLBannerCustomEvent.h" #import "MPWebView.h" +#import "MPError.h" #import "MPLogging.h" #import "MPAdConfiguration.h" #import "MPAnalyticsTracker.h" @@ -20,7 +21,9 @@ @interface MPHTMLBannerCustomEvent () @implementation MPHTMLBannerCustomEvent -@synthesize bannerAgent = _bannerAgent; +// Explicitly `@synthesize` here to fix a "-Wobjc-property-synthesis" warning because super class `delegate` is +// `id` and this `delegate` is `id` +@synthesize delegate; - (BOOL)enableAutomaticImpressionAndClickTracking { @@ -29,12 +32,13 @@ - (BOOL)enableAutomaticImpressionAndClickTracking - (void)requestAdWithSize:(CGSize)size customEventInfo:(NSDictionary *)info { - MPLogInfo(@"Loading MoPub HTML banner"); - MPLogTrace(@"Loading banner with HTML source: %@", [[self.delegate configuration] adResponseHTMLString]); + MPAdConfiguration * configuration = self.delegate.configuration; + + MPLogAdEvent([MPLogEvent adLoadAttemptForAdapter:NSStringFromClass(configuration.customEventClass) dspCreativeId:configuration.dspCreativeId dspName:nil], self.adUnitId); CGRect adWebViewFrame = CGRectMake(0, 0, size.width, size.height); self.bannerAgent = [[MPAdWebViewAgent alloc] initWithAdWebViewFrame:adWebViewFrame delegate:self]; - [self.bannerAgent loadConfiguration:[self.delegate configuration]]; + [self.bannerAgent loadConfiguration:configuration]; } - (void)dealloc @@ -42,11 +46,6 @@ - (void)dealloc self.bannerAgent.delegate = nil; } -- (void)rotateToOrientation:(UIInterfaceOrientation)newOrientation -{ - [self.bannerAgent rotateToOrientation:newOrientation]; -} - #pragma mark - MPAdWebViewAgentDelegate - (CLLocation *)location @@ -66,14 +65,17 @@ - (UIViewController *)viewControllerForPresentingModalView - (void)adDidFinishLoadingAd:(MPWebView *)ad { - MPLogInfo(@"MoPub HTML banner did load"); + MPLogAdEvent([MPLogEvent adLoadSuccessForAdapter:NSStringFromClass(self.class)], self.adUnitId); [self.delegate bannerCustomEvent:self didLoadAd:ad]; } - (void)adDidFailToLoadAd:(MPWebView *)ad { - MPLogInfo(@"MoPub HTML banner did fail"); - [self.delegate bannerCustomEvent:self didFailToLoadAdWithError:nil]; + NSString * message = [NSString stringWithFormat:@"Failed to load creative:\n%@", self.delegate.configuration.adResponseHTMLString]; + NSError * error = [NSError errorWithCode:MOPUBErrorAdapterFailedToLoadAd localizedDescription:message]; + + MPLogAdEvent([MPLogEvent adLoadFailedForAdapter:NSStringFromClass(self.class) error:error], self.adUnitId); + [self.delegate bannerCustomEvent:self didFailToLoadAdWithError:error]; } - (void)adDidClose:(MPWebView *)ad @@ -83,19 +85,16 @@ - (void)adDidClose:(MPWebView *)ad - (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]; } diff --git a/MoPubSDK/Internal/HTML/MPHTMLInterstitialCustomEvent.h b/MoPubSDK/Internal/HTML/MPHTMLInterstitialCustomEvent.h index 7a1c16fc5..dcd36f0cf 100644 --- a/MoPubSDK/Internal/HTML/MPHTMLInterstitialCustomEvent.h +++ b/MoPubSDK/Internal/HTML/MPHTMLInterstitialCustomEvent.h @@ -1,20 +1,16 @@ // // MPHTMLInterstitialCustomEvent.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // #import "MPInterstitialCustomEvent.h" -#import "MPHTMLInterstitialViewController.h" #import "MPPrivateInterstitialCustomEventDelegate.h" -@interface MPHTMLInterstitialCustomEvent : MPInterstitialCustomEvent +@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/MoPubSDK/Internal/HTML/MPHTMLInterstitialCustomEvent.m b/MoPubSDK/Internal/HTML/MPHTMLInterstitialCustomEvent.m index 18e1bc994..c1a4325f9 100644 --- a/MoPubSDK/Internal/HTML/MPHTMLInterstitialCustomEvent.m +++ b/MoPubSDK/Internal/HTML/MPHTMLInterstitialCustomEvent.m @@ -1,14 +1,16 @@ // // MPHTMLInterstitialCustomEvent.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // #import "MPHTMLInterstitialCustomEvent.h" -#import "MPLogging.h" +#import "MPHTMLInterstitialViewController.h" #import "MPAdConfiguration.h" +#import "MPError.h" +#import "MPLogging.h" @interface MPHTMLInterstitialCustomEvent () @@ -17,9 +19,14 @@ @interface MPHTMLInterstitialCustomEvent () @end +@interface MPHTMLInterstitialCustomEvent (MPInterstitialViewControllerDelegate) +@end + @implementation MPHTMLInterstitialCustomEvent -@synthesize interstitial = _interstitial; +// Explicitly `@synthesize` here to fix a "-Wobjc-property-synthesis" warning because super class `delegate` is +// `id` and this `delegate` is `id` +@synthesize delegate; - (BOOL)enableAutomaticImpressionAndClickTracking { @@ -32,9 +39,8 @@ - (BOOL)enableAutomaticImpressionAndClickTracking - (void)requestInterstitialWithCustomEventInfo:(NSDictionary *)info { - MPLogInfo(@"Loading MoPub HTML interstitial"); - MPAdConfiguration *configuration = [self.delegate configuration]; - MPLogTrace(@"Loading HTML interstitial with source: %@", [configuration adResponseHTMLString]); + MPAdConfiguration * configuration = self.delegate.configuration; + MPLogAdEvent([MPLogEvent adLoadAttemptForAdapter:NSStringFromClass(self.class) dspCreativeId:configuration.dspCreativeId dspName:nil], self.adUnitId); self.interstitial = [[MPHTMLInterstitialViewController alloc] init]; self.interstitial.delegate = self; @@ -45,11 +51,23 @@ - (void)requestInterstitialWithCustomEventInfo:(NSDictionary *)info - (void)showInterstitialFromRootViewController:(UIViewController *)rootViewController { - [self.interstitial presentInterstitialFromViewController:rootViewController]; + MPLogAdEvent([MPLogEvent adShowAttemptForAdapter:NSStringFromClass(self.class)], self.adUnitId); + [self.interstitial presentInterstitialFromViewController:rootViewController complete:^(NSError * error) { + if (error != nil) { + MPLogAdEvent([MPLogEvent adShowFailedForAdapter:NSStringFromClass(self.class) error:error], self.adUnitId); + } + else { + MPLogAdEvent([MPLogEvent adShowSuccessForAdapter:NSStringFromClass(self.class)], self.adUnitId); + } + }]; } +@end + #pragma mark - MPInterstitialViewControllerDelegate +@implementation MPHTMLInterstitialCustomEvent (MPInterstitialViewControllerDelegate) + - (CLLocation *)location { return [self.delegate location]; @@ -60,27 +78,28 @@ - (NSString *)adUnitId return [self.delegate adUnitId]; } -- (void)interstitialDidLoadAd:(MPInterstitialViewController *)interstitial +- (void)interstitialDidLoadAd:(id)interstitial { - MPLogInfo(@"MoPub HTML interstitial did load"); + MPLogAdEvent([MPLogEvent adLoadSuccessForAdapter:NSStringFromClass(self.class)], self.adUnitId); [self.delegate interstitialCustomEvent:self didLoadAd:self.interstitial]; } -- (void)interstitialDidFailToLoadAd:(MPInterstitialViewController *)interstitial +- (void)interstitialDidFailToLoadAd:(id)interstitial { - MPLogInfo(@"MoPub HTML interstitial did fail"); - [self.delegate interstitialCustomEvent:self didFailToLoadAdWithError:nil]; + NSString * message = [NSString stringWithFormat:@"Failed to load creative:\n%@", self.delegate.configuration.adResponseHTMLString]; + NSError * error = [NSError errorWithCode:MOPUBErrorAdapterFailedToLoadAd localizedDescription:message]; + + MPLogAdEvent([MPLogEvent adLoadFailedForAdapter:NSStringFromClass(self.class) error:error], self.adUnitId); + [self.delegate interstitialCustomEvent:self didFailToLoadAdWithError:error]; } -- (void)interstitialWillAppear:(MPInterstitialViewController *)interstitial +- (void)interstitialWillAppear:(id)interstitial { - MPLogInfo(@"MoPub HTML interstitial will appear"); [self.delegate interstitialCustomEventWillAppear:self]; } -- (void)interstitialDidAppear:(MPInterstitialViewController *)interstitial +- (void)interstitialDidAppear:(id)interstitial { - MPLogInfo(@"MoPub HTML interstitial did appear"); [self.delegate interstitialCustomEventDidAppear:self]; if (!self.trackedImpression) { @@ -89,15 +108,13 @@ - (void)interstitialDidAppear:(MPInterstitialViewController *)interstitial } } -- (void)interstitialWillDisappear:(MPInterstitialViewController *)interstitial +- (void)interstitialWillDisappear:(id)interstitial { - MPLogInfo(@"MoPub HTML interstitial will disappear"); [self.delegate interstitialCustomEventWillDisappear:self]; } -- (void)interstitialDidDisappear:(MPInterstitialViewController *)interstitial +- (void)interstitialDidDisappear:(id)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, @@ -106,15 +123,13 @@ - (void)interstitialDidDisappear:(MPInterstitialViewController *)interstitial self.interstitial = nil; } -- (void)interstitialDidReceiveTapEvent:(MPInterstitialViewController *)interstitial +- (void)interstitialDidReceiveTapEvent:(id)interstitial { - MPLogInfo(@"MoPub HTML interstitial did receive tap event"); [self.delegate interstitialCustomEventDidReceiveTapEvent:self]; } -- (void)interstitialWillLeaveApplication:(MPInterstitialViewController *)interstitial +- (void)interstitialWillLeaveApplication:(id)interstitial { - MPLogInfo(@"MoPub HTML interstitial will leave application"); [self.delegate interstitialCustomEventWillLeaveApplication:self]; } diff --git a/MoPubSDK/Internal/HTML/MPHTMLInterstitialViewController.h b/MoPubSDK/Internal/HTML/MPHTMLInterstitialViewController.h index bc29f15de..554d859b0 100644 --- a/MoPubSDK/Internal/HTML/MPHTMLInterstitialViewController.h +++ b/MoPubSDK/Internal/HTML/MPHTMLInterstitialViewController.h @@ -1,7 +1,7 @@ // // MPHTMLInterstitialViewController.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/Internal/HTML/MPHTMLInterstitialViewController.m b/MoPubSDK/Internal/HTML/MPHTMLInterstitialViewController.m index ca2ceda8f..b2fb47e53 100644 --- a/MoPubSDK/Internal/HTML/MPHTMLInterstitialViewController.m +++ b/MoPubSDK/Internal/HTML/MPHTMLInterstitialViewController.m @@ -1,7 +1,7 @@ // // MPHTMLInterstitialViewController.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -21,10 +21,6 @@ @interface MPHTMLInterstitialViewController () @implementation MPHTMLInterstitialViewController -@synthesize delegate = _delegate; -@synthesize backingViewAgent = _backingViewAgent; -@synthesize backingView = _backingView; - - (void)dealloc { self.backingViewAgent.delegate = nil; @@ -76,11 +72,6 @@ - (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; @@ -101,18 +92,6 @@ - (void)didDismissInterstitial [self.delegate interstitialDidDisappear:self]; } -#pragma mark - Autorotation - -- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator -{ - [coordinator animateAlongsideTransition:^(id context) { - UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation]; - [self.backingViewAgent rotateToOrientation:orientation]; - } completion:nil]; - - [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]; -} - #pragma mark - MPAdWebViewAgentDelegate - (CLLocation *)location diff --git a/MoPubSDK/Internal/HTML/MPWebView.h b/MoPubSDK/Internal/HTML/MPWebView.h index 94e5bbbe9..31c01a4d0 100644 --- a/MoPubSDK/Internal/HTML/MPWebView.h +++ b/MoPubSDK/Internal/HTML/MPWebView.h @@ -1,28 +1,24 @@ // // MPWebView.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // -/*** - * 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. +/** + * @c MPWebView + * This class is a wrapper class for @c WKWebView. @c MPWebView 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. + * It's highly recommended that the caller uses @c `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 + * @c 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. - ***/ + * MPWebView currently does not support a few other features of WKWebView, such as pagination -- as WKWebView. + */ #import +#import @class MPWebView; @@ -32,7 +28,7 @@ - (BOOL)webView:(MPWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request - navigationType:(UIWebViewNavigationType)navigationType; + navigationType:(WKNavigationType)navigationType; - (void)webViewDidStartLoad:(MPWebView *)webView; @@ -47,11 +43,6 @@ typedef void (^MPWebViewJavascriptEvaluationCompletionHandler)(id result, NSErro @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; // When set to `YES`, `shouldConformToSafeArea` sets constraints on the WKWebView to always stay within the safe area @@ -59,17 +50,10 @@ typedef void (^MPWebViewJavascriptEvaluationCompletionHandler)(id result, NSErro // anchors to fill the whole container. Default is `NO`. // // This property has no effect on versions of iOS less than 11 or phones other than iPhone X. -// -// This property has no effect on UIWebView-based MPWebViews, as UIWebView only supports springs and struts, however -// this should not be an issue because UIWebView doesn't seem to be glitchy with the safe area. @property (nonatomic, assign) BOOL shouldConformToSafeArea; @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 >= MP_IOS_9_0 - (void)loadData:(NSData *)data MIMEType:(NSString *)MIMEType textEncodingName:(NSString *)encodingName @@ -77,10 +61,6 @@ textEncodingName:(NSString *)encodingName @property (nonatomic) BOOL allowsLinkPreview; @property (nonatomic, readonly) BOOL allowsPictureInPictureMediaPlayback; -#endif - -+ (void)forceWKWebView:(BOOL)shouldForce; -+ (BOOL)isForceWKWebView; - (void)loadHTMLString:(NSString *)string baseURL:(NSURL *)baseURL; @@ -109,8 +89,6 @@ textEncodingName:(NSString *)encodingName @property (nonatomic, readonly) BOOL mediaPlaybackRequiresUserAction; @property (nonatomic, readonly) BOOL mediaPlaybackAllowsAirPlay; -// UIWebView+MPAdditions methods - (void)mp_setScrollable:(BOOL)scrollable; -- (void)disableJavaScriptDialogs; @end diff --git a/MoPubSDK/Internal/HTML/MPWebView.m b/MoPubSDK/Internal/HTML/MPWebView.m index 76da75d36..8463b12d2 100644 --- a/MoPubSDK/Internal/HTML/MPWebView.m +++ b/MoPubSDK/Internal/HTML/MPWebView.m @@ -1,33 +1,30 @@ // // MPWebView.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // #import "MPWebView.h" - +#import "MPContentBlocker.h" #import static BOOL const kMoPubAllowsInlineMediaPlaybackDefault = YES; static BOOL const kMoPubRequiresUserActionForMediaPlaybackDefault = NO; -// Set defaults for this as its default differs between UIWebView and WKWebView +// Set defaults for this as its default differs between different iOS versions. static BOOL const kMoPubAllowsLinkPreviewDefault = NO; static NSString *const kMoPubJavaScriptDisableDialogScript = @"window.alert = function() { }; window.prompt = function() { }; window.confirm = function() { };"; static NSString *const kMoPubFrameKeyPathString = @"frame"; -static BOOL gForceWKWebView = NO; - -@interface MPWebView () +@interface MPWebView () @property (weak, nonatomic) WKWebView *wkWebView; -@property (weak, nonatomic) UIWebView *uiWebView; -@property (strong, nonatomic) NSArray *wkWebViewLayoutConstraints; +@property (strong, nonatomic) NSArray *webViewLayoutConstraints; @property (nonatomic, assign) BOOL hasMovedToWindow; @@ -37,7 +34,7 @@ @implementation MPWebView - (instancetype)init { if (self = [super init]) { - [self setUpStepsForceUIWebView:NO]; + [self setUp]; } return self; @@ -45,7 +42,7 @@ - (instancetype)init { - (instancetype)initWithCoder:(NSCoder *)aDecoder { if (self = [super initWithCoder:aDecoder]) { - [self setUpStepsForceUIWebView:NO]; + [self setUp]; } return self; @@ -53,65 +50,40 @@ - (instancetype)initWithCoder:(NSCoder *)aDecoder { - (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]; + [self setUp]; } return self; } -- (void)setUpStepsForceUIWebView:(BOOL)forceUIWebView { - // set up web view - UIView *webView; - - if ((gForceWKWebView || !forceUIWebView) && [WKWebView class]) { - WKUserContentController *contentController = [[WKUserContentController alloc] init]; - WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init]; - config.allowsInlineMediaPlayback = kMoPubAllowsInlineMediaPlaybackDefault; - if (@available(iOS 9.0, *)) { - config.requiresUserActionForMediaPlayback = kMoPubRequiresUserActionForMediaPlaybackDefault; - } else { - config.mediaPlaybackRequiresUserAction = kMoPubRequiresUserActionForMediaPlaybackDefault; - } - config.userContentController = contentController; - - WKWebView *wkWebView = [[WKWebView alloc] initWithFrame:self.bounds configuration:config]; +- (void)setUp { + WKUserContentController *contentController = [[WKUserContentController alloc] init]; + WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init]; + config.allowsInlineMediaPlayback = kMoPubAllowsInlineMediaPlaybackDefault; + config.requiresUserActionForMediaPlayback = kMoPubRequiresUserActionForMediaPlaybackDefault; + config.userContentController = contentController; - 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]; + if (@available(iOS 11, *)) { + [WKContentRuleListStore.defaultStore compileContentRuleListForIdentifier:@"ContentBlockingRules" + encodedContentRuleList:MPContentBlocker.blockedResourcesList + completionHandler:^(WKContentRuleList * rulesList, NSError * error) { + if (error == nil) { + [config.userContentController addContentRuleList:rulesList]; + } + }]; } - webView.backgroundColor = [UIColor clearColor]; - webView.opaque = NO; + WKWebView *wkWebView = [[WKWebView alloc] initWithFrame:self.bounds configuration:config]; + wkWebView.UIDelegate = self; + wkWebView.navigationDelegate = self; + self.wkWebView = wkWebView; - webView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + // Put WKWebView onto the offscreen view so any loading will complete correctly; see comment below. + [self retainWKWebViewOffscreen:wkWebView]; + + wkWebView.backgroundColor = [UIColor clearColor]; + wkWebView.opaque = NO; + wkWebView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; // set default scalesPageToFit self.scalesPageToFit = NO; @@ -123,7 +95,7 @@ - (void)setUpStepsForceUIWebView:(BOOL)forceUIWebView { self.backgroundColor = [UIColor clearColor]; self.opaque = NO; - // set default for allowsLinkPreview as they're different between WKWebView and UIWebView + // set default for allowsLinkPreview as they're different between iOS versions self.allowsLinkPreview = kMoPubAllowsLinkPreviewDefault; // set up KVO to adjust the frame of the WKWebView to avoid white screens @@ -173,7 +145,7 @@ - (void)didMoveToWindow { && [self.wkWebView.superview isEqual:gOffscreenView]) { self.wkWebView.frame = self.bounds; [self addSubview:self.wkWebView]; - [self constrainWebViewShouldUseSafeArea:self.shouldConformToSafeArea]; + [self constrainView:self.wkWebView shouldUseSafeArea:self.shouldConformToSafeArea]; self.hasMovedToWindow = YES; // Don't keep OffscreenView if we don't need it; it can always be re-allocated again later @@ -192,7 +164,7 @@ - (void)observeValueForKeyPath:(NSString *)keyPath // 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]) { - if (@available(iOS 11.0, *)) { + if (@available(iOS 11, *)) { // In iOS 11, WKWebView loads web view contents into the safe area only unless `viewport-fit=cover` is // included in the page's viewport tag. Also, as of iOS 11, it appears WKWebView does not redraw page // contents to match the safe area of a new position after being moved. As a result, making `wkWebView`'s @@ -229,282 +201,147 @@ - (void)setShouldConformToSafeArea:(BOOL)shouldConformToSafeArea { _shouldConformToSafeArea = shouldConformToSafeArea; if (self.hasMovedToWindow) { - [self constrainWebViewShouldUseSafeArea:shouldConformToSafeArea]; + [self constrainView:self.wkWebView shouldUseSafeArea:shouldConformToSafeArea]; } } -- (void)constrainWebViewShouldUseSafeArea:(BOOL)shouldUseSafeArea { - if (@available(iOS 11.0, *)) { - self.wkWebView.translatesAutoresizingMaskIntoConstraints = NO; +- (void)constrainView:(UIView *)view shouldUseSafeArea:(BOOL)shouldUseSafeArea { + if (@available(iOS 11, *)) { + view.translatesAutoresizingMaskIntoConstraints = NO; - if (self.wkWebViewLayoutConstraints) { - [NSLayoutConstraint deactivateConstraints:self.wkWebViewLayoutConstraints]; + if (self.webViewLayoutConstraints) { + [NSLayoutConstraint deactivateConstraints:self.webViewLayoutConstraints]; } if (shouldUseSafeArea) { - self.wkWebViewLayoutConstraints = @[ - [self.wkWebView.topAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.topAnchor], - [self.wkWebView.leadingAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.leadingAnchor], - [self.wkWebView.trailingAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.trailingAnchor], - [self.wkWebView.bottomAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.bottomAnchor], - ]; + self.webViewLayoutConstraints = @[ + [view.topAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.topAnchor], + [view.leadingAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.leadingAnchor], + [view.trailingAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.trailingAnchor], + [view.bottomAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.bottomAnchor], + ]; } else { - self.wkWebViewLayoutConstraints = @[ - [self.wkWebView.topAnchor constraintEqualToAnchor:self.topAnchor], - [self.wkWebView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor], - [self.wkWebView.trailingAnchor constraintEqualToAnchor:self.trailingAnchor], - [self.wkWebView.bottomAnchor constraintEqualToAnchor:self.bottomAnchor], - ]; + self.webViewLayoutConstraints = @[ + [view.topAnchor constraintEqualToAnchor:self.topAnchor], + [view.leadingAnchor constraintEqualToAnchor:self.leadingAnchor], + [view.trailingAnchor constraintEqualToAnchor:self.trailingAnchor], + [view.bottomAnchor constraintEqualToAnchor:self.bottomAnchor], + ]; } - [NSLayoutConstraint activateConstraints:self.wkWebViewLayoutConstraints]; + [NSLayoutConstraint activateConstraints:self.webViewLayoutConstraints]; } } - (BOOL)isLoading { - return self.uiWebView ? self.uiWebView.isLoading : self.wkWebView.isLoading; + return 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 (@available(iOS 9.0, *)) { - [self.wkWebView loadData:data - MIMEType:MIMEType - characterEncodingName:encodingName - baseURL:baseURL]; - } + [self.wkWebView loadData:data + MIMEType:MIMEType + characterEncodingName:encodingName + baseURL:baseURL]; } -+ (void)forceWKWebView:(BOOL)shouldForce -{ - gForceWKWebView = shouldForce; -} - -+ (BOOL)isForceWKWebView -{ - return gForceWKWebView; -} - -- (void)loadHTMLString:(NSString *)string - baseURL:(NSURL *)baseURL { - if (self.uiWebView) { - [self.uiWebView loadHTMLString:string - baseURL:baseURL]; - } else { - [self.wkWebView loadHTMLString:string - baseURL:baseURL]; - } +- (void)loadHTMLString:(NSString *)string baseURL:(NSURL *)baseURL { + [self.wkWebView loadHTMLString:string baseURL:baseURL]; } - (void)loadRequest:(NSURLRequest *)request { - if (self.uiWebView) { - [self.uiWebView loadRequest:request]; - } else { - [self.wkWebView loadRequest:request]; - } + [self.wkWebView loadRequest:request]; } - (void)stopLoading { - if (self.uiWebView) { - [self.uiWebView stopLoading]; - } else { - [self.wkWebView stopLoading]; - } + [self.wkWebView stopLoading]; } - (void)reload { - if (self.uiWebView) { - [self.uiWebView reload]; - } else { - [self.wkWebView reload]; - } + [self.wkWebView reload]; } - (BOOL)canGoBack { - return self.uiWebView ? self.uiWebView.canGoBack : self.wkWebView.canGoBack; + return self.wkWebView.canGoBack; } - (BOOL)canGoForward { - return self.uiWebView ? self.uiWebView.canGoForward : self.wkWebView.canGoForward; + return self.wkWebView.canGoForward; } - (void)goBack { - if (self.uiWebView) { - [self.uiWebView goBack]; - } else { - [self.wkWebView goBack]; - } + [self.wkWebView goBack]; } - (void)goForward { - if (self.uiWebView) { - [self.uiWebView goForward]; - } else { - [self.wkWebView goForward]; - } + [self.wkWebView goForward]; } - (void)setAllowsLinkPreview:(BOOL)allowsLinkPreview { - if (@available(iOS 9.0, *)) { - if (self.uiWebView) { - self.uiWebView.allowsLinkPreview = allowsLinkPreview; - } else { - self.wkWebView.allowsLinkPreview = allowsLinkPreview; - } - } + self.wkWebView.allowsLinkPreview = allowsLinkPreview; } - (BOOL)allowsLinkPreview { - if (@available(iOS 9.0, *)) { - if (self.uiWebView) { - return self.uiWebView.allowsLinkPreview; - } else { - return self.wkWebView.allowsLinkPreview; - } - } - - return NO; + return self.wkWebView.allowsLinkPreview; } - (void)setScalesPageToFit:(BOOL)scalesPageToFit { - if (self.uiWebView) { - self.uiWebView.scalesPageToFit = scalesPageToFit; - } else { - if (scalesPageToFit) { - self.wkWebView.scrollView.delegate = nil; + 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; - } + [self.wkWebView.configuration.userContentController removeAllUserScripts]; + } else { + // Make sure the scroll view can't scroll (prevent double tap to zoom) + self.wkWebView.scrollView.delegate = self; } } - (BOOL)scalesPageToFit { - return self.uiWebView ? self.uiWebView.scalesPageToFit : self.scrollView.delegate == nil; + return self.scrollView.delegate == nil; } - (UIScrollView *)scrollView { - return self.uiWebView ? self.uiWebView.scrollView : self.wkWebView.scrollView; + return 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]; - } + [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. + // 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 + // 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 @""; - } + [self.wkWebView evaluateJavaScript:javaScriptString completionHandler:nil]; + return @""; } - (BOOL)allowsInlineMediaPlayback { - return self.uiWebView ? self.uiWebView.allowsInlineMediaPlayback : self.wkWebView.configuration.allowsInlineMediaPlayback; + return self.wkWebView.configuration.allowsInlineMediaPlayback; } - (BOOL)mediaPlaybackRequiresUserAction { - if (self.uiWebView) { - return self.uiWebView.mediaPlaybackRequiresUserAction; - } else { - if (@available(iOS 9.0, *)) { - return self.wkWebView.configuration.requiresUserActionForMediaPlayback; - } else if (![self.wkWebView.configuration respondsToSelector:@selector(requiresUserActionForMediaPlayback)]) { - return self.wkWebView.configuration.mediaPlaybackRequiresUserAction; - } else { - return NO; - } - } + return self.wkWebView.configuration.requiresUserActionForMediaPlayback; } - (BOOL)mediaPlaybackAllowsAirPlay { - if (self.uiWebView) { - return self.uiWebView.mediaPlaybackAllowsAirPlay; - } else { - if (@available(iOS 9.0, *)) { - return self.wkWebView.configuration.allowsAirPlayForMediaPlayback; - } else if (![self.wkWebView.configuration respondsToSelector:@selector(allowsAirPlayForMediaPlayback)]) { - return self.wkWebView.configuration.mediaPlaybackAllowsAirPlay; - } else { - return NO; - } - } + return self.wkWebView.configuration.allowsAirPlayForMediaPlayback; } - (BOOL)allowsPictureInPictureMediaPlayback { - if (@available(iOS 9.0, *)) { - if (self.uiWebView) { - return self.uiWebView.allowsPictureInPictureMediaPlayback; - } else { - return self.wkWebView.configuration.allowsPictureInPictureMediaPlayback; - } - } - - return NO; + return self.wkWebView.configuration.allowsPictureInPictureMediaPlayback; } -#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 +#pragma mark - UIScrollView related /// Find all subviews that are UIScrollViews or subclasses and set their scrolling and bounce. - (void)mp_setScrollable:(BOOL)scrollable { @@ -513,14 +350,6 @@ - (void)mp_setScrollable:(BOOL)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 { @@ -557,32 +386,9 @@ - (void)webView:(WKWebView *)webView 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; + shouldStartLoadWithRequest:navigationAction.request + navigationType:navigationAction.navigationType] ? WKNavigationActionPolicyAllow : WKNavigationActionPolicyCancel; } decisionHandler(policy); @@ -617,11 +423,7 @@ - (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView { - (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame -#if __IPHONE_OS_VERSION_MAX_ALLOWED < MP_IOS_9_0 // 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(); } diff --git a/MoPubSDK/Internal/Interstitials/MPBaseInterstitialAdapter.h b/MoPubSDK/Internal/Interstitials/MPBaseInterstitialAdapter.h index 1bb129aa7..f48397221 100644 --- a/MoPubSDK/Internal/Interstitials/MPBaseInterstitialAdapter.h +++ b/MoPubSDK/Internal/Interstitials/MPBaseInterstitialAdapter.h @@ -1,7 +1,7 @@ // // MPBaseInterstitialAdapter.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -67,5 +67,6 @@ - (void)interstitialDidExpireForAdapter:(MPBaseInterstitialAdapter *)adapter; - (void)interstitialDidReceiveTapEventForAdapter:(MPBaseInterstitialAdapter *)adapter; - (void)interstitialWillLeaveApplicationForAdapter:(MPBaseInterstitialAdapter *)adapter; +- (void)interstitialDidReceiveImpressionEventForAdapter:(MPBaseInterstitialAdapter *)adapter; @end diff --git a/MoPubSDK/Internal/Interstitials/MPBaseInterstitialAdapter.m b/MoPubSDK/Internal/Interstitials/MPBaseInterstitialAdapter.m index a87dc78c7..f4b2f462b 100644 --- a/MoPubSDK/Internal/Interstitials/MPBaseInterstitialAdapter.m +++ b/MoPubSDK/Internal/Interstitials/MPBaseInterstitialAdapter.m @@ -1,7 +1,7 @@ // // MPBaseInterstitialAdapter.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -26,10 +26,6 @@ - (void)startTimeoutTimer; @implementation MPBaseInterstitialAdapter -@synthesize delegate = _delegate; -@synthesize configuration = _configuration; -@synthesize timeoutTimer = _timeoutTimer; - - (id)initWithDelegate:(id)delegate { self = [super init]; @@ -72,11 +68,10 @@ - (void)startTimeoutTimer self.configuration.adTimeoutInterval : INTERSTITIAL_TIMEOUT_INTERVAL; if (timeInterval > 0) { - self.timeoutTimer = [[MPCoreInstanceProvider sharedProvider] buildMPTimerWithTimeInterval:timeInterval - target:self - selector:@selector(timeout) - repeats:NO]; - + self.timeoutTimer = [MPTimer timerWithTimeInterval:timeInterval + target:self + selector:@selector(timeout) + repeats:NO]; [self.timeoutTimer scheduleNow]; } } @@ -88,7 +83,7 @@ - (void)didStopLoading - (void)timeout { - NSError * error = [MOPUBError errorWithCode:MOPUBErrorAdRequestTimedOut localizedDescription:@"Interstitial ad request timed out"]; + NSError * error = [NSError errorWithCode:MOPUBErrorAdRequestTimedOut localizedDescription:@"Interstitial ad request timed out"]; [self.delegate adapter:self didFailToLoadAdWithError:error]; self.delegate = nil; } @@ -105,6 +100,7 @@ - (void)showInterstitialFromViewController:(UIViewController *)controller - (void)trackImpression { [[MPAnalyticsTracker sharedTracker] trackImpressionForConfiguration:self.configuration]; + [self.delegate interstitialDidReceiveImpressionEventForAdapter:self]; } - (void)trackClick diff --git a/MoPubSDK/Internal/Interstitials/MPInterstitialAdManager.h b/MoPubSDK/Internal/Interstitials/MPInterstitialAdManager.h index 4a6be0f2a..7f29cb935 100644 --- a/MoPubSDK/Internal/Interstitials/MPInterstitialAdManager.h +++ b/MoPubSDK/Internal/Interstitials/MPInterstitialAdManager.h @@ -1,7 +1,7 @@ // // MPInterstitialAdManager.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -18,6 +18,10 @@ @property (nonatomic, weak) id delegate; @property (nonatomic, assign, readonly) BOOL ready; +@property (nonatomic, readonly) Class customEventClass; +@property (nonatomic, readonly) NSString* dspCreativeId; +@property (nonatomic, readonly) NSString* lineItemId; + - (id)initWithDelegate:(id)delegate; - (void)loadInterstitialWithAdUnitID:(NSString *)ID targeting:(MPAdTargeting *)targeting; diff --git a/MoPubSDK/Internal/Interstitials/MPInterstitialAdManager.m b/MoPubSDK/Internal/Interstitials/MPInterstitialAdManager.m index 52e9c2c5f..29da5bb84 100644 --- a/MoPubSDK/Internal/Interstitials/MPInterstitialAdManager.m +++ b/MoPubSDK/Internal/Interstitials/MPInterstitialAdManager.m @@ -1,7 +1,7 @@ // // MPInterstitialAdManager.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -14,6 +14,7 @@ #import "MPAdTargeting.h" #import "MPInterstitialAdController.h" #import "MPInterstitialCustomEventAdapter.h" +#import "MPConstants.h" #import "MPCoreInstanceProvider.h" #import "MPInterstitialAdManagerDelegate.h" #import "MPLogging.h" @@ -32,6 +33,7 @@ @interface MPInterstitialAdManager () @property (nonatomic, strong) NSMutableArray *remainingConfigurations; @property (nonatomic, assign) NSTimeInterval adapterLoadStartTimestamp; @property (nonatomic, strong) MPAdTargeting * targeting; +@property (nonatomic, strong) NSURL *mostRecentlyLoadedURL; // ADF-4286: avoid infinite ad reloads - (void)setUpAdapterWithConfiguration:(MPAdConfiguration *)configuration; @@ -41,13 +43,6 @@ - (void)setUpAdapterWithConfiguration:(MPAdConfiguration *)configuration; @implementation MPInterstitialAdManager -@synthesize loading = _loading; -@synthesize ready = _ready; -@synthesize delegate = _delegate; -@synthesize communicator = _communicator; -@synthesize adapter = _adapter; -@synthesize requestingConfiguration = _requestingConfiguration; - - (id)initWithDelegate:(id)delegate { self = [super init]; @@ -66,6 +61,20 @@ - (void)dealloc self.adapter = nil; } +- (Class)customEventClass +{ + return self.requestingConfiguration.customEventClass; +} + +- (NSString*)dspCreativeId +{ + return self.requestingConfiguration.dspCreativeId; +} + +- (NSString*)lineItemId { + return self.requestingConfiguration.lineItemId; +} + - (void)setAdapter:(MPBaseInterstitialAdapter *)adapter { if (self.adapter != adapter) { @@ -79,36 +88,35 @@ - (void)setAdapter:(MPBaseInterstitialAdapter *)adapter - (void)loadAdWithURL:(NSURL *)URL { if (self.loading) { - MPLogWarn(@"Interstitial controller is already loading an ad. " - @"Wait for previous load to finish."); + MPLogEvent([MPLogEvent error:NSError.adAlreadyLoading message:nil]); return; } self.loading = YES; + self.mostRecentlyLoadedURL = URL; [self.communicator loadURL:URL]; } - (void)loadInterstitialWithAdUnitID:(NSString *)ID targeting:(MPAdTargeting *)targeting { + MPLogAdEvent(MPLogEvent.adLoadAttempt, ID); + if (self.ready) { [self.delegate managerDidLoadInterstitial:self]; } else { self.targeting = targeting; - [self loadAdWithURL:[MPAdServerURLBuilder URLWithAdUnitID:ID - keywords:targeting.keywords - userDataKeywords:targeting.userDataKeywords - location:targeting.location]]; + [self loadAdWithURL:[MPAdServerURLBuilder URLWithAdUnitID:ID targeting:targeting]]; } } - (void)presentInterstitialFromViewController:(UIViewController *)controller { + MPLogAdEvent(MPLogEvent.adShowAttempt, self.delegate.interstitialAdController.adUnitId); + // Don't allow the ad to be shown if it isn't ready. if (!self.ready) { - // We don't want to remotely log this event -- it's simply for publisher troubleshooting -- so use NSLog - // rather than MPLog. - NSLog(@"Interstitial ad view is not ready to be shown"); + MPLogInfo(@"Interstitial ad view is not ready to be shown"); return; } @@ -140,7 +148,7 @@ - (void)communicatorDidReceiveAdConfigurations:(NSArray *)c if (self.remainingConfigurations.count == 0 && self.requestingConfiguration == nil) { MPLogInfo(kMPClearErrorLogFormatWithAdUnitID, self.delegate.interstitialAdController.adUnitId); self.loading = NO; - [self.delegate manager:self didFailToLoadInterstitialWithError:[MOPUBError errorWithCode:MOPUBErrorNoInventory]]; + [self.delegate manager:self didFailToLoadInterstitialWithError:[NSError errorWithCode:MOPUBErrorNoInventory]]; return; } @@ -154,21 +162,14 @@ - (void)fetchAdWithConfiguration:(MPAdConfiguration *)configuration if (configuration.adUnitWarmingUp) { MPLogInfo(kMPWarmingUpErrorLogFormatWithAdUnitID, self.delegate.interstitialAdController.adUnitId); self.loading = NO; - [self.delegate manager:self didFailToLoadInterstitialWithError:[MOPUBError errorWithCode:MOPUBErrorAdUnitWarmingUp]]; + [self.delegate manager:self didFailToLoadInterstitialWithError:[NSError errorWithCode:MOPUBErrorAdUnitWarmingUp]]; return; } if ([configuration.networkType isEqualToString:kAdTypeClear]) { MPLogInfo(kMPClearErrorLogFormatWithAdUnitID, self.delegate.interstitialAdController.adUnitId); self.loading = NO; - [self.delegate manager:self didFailToLoadInterstitialWithError:[MOPUBError errorWithCode:MOPUBErrorNoInventory]]; - return; - } - - if (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]]; + [self.delegate manager:self didFailToLoadInterstitialWithError:[NSError errorWithCode:MOPUBErrorNoInventory]]; return; } @@ -183,7 +184,7 @@ - (void)communicatorDidFailWithError:(NSError *)error [self.delegate manager:self didFailToLoadInterstitialWithError:error]; } -- (void)setUpAdapterWithConfiguration:(MPAdConfiguration *)configuration; +- (void)setUpAdapterWithConfiguration:(MPAdConfiguration *)configuration { // Notify Ad Server of the adapter load. This is fire and forget. [self.communicator sendBeforeLoadUrlWithConfiguration:configuration]; @@ -196,11 +197,21 @@ - (void)setUpAdapterWithConfiguration:(MPAdConfiguration *)configuration; return; } + [self.delegate managerWillStartInterstitialAttempt:self]; + MPBaseInterstitialAdapter *adapter = [[MPInterstitialCustomEventAdapter alloc] initWithDelegate:self]; self.adapter = adapter; [self.adapter _getAdWithConfiguration:configuration targeting:self.targeting]; } +- (MPAdType)adTypeForAdServerCommunicator:(MPAdServerCommunicator *)adServerCommunicator { + return MPAdTypeFullscreen; +} + +- (NSString *)adUnitIDForAdServerCommunicator:(MPAdServerCommunicator *)adServerCommunicator { + return [self.delegate adUnitId]; +} + #pragma mark - MPInterstitialAdapterDelegate - (void)adapterDidFinishLoadingAd:(MPBaseInterstitialAdapter *)adapter @@ -209,15 +220,20 @@ - (void)adapterDidFinishLoadingAd:(MPBaseInterstitialAdapter *)adapter self.ready = YES; self.loading = NO; + [self.delegate managerDidSucceedInterstitialAttempt:self]; + // Record the end of the adapter load and send off the fire and forget after-load-url tracker. NSTimeInterval duration = NSDate.now.timeIntervalSince1970 - self.adapterLoadStartTimestamp; [self.communicator sendAfterLoadUrlWithConfiguration:self.requestingConfiguration adapterLoadDuration:duration adapterLoadResult:MPAfterLoadResultAdLoaded]; + MPLogAdEvent(MPLogEvent.adDidLoad, self.delegate.interstitialAdController.adUnitId); [self.delegate managerDidLoadInterstitial:self]; } - (void)adapter:(MPBaseInterstitialAdapter *)adapter didFailToLoadAdWithError:(NSError *)error { + [self.delegate manager:self didFailInterstitialAttemptWithError:error]; + // Record the end of the adapter load and send off the fire and forget after-load-url tracker // with the appropriate error code result. NSTimeInterval duration = NSDate.now.timeIntervalSince1970 - self.adapterLoadStartTimestamp; @@ -230,7 +246,8 @@ - (void)adapter:(MPBaseInterstitialAdapter *)adapter didFailToLoadAdWithError:(N [self fetchAdWithConfiguration:self.requestingConfiguration]; } // No more configurations to try. Send new request to Ads server to get more Ads. - else if (self.requestingConfiguration.nextURL != nil) { + else if (self.requestingConfiguration.nextURL != nil + && [self.requestingConfiguration.nextURL isEqual:self.mostRecentlyLoadedURL] == false) { self.ready = NO; self.loading = NO; [self loadAdWithURL:self.requestingConfiguration.nextURL]; @@ -240,46 +257,59 @@ - (void)adapter:(MPBaseInterstitialAdapter *)adapter didFailToLoadAdWithError:(N self.ready = NO; self.loading = NO; - MPLogInfo(kMPClearErrorLogFormatWithAdUnitID, self.delegate.interstitialAdController.adUnitId); - [self.delegate manager:self didFailToLoadInterstitialWithError:[MOPUBError errorWithCode:MOPUBErrorNoInventory]]; + NSError * clearResponseError = [NSError errorWithCode:MOPUBErrorNoInventory localizedDescription:[NSString stringWithFormat:kMPClearErrorLogFormatWithAdUnitID, self.delegate.interstitialAdController.adUnitId]]; + MPLogAdEvent([MPLogEvent adFailedToLoadWithError:clearResponseError], self.delegate.interstitialAdController.adUnitId); + [self.delegate manager:self didFailToLoadInterstitialWithError:clearResponseError]; } } - (void)interstitialWillAppearForAdapter:(MPBaseInterstitialAdapter *)adapter { + MPLogAdEvent(MPLogEvent.adWillAppear, self.delegate.interstitialAdController.adUnitId); [self.delegate managerWillPresentInterstitial:self]; } - (void)interstitialDidAppearForAdapter:(MPBaseInterstitialAdapter *)adapter { + MPLogAdEvent(MPLogEvent.adDidAppear, self.delegate.interstitialAdController.adUnitId); [self.delegate managerDidPresentInterstitial:self]; } - (void)interstitialWillDisappearForAdapter:(MPBaseInterstitialAdapter *)adapter { + MPLogAdEvent(MPLogEvent.adWillDisappear, self.delegate.interstitialAdController.adUnitId); [self.delegate managerWillDismissInterstitial:self]; } - (void)interstitialDidDisappearForAdapter:(MPBaseInterstitialAdapter *)adapter { self.ready = NO; + + MPLogAdEvent(MPLogEvent.adDidDisappear, self.delegate.interstitialAdController.adUnitId); [self.delegate managerDidDismissInterstitial:self]; } - (void)interstitialDidExpireForAdapter:(MPBaseInterstitialAdapter *)adapter { self.ready = NO; + + MPLogAdEvent([MPLogEvent adExpiredWithTimeInterval:MPConstants.adsExpirationInterval], self.delegate.interstitialAdController.adUnitId); [self.delegate managerDidExpireInterstitial:self]; } - (void)interstitialDidReceiveTapEventForAdapter:(MPBaseInterstitialAdapter *)adapter { + MPLogAdEvent(MPLogEvent.adWillPresentModal, self.delegate.interstitialAdController.adUnitId); [self.delegate managerDidReceiveTapEventFromInterstitial:self]; } - (void)interstitialWillLeaveApplicationForAdapter:(MPBaseInterstitialAdapter *)adapter { - //noop + MPLogAdEvent(MPLogEvent.adWillLeaveApplication, self.delegate.interstitialAdController.adUnitId); +} + +- (void)interstitialDidReceiveImpressionEventForAdapter:(MPBaseInterstitialAdapter *)adapter { + [self.delegate interstitialAdManager:self didReceiveImpressionEventWithImpressionData:self.requestingConfiguration.impressionData]; } @end diff --git a/MoPubSDK/Internal/Interstitials/MPInterstitialAdManagerDelegate.h b/MoPubSDK/Internal/Interstitials/MPInterstitialAdManagerDelegate.h index ed5e47eda..4f8c0733c 100644 --- a/MoPubSDK/Internal/Interstitials/MPInterstitialAdManagerDelegate.h +++ b/MoPubSDK/Internal/Interstitials/MPInterstitialAdManagerDelegate.h @@ -1,7 +1,7 @@ // // MPInterstitialAdManagerDelegate.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -10,13 +10,20 @@ @class MPInterstitialAdManager; @class MPInterstitialAdController; +@class MPImpressionData; @class CLLocation; @protocol MPInterstitialAdManagerDelegate - (MPInterstitialAdController *)interstitialAdController; - (CLLocation *)location; +- (NSString *)adUnitId; - (id)interstitialDelegate; + +- (void)managerWillStartInterstitialAttempt:(MPInterstitialAdManager *)manager; +- (void)managerDidSucceedInterstitialAttempt:(MPInterstitialAdManager *)manager; +- (void)manager:(MPInterstitialAdManager *)manager didFailInterstitialAttemptWithError:(NSError*)error; + - (void)managerDidLoadInterstitial:(MPInterstitialAdManager *)manager; - (void)manager:(MPInterstitialAdManager *)manager didFailToLoadInterstitialWithError:(NSError *)error; @@ -25,6 +32,7 @@ didFailToLoadInterstitialWithError:(NSError *)error; - (void)managerWillDismissInterstitial:(MPInterstitialAdManager *)manager; - (void)managerDidDismissInterstitial:(MPInterstitialAdManager *)manager; - (void)managerDidExpireInterstitial:(MPInterstitialAdManager *)manager; +- (void)interstitialAdManager:(MPInterstitialAdManager *)manager didReceiveImpressionEventWithImpressionData:(MPImpressionData *)impressionData; - (void)managerDidReceiveTapEventFromInterstitial:(MPInterstitialAdManager *)manager; @end diff --git a/MoPubSDK/Internal/Interstitials/MPInterstitialCustomEventAdapter.h b/MoPubSDK/Internal/Interstitials/MPInterstitialCustomEventAdapter.h index 9cf4f77e8..294920724 100644 --- a/MoPubSDK/Internal/Interstitials/MPInterstitialCustomEventAdapter.h +++ b/MoPubSDK/Internal/Interstitials/MPInterstitialCustomEventAdapter.h @@ -1,7 +1,7 @@ // // MPInterstitialCustomEventAdapter.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/Internal/Interstitials/MPInterstitialCustomEventAdapter.m b/MoPubSDK/Internal/Interstitials/MPInterstitialCustomEventAdapter.m index fdbb7246b..b391aa8c9 100644 --- a/MoPubSDK/Internal/Interstitials/MPInterstitialCustomEventAdapter.m +++ b/MoPubSDK/Internal/Interstitials/MPInterstitialCustomEventAdapter.m @@ -1,7 +1,7 @@ // // MPInterstitialCustomEventAdapter.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -12,6 +12,7 @@ #import "MPAdTargeting.h" #import "MPConstants.h" #import "MPCoreInstanceProvider.h" +#import "MPError.h" #import "MPHTMLInterstitialCustomEvent.h" #import "MPLogging.h" #import "MPInterstitialCustomEvent.h" @@ -30,10 +31,6 @@ @interface MPInterstitialCustomEventAdapter () @end @implementation MPInterstitialCustomEventAdapter -@synthesize hasTrackedImpression = _hasTrackedImpression; -@synthesize hasTrackedClick = _hasTrackedClick; - -@synthesize interstitialCustomEvent = _interstitialCustomEvent; - (void)dealloc { @@ -56,21 +53,15 @@ - (void)getAdWithConfiguration:(MPAdConfiguration *)configuration targeting:(MPA MPInterstitialCustomEvent *customEvent = [[configuration.customEventClass alloc] init]; if (![customEvent isKindOfClass:[MPInterstitialCustomEvent class]]) { - MPLogError(@"**** Custom Event Class: %@ does not extend MPInterstitialCustomEvent ****", NSStringFromClass(configuration.customEventClass)); - [self.delegate adapter:self didFailToLoadAdWithError:nil]; + NSError * error = [NSError customEventClass:configuration.customEventClass doesNotInheritFrom:MPInterstitialCustomEvent.class]; + MPLogEvent([MPLogEvent error:error message:nil]); + [self.delegate adapter:self didFailToLoadAdWithError:error]; return; } customEvent.delegate = self; customEvent.localExtras = targeting.localExtras; - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wundeclared-selector" - 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(configuration.customEventClass)); - } -#pragma clang diagnostic pop - self.interstitialCustomEvent = customEvent; + [self.interstitialCustomEvent requestInterstitialWithCustomEventInfo:configuration.customEventClassData adMarkup:configuration.advancedBidPayload]; } diff --git a/MoPubSDK/Internal/Interstitials/MPInterstitialViewController.h b/MoPubSDK/Internal/Interstitials/MPInterstitialViewController.h index cfbd5dee1..ccf03f00e 100644 --- a/MoPubSDK/Internal/Interstitials/MPInterstitialViewController.h +++ b/MoPubSDK/Internal/Interstitials/MPInterstitialViewController.h @@ -1,28 +1,36 @@ // // MPInterstitialViewController.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // #import +#import "MPExtendedHitBoxButton.h" #import "MPGlobal.h" @class CLLocation; +/** + The purpose of this @c MPInterstitialViewController protocol is to define the common interface + between interstitial view controllers without forcing them to subclass @c MPInterstitialViewController. + */ +@protocol MPInterstitialViewController +@end + @protocol MPInterstitialViewControllerDelegate; //////////////////////////////////////////////////////////////////////////////////////////////////// -@interface MPInterstitialViewController : UIViewController +@interface MPInterstitialViewController : UIViewController @property (nonatomic, assign) MPInterstitialCloseButtonStyle closeButtonStyle; @property (nonatomic, assign) MPInterstitialOrientationType orientationType; -@property (nonatomic, strong) UIButton *closeButton; +@property (nonatomic, strong) MPExtendedHitBoxButton *closeButton; @property (nonatomic, weak) id delegate; -- (void)presentInterstitialFromViewController:(UIViewController *)controller; +- (void)presentInterstitialFromViewController:(UIViewController *)controller complete:(void(^)(NSError *))complete; - (void)dismissInterstitialAnimated:(BOOL)animated; - (BOOL)shouldDisplayCloseButton; - (void)willPresentInterstitial; @@ -35,17 +43,22 @@ //////////////////////////////////////////////////////////////////////////////////////////////////// -@protocol MPInterstitialViewControllerDelegate +@protocol MPInterstitialViewControllerAppearanceDelegate + +- (void)interstitialWillAppear:(id)interstitial; +- (void)interstitialDidAppear:(id)interstitial; +- (void)interstitialWillDisappear:(id)interstitial; +- (void)interstitialDidDisappear:(id)interstitial; + +@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; +- (void)interstitialDidLoadAd:(id)interstitial; +- (void)interstitialDidFailToLoadAd:(id)interstitial; +- (void)interstitialDidReceiveTapEvent:(id)interstitial; +- (void)interstitialWillLeaveApplication:(id)interstitial; @optional - (CLLocation *)location; diff --git a/MoPubSDK/Internal/Interstitials/MPInterstitialViewController.m b/MoPubSDK/Internal/Interstitials/MPInterstitialViewController.m index 9e32143a3..3b19877a3 100644 --- a/MoPubSDK/Internal/Interstitials/MPInterstitialViewController.m +++ b/MoPubSDK/Internal/Interstitials/MPInterstitialViewController.m @@ -1,16 +1,15 @@ // // MPInterstitialViewController.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // #import "MPInterstitialViewController.h" -#import "MPGlobal.h" +#import "MPError.h" #import "MPLogging.h" -#import "UIButton+MPAdditions.h" static const CGFloat kCloseButtonPadding = 5.0; static const CGFloat kCloseButtonEdgeInset = 5.0; @@ -18,13 +17,10 @@ @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 @@ -32,18 +28,12 @@ - (void)setApplicationStatusBarHidden:(BOOL)hidden; @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]; + self.modalPresentationStyle = UIModalPresentationFullScreen; } - (BOOL)prefersHomeIndicatorAutoHidden { @@ -52,22 +42,23 @@ - (BOOL)prefersHomeIndicatorAutoHidden { #pragma mark - Public -- (void)presentInterstitialFromViewController:(UIViewController *)controller +- (void)presentInterstitialFromViewController:(UIViewController *)controller complete:(void(^)(NSError *))complete { if (self.presentingViewController) { - MPLogWarn(@"Cannot present an interstitial that is already on-screen."); + if (complete != nil) { + complete(NSError.fullscreenAdAlreadyOnScreen); + } return; } [self willPresentInterstitial]; - - self.applicationHasStatusBar = !([UIApplication sharedApplication].isStatusBarHidden); - [self setApplicationStatusBarHidden:YES]; - [self layoutCloseButton]; [controller presentViewController:self animated:MP_ANIMATED completion:^{ [self didPresentInterstitial]; + if (complete != nil) { + complete(nil); + } }]; } @@ -101,7 +92,7 @@ - (BOOL)shouldDisplayCloseButton - (UIButton *)closeButton { if (!_closeButton) { - _closeButton = [UIButton buttonWithType:UIButtonTypeCustom]; + _closeButton = [MPExtendedHitBoxButton buttonWithType:UIButtonTypeCustom]; _closeButton.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleBottomMargin; @@ -127,9 +118,9 @@ - (void)layoutCloseButton kCloseButtonPadding, self.closeButton.bounds.size.width, self.closeButton.bounds.size.height); - self.closeButton.mp_TouchAreaInsets = UIEdgeInsetsMake(kCloseButtonEdgeInset, kCloseButtonEdgeInset, kCloseButtonEdgeInset, kCloseButtonEdgeInset); + self.closeButton.touchAreaInsets = UIEdgeInsetsMake(kCloseButtonEdgeInset, kCloseButtonEdgeInset, kCloseButtonEdgeInset, kCloseButtonEdgeInset); [self setCloseButtonStyle:self.closeButtonStyle]; - if (@available(iOS 11.0, *)) { + if (@available(iOS 11, *)) { self.closeButton.translatesAutoresizingMaskIntoConstraints = NO; [NSLayoutConstraint activateConstraints:@[ [self.closeButton.topAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor constant:kCloseButtonPadding], @@ -172,8 +163,6 @@ - (void)closeButtonPressed - (void)dismissInterstitialAnimated:(BOOL)animated { - [self setApplicationStatusBarHidden:!self.applicationHasStatusBar]; - [self willDismissInterstitial]; UIViewController *presentingViewController = self.presentingViewController; @@ -187,32 +176,12 @@ - (void)dismissInterstitialAnimated:(BOOL)animated } } -#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) - -- (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()]; @@ -234,7 +203,7 @@ - (NSUInteger)supportedInterfaceOrientations // just return the application's supported orientations. if (!interstitialSupportedOrientations) { - MPLogError(@"Your application does not support this interstitial's desired orientation " + MPLogInfo(@"Your application does not support this interstitial's desired orientation " @"(%@).", orientationDescription); return applicationSupportedOrientations; } else { diff --git a/MoPubSDK/Internal/Interstitials/MPPrivateInterstitialCustomEventDelegate.h b/MoPubSDK/Internal/Interstitials/MPPrivateInterstitialCustomEventDelegate.h index c180c800f..1e9ceea6d 100644 --- a/MoPubSDK/Internal/Interstitials/MPPrivateInterstitialCustomEventDelegate.h +++ b/MoPubSDK/Internal/Interstitials/MPPrivateInterstitialCustomEventDelegate.h @@ -1,7 +1,7 @@ // // MPPrivateInterstitialCustomEventDelegate.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/Internal/MPAdServerKeys.h b/MoPubSDK/Internal/MPAdServerKeys.h index ffe4901b1..35b041777 100644 --- a/MoPubSDK/Internal/MPAdServerKeys.h +++ b/MoPubSDK/Internal/MPAdServerKeys.h @@ -1,7 +1,7 @@ // // MPAdServerKeys.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -13,9 +13,12 @@ extern NSString * const kAdServerIDKey; extern NSString * const kServerAPIVersionKey; extern NSString * const kApplicationVersionKey; extern NSString * const kIdfaKey; +extern NSString * const kMoPubIDKey; extern NSString * const kBundleKey; extern NSString * const kDoNotTrackIdKey; extern NSString * const kSDKVersionKey; +extern NSString * const kSDKEngineNameKey; +extern NSString * const kSDKEngineVersionKey; #pragma mark - Ad Server Ad Request Endpoint Keys extern NSString * const kOrientationKey; @@ -37,10 +40,18 @@ extern NSString * const kViewabilityStatusKey; extern NSString * const kKeywordsKey; extern NSString * const kUserDataKeywordsKey; extern NSString * const kAdvancedBiddingKey; +extern NSString * const kNetworkAdaptersKey; extern NSString * const kLocationLatitudeLongitudeKey; extern NSString * const kLocationHorizontalAccuracy; extern NSString * const kLocationIsFromSDK; extern NSString * const kLocationLastUpdatedMilliseconds; +extern NSString * const kBackoffMsKey; +extern NSString * const kBackoffReasonKey; +extern NSString * const kCreativeSafeWidthKey; +extern NSString * const kCreativeSafeHeightKey; + +#pragma mark - Ad Server Response Keys +extern NSString * const kEnableDebugLogging; #pragma mark - Open Endpoint Request Keys extern NSString * const kOpenEndpointSessionTrackingKey; @@ -94,3 +105,20 @@ extern NSString * const kRewardedCurrencyNameKey; extern NSString * const kRewardedCurrencyAmountKey; extern NSString * const kRewardedCustomEventNameKey; extern NSString * const kRewardedCustomDataKey; + +#pragma mark - Impression Level Revenue Data Keys + +extern NSString * const kImpressionDataImpressionIDKey; +extern NSString * const kImpressionDataAdUnitIDKey; +extern NSString * const kImpressionDataAdUnitNameKey; +extern NSString * const kImpressionDataAdUnitFormatKey; +extern NSString * const kImpressionDataAdGroupIDKey; +extern NSString * const kImpressionDataAdGroupNameKey; +extern NSString * const kImpressionDataAdGroupTypeKey; +extern NSString * const kImpressionDataAdGroupPriorityKey; +extern NSString * const kImpressionDataCurrencyKey; +extern NSString * const kImpressionDataCountryKey; +extern NSString * const kImpressionDataNetworkNameKey; +extern NSString * const kImpressionDataNetworkPlacementIDKey; +extern NSString * const kImpressionDataPublisherRevenueKey; +extern NSString * const kImpressionDataPrecisionKey; diff --git a/MoPubSDK/Internal/MPAdServerKeys.m b/MoPubSDK/Internal/MPAdServerKeys.m index 3188f1d0e..06d3936b6 100644 --- a/MoPubSDK/Internal/MPAdServerKeys.m +++ b/MoPubSDK/Internal/MPAdServerKeys.m @@ -1,7 +1,7 @@ // // MPAdServerKeys.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -9,88 +9,116 @@ #import "MPAdServerKeys.h" #pragma mark - Ad Server All URL Request Keys -NSString * const kAdServerIDKey = @"id"; -NSString * const kServerAPIVersionKey = @"v"; -NSString * const kApplicationVersionKey = @"av"; -NSString * const kIdfaKey = @"udid"; -NSString * const kBundleKey = @"bundle"; -NSString * const kDoNotTrackIdKey = @"dnt"; -NSString * const kSDKVersionKey = @"nv"; +NSString * const kAdServerIDKey = @"id"; +NSString * const kServerAPIVersionKey = @"v"; +NSString * const kApplicationVersionKey = @"av"; +NSString * const kIdfaKey = @"udid"; +NSString * const kMoPubIDKey = @"mid"; +NSString * const kBundleKey = @"bundle"; +NSString * const kDoNotTrackIdKey = @"dnt"; +NSString * const kSDKVersionKey = @"nv"; +NSString * const kSDKEngineNameKey = @"e_name"; +NSString * const kSDKEngineVersionKey = @"e_ver"; #pragma mark - Ad Server Ad Request Endpoint Keys -NSString * const kOrientationKey = @"o"; -NSString * const kScaleFactorKey = @"sc"; -NSString * const kTimeZoneKey = @"z"; -NSString * const kIsMRAIDEnabledSDKKey = @"mr"; -NSString * const kConnectionTypeKey = @"ct"; -NSString * const kCarrierNameKey = @"cn"; -NSString * const kISOCountryCodeKey = @"iso"; -NSString * const kMobileNetworkCodeKey = @"mnc"; -NSString * const kMobileCountryCodeKey = @"mcc"; -NSString * const kDeviceNameKey = @"dn"; -NSString * const kDesiredAdAssetsKey = @"assets"; -NSString * const kAdSequenceKey = @"seq"; -NSString * const kScreenResolutionWidthKey = @"w"; -NSString * const kScreenResolutionHeightKey = @"h"; -NSString * const kAppTransportSecurityStatusKey = @"ats"; -NSString * const kViewabilityStatusKey = @"vv"; -NSString * const kKeywordsKey = @"q"; -NSString * const kUserDataKeywordsKey = @"user_data_q"; -NSString * const kAdvancedBiddingKey = @"abt"; -NSString * const kLocationLatitudeLongitudeKey = @"ll"; -NSString * const kLocationHorizontalAccuracy = @"lla"; -NSString * const kLocationIsFromSDK = @"llsdk"; -NSString * const kLocationLastUpdatedMilliseconds = @"llf"; +NSString * const kOrientationKey = @"o"; +NSString * const kScaleFactorKey = @"sc"; +NSString * const kTimeZoneKey = @"z"; +NSString * const kIsMRAIDEnabledSDKKey = @"mr"; +NSString * const kConnectionTypeKey = @"ct"; +NSString * const kCarrierNameKey = @"cn"; +NSString * const kISOCountryCodeKey = @"iso"; +NSString * const kMobileNetworkCodeKey = @"mnc"; +NSString * const kMobileCountryCodeKey = @"mcc"; +NSString * const kDeviceNameKey = @"dn"; +NSString * const kDesiredAdAssetsKey = @"assets"; +NSString * const kAdSequenceKey = @"seq"; +NSString * const kScreenResolutionWidthKey = @"w"; +NSString * const kScreenResolutionHeightKey = @"h"; +NSString * const kAppTransportSecurityStatusKey = @"ats"; +NSString * const kViewabilityStatusKey = @"vv"; +NSString * const kKeywordsKey = @"q"; +NSString * const kUserDataKeywordsKey = @"user_data_q"; +NSString * const kAdvancedBiddingKey = @"abt"; +NSString * const kNetworkAdaptersKey = @"adapters"; +NSString * const kLocationLatitudeLongitudeKey = @"ll"; +NSString * const kLocationHorizontalAccuracy = @"lla"; +NSString * const kLocationIsFromSDK = @"llsdk"; +NSString * const kLocationLastUpdatedMilliseconds = @"llf"; +NSString * const kBackoffMsKey = @"backoff_ms"; +NSString * const kBackoffReasonKey = @"backoff_reason"; +NSString * const kCreativeSafeWidthKey = @"cw"; +NSString * const kCreativeSafeHeightKey = @"ch"; + +#pragma mark - Ad Server Response Keys +NSString * const kEnableDebugLogging = @"enable_debug_logging"; #pragma mark - Open Endpoint Request Keys -NSString * const kOpenEndpointSessionTrackingKey = @"st"; +NSString * const kOpenEndpointSessionTrackingKey = @"st"; #pragma mark - Synchronization Keys Shared With Other Endpoints -NSString * const kGDPRAppliesKey = @"gdpr_applies"; -NSString * const kCurrentConsentStatusKey = @"current_consent_status"; -NSString * const kConsentedVendorListVersionKey = @"consented_vendor_list_version"; -NSString * const kConsentedPrivacyPolicyVersionKey = @"consented_privacy_policy_version"; -NSString * const kForceGDPRAppliesKey = @"force_gdpr_applies"; +NSString * const kGDPRAppliesKey = @"gdpr_applies"; +NSString * const kCurrentConsentStatusKey = @"current_consent_status"; +NSString * const kConsentedVendorListVersionKey = @"consented_vendor_list_version"; +NSString * const kConsentedPrivacyPolicyVersionKey = @"consented_privacy_policy_version"; +NSString * const kForceGDPRAppliesKey = @"force_gdpr_applies"; #pragma mark - Synchronization Endpoint: Request Keys -NSString * const kLastChangedMsKey = @"last_changed_ms"; -NSString * const kLastSynchronizedConsentStatusKey = @"last_consent_status"; -NSString * const kCachedIabVendorListHashKey = @"cached_vendor_list_iab_hash"; -NSString * const kForcedGDPRAppliesChangedKey = @"force_gdpr_applies_changed"; +NSString * const kLastChangedMsKey = @"last_changed_ms"; +NSString * const kLastSynchronizedConsentStatusKey = @"last_consent_status"; +NSString * const kCachedIabVendorListHashKey = @"cached_vendor_list_iab_hash"; +NSString * const kForcedGDPRAppliesChangedKey = @"force_gdpr_applies_changed"; #pragma mark - Synchronization Endpoint: Shared Keys -NSString * const kConsentChangedReasonKey = @"consent_change_reason"; -NSString * const kExtrasKey = @"extras"; +NSString * const kConsentChangedReasonKey = @"consent_change_reason"; +NSString * const kExtrasKey = @"extras"; #pragma mark - Synchronization Endpoint: Response Keys -NSString * const kForceExplicitNoKey = @"force_explicit_no"; -NSString * const kInvalidateConsentKey = @"invalidate_consent"; -NSString * const kReacquireConsentKey = @"reacquire_consent"; -NSString * const kIsWhitelistedKey = @"is_whitelisted"; -NSString * const kIsGDPRRegionKey = @"is_gdpr_region"; -NSString * const kVendorListUrlKey = @"current_vendor_list_link"; -NSString * const kVendorListVersionKey = @"current_vendor_list_version"; -NSString * const kPrivacyPolicyUrlKey = @"current_privacy_policy_link"; -NSString * const kPrivacyPolicyVersionKey = @"current_privacy_policy_version"; -NSString * const kIabVendorListKey = @"current_vendor_list_iab_format"; -NSString * const kIabVendorListHashKey = @"current_vendor_list_iab_hash"; -NSString * const kSyncFrequencyKey = @"call_again_after_secs"; +NSString * const kForceExplicitNoKey = @"force_explicit_no"; +NSString * const kInvalidateConsentKey = @"invalidate_consent"; +NSString * const kReacquireConsentKey = @"reacquire_consent"; +NSString * const kIsWhitelistedKey = @"is_whitelisted"; +NSString * const kIsGDPRRegionKey = @"is_gdpr_region"; +NSString * const kVendorListUrlKey = @"current_vendor_list_link"; +NSString * const kVendorListVersionKey = @"current_vendor_list_version"; +NSString * const kPrivacyPolicyUrlKey = @"current_privacy_policy_link"; +NSString * const kPrivacyPolicyVersionKey = @"current_privacy_policy_version"; +NSString * const kIabVendorListKey = @"current_vendor_list_iab_format"; +NSString * const kIabVendorListHashKey = @"current_vendor_list_iab_hash"; +NSString * const kSyncFrequencyKey = @"call_again_after_secs"; #pragma mark - Consent Dialog Endpoint: Request Keys -NSString * const kLanguageKey = @"language"; +NSString * const kLanguageKey = @"language"; #pragma mark - Consent Dialog Endpoint: Response Keys -NSString * const kDialogHTMLKey = @"dialog_html"; +NSString * const kDialogHTMLKey = @"dialog_html"; #pragma mark - Rewarded Keys -NSString * const kCustomerIdKey = @"customer_id"; -NSString * const kRewardedCurrencyNameKey = @"rcn"; -NSString * const kRewardedCurrencyAmountKey = @"rca"; -NSString * const kRewardedCustomEventNameKey = @"cec"; -NSString * const kRewardedCustomDataKey = @"rcd"; +NSString * const kCustomerIdKey = @"customer_id"; +NSString * const kRewardedCurrencyNameKey = @"rcn"; +NSString * const kRewardedCurrencyAmountKey = @"rca"; +NSString * const kRewardedCustomEventNameKey = @"cec"; +NSString * const kRewardedCustomDataKey = @"rcd"; + +#pragma mark - Impression Level Revenue Data Keys + +NSString * const kImpressionDataImpressionIDKey = @"id"; +NSString * const kImpressionDataAdUnitIDKey = @"adunit_id"; +NSString * const kImpressionDataAdUnitNameKey = @"adunit_name"; +NSString * const kImpressionDataAdUnitFormatKey = @"adunit_format"; +NSString * const kImpressionDataAdGroupIDKey = @"adgroup_id"; +NSString * const kImpressionDataAdGroupNameKey = @"adgroup_name"; +NSString * const kImpressionDataAdGroupTypeKey = @"adgroup_type"; +NSString * const kImpressionDataAdGroupPriorityKey = @"adgroup_priority"; +NSString * const kImpressionDataCurrencyKey = @"currency"; +NSString * const kImpressionDataCountryKey = @"country"; +NSString * const kImpressionDataNetworkNameKey = @"network_name"; +NSString * const kImpressionDataNetworkPlacementIDKey = @"network_placement_id"; +NSString * const kImpressionDataPublisherRevenueKey = @"publisher_revenue"; +NSString * const kImpressionDataPrecisionKey = @"precision"; diff --git a/MoPubSDK/Internal/MPAdvancedBiddingManager.h b/MoPubSDK/Internal/MPAdvancedBiddingManager.h deleted file mode 100644 index dd5225965..000000000 --- a/MoPubSDK/Internal/MPAdvancedBiddingManager.h +++ /dev/null @@ -1,41 +0,0 @@ -// -// MPAdvancedBiddingManager.h -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import -#import "MPAdvancedBidder.h" - -/** - * Internally manages all aspects related to advanced bidding. - */ -@interface MPAdvancedBiddingManager : NSObject -/** - * A boolean value indicating whether advanced bidding is enabled. This boolean defaults to `YES`. - * To disable advanced bidding, set this value to `NO`. - */ -@property (nonatomic, assign) BOOL advancedBiddingEnabled; - -/** - * A UTF-8 JSON string representation of the Advanced Bidding tokens. - * @remark If `advancedBiddingEnabled` is set to `NO`, this will always return `nil`. - */ -@property (nonatomic, copy, readonly) NSString * _Nullable bidderTokensJson; - -/** - * Singleton instance of the manager. - */ -+ (MPAdvancedBiddingManager * _Nonnull)sharedManager; - -/** - Initializes each Advanced Bidder and retains a reference. If an Advanced Bidder is - already initialized, nothing will be done. - @param bidders Array of bidders - @param complete Completion block - */ -- (void)initializeBidders:(NSArray> * _Nonnull)bidders complete:(void(^_Nullable)(void))complete; - -@end diff --git a/MoPubSDK/Internal/MPAdvancedBiddingManager.m b/MoPubSDK/Internal/MPAdvancedBiddingManager.m deleted file mode 100644 index 06c5c3f07..000000000 --- a/MoPubSDK/Internal/MPAdvancedBiddingManager.m +++ /dev/null @@ -1,115 +0,0 @@ -// -// MPAdvancedBiddingManager.m -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import -#import "MPAdvancedBiddingManager.h" -#import "MPLogging.h" - -// JSON constants -static NSString const * kTokenKey = @"token"; - -@interface MPAdvancedBiddingManager() - -// Dictionary of Advanced Bidding network name to instance of that Advanced Bidder. -@property (nonatomic, strong) NSMutableDictionary> * bidders; - -// Advanced Bidder initialization queue. -@property (nonatomic, strong) dispatch_queue_t queue; - -@end - -@implementation MPAdvancedBiddingManager - -#pragma mark - Initialization - -+ (MPAdvancedBiddingManager *)sharedManager { - static MPAdvancedBiddingManager * sharedMyManager = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - sharedMyManager = [[self alloc] init]; - }); - return sharedMyManager; -} - -- (instancetype)init { - if (self = [super init]) { - _advancedBiddingEnabled = YES; - _bidders = [NSMutableDictionary dictionary]; - _queue = dispatch_queue_create("Advanced Bidder Initialization Queue", NULL); - } - - return self; -} - -#pragma mark - Bidders - -- (NSString *)bidderTokensJson { - // No bidders. - if (self.bidders.count == 0) { - return nil; - } - - // Advanced Bidding is not enabled. - if (!self.advancedBiddingEnabled) { - return nil; - } - - // Generate the JSON dictionary for all participating bidders. - NSMutableDictionary * tokens = [NSMutableDictionary dictionary]; - [self.bidders enumerateKeysAndObjectsUsingBlock:^(NSString * network, id bidder, BOOL * stop) { - if (bidder.token != nil) { - tokens[network] = @{ kTokenKey: bidder.token }; - } - }]; - - // Serialize the JSON dictionary into a JSON string. - NSError * error = nil; - NSData * jsonData = [NSJSONSerialization dataWithJSONObject:tokens options:0 error:&error]; - if (jsonData == nil) { - MPLogError(@"Failed to generate a JSON string from\n%@\nReason: %@", tokens, error.localizedDescription); - return nil; - } - - return [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; -} - -- (void)initializeBidders:(NSArray> * _Nonnull)bidders complete:(void(^_Nullable)(void))complete { - // No bidders to initialize, complete immediately - if (bidders.count == 0) { - if (complete) { - complete(); - } - return; - } - - // Asynchronous dispatch the initialization as it may take some time. - __weak __typeof__(self) weakSelf = self; - dispatch_async(self.queue, ^{ - __typeof__(self) strongSelf = weakSelf; - if (strongSelf != nil) { - for (Class advancedBidderClass in bidders) { - // Create an instance of the Advanced Bidder - id advancedBidder = (id)[[[advancedBidderClass class] alloc] init]; - NSString * network = advancedBidder.creativeNetworkName; - - // Verify that the Advanced Bidder has a creative network name and that it's - // not already created. - if (network != nil && strongSelf.bidders[network] == nil) { - strongSelf.bidders[network] = advancedBidder; - } - } - } - - // Notify completion block handler. - if (complete) { - complete(); - } - }); // End dispatch_async -} - -@end diff --git a/MoPubSDK/Internal/MPConsentDialogViewController.h b/MoPubSDK/Internal/MPConsentDialogViewController.h index 732cbd6b7..a70a20ce1 100644 --- a/MoPubSDK/Internal/MPConsentDialogViewController.h +++ b/MoPubSDK/Internal/MPConsentDialogViewController.h @@ -1,7 +1,7 @@ // // MPConsentDialogViewController.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/Internal/MPConsentDialogViewController.m b/MoPubSDK/Internal/MPConsentDialogViewController.m index e1d38d953..2b510e913 100644 --- a/MoPubSDK/Internal/MPConsentDialogViewController.m +++ b/MoPubSDK/Internal/MPConsentDialogViewController.m @@ -1,7 +1,7 @@ // // MPConsentDialogViewController.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -10,6 +10,7 @@ #import "MPConsentDialogViewController.h" #import "MPGlobal.h" #import "MPWebView.h" +#import "MoPub+Utility.h" typedef void(^MPConsentDialogViewControllerCompletion)(BOOL success, NSError *error); @@ -48,6 +49,9 @@ - (instancetype)initWithDialogHTML:(NSString *)dialogHTML { // Initialize web view [self setUpWebView]; + + // Ensure fullscreen presentation + self.modalPresentationStyle = UIModalPresentationFullScreen; } return self; @@ -93,7 +97,7 @@ - (void)closeConsentDialog { } - (void)setUpWebView { - self.webView = [[MPWebView alloc] initWithFrame:CGRectZero forceUIWebView:NO]; + self.webView = [[MPWebView alloc] initWithFrame:CGRectZero]; self.webView.delegate = self; self.webView.scrollView.bounces = NO; self.webView.backgroundColor = [UIColor whiteColor]; @@ -105,7 +109,7 @@ - (void)layoutWebView { [self.view addSubview:self.webView]; // Set up autolayout constraints on iOS 11+. This web view should always stay within the safe area. - if (@available(iOS 11.0, *)) { + if (@available(iOS 11, *)) { self.webView.translatesAutoresizingMaskIntoConstraints = NO; [NSLayoutConstraint activateConstraints:@[ [self.webView.topAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor], @@ -133,7 +137,7 @@ - (void)setUpCloseButton { forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:self.closeButton]; - if (@available(iOS 11.0, *)) { + if (@available(iOS 11, *)) { self.closeButton.translatesAutoresizingMaskIntoConstraints = NO; [NSLayoutConstraint activateConstraints:@[ [self.closeButton.widthAnchor constraintEqualToConstant:kCloseButtonDimension], @@ -198,13 +202,13 @@ - (void)webViewDidFinishLoad:(MPWebView *)webView { } } -- (BOOL)webView:(MPWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType { +- (BOOL)webView:(MPWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(WKNavigationType)navigationType { BOOL requestIsMoPubScheme = [request.URL.scheme isEqualToString:kMoPubScheme]; BOOL requestIsMoPubHost = [request.URL.host isEqualToString:MPAPIEndpoints.baseHostname]; // Kick to Safari if the URL is not of MoPub scheme or hostname if (!requestIsMoPubScheme && !requestIsMoPubHost) { - [[UIApplication sharedApplication] openURL:request.URL]; + [MoPub openURL:request.URL]; return NO; } diff --git a/MoPubSDK/Internal/MPConsentManager.h b/MoPubSDK/Internal/MPConsentManager.h index fc2ab61ec..0ad842c04 100644 --- a/MoPubSDK/Internal/MPConsentManager.h +++ b/MoPubSDK/Internal/MPConsentManager.h @@ -1,7 +1,7 @@ // // MPConsentManager.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -14,11 +14,29 @@ @interface MPConsentManager : NSObject /** - Ad unit ID sent to Ad Server as a proxy for the MoPub app ID. + Ad unit ID sent to Ad Server as a proxy for the MoPub app ID. If a known + good adunit ID is already cached, setting this will have no effect. @remark This should only be set by SDK initialization and must be non-nil. */ @property (nonatomic, strong, nonnull) NSString * adUnitIdUsedForConsent; +/** + Sets @c self.adUnitIdUsedForConsent, and caches to disk if @c isKnownGood is set to @c YES. + No-op if a known good adunit is already cached to disk. + @remark @c isKnownGood should only be set to @c YES when the adunit ID has been verified with the server + */ +- (void)setAdUnitIdUsedForConsent:(NSString * _Nonnull)adUnitIdUsedForConsent isKnownGood:(BOOL)isKnownGood; + +/** + Clears @c self.adUnitIdUsedForConsent as well as the backing cache. + */ +- (void)clearAdUnitIdUsedForConsent; + +/** + This API can be used if you want to allow supported SDK networks to collect user information on the basis of legitimate interest. The default value is @c NO. + */ +@property (nonatomic, assign) BOOL allowLegitimateInterest; + /** Flag indicating that personally identifiable information can be collected. */ diff --git a/MoPubSDK/Internal/MPConsentManager.m b/MoPubSDK/Internal/MPConsentManager.m index 92bc67d77..856414486 100644 --- a/MoPubSDK/Internal/MPConsentManager.m +++ b/MoPubSDK/Internal/MPConsentManager.m @@ -1,7 +1,7 @@ // // MPConsentManager.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -15,6 +15,7 @@ #import "MPConsentError.h" #import "MPConsentManager.h" #import "MPConstants.h" +#import "MPError.h" #import "MPHTTPNetworkSession.h" #import "MPIdentityProvider.h" #import "MPLogging.h" @@ -24,6 +25,7 @@ #import "MPAdConversionTracker.h" // NSUserDefault keys +static NSString * const kAdUnitIdUsedForConsentStorageKey = @"com.mopub.mopub-ios-sdk.consent.ad.unit.id"; static NSString * const kConsentedIabVendorListStorageKey = @"com.mopub.mopub-ios-sdk.consented.iab.vendor.list"; static NSString * const kConsentedPrivacyPolicyVersionStorageKey = @"com.mopub.mopub-ios-sdk.consented.privacy.policy.version"; static NSString * const kConsentedVendorListVersionStorageKey = @"com.mopub.mopub-ios-sdk.consented.vendor.list.version"; @@ -196,7 +198,17 @@ - (BOOL)shouldReacquireConsent { } - (void)setShouldReacquireConsent:(BOOL)shouldReacquireConsent { + // Capture old `isConsentNeeded` value + BOOL oldIsConsentNeeded = self.isConsentNeeded; + + // Update the cached value [NSUserDefaults.standardUserDefaults setBool:shouldReacquireConsent forKey:kShouldReacquireConsentStorageKey]; + + // Broadcast the `kMPConsentNeededNotification` if the `isConsentNeeded` computed property + // transitions from `NO` to `YES`. + if (!oldIsConsentNeeded && self.isConsentNeeded) { + [self notifyConsentNeeded]; + } } #pragma mark - ISO Language Code @@ -216,7 +228,7 @@ - (NSString * _Nullable)removeRegionFromLanguageCode:(NSString * _Nullable)isoLa - (void)grantConsent { MPLogInfo(@"Grant consent was called with publisher whitelist status of: %@whitelisted", self.isWhitelisted ? @"" : @"not "); if (!self.isWhitelisted) { - MPLogWarn(@"You do not have approval to use the grantConsent API. Please reach out to your account teams or support@mopub.com for more information."); + MPLogInfo(@"You do not have approval to use the grantConsent API. Please reach out to your account teams or support@mopub.com for more information."); } // Reset the reacquire consent flag since the user has taken action. @@ -228,13 +240,10 @@ - (void)grantConsent { // Grant consent and if the state has transitioned, immediately synchronize // with the server as this is an externally induced state change. if ([self setCurrentStatus:grantStatus reason:grantReason shouldBroadcast:YES]) { + MPLogDebug(@"Consent synchronization triggered by publisher granting consent"); [self synchronizeConsentWithCompletion:^(NSError * _Nullable error) { - if (error) { - MPLogError(@"Consent synchronization failed: %@", error.localizedDescription); - } - else { - MPLogInfo(@"Consent synchronization completed"); - } + // Consent synchronization success/fail logging is already handled + // by `synchronizeConsentWithCompletion:`. }]; } } @@ -246,13 +255,10 @@ - (void)revokeConsent { // Revoke consent and if the state has transitioned, immediately synchronize // with the server as this is an externally induced state change. if ([self setCurrentStatus:MPConsentStatusDenied reason:kConsentedChangedReasonPublisherDenied shouldBroadcast:YES]) { + MPLogDebug(@"Consent synchronization triggered by publisher revoking consent"); [self synchronizeConsentWithCompletion:^(NSError * _Nullable error) { - if (error) { - MPLogError(@"Consent synchronization failed: %@", error.localizedDescription); - } - else { - MPLogInfo(@"Consent synchronization completed"); - } + // Consent synchronization success/fail logging is already handled + // by `synchronizeConsentWithCompletion:`. }]; } } @@ -266,6 +272,13 @@ - (BOOL)isConsentDialogLoaded { - (void)loadConsentDialogWithCompletion:(void (^)(NSError *error))completion { // Helper block to call completion if not nil void (^callCompletion)(NSError *error) = ^(NSError *error) { + if (error != nil) { + MPLogEvent([MPLogEvent consentDialogLoadFailedWithError:error]); + } + else { + MPLogEvent(MPLogEvent.consentDialogLoadSuccess); + } + if (completion != nil) { completion(error); } @@ -276,7 +289,7 @@ - (void)loadConsentDialogWithCompletion:(void (^)(NSError *error))completion { self.consentDialogViewController = nil; NSError *limitAdTrackingError = [NSError errorWithDomain:kConsentErrorDomain code:MPConsentErrorCodeLimitAdTrackingEnabled - userInfo:nil]; + userInfo:@{ NSLocalizedDescriptionKey: @"Consent dialog will not be loaded because Limit Ad Tracking is on" }]; callCompletion(limitAdTrackingError); return; } @@ -286,7 +299,7 @@ - (void)loadConsentDialogWithCompletion:(void (^)(NSError *error))completion { self.consentDialogViewController = nil; NSError *gdprIsNotApplicableError = [NSError errorWithDomain:kConsentErrorDomain code:MPConsentErrorCodeGDPRIsNotApplicable - userInfo:nil]; + userInfo:@{ NSLocalizedDescriptionKey: @"Consent dialog will not be loaded because GDPR is not applicable" }]; callCompletion(gdprIsNotApplicableError); return; } @@ -337,6 +350,22 @@ - (void)loadConsentDialogWithCompletion:(void (^)(NSError *error))completion { - (void)showConsentDialogFromViewController:(UIViewController *)viewController didShow:(void (^)(void))didShow didDismiss:(void (^)(void))didDismiss { + // Ensure that this method is invoked from the main thread. + if (!NSThread.isMainThread) { + __weak __typeof__(self) weakSelf = self; + dispatch_async(dispatch_get_main_queue(), ^{ + [weakSelf showConsentDialogFromViewController:viewController didShow:didShow didDismiss:didDismiss]; + }); + return; + } + + // If `viewController` is already presenting the consent dialog modally, do nothing. + if (viewController.presentedViewController == self.consentDialogViewController) { + MPLogEvent([MPLogEvent error:NSError.consentDialogAlreadyShowing message:nil]); + return; + } + + MPLogEvent(MPLogEvent.consentDialogShowAttempted); if (self.isConsentDialogLoaded) { [viewController presentViewController:self.consentDialogViewController animated:YES @@ -344,6 +373,12 @@ - (void)showConsentDialogFromViewController:(UIViewController *)viewController // Save @c didDismiss block for later self.consentDialogDidDismissCompletionBlock = didDismiss; + MPLogEvent(MPLogEvent.consentDialogShowSuccess); + } + // Consent dialog not loaded + else { + NSError * error = NSError.noConsentDialogLoaded; + MPLogEvent([MPLogEvent consentDialogShowFailedWithError:error]); } } @@ -362,13 +397,10 @@ - (void)consentDialogViewControllerDidReceiveConsentResponse:(BOOL)response // It is possible that the user responded to the consent dialog while // in a "do not track" state. if (didTransition) { + MPLogDebug(@"Consent synchronization triggered by user responding to consent dialog"); [self synchronizeConsentWithCompletion:^(NSError * _Nullable error) { - if (error) { - MPLogInfo(@"Error when syncing consent dialog response: %@", error); - return; - } - - MPLogInfo(@"Did sync consent dialog response."); + // Consent synchronization success/fail logging is already handled + // by `synchronizeConsentWithCompletion:`. }]; } } @@ -380,6 +412,15 @@ - (void)consentDialogViewControllerWillDisappear:(MPConsentDialogViewController } - (void)consentDialogViewControllerDidDismiss:(MPConsentDialogViewController *)consentDialogViewController { + // Ensure that this method is invoked from the main thread. + if (!NSThread.isMainThread) { + __weak __typeof__(self) weakSelf = self; + dispatch_async(dispatch_get_main_queue(), ^{ + [weakSelf consentDialogViewControllerDidDismiss:consentDialogViewController]; + }); + return; + } + // Execute @c consentDialogWillDismissCompletionBlock if needed if (self.consentDialogDidDismissCompletionBlock) { self.consentDialogDidDismissCompletionBlock(); @@ -397,13 +438,29 @@ - (void)onApplicationWillEnterForeground:(NSNotification *)notification { [self checkForDoNotTrackAndTransition]; // If IDFA changed, status will be set to MPConsentStatusUnknown. [self checkForIfaChange]; + + /* + ADF-4318: This early return is to avoid a `NSAssert` crash in iPadOS 13+ debug build. + + `ApplicationWillEnterForegroundNotification` is posted right after the first fresh + install app launch for iPadOS 13 multi-scene, while it's not posted after the first fresh + install app launch for the single-scene case (pre iOS 13). + + The consent manager shared instance is called during `applicationDidFinishLaunching` and thus + starts observing `ApplicationWillEnterForegroundNotification` before MoPub SDK and this consent + manager is initialized with an ad unit ID. Consequently, the `NSAssert` in + `synchronizeConsentWithCompletion` is always triggered and crash debug build of this app. So, + to avoid such crash in debug build, we should avoid `synchronizeConsentWithCompletion` before + `adUnitIdUsedForConsent` is assigned. + */ + if (self.adUnitIdUsedForConsent.length == 0) { + return; + } + + MPLogDebug(@"Consent synchronization triggered by application foreground."); [self synchronizeConsentWithCompletion:^(NSError * _Nullable error) { - if (error) { - MPLogError(@"Consent synchronization failed: %@", error.localizedDescription); - } - else { - MPLogInfo(@"Consent synchronization completed"); - } + // Consent synchronization success/fail logging is already handled + // by `synchronizeConsentWithCompletion:`. }]; } @@ -413,11 +470,15 @@ - (void)onApplicationWillEnterForeground:(NSNotification *)notification { Broadcasts a @c NSNotification that the consent status has changed. @param newStatus The new consent state. @param oldStatus The previous consent state. + @param reasonForChange Optional reason for consent state change. @param canCollectPii Flag indicating that collection of PII is allowed. */ - (void)notifyConsentChangedTo:(MPConsentStatus)newStatus fromOldStatus:(MPConsentStatus)oldStatus + reason:(NSString * _Nullable)reasonForChange canCollectPii:(BOOL)canCollectPii { + MPLogEvent([MPLogEvent consentUpdatedTo:newStatus from:oldStatus reason:reasonForChange canCollectPersonalInfo:canCollectPii]); + // Build the NSNotification userInfo dictionary. NSDictionary * userInfo = @{ kMPConsentChangedInfoNewConsentStatusKey: @(newStatus), kMPConsentChangedInfoPreviousConsentStatusKey: @(oldStatus), @@ -433,6 +494,14 @@ - (void)notifyConsentChangedTo:(MPConsentStatus)newStatus [self handlePersonalDataOnStateChangeTo:newStatus fromOldStatus:oldStatus]; } +/** + Logs that consent needs to be acquired/reacquired. + This should only be fired when @c isConsentNeeded changes from @c NO to @c YES. + */ +- (void)notifyConsentNeeded { + MPLogEvent(MPLogEvent.consentShouldShowDialog); +} + #pragma mark - Ad Server Communication /** @@ -442,6 +511,8 @@ - (void)notifyConsentChangedTo:(MPConsentStatus)newStatus @param completion Required completion block to listen for the result of the synchronization. */ - (void)synchronizeConsentWithCompletion:(void (^ _Nonnull)(NSError * error))completion { + MPLogEvent(MPLogEvent.consentSyncAttempted); + // Invalidate the next update timer since we are synchronizing right now. [self.nextUpdateTimer invalidate]; self.nextUpdateTimer = nil; @@ -450,7 +521,7 @@ - (void)synchronizeConsentWithCompletion:(void (^ _Nonnull)(NSError * error))com // is no longer required. This call will complete without error and no // next update timer will be created. if (self.isGDPRApplicable == MPBoolNo) { - MPLogInfo(@"GDPR not applicable, consent synchronization will complete immediately"); + MPLogEvent([MPLogEvent consentSyncCompletedWithMessage:@"GDPR not applicable, consent synchronization will complete immediately"]); completion(nil); return; } @@ -462,17 +533,19 @@ - (void)synchronizeConsentWithCompletion:(void (^ _Nonnull)(NSError * error))com // In the case that raw (MoPub) GDPR applicability is unknown, we should perform a sync // to determine the final state. if (!MPIdentityProvider.advertisingTrackingEnabled && self.ifaForConsent == nil && self.rawIsGDPRApplicable != MPBoolUnknown) { - MPLogInfo(@"Currently in a do not track state, consent synchronization will complete immediately"); + MPLogEvent([MPLogEvent consentSyncCompletedWithMessage:@"Currently in a do not track state, consent synchronization will complete immediately"]); completion(nil); return; } // Before beginning the sync, check for a nil or empty ad unit ID, and output to the log if there's an issue. // Otherwise, output the ad unit ID to the log. - if (self.adUnitIdUsedForConsent == nil || [self.adUnitIdUsedForConsent isEqualToString:@""]) { - MPLogError(@"Warning: no ad unit available for GDPR sync. Please make sure that the SDK is initialized correctly via `initializeSdkWithConfiguration:completion:` as soon as possible after app startup."); + if (self.adUnitIdUsedForConsent.length == 0) { + NSString * description = @"Warning: no ad unit available for GDPR sync. Please make sure that the SDK is initialized correctly via `initializeSdkWithConfiguration:completion:` as soon as possible after app startup."; + MPLogInfo(@"%@", description); + NSAssert(NO, description); // Crash the app if this is set up incorrectly } else { - MPLogInfo(@"Ad unit used for GDPR sync: %@", self.adUnitIdUsedForConsent); + MPLogDebug(@"Ad unit used for GDPR sync: %@", self.adUnitIdUsedForConsent); } // Capture the current status being synchronized with the server @@ -487,52 +560,64 @@ - (void)synchronizeConsentWithCompletion:(void (^ _Nonnull)(NSError * error))com [MPHTTPNetworkSession startTaskWithHttpRequest:syncRequest responseHandler:^(NSData * _Nonnull data, NSHTTPURLResponse * _Nonnull response) { __typeof__(self) strongSelf = weakSelf; - // Update the last successfully synchronized state. - // We still update this state even if we failed to parse the response - // because this is a reflection of what we last sent to the server. - // If we've made it this far, it means that the `synchronizedStatus` was - // successfully sent to the server. However, it may be the case that the - // server sends us back an invalid response. - [NSUserDefaults.standardUserDefaults setObject:synchronizedStatus forKey:kLastSynchronizedConsentStatusStorageKey]; - - // Reset the GDPR applies transition state since it was successfully sent to - // ad server. - strongSelf.isForcedGDPRAppliesTransition = NO; - - // Deserialize the JSON response and attempt to parse it - NSError * deserializationError = nil; - NSDictionary * json = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&deserializationError]; - if (deserializationError != nil) { - // Schedule the next timer and complete with error. - strongSelf.nextUpdateTimer = [strongSelf newNextUpdateTimer]; - MPLogError(@"%@", deserializationError.localizedDescription); - completion(deserializationError); - return; - } - - // Attempt to parse and update the consent state - NSError * parseError = nil; - if ([strongSelf updateConsentStateWithParameters:json]) { - MPLogTrace(@"Successfully parsed consent synchronization response"); - } - else { - parseError = [NSError errorWithDomain:kConsentErrorDomain code:MPConsentErrorCodeFailedToParseSynchronizationResponse userInfo:@{ NSLocalizedDescriptionKey: @"Failed to parse consent synchronization response; one or more required fields are missing" }]; - MPLogError(@"%@", parseError.localizedDescription); - } - - // Schedule the next timer and complete. - strongSelf.nextUpdateTimer = [strongSelf newNextUpdateTimer]; - completion(parseError); + [strongSelf didFinishSynchronizationWithData:data + synchronizedStatus:synchronizedStatus + completion:completion]; } errorHandler:^(NSError * _Nonnull error) { __typeof__(self) strongSelf = weakSelf; - // Schedule the next timer and complete with error. - strongSelf.nextUpdateTimer = [strongSelf newNextUpdateTimer]; - MPLogError(@"%@", error.localizedDescription); - completion(error); + [strongSelf didFailSynchronizationWithError:error completion:completion]; }]; } +- (void)didFinishSynchronizationWithData:(NSData *)data synchronizedStatus:(NSString *)synchronizedStatus completion:(void (^ _Nonnull)(NSError * error))completion { + // Update the last successfully synchronized state. + // We still update this state even if we failed to parse the response + // because this is a reflection of what we last sent to the server. + // If we've made it this far, it means that the `synchronizedStatus` was + // successfully sent to the server. However, it may be the case that the + // server sends us back an invalid response. + [NSUserDefaults.standardUserDefaults setObject:synchronizedStatus forKey:kLastSynchronizedConsentStatusStorageKey]; + + // Cache the working adunit ID + [self cacheAdUnitIdUsedForConsent]; + + // Reset the GDPR applies transition state since it was successfully sent to + // ad server. + self.isForcedGDPRAppliesTransition = NO; + + // Deserialize the JSON response and attempt to parse it + NSError * deserializationError = nil; + NSDictionary * json = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&deserializationError]; + if (deserializationError != nil) { + // Complete with error. + MPLogEvent([MPLogEvent consentSyncFailedWithError:deserializationError]); + completion(deserializationError); + } + else if (![self updateConsentStateWithParameters:json]) { + // Attempt to parse and update the consent state + NSError * parseError = [NSError errorWithDomain:kConsentErrorDomain code:MPConsentErrorCodeFailedToParseSynchronizationResponse userInfo:@{ NSLocalizedDescriptionKey: @"Failed to parse consent synchronization response; one or more required fields are missing" }]; + MPLogEvent([MPLogEvent consentSyncFailedWithError:parseError]); + completion(parseError); + } + else { + // Success + MPLogEvent([MPLogEvent consentSyncCompletedWithMessage:nil]); + completion(nil); + } + + // `updateConsentStateWithParameters` might update `syncFrequency`, which is referenced in + // `newNextUpdateTimer`, so, call `updateConsentStateWithParameters` before `newNextUpdateTimer` + self.nextUpdateTimer = [self newNextUpdateTimer]; +} + +- (void)didFailSynchronizationWithError:(NSError *)error completion:(void (^ _Nonnull)(NSError * error))completion { + // Schedule the next timer and complete with error. + self.nextUpdateTimer = [self newNextUpdateTimer]; + MPLogEvent([MPLogEvent consentSyncFailedWithError:error]); + completion(error); +} + #pragma mark - Next Update Timer /** @@ -548,13 +633,10 @@ - (MPTimer * _Nonnull)newNextUpdateTimer { - (void)onNextUpdateFiredWithTimer { // Synchronize with the server because it's time. + MPLogDebug(@"Scheduled consent synchronization timer fired."); [self synchronizeConsentWithCompletion:^(NSError * _Nullable error) { - if (error) { - MPLogError(@"Consent synchronization failed: %@", error.localizedDescription); - } - else { - MPLogInfo(@"Consent synchronization completed"); - } + // Consent synchronization success/fail logging is already handled + // by `synchronizeConsentWithCompletion:`. }]; } @@ -623,7 +705,7 @@ - (BOOL)setCurrentStatus:(MPConsentStatus)currentStatus // Nothing needs to be done if we're not changing state. MPConsentStatus oldStatus = self.currentStatus; if (oldStatus == currentStatus) { - MPLogWarn(@"Attempted to set consent status to same value"); + MPLogInfo(@"Attempted to set consent status to same value"); return NO; } @@ -631,10 +713,13 @@ - (BOOL)setCurrentStatus:(MPConsentStatus)currentStatus // and will not transition out of it. BOOL trackingEnabledOnDevice = MPIdentityProvider.advertisingTrackingEnabled; if (oldStatus == MPConsentStatusDoNotTrack && !trackingEnabledOnDevice) { - MPLogWarn(@"Attempted to set consent status while in a do not track state"); + MPLogInfo(@"Attempted to set consent status while in a do not track state"); return NO; } + // Capture old `isConsentNeeded` value + BOOL oldIsConsentNeeded = self.isConsentNeeded; + // Save IFA for this particular case so it can be used to remove personal data later. if (oldStatus != MPConsentStatusConsented && currentStatus == MPConsentStatusConsented) { [self storeIfa]; @@ -675,10 +760,16 @@ - (BOOL)setCurrentStatus:(MPConsentStatus)currentStatus } if (shouldBroadcast) { - [self notifyConsentChangedTo:self.currentStatus fromOldStatus:oldStatus canCollectPii:self.canCollectPersonalInfo]; + [self notifyConsentChangedTo:self.currentStatus fromOldStatus:oldStatus reason:reasonForChange canCollectPii:self.canCollectPersonalInfo]; + + // Broadcast the `kMPConsentNeededNotification` if the `isConsentNeeded` computed property + // transitions from `NO` to `YES`. + if (!oldIsConsentNeeded && self.isConsentNeeded) { + [self notifyConsentNeeded]; + } } - MPLogInfo(@"Consent state changed to %@: %@", [NSString stringFromConsentStatus:currentStatus], reasonForChange); + MPLogDebug(@"Consent state changed to %@: %@", [NSString stringFromConsentStatus:currentStatus], reasonForChange); return YES; } @@ -689,7 +780,7 @@ - (BOOL)setCurrentStatus:(MPConsentStatus)currentStatus @return @c YES if the parameters were successfully parsed; @c NO otherwise. */ - (BOOL)updateConsentStateWithParameters:(NSDictionary * _Nonnull)newState { - MPLogTrace(@"Attempting to update consent with new state:\n%@", newState); + MPLogDebug(@"Attempting to update consent with new state:\n%@", newState); // Validate required parameters NSString * isWhitelistedValue = newState[kIsWhitelistedKey]; @@ -703,13 +794,14 @@ - (BOOL)updateConsentStateWithParameters:(NSDictionary * _Nonnull)newState { currentIabVendorListHash == nil || vendorListUrl == nil || vendorListVersion == nil || privacyPolicyUrl == nil || privacyPolicyVersion == nil) { - MPLogError(@"Failed to parse new state. Missing required fields."); + MPLogInfo(@"Failed to parse new state. Missing required fields."); return NO; } // Extract the old field values for comparison. MPConsentStatus oldStatus = self.currentStatus; MPBool oldGDPRApplicableStatus = self.isGDPRApplicable; + BOOL oldIsConsentNeeded = self.isConsentNeeded; // Update the required fields. NSUserDefaults * defaults = NSUserDefaults.standardUserDefaults; @@ -777,7 +869,13 @@ - (BOOL)updateConsentStateWithParameters:(NSDictionary * _Nonnull)newState { // Broadcast the `kMPConsentChangedNotification` if needed. if ((oldStatus != self.currentStatus) || (oldGDPRApplicableStatus != self.isGDPRApplicable)) { - [self notifyConsentChangedTo:self.currentStatus fromOldStatus:oldStatus canCollectPii:self.canCollectPersonalInfo]; + [self notifyConsentChangedTo:self.currentStatus fromOldStatus:oldStatus reason:consentChangeReason canCollectPii:self.canCollectPersonalInfo]; + } + + // Broadcast the `kMPConsentNeededNotification` if the `isConsentNeeded` computed property + // transitions from `NO` to `YES`. + if (!oldIsConsentNeeded && self.isConsentNeeded) { + [self notifyConsentNeeded]; } return YES; @@ -807,6 +905,44 @@ - (void)forceStatusShouldForceExplicitNo:(BOOL)shouldForceExplicitNo } } +#pragma mark - Caching Adunit ID + +- (NSString *)adUnitIdUsedForConsent { + // If an adunit ID is cached, use the cached one rather than what's currently stored in the ivar, + // as the cache is known good. + NSString * cachedAdUnitId = [NSUserDefaults.standardUserDefaults stringForKey:kAdUnitIdUsedForConsentStorageKey]; + + if (cachedAdUnitId == nil) { + return _adUnitIdUsedForConsent; + } + + return cachedAdUnitId; +} + +- (void)cacheAdUnitIdUsedForConsent { + // If an adunit ID is already cached, we know it's good, so do not cache a new one. + NSString * cachedAdUnitId = [NSUserDefaults.standardUserDefaults stringForKey:kAdUnitIdUsedForConsentStorageKey]; + if (cachedAdUnitId != nil) { + return; + } + + [NSUserDefaults.standardUserDefaults setObject:self.adUnitIdUsedForConsent forKey:kAdUnitIdUsedForConsentStorageKey]; +} + +- (void)setAdUnitIdUsedForConsent:(NSString *)adUnitIdUsedForConsent isKnownGood:(BOOL)isKnownGood { + self.adUnitIdUsedForConsent = adUnitIdUsedForConsent; + + if (isKnownGood) { + [self cacheAdUnitIdUsedForConsent]; + } +} + +- (void)clearAdUnitIdUsedForConsent { + [NSUserDefaults.standardUserDefaults setObject:nil forKey:kAdUnitIdUsedForConsentStorageKey]; + // Using ivar here to get around warning about nullability + _adUnitIdUsedForConsent = nil; +} + @end @implementation MPConsentManager (State) @@ -824,6 +960,9 @@ - (void)setForceIsGDPRApplicable:(BOOL)forceIsGDPRApplicable { return; } + // Capture old `isConsentNeeded` value + BOOL oldIsConsentNeeded = self.isConsentNeeded; + // Capture old can collect PII value BOOL oldCanCollectPII = self.canCollectPersonalInfo; @@ -833,25 +972,28 @@ - (void)setForceIsGDPRApplicable:(BOOL)forceIsGDPRApplicable { // Broadcast the `kMPConsentChangedNotification` if needed. if (oldCanCollectPII != self.canCollectPersonalInfo) { - [self notifyConsentChangedTo:self.currentStatus fromOldStatus:self.currentStatus canCollectPii:self.canCollectPersonalInfo]; + [self notifyConsentChangedTo:self.currentStatus fromOldStatus:self.currentStatus reason:nil canCollectPii:self.canCollectPersonalInfo]; + } + + // Broadcast the `kMPConsentNeededNotification` if the `isConsentNeeded` computed property + // transitions from `NO` to `YES`. + if (!oldIsConsentNeeded && self.isConsentNeeded) { + [self notifyConsentNeeded]; } // Start sync cycle if needed if (self.adUnitIdUsedForConsent != nil && // If @c adUnitIdUsedForConsent is non-nil (i.e., if SDK init has been called; otherwise the sync will happen as part of init) AND (forceIsGDPRApplicable && self.rawIsGDPRApplicable == MPBoolNo)) { // If GDPR was not already applicable and it has become so (otherwise there's already an active sync cycle and the effective @c isGDPRApplilcableValue didn't actually change) + MPLogDebug(@"Consent synchronization triggered by forcing GDPR applicable"); [self synchronizeConsentWithCompletion:^(NSError *error){ - if (error) { - MPLogError(@"Force GDPR consent synchronization failed: %@", error.localizedDescription); - } - else { - MPLogInfo(@"Force GDPR consent synchronization completed"); - } + // Consent synchronization success/fail logging is already handled + // by `synchronizeConsentWithCompletion:`. }]; } } - (BOOL)forceIsGDPRApplicable { - return [[NSUserDefaults standardUserDefaults] boolForKey:kForceGDPRAppliesStorageKey]; + return [NSUserDefaults.standardUserDefaults boolForKey:kForceGDPRAppliesStorageKey]; } #pragma mark - Read Only Properties @@ -984,7 +1126,11 @@ - (void)handlePersonalDataOnStateChangeTo:(MPConsentStatus)newStatus fromOldStat [self updateAppConversionTracking]; if (oldStatus == MPConsentStatusConsented && newStatus != MPConsentStatusConsented) { + MPLogDebug(@"Consent synchronization triggered by one last time"); [self synchronizeConsentWithCompletion:^(NSError * _Nullable error) { + // Consent synchronization success/fail logging is already handled + // by `synchronizeConsentWithCompletion:`. + if (!error) { [self removeIfa]; } diff --git a/MoPubSDK/Internal/MPCoreInstanceProvider+MRAID.h b/MoPubSDK/Internal/MPCoreInstanceProvider+MRAID.h index 5319b4a27..bdfd203ef 100644 --- a/MoPubSDK/Internal/MPCoreInstanceProvider+MRAID.h +++ b/MoPubSDK/Internal/MPCoreInstanceProvider+MRAID.h @@ -1,7 +1,7 @@ // // MPCoreInstanceProvider+MRAID.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/Internal/MPCoreInstanceProvider+MRAID.m b/MoPubSDK/Internal/MPCoreInstanceProvider+MRAID.m index 33a34589d..8e6247ed7 100644 --- a/MoPubSDK/Internal/MPCoreInstanceProvider+MRAID.m +++ b/MoPubSDK/Internal/MPCoreInstanceProvider+MRAID.m @@ -1,7 +1,7 @@ // // MPCoreInstanceProvider+MRAID.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/Internal/MPCoreInstanceProvider.h b/MoPubSDK/Internal/MPCoreInstanceProvider.h index 05c064724..18981be1d 100644 --- a/MoPubSDK/Internal/MPCoreInstanceProvider.h +++ b/MoPubSDK/Internal/MPCoreInstanceProvider.h @@ -1,7 +1,7 @@ // // MPCoreInstanceProvider.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -60,6 +60,4 @@ typedef NS_OPTIONS(NSUInteger, MPATSSetting) { - (MPNetworkStatus)currentRadioAccessTechnology; -- (MPTimer *)buildMPTimerWithTimeInterval:(NSTimeInterval)seconds target:(id)target selector:(SEL)selector repeats:(BOOL)repeats; - @end diff --git a/MoPubSDK/Internal/MPCoreInstanceProvider.m b/MoPubSDK/Internal/MPCoreInstanceProvider.m index fb323fa84..b18a7353b 100644 --- a/MoPubSDK/Internal/MPCoreInstanceProvider.m +++ b/MoPubSDK/Internal/MPCoreInstanceProvider.m @@ -1,7 +1,7 @@ // // MPCoreInstanceProvider.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -20,9 +20,6 @@ #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"; @@ -47,10 +44,6 @@ @interface MPCoreInstanceProvider () @implementation MPCoreInstanceProvider -@synthesize singletons = _singletons; -@synthesize carrierInfo = _carrierInfo; -@synthesize twitterDeepLinkStatus = _twitterDeepLinkStatus; - static MPCoreInstanceProvider *sharedProvider = nil; + (instancetype)sharedProvider @@ -184,14 +177,6 @@ - (MPATSSetting)appTransportSecuritySettings } // 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; @@ -205,7 +190,7 @@ - (MPATSSetting)appTransportSecuritySettings // 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")) { + if (@available(iOS 10, *)) { // 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 @@ -239,11 +224,6 @@ - (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]; -} - static CTTelephonyNetworkInfo * gTelephonyNetworkInfo; - (MPNetworkStatus)currentRadioAccessTechnology { if (!gTelephonyNetworkInfo) { diff --git a/MoPubSDK/Internal/MPExtendedHitBoxButton.h b/MoPubSDK/Internal/MPExtendedHitBoxButton.h new file mode 100644 index 000000000..99f3179e3 --- /dev/null +++ b/MoPubSDK/Internal/MPExtendedHitBoxButton.h @@ -0,0 +1,24 @@ +// +// MPExtendedHitBoxButton.h +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + Extends the hit box of the @c UIButton by the amount of points specified by @c touchAreaInsets + */ +@interface MPExtendedHitBoxButton : UIButton +/** + The amount of points to extend the hitbox of the button. Positive values indicate that the hitbox is increased beyond + the bounds of the button. + */ +@property (nonatomic, assign) UIEdgeInsets touchAreaInsets; +@end + +NS_ASSUME_NONNULL_END diff --git a/MoPubSDK/Internal/MPExtendedHitBoxButton.m b/MoPubSDK/Internal/MPExtendedHitBoxButton.m new file mode 100644 index 000000000..5f5967aa4 --- /dev/null +++ b/MoPubSDK/Internal/MPExtendedHitBoxButton.m @@ -0,0 +1,28 @@ +// +// MPExtendedHitBoxButton.m +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import "MPExtendedHitBoxButton.h" + +@implementation MPExtendedHitBoxButton + +#pragma mark - Overrides + +- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event { + UIEdgeInsets touchAreaInsets = self.touchAreaInsets; + CGRect bounds = self.bounds; + + // Increase the bounding rectangle by the amount of points specified by touchAreaInsets. + // This will have the effect of enlarging the hitbox of the button. + 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/MoPubSDK/Internal/MPHTTPNetworkSession.h b/MoPubSDK/Internal/MPHTTPNetworkSession.h index 830e51bea..470c30717 100644 --- a/MoPubSDK/Internal/MPHTTPNetworkSession.h +++ b/MoPubSDK/Internal/MPHTTPNetworkSession.h @@ -1,7 +1,7 @@ // // MPHTTPNetworkSession.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/Internal/MPHTTPNetworkSession.m b/MoPubSDK/Internal/MPHTTPNetworkSession.m index 5be4ab735..7e8c14527 100644 --- a/MoPubSDK/Internal/MPHTTPNetworkSession.m +++ b/MoPubSDK/Internal/MPHTTPNetworkSession.m @@ -1,7 +1,7 @@ // // MPHTTPNetworkSession.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -212,7 +212,7 @@ - (void)URLSession:(NSURLSession *)session // Validate that response is not an error. if (error != nil) { - MPLogError(@"Network request failed with: %@", error.localizedDescription); + MPLogEvent([MPLogEvent error:error message:nil]); safe_block(taskData.errorHandler, error); return; } @@ -220,8 +220,8 @@ - (void)URLSession:(NSURLSession *)session // Validate response is a HTTP response. NSHTTPURLResponse * httpResponse = [task.response isKindOfClass:[NSHTTPURLResponse class]] ? (NSHTTPURLResponse *)task.response : nil; if (httpResponse == nil) { - NSError * notHttpResponseError = [NSError errorWithDomain:kMoPubSDKNetworkDomain code:MOPUBErrorUnexpectedNetworkResponse userInfo:@{ NSLocalizedDescriptionKey: @"response is not of type NSHTTPURLResponse" }]; - MPLogError(@"Network request failed with: %@", notHttpResponseError.localizedDescription); + NSError * notHttpResponseError = [NSError networkResponseIsNotHTTP]; + MPLogEvent([MPLogEvent error:notHttpResponseError message:nil]); safe_block(taskData.errorHandler, notHttpResponseError); return; } @@ -230,15 +230,15 @@ - (void)URLSession:(NSURLSession *)session // See https://en.wikipedia.org/wiki/List_of_HTTP_status_codes for all valid status codes. if (httpResponse.statusCode >= 400) { NSError * not200ResponseError = [NSError networkErrorWithHTTPStatusCode:httpResponse.statusCode]; - MPLogError(@"Network request failed with: %@", not200ResponseError.localizedDescription); + MPLogEvent([MPLogEvent error:not200ResponseError message:nil]); safe_block(taskData.errorHandler, not200ResponseError); return; } // Validate that there is data if (taskData.responseData == nil) { - NSError * noDataError = [NSError errorWithDomain:kMoPubSDKNetworkDomain code:MOPUBErrorNoNetworkData userInfo:@{ NSLocalizedDescriptionKey: @"no data found in the NSHTTPURLResponse" }]; - MPLogError(@"Network request failed with: %@", noDataError.localizedDescription); + NSError * noDataError = [NSError networkResponseContainedNoData]; + MPLogEvent([MPLogEvent error:noDataError message:nil]); safe_block(taskData.errorHandler, noDataError); return; } diff --git a/MoPubSDK/Internal/MPHTTPNetworkTaskData.h b/MoPubSDK/Internal/MPHTTPNetworkTaskData.h index 6e1e153cc..eaf785ea6 100644 --- a/MoPubSDK/Internal/MPHTTPNetworkTaskData.h +++ b/MoPubSDK/Internal/MPHTTPNetworkTaskData.h @@ -1,7 +1,7 @@ // // MPHTTPNetworkTaskData.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/Internal/MPHTTPNetworkTaskData.m b/MoPubSDK/Internal/MPHTTPNetworkTaskData.m index f0ebd2bca..2b3d8d808 100644 --- a/MoPubSDK/Internal/MPHTTPNetworkTaskData.m +++ b/MoPubSDK/Internal/MPHTTPNetworkTaskData.m @@ -1,7 +1,7 @@ // // MPHTTPNetworkTaskData.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/Internal/MPMediationManager.h b/MoPubSDK/Internal/MPMediationManager.h index 5b35fbc2f..882e015ef 100644 --- a/MoPubSDK/Internal/MPMediationManager.h +++ b/MoPubSDK/Internal/MPMediationManager.h @@ -1,52 +1,80 @@ // // MPMediationManager.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // #import -#import "MPMediationSdkInitializable.h" +#import "MPAdapterConfiguration.h" +NS_ASSUME_NONNULL_BEGIN + +/** + Initialization completion block. + */ +typedef void(^MPMediationInitializationCompletionBlock)(NSError * _Nullable error, NSArray> * _Nullable initializedAdapters); + +/** + Manages all mediated network adapters that interface with the MoPub SDK. + */ @interface MPMediationManager : NSObject +/** + Dictionary of all instantiated adapter information providers. + */ +@property (nonatomic, readonly) NSMutableDictionary> * adapters; + +/** + Optional JSON payload to include with every MoPub ad request using the @c kNetworkAdaptersKey metadata key. + This value may be @c nil if there are no initialized adapter information providers in the runtime. + */ +@property (nonatomic, readonly, nullable) NSDictionary * adRequestPayload; + /** Singleton instance of the manager. */ -+ (instancetype _Nonnull)sharedManager; ++ (instancetype)sharedManager; /** - Initializes the inputted mediated network SDKs from the cache. - @param networks Networks to initialize. If @c nil, nothing will be done. - @param completion Optional completion block. + Initializes the specified adapter information providers and their underlying network SDKs. + @param providers Optional additional adapter information providers to initialize along with the officially supported networks. + @param configurations Optional configuration parameters for the underlying network SDKs that the providers manage. Only @c NSString, @c NSNumber, @c NSArray, and @c NSDictionary types are allowed. This value may be @c nil. + @param options Optional MoPub request options for the mediated networks. + @param complete Required completion block specifying the initialization error (if any) and the adapter information providers that were successfully initialized. */ -- (void)initializeMediatedNetworks:(NSArray> * _Nullable)networks - completion:(void (^ _Nullable)(NSError * _Nullable error))completion; +- (void)initializeWithAdditionalProviders:(NSArray> * _Nullable)providers + configurations:(NSDictionary *> * _Nullable)configurations + requestOptions:(NSDictionary *> * _Nullable)options + complete:(MPMediationInitializationCompletionBlock)complete; /** Sets the initialization parameters for a given network in the cache. @param params Initialization parameters sent to the @c MPSdkInitializable instance when initialized. @param networkClass Network class. */ -- (void)setCachedInitializationParameters:(NSDictionary * _Nullable)params forNetwork:(Class _Nonnull)networkClass; +- (void)setCachedInitializationParameters:(NSDictionary * _Nullable)params + forNetwork:(Class)networkClass; /** Retrieves the cached initialization parameters for a given network. @param networkClass Network class. @returns The cached parameters or @c nil. */ -- (NSDictionary * _Nullable)cachedInitializationParametersForNetwork:(Class _Nonnull)networkClass; +- (NSDictionary * _Nullable)cachedInitializationParametersForNetwork:(Class)networkClass; /** - Retrieves all of the currently cached networks. - @return A list of all cached networks or @c nil. + Clears the cache. */ -- (NSArray> * _Nullable)allCachedNetworks; +- (void)clearCache; /** - Clears the cache. + Retrieves the Advanced Bidding tokens only. + @remarks This is deprecated. */ -- (void)clearCache; +- (NSDictionary * _Nullable)advancedBiddingTokens; @end + +NS_ASSUME_NONNULL_END diff --git a/MoPubSDK/Internal/MPMediationManager.m b/MoPubSDK/Internal/MPMediationManager.m index c41f864ea..c581f6e57 100644 --- a/MoPubSDK/Internal/MPMediationManager.m +++ b/MoPubSDK/Internal/MPMediationManager.m @@ -1,19 +1,57 @@ // // MPMediationManager.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // #import "MPMediationManager.h" +#import "MPError.h" #import "MPLogging.h" +#import "NSBundle+MPAdditions.h" + +// Macros for dispatching asynchronously to the main queue +#define mp_safe_block(block, ...) block ? block(__VA_ARGS__) : nil /** Key of the @c NSUserDefaults entry for the network initialization cache. */ static NSString * const kNetworkSDKInitializationParametersKey = @"com.mopub.mopub-ios-sdk.network-init-info"; +// File name and extension of the certified adapter information providers file. +// This should correspond to `MPAdapters.plist` in the Resources directory. +static NSString * kAdaptersFile = @"MPAdapters"; +static NSString * kAdaptersFileType = @"plist"; + +// Ad request JSON payload keys. +static NSString const * kAdapterOptionsKey = @"options"; +static NSString const * kAdapterVersionKey = @"adapter_version"; +static NSString const * kNetworkSdkVersionKey = @"sdk_version"; +static NSString const * kTokenKey = @"token"; + +@interface MPMediationManager() +/** + Dictionary of all instantiated adapter information providers. + */ +@property (nonatomic, strong, readwrite) NSMutableDictionary> * adapters; + +/** + All certified adapter information classes that exist within the current runtime. + */ +@property (nonatomic, strong, readonly) NSSet> * certifiedAdapterClasses; + +/** + Flag indicating if an initialization is already in progress. + */ +@property (nonatomic, assign) BOOL isInitializing; + +/** + Initialization queue. + */ +@property (nonatomic, strong) dispatch_queue_t queue; +@end + @implementation MPMediationManager #pragma mark - Initialization @@ -28,9 +66,177 @@ + (instancetype)sharedManager { return sharedInstance; } +- (instancetype)init { + if (self = [super init]) { + _adapters = [NSMutableDictionary dictionary]; + _certifiedAdapterClasses = MPMediationManager.certifiedAdapterInformationProviderClasses; + _isInitializing = NO; + _queue = dispatch_queue_create("Mediated Adapter Initialization Queue", DISPATCH_QUEUE_SERIAL); + } + + return self; +} + +- (void)initializeWithAdditionalProviders:(NSArray> *)providers + configurations:(NSDictionary *> *)configurations + requestOptions:(NSDictionary *> * _Nullable)options + complete:(MPMediationInitializationCompletionBlock)complete { + // Initialization is already in progress; error out. + if (self.isInitializing) { + mp_safe_block(complete, NSError.sdkInitializationInProgress, nil); + return; + } + + // Start the initialization process + self.isInitializing = YES; + + // Combines the additional providers with the existing set of certified adapter + // information providers (if needed). + NSSet * classesToInitialize = (providers != nil ? [self.certifiedAdapterClasses setByAddingObjectsFromArray:providers] : self.certifiedAdapterClasses); + + // There are no adapter information providers to initialize. Do nothing. + if (classesToInitialize.count == 0) { + self.isInitializing = NO; + mp_safe_block(complete, nil, nil); + return; + } + + // Holds all of the successfully initialized adapter information providers. + NSMutableDictionary> * initializedAdapters = [NSMutableDictionary dictionaryWithCapacity:classesToInitialize.count]; + + // Attempt to instantiate and initialize each adapter information provider. + // If a network has an invalid `moPubNetworkName` or has already been initialized, + // that network will be skipped. + [classesToInitialize enumerateObjectsUsingBlock:^(Class adapterInfoProviderClass, BOOL * _Nonnull stop) { + // Create an instance of the adapter configuration + id adapterInfoProvider = (id)[[[adapterInfoProviderClass class] alloc] init]; + NSString * network = adapterInfoProvider.moPubNetworkName; + + // Verify that the adapter information provider has a MoPub network name and that it's + // not already created. + if (network.length == 0 || initializedAdapters[network] != nil) { + return; + } + + // Retrieve the full set of initialization parameters. + NSDictionary * initializationParams = [self parametersForAdapter:adapterInfoProvider overrideConfiguration:configurations[NSStringFromClass(adapterInfoProviderClass)]]; + + // Populate the request options (if any) + [adapterInfoProvider addMoPubRequestOptions:options[NSStringFromClass(adapterInfoProviderClass)]]; + + // Queue up the adapter's underlying SDK initialization. + dispatch_async(self.queue, ^{ + [adapterInfoProvider initializeNetworkWithConfiguration:initializationParams complete:^(NSError * error) { + // Log adapter initialization error. + if (error != nil) { + NSString * logPrefix = [NSString stringWithFormat:@"Adapter %@ encountered an error during initialization", NSStringFromClass(adapterInfoProviderClass)]; + MPLogEvent([MPLogEvent error:error message:logPrefix]); + } + }]; + }); + + // Adapter initialization is complete. + initializedAdapters[network] = adapterInfoProvider; + }]; + + // Set the initialized adapters and update the internal state. + self.adapters = initializedAdapters; + self.isInitializing = NO; + mp_safe_block(complete, nil, initializedAdapters.allValues); +} + +#pragma mark - Computed Properties + +- (NSDictionary *)adRequestPayload { + // There are no initialized adapter information providers; send nothing. + if (self.adapters.count == 0) { + return nil; + } + + // Build the JSON payload. + NSMutableDictionary * payload = [NSMutableDictionary dictionaryWithCapacity:self.adapters.count]; + [self.adapters enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, id _Nonnull adapter, BOOL * _Nonnull stop) { + NSMutableDictionary * adapterPayload = [NSMutableDictionary dictionary]; + adapterPayload[kAdapterOptionsKey] = adapter.moPubRequestOptions; + adapterPayload[kAdapterVersionKey] = adapter.adapterVersion; + adapterPayload[kNetworkSdkVersionKey] = adapter.networkSdkVersion; + // Advanced Bidding tokens have been disabled from the adapter payload + // since we are currently sending the tokens in the former `abt` field, + // and do not want to send the tokens twice. + // Once the `abt` field has been deprecated, this token should be re-enabled. + //adapterPayload[kTokenKey] = adapter.biddingToken; + + payload[key] = adapterPayload; + }]; + + return payload; +} + +#pragma mark - Certified Adapter Information Providers + +/** + Attempts to retrieve @c MPAdapters.plist from the current bundle's resources. + @return The file path if available; otherwise @c nil. + */ ++ (NSString *)adapterInformationProvidersFilePath { + // Retrieve the plist containing the default adapter information provider class names. + NSBundle * parentBundle = [NSBundle resourceBundleForClass:self.class]; + NSString * filepath = [parentBundle pathForResource:kAdaptersFile ofType:kAdaptersFileType]; + return filepath; +} + +/** + Retrieves the certified adapter information classes that exist within the + current runtime. + @return List of certified adapter information classes that exist in the runtime. + */ ++ (NSSet> * _Nonnull)certifiedAdapterInformationProviderClasses { + // Certified adapters file not present. Do not continue. + NSString * filepath = MPMediationManager.adapterInformationProvidersFilePath; + if (filepath == nil) { + MPLogInfo(@"Could not find MPAdapters.plist."); + return [NSSet set]; + } + + // Try to retrieve the class for each certified adapter + NSArray * adapterClassNames = [NSArray arrayWithContentsOfFile:filepath]; + NSMutableSet> * adapterInfoClasses = [NSMutableSet setWithCapacity:adapterClassNames.count]; + [adapterClassNames enumerateObjectsUsingBlock:^(NSString * _Nonnull className, NSUInteger idx, BOOL * _Nonnull stop) { + // Adapter information provider is valid since we can retrieve the class and it conforms + // to the `MPAdapterConfiguration` protocol. + Class adapterClass = NSClassFromString(className); + if (adapterClass != Nil && [adapterClass conformsToProtocol:@protocol(MPAdapterConfiguration)]) { + [adapterInfoClasses addObject:adapterClass]; + } + }]; + + return adapterInfoClasses; +} + +/** + Combines cached initialization parameters with override parameters. + @param adapter Adapter information provider that will be populated. + @param configuration Externally-specified initialization parameters. + @return The combined initialization parameters with any @c moPubRequestOptions removed. In the event that + there are no parameters, @c nil is returned. + */ +- (NSDictionary *)parametersForAdapter:(id)adapter + overrideConfiguration:(NSDictionary * _Nullable)configuration { + // Retrieve the adapter's cached initialization parameters and inputted initialization parameters. + // Combine the two dictionaries, giving preference to the publisher-inputted parameters. + NSDictionary * cachedParameters = [self cachedInitializationParametersForNetwork:adapter.class]; + + NSMutableDictionary * initializationParams = (cachedParameters != nil ? [NSMutableDictionary dictionaryWithDictionary:cachedParameters] : [NSMutableDictionary dictionary]); + if (configuration != nil) { + [initializationParams addEntriesFromDictionary:configuration]; + } + + return (initializationParams.count > 0 ? initializationParams : nil); +} + #pragma mark - Cache -- (void)setCachedInitializationParameters:(NSDictionary * _Nullable)params forNetwork:(Class _Nonnull)networkClass { +- (void)setCachedInitializationParameters:(NSDictionary * _Nullable)params forNetwork:(Class _Nonnull)networkClass { // Empty network names and parameters are invalid. NSString * network = NSStringFromClass(networkClass); if (network.length == 0 || params == nil) { @@ -51,45 +257,17 @@ - (void)setCachedInitializationParameters:(NSDictionary * _Nullable)params forNe } } -- (NSDictionary * _Nullable)cachedInitializationParametersForNetwork:(Class)networkClass { +- (NSDictionary * _Nullable)cachedInitializationParametersForNetwork:(Class)networkClass { // Empty network names are invalid. NSString * network = NSStringFromClass(networkClass); if (network.length == 0) { return nil; } - NSDictionary * networkParameters = nil; - @synchronized (self) { - NSDictionary * cachedParameters = [[NSUserDefaults standardUserDefaults] objectForKey:kNetworkSDKInitializationParametersKey]; - if (cachedParameters != nil) { - networkParameters = [cachedParameters objectForKey:network]; - } - } - - return networkParameters; -} - -- (NSArray> * _Nullable)allCachedNetworks { - NSMutableArray> * cachedNetworks = nil; - @synchronized (self) { - NSDictionary * cachedParameters = [[NSUserDefaults standardUserDefaults] objectForKey:kNetworkSDKInitializationParametersKey]; - NSArray * cacheKeys = [cachedParameters allKeys]; - if (cacheKeys == nil) { - return nil; - } - - // Convert the strings of class names into class types. - cachedNetworks = [NSMutableArray array]; - [cacheKeys enumerateObjectsUsingBlock:^(NSString * key, NSUInteger idx, BOOL * _Nonnull stop) { - Class c = NSClassFromString(key); - if ([c conformsToProtocol:@protocol(MPMediationSdkInitializable)]) { - [cachedNetworks addObject:c]; - } - }]; + NSDictionary *cachedParameters = [[NSUserDefaults standardUserDefaults] objectForKey:kNetworkSDKInitializationParametersKey]; + return [cachedParameters objectForKey:network]; } - - return cachedNetworks; } - (void)clearCache { @@ -97,42 +275,27 @@ - (void)clearCache { [[NSUserDefaults standardUserDefaults] removeObjectForKey:kNetworkSDKInitializationParametersKey]; [[NSUserDefaults standardUserDefaults] synchronize]; - MPLogInfo(@"Cleared cached SDK initialization parameters"); + MPLogDebug(@"Cleared cached SDK initialization parameters"); } } -#pragma mark - Mediation - -- (void)initializeMediatedNetworks:(NSArray> *)networks - completion:(void (^ _Nullable)(NSError * _Nullable error))completion { - // Nothing to initialize - if (networks.count == 0) { - if (completion != nil) { - completion(nil); - } +#pragma mark - Advanced Bidding - return; +- (NSDictionary *)advancedBiddingTokens { + // No adapters. + if (self.adapters.count == 0) { + return nil; } - // Network SDK initializations should occur on the main thread since - // some of those SDKs require it. - dispatch_async(dispatch_get_main_queue(), ^{ - for (Class mediationClass in networks) { - id mediationNetwork = (id)[[mediationClass class] new]; - NSDictionary * cachedInitializationParams = [self cachedInitializationParametersForNetwork:mediationClass]; - - // Only attempt initialization if they exist. Otherwise, we should wait for the - // on-demand initialization to occur with the correct parameters. - if (cachedInitializationParams != nil) { - [mediationNetwork initializeSdkWithParameters:cachedInitializationParams]; - MPLogInfo(@"Loaded mediated network: %@", NSStringFromClass(mediationClass)); - } + // Generate the JSON dictionary for all participating bidders. + NSMutableDictionary * tokens = [NSMutableDictionary dictionary]; + [self.adapters enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull networkName, id _Nonnull adapter, BOOL * _Nonnull stop) { + if (adapter.biddingToken != nil) { + tokens[networkName] = @{ kTokenKey: adapter.biddingToken }; } + }]; - if (completion != nil) { - completion(nil); - } - }); + return tokens; } @end diff --git a/MoPubSDK/Internal/MPMemoryCache.h b/MoPubSDK/Internal/MPMemoryCache.h index 557989a4e..429a82b5c 100644 --- a/MoPubSDK/Internal/MPMemoryCache.h +++ b/MoPubSDK/Internal/MPMemoryCache.h @@ -1,7 +1,7 @@ // // MPMemoryCache.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/Internal/MPMemoryCache.m b/MoPubSDK/Internal/MPMemoryCache.m index 99c8be4c1..cadf51dac 100644 --- a/MoPubSDK/Internal/MPMemoryCache.m +++ b/MoPubSDK/Internal/MPMemoryCache.m @@ -1,7 +1,7 @@ // // MPMemoryCache.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -47,7 +47,7 @@ - (NSData * _Nullable)dataForKey:(NSString * _Nonnull)key { return nil; } - MPLogTrace(@"%@ retrieved data for key %@", NSStringFromClass(self.class), key); + MPLogDebug(@"%@ retrieved data for key %@", NSStringFromClass(self.class), key); return [self.memcache objectForKey:key]; } @@ -58,12 +58,12 @@ - (void)setData:(NSData * _Nullable)data forKey:(NSString * _Nonnull)key { // Set cache entry if (data != nil) { - MPLogTrace(@"%@ set data %@ for key %@", NSStringFromClass(self.class), data, key); + MPLogDebug(@"%@ set data %@ for key %@", NSStringFromClass(self.class), data, key); [self.memcache setObject:data forKey:key]; } // Remove cache entry else { - MPLogTrace(@"%@ removed cache entry %@", NSStringFromClass(self.class), key); + MPLogDebug(@"%@ removed cache entry %@", NSStringFromClass(self.class), key); [self.memcache removeObjectForKey:key]; } } @@ -71,7 +71,7 @@ - (void)setData:(NSData * _Nullable)data forKey:(NSString * _Nonnull)key { #pragma mark - NSCacheDelegate - (void)cache:(NSCache *)cache willEvictObject:(id)obj { - MPLogTrace(@"%@ evicted %@", NSStringFromClass(self.class), obj); + MPLogDebug(@"%@ evicted %@", NSStringFromClass(self.class), obj); } @end @@ -81,7 +81,7 @@ @implementation MPMemoryCache (UIImage) - (UIImage * _Nullable)imageForKey:(NSString * _Nonnull)key { NSData * imageData = [self dataForKey:key]; if (imageData == nil) { - MPLogTrace(@"%@ found no image data for key %@", NSStringFromClass(self.class), key); + MPLogDebug(@"%@ found no image data for key %@", NSStringFromClass(self.class), key); return nil; } diff --git a/MoPubSDK/Internal/MPRateLimitConfiguration.h b/MoPubSDK/Internal/MPRateLimitConfiguration.h new file mode 100644 index 000000000..5ce0bd30a --- /dev/null +++ b/MoPubSDK/Internal/MPRateLimitConfiguration.h @@ -0,0 +1,40 @@ +// +// MPRateLimitConfiguration.h +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface MPRateLimitConfiguration : NSObject + +/** + Returns the number of milliseconds of the last rate limit, or 0 for first request or if the last request was not rate limited. + */ +@property (nonatomic, readonly) NSUInteger lastRateLimitMilliseconds; + +/** + Returns the reason for the last rate limit, or nil for first request or if the last request did not include a reason. + */ +@property (nonatomic, copy, readonly, nullable) NSString * lastRateLimitReason; + +/** + Returns present rate limit state. @c YES if presently rate limited, @c NO otherwise + */ +@property (nonatomic, readonly) BOOL isRateLimited; + +/** + Sets rate limit state to rate limited. Automatically expires after @c milliseconds milliseconds. Rate limiting to 0 or + negative milliseconds will result in no rate limit, but the number and reason will still be saved for later. + @param milliseconds The number of milliseconds to rate limit for. If 0, no rate limit will be put into effect, but the number will still be saved for later + @param reason The reason for the rate limit. This is copied directly from the server response. This parameter is optional. + */ +- (void)setRateLimitTimerWithMilliseconds:(NSInteger)milliseconds reason:(NSString * _Nullable)reason; + +@end + +NS_ASSUME_NONNULL_END diff --git a/MoPubSDK/Internal/MPRateLimitConfiguration.m b/MoPubSDK/Internal/MPRateLimitConfiguration.m new file mode 100644 index 000000000..4265725a3 --- /dev/null +++ b/MoPubSDK/Internal/MPRateLimitConfiguration.m @@ -0,0 +1,53 @@ +// +// MPRateLimitConfiguration.m +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import "MPRateLimitConfiguration.h" +#import "MPRealTimeTimer.h" + +@interface MPRateLimitConfiguration () + +@property (nonatomic, strong) MPRealTimeTimer * timer; + +@end + +@implementation MPRateLimitConfiguration + +- (BOOL)isRateLimited { + return self.timer != nil; +} + +- (void)setRateLimitTimerWithMilliseconds:(NSInteger)milliseconds reason:(NSString *)reason { + @synchronized(self) { + // Intentionally treat accidental less than 0 as 0 + if (milliseconds < 0) { + milliseconds = 0; + } + + // If already rate limited, reset the timer by invalidating the present timer. This guarantees the rate limit value from the most recent response is used. + if (self.isRateLimited) { + [self.timer invalidate]; + } + + _lastRateLimitMilliseconds = milliseconds; + _lastRateLimitReason = reason; + + if (milliseconds == 0) { + self.timer = nil; + return; + } + + __weak __typeof__(self) weakSelf = self; + self.timer = [[MPRealTimeTimer alloc] initWithInterval:(((double)milliseconds) / 1000.0) block:^(MPRealTimeTimer * timer) { + [weakSelf.timer invalidate]; + weakSelf.timer = nil; + }]; + [self.timer scheduleNow]; + } +} + +@end diff --git a/MoPubSDK/Internal/MPRateLimitManager.h b/MoPubSDK/Internal/MPRateLimitManager.h new file mode 100644 index 000000000..61644aec8 --- /dev/null +++ b/MoPubSDK/Internal/MPRateLimitManager.h @@ -0,0 +1,50 @@ +// +// MPRateLimitManager.h +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface MPRateLimitManager : NSObject + +// This is a singleton. ++ (instancetype)sharedInstance; + +/** + Current rate limit state for the given ad unit ID. + @param adUnitId The ad unit ID to check rate limit state for + @return @c YES if presently rate limited for this ad unit Id, @c NO otherwise + */ +- (BOOL)isRateLimitedForAdUnitId:(NSString *)adUnitId; + +/** + Sets rate limit state to rate limited. Automatically expires after @c milliseconds milliseconds. Rate limiting to 0 or + negative milliseconds will result in no rate limit, but the number and reason will still be saved for later. + @param adUnitId The ad unit ID to rate limit for + @param milliseconds The number of milliseconds to rate limit for. If 0, no rate limit will be put into effect, but the number will still be saved for later + @param reason The reason for the rate limit. This is copied directly from the server response. This parameter is optional. + */ +- (void)setRateLimitTimerWithAdUnitId:(NSString *)adUnitId milliseconds:(NSInteger)milliseconds reason:(NSString * _Nullable)reason; + +/** + Given an ad unit ID, returns the last millisecond rate limit value for that ad unit ID. + @param adUnitId The ad unit ID to check last millisecond value for + @return The number of milliseconds for the last rate limit, or 0 for first request or if the last request didn't rate limit + */ +- (NSUInteger)lastRateLimitMillisecondsForAdUnitId:(NSString *)adUnitId; + +/** + Given an ad unit ID, returns the last reason value for that ad unit ID. + @param adUnitId The ad unit ID to check reason for + @return The last reason given for this ad unit id, or @c nil for first request or if no reason was given + */ +- (NSString * _Nullable)lastRateLimitReasonForAdUnitId:(NSString *)adUnitId; + +@end + +NS_ASSUME_NONNULL_END diff --git a/MoPubSDK/Internal/MPRateLimitManager.m b/MoPubSDK/Internal/MPRateLimitManager.m new file mode 100644 index 000000000..3b58aae0d --- /dev/null +++ b/MoPubSDK/Internal/MPRateLimitManager.m @@ -0,0 +1,73 @@ +// +// MPRateLimitManager.m +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import "MPRateLimitManager.h" +#import "MPRateLimitConfiguration.h" + +@interface MPRateLimitManager () + +// Ad Unit IDs are used as keys; @c MPRateLimitConfiguration objects are used as values +@property (nonatomic, strong) NSMutableDictionary * configurationDictionary; + +@end + +@implementation MPRateLimitManager + ++ (instancetype)sharedInstance { + static MPRateLimitManager * sharedInstance = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sharedInstance = [[self alloc] init]; + }); + return sharedInstance; +} + +- (instancetype)init { + if (self = [super init]) { + _configurationDictionary = [NSMutableDictionary dictionary]; + } + + return self; +} + +- (void)setRateLimitTimerWithAdUnitId:(NSString *)adUnitId milliseconds:(NSInteger)milliseconds reason:(NSString *)reason { + // Fast fail if @c adUnitId is @c nil + if (adUnitId == nil) { + return; + } + + @synchronized (self) { + // Make new configuration if one does not already exist for this ad unit ID + if (self.configurationDictionary[adUnitId] == nil) { + self.configurationDictionary[adUnitId] = [[MPRateLimitConfiguration alloc] init]; + } + + // Set the rate limit timer + MPRateLimitConfiguration * config = self.configurationDictionary[adUnitId]; + [config setRateLimitTimerWithMilliseconds:milliseconds reason:reason]; + } +} + +// Getter methods will return a default value upon a @c nil ad unit ID to avoid crashing on dictionary +// lookups. The return statement template is +// `return adUnitId != nil ? : ` +// Using `!=` instead of `==` allows the configuration value to be listed first, then default second. + +- (BOOL)isRateLimitedForAdUnitId:(NSString *)adUnitId { + return adUnitId != nil ? self.configurationDictionary[adUnitId].isRateLimited : NO; +} + +- (NSUInteger)lastRateLimitMillisecondsForAdUnitId:(NSString *)adUnitId { + return adUnitId != nil ? self.configurationDictionary[adUnitId].lastRateLimitMilliseconds : 0; +} + +- (NSString *)lastRateLimitReasonForAdUnitId:(NSString *)adUnitId { + return adUnitId != nil ? self.configurationDictionary[adUnitId].lastRateLimitReason : nil; +} + +@end diff --git a/MoPubSDK/Internal/MPReachabilityManager.h b/MoPubSDK/Internal/MPReachabilityManager.h index 3307415d7..e6c26fd07 100644 --- a/MoPubSDK/Internal/MPReachabilityManager.h +++ b/MoPubSDK/Internal/MPReachabilityManager.h @@ -1,7 +1,7 @@ // // MPReachabilityManager.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/Internal/MPReachabilityManager.m b/MoPubSDK/Internal/MPReachabilityManager.m index be85cab89..e49285647 100644 --- a/MoPubSDK/Internal/MPReachabilityManager.m +++ b/MoPubSDK/Internal/MPReachabilityManager.m @@ -1,7 +1,7 @@ // // MPReachabilityManager.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/Internal/MPURL.h b/MoPubSDK/Internal/MPURL.h index 5318137ef..66e2bd79c 100644 --- a/MoPubSDK/Internal/MPURL.h +++ b/MoPubSDK/Internal/MPURL.h @@ -1,7 +1,7 @@ // // MPURL.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/Internal/MPURL.m b/MoPubSDK/Internal/MPURL.m index c2d3f1b9f..4e6387c6d 100644 --- a/MoPubSDK/Internal/MPURL.m +++ b/MoPubSDK/Internal/MPURL.m @@ -1,7 +1,7 @@ // // MPURL.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/Internal/MPURLRequest.h b/MoPubSDK/Internal/MPURLRequest.h index ff2c04d36..76549071b 100644 --- a/MoPubSDK/Internal/MPURLRequest.h +++ b/MoPubSDK/Internal/MPURLRequest.h @@ -1,7 +1,7 @@ // // MPURLRequest.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/Internal/MPURLRequest.m b/MoPubSDK/Internal/MPURLRequest.m index 3dcc19573..1e1e1c79f 100644 --- a/MoPubSDK/Internal/MPURLRequest.m +++ b/MoPubSDK/Internal/MPURLRequest.m @@ -1,15 +1,16 @@ // // MPURLRequest.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // -#import "MPURLRequest.h" #import "MPAPIEndpoints.h" #import "MPLogging.h" #import "MPURL.h" +#import "MPURLRequest.h" +#import "MPWebBrowserUserAgentInfo.h" // All requests have a 10 second timeout. const NSTimeInterval kRequestTimeoutInterval = 10.0; @@ -31,7 +32,7 @@ - (instancetype)initWithURL:(NSURL *)URL { postData = mpUrl.postData; } else { - MPLogFatal(@"POST Data is not serializable into JSON:\n%@", mpUrl.postData); + MPLogInfo(@"🚨 POST data failed to serialize into JSON:\n%@", mpUrl.postData); } } @@ -56,7 +57,7 @@ - (instancetype)initWithURL:(NSURL *)URL { if (self = [super initWithURL:requestUrl]) { // Generate the request [self setHTTPShouldHandleCookies:NO]; - [self setValue:MPURLRequest.userAgent forHTTPHeaderField:@"User-Agent"]; + [self setValue:MPWebBrowserUserAgentInfo.userAgent forHTTPHeaderField:@"User-Agent"]; [self setCachePolicy:NSURLRequestReloadIgnoringCacheData]; [self setTimeoutInterval:kRequestTimeoutInterval]; @@ -77,7 +78,7 @@ - (instancetype)initWithURL:(NSURL *)URL { [self setHTTPBody:jsonData]; } else { - MPLogError(@"Could not generate JSON body from %@", postData); + MPLogEvent([MPLogEvent error:error message:nil]); } } } @@ -92,45 +93,12 @@ + (MPURLRequest *)requestWithURL:(NSURL *)URL { - (NSString *)description { if (self.HTTPBody != nil) { NSString * httpBody = [[NSString alloc] initWithData:self.HTTPBody encoding:NSUTF8StringEncoding]; - return [NSString stringWithFormat:@"%@\n%@", self.URL, httpBody]; + return [NSString stringWithFormat:@"%@\n\t%@", self.URL, httpBody]; } else { return self.URL.absoluteString; } } -/** - Global variable for holding the user agent string - */ -NSString * gUserAgent = nil; - -/** - Retrieves the current user agent as determined by @c UIWebView. - @returns The user agent. - */ -+ (NSString *)userAgent { - if (gUserAgent == nil) { - // The user agent string cannot be obtained from a UIWebView unless on the - // main thread (there'll be a crash if you try to obtain on a thread other than main). - // Therefore, obtain the string on main thread and block this thread if needed to get it. - - // Make a block to obtain the user agent string - void (^obtainUserAgentBlock)(void) = ^void(void) { - // Only set @c gUserAgent on the main thread to avoid undefined behavior. - gUserAgent = [[[UIWebView alloc] init] stringByEvaluatingJavaScriptFromString:@"navigator.userAgent"]; - }; - - if ([NSThread isMainThread]) { - // Run the block directly if on main thread. - obtainUserAgentBlock(); - } else { - // Block this thread to obtain user agent string on main thread. - dispatch_sync(dispatch_get_main_queue(), obtainUserAgentBlock); - } - } - - return gUserAgent; -} - @end NS_ASSUME_NONNULL_END diff --git a/MoPubSDK/Internal/MPVASTTracking.h b/MoPubSDK/Internal/MPVASTTracking.h index 741835d0d..02ffb3f9c 100644 --- a/MoPubSDK/Internal/MPVASTTracking.h +++ b/MoPubSDK/Internal/MPVASTTracking.h @@ -1,38 +1,34 @@ // // MPVASTTracking.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // #import - -@class MPVideoConfig; - -typedef NS_ENUM(NSUInteger, MPVideoEventType) { - MPVideoEventTypeTimeUpdate = 0, - MPVideoEventTypeMuted, - MPVideoEventTypeUnmuted, - MPVideoEventTypePause, - MPVideoEventTypeResume, - MPVideoEventTypeFullScreen, - MPVideoEventTypeExitFullScreen, - MPVideoEventTypeExpand, - MPVideoEventTypeCollapse, - MPVideoEventTypeCompleted, - MPVideoEventTypeImpression, - MPVideoEventTypeClick, - MPVideoEventTypeError -}; +#import "MPVideoConfig.h" @interface MPVASTTracking : NSObject -@property (nonatomic, readonly) MPVideoConfig *videoConfig; -@property (nonatomic) NSTimeInterval videoDuration; +- (instancetype)initWithVideoConfig:(MPVideoConfig *)videoConfig videoURL:(NSURL *)videoURL; + +/** + Call this when a new video event (@c MPVideoEvent) happens. + + @note Some events allows repetition, and some don't. + @note For @c MPVideoEventProgress, call @c handleVideoProgressEvent:videoDuration: instead. + */ +- (void)handleVideoEvent:(NSString *)videoEvent videoTimeOffset:(NSTimeInterval)videoTimeOffset; + +/** + Call this when the video play progress is updated. -- (instancetype)initWithMPVideoConfig:(MPVideoConfig *)videoConfig videoView:(UIView *)videoView; -- (void)handleVideoEvent:(MPVideoEventType)videoEventType videoTimeOffset:(NSTimeInterval)timeOffset; -- (void)handleNewVideoView:(UIView *)videoView; + @note Do not call this for video complete event. Use @c MPVideoEventComplete instead. Neither + custom timer nor iOS video player time observer manages the video complete event very well (with a + high chance of not firing at all due to timing issue), and iOS provides a specific notification for + the video complete event. + */ +- (void)handleVideoProgressEvent:(NSTimeInterval)videoTimeOffset videoDuration:(NSTimeInterval)videoDuration; @end diff --git a/MoPubSDK/Internal/MPVASTTracking.m b/MoPubSDK/Internal/MPVASTTracking.m index e657ace65..e6fc22e9e 100644 --- a/MoPubSDK/Internal/MPVASTTracking.m +++ b/MoPubSDK/Internal/MPVASTTracking.m @@ -1,254 +1,155 @@ // // MPVASTTracking.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // -#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 +static dispatch_once_t dispatchOnceToken; // for `oneOffEventTypes` +static NSSet *oneOffEventTypes; @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, strong) MPVideoConfig *videoConfig; +@property (nonatomic, strong) NSURL *videoURL; +@property (nonatomic, strong) id analyticsTracker; -@property (nonatomic) NSMutableArray *variableProgressTrackers; //NSMutableArray +/** + The key is a @c MPVideoEvent string, and the value is a set of fired @c MPVASTTrackingEvent of the same type. -@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; + Each event could associate to multiple tracking URL's and thus multiple `MPVASTTrackingEvent`s. For + most events except @c MPVideoEventProgress, all the tracking URL's are sent at the same time. For + the @c MPVideoEventProgress case, typically different URL's are supposed to be sent at different + times (at 0s, 5s, 10s, and so on), and thus we have to use a dictionary with @c MPVideoEvent as key + and @c NSMutableSet as the value to keep track of the fired status, instead + of just a simple @c NSMutableSet that cannot differentiate the @c MPVideoEventProgress + events of different play times. + */ +@property (nonatomic, strong) NSMutableDictionary *> *firedTable; @end @implementation MPVASTTracking -- (instancetype)initWithMPVideoConfig:(MPVideoConfig *)videoConfig videoView:(UIView *)videoView; -{ +- (instancetype)initWithVideoConfig:(MPVideoConfig *)videoConfig videoURL:(NSURL *)videoURL { 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]; - } + _videoURL = videoURL; + _analyticsTracker = [MPAnalyticsTracker sharedTracker]; + _firedTable = [NSMutableDictionary new]; + + dispatch_once(&dispatchOnceToken, ^{ + oneOffEventTypes = [NSSet setWithObjects: + MPVideoEventClick, + MPVideoEventCloseLinear, + MPVideoEventComplete, + MPVideoEventCreativeView, + MPVideoEventFirstQuartile, + MPVideoEventImpression, + MPVideoEventMidpoint, + MPVideoEventProgress, + MPVideoEventSkip, + MPVideoEventStart, + MPVideoEventThirdQuartile, + nil]; + }); } 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)handleVideoEvent:(MPVideoEvent)videoEvent videoTimeOffset:(NSTimeInterval)videoTimeOffset { + if ([oneOffEventTypes containsObject:videoEvent] + && self.firedTable[videoEvent] != nil) { + return; // do not fire more than once + } + + if (self.videoConfig == nil && [videoEvent isEqualToString:MPVideoEventError] == NO) { + return; // only allow `videoConfig` to be nil for error event } -} -- (void)handleProgressTrackers:(NSTimeInterval)timeOffset -{ - if (timeOffset >= kStartTrackerTime && !self.startTracker.trackersFired) { - [self cleanAndSendTrackingEvents:self.startTracker timeOffset:timeOffset]; + if ([videoEvent isEqualToString:MPVideoEventProgress]) { + return; // call `handleVideoProgressEvent:videoDuration:` instead } - if ((0.75 * self.videoDuration) <= timeOffset && !self.thirdQuartileTracker.trackersFired) { - [self cleanAndSendTrackingEvents:self.thirdQuartileTracker timeOffset:timeOffset]; + NSMutableSet *firedEvents = [NSMutableSet new]; + NSMutableSet *urls = [NSMutableSet new]; + for (MPVASTTrackingEvent *event in [self.videoConfig trackingEventsForKey:videoEvent]) { + [urls addObject:event.URL]; + [firedEvents addObject:event]; } - if ((0.50 * self.videoDuration) <= timeOffset && !self.midPointTracker.trackersFired) { - [self cleanAndSendTrackingEvents:self.midPointTracker timeOffset:timeOffset]; + if (urls.count > 0) { + [self processAndSendURLs:urls videoTimeOffset:videoTimeOffset]; } + self.firedTable[videoEvent] = firedEvents; +} - if ((0.25 * self.videoDuration) <= timeOffset && !self.firstQuartileTracker.trackersFired) { - [self cleanAndSendTrackingEvents:self.firstQuartileTracker timeOffset:timeOffset]; +- (void)handleVideoProgressEvent:(NSTimeInterval)videoTimeOffset videoDuration:(NSTimeInterval)videoDuration { + if (videoTimeOffset < 0 || videoDuration <= 0) { + return; } - 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.firedTable[MPVideoEventStart] == nil) { + [self handleVideoEvent:MPVideoEventStart videoTimeOffset:videoTimeOffset]; + } + if ((0.25 * videoDuration) <= videoTimeOffset + && self.firedTable[MPVideoEventFirstQuartile] == nil) { + [self handleVideoEvent:MPVideoEventFirstQuartile videoTimeOffset:videoTimeOffset]; + } + if ((0.50 * videoDuration) <= videoTimeOffset + && self.firedTable[MPVideoEventMidpoint] == nil) { + [self handleVideoEvent:MPVideoEventMidpoint videoTimeOffset:videoTimeOffset]; + } + if ((0.75 * videoDuration) <= videoTimeOffset + && self.firedTable[MPVideoEventThirdQuartile] == nil) { + [self handleVideoEvent:MPVideoEventThirdQuartile videoTimeOffset:videoTimeOffset]; + } + // The Complete event is not handled in this method intentionally. Please see header comments. + + // `MPVideoEventProgress` specific handling: do not use `handleVideoEvent:videoTimeOffset:` + NSMutableSet *urls = [NSMutableSet new]; + NSMutableSet *firedProgressEvents = self.firedTable[MPVideoEventProgress]; + for (MPVASTTrackingEvent *event in [self.videoConfig trackingEventsForKey:MPVideoEventProgress]) { + if ([firedProgressEvents containsObject:event] == NO + && [event.progressOffset timeIntervalForVideoWithDuration:videoDuration] <= videoTimeOffset) { + [urls addObject:event.URL]; + + if (firedProgressEvents == nil) { + firedProgressEvents = [NSMutableSet new]; + } + [firedProgressEvents addObject:event]; } } - if (self.customViewabilityTracker && !self.customViewabilityTracker.trackersFired && - [self.customViewabilityTrackingAgent shouldTrackImpressionWithCurrentPlaybackTime:timeOffset]) { - [self cleanAndSendTrackingEvents:self.customViewabilityTracker timeOffset:timeOffset]; + if (urls.count > 0) { + [self processAndSendURLs:urls videoTimeOffset:videoTimeOffset]; } + self.firedTable[MPVideoEventProgress] = firedProgressEvents; } -- (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]]; - } - [[MPAnalyticsTracker sharedTracker] sendTrackingRequestForURLs:cleanedTrackingURLs]; +#pragma mark - Private + +- (void)processAndSendURLs:(NSSet *)urls + videoTimeOffset:(NSTimeInterval)videoTimeOffset { + if ([urls count] == 0) { + return; } - vastEventTracker.trackersFired = YES; -} -- (void)handleNewVideoView:(UIView *)videoView -{ - self.customViewabilityTrackingAgent = [[MOPUBNativeVideoImpressionAgent alloc] initWithVideoView:videoView requiredVisibilityPercentage:self.videoConfig.minimumFractionOfVideoVisible requiredPlaybackDuration:self.videoConfig.minimumViewabilityTimeInterval]; + NSMutableArray *processedURLs = [NSMutableArray new]; + for (NSURL *url in urls) { + [processedURLs addObject:[MPVASTMacroProcessor + macroExpandedURLForURL:url + errorCode:nil + videoTimeOffset:videoTimeOffset + videoAssetURL:self.videoURL]]; + } + [self.analyticsTracker sendTrackingRequestForURLs:processedURLs]; } @end diff --git a/MoPubSDK/Internal/MPWebBrowserUserAgentInfo.h b/MoPubSDK/Internal/MPWebBrowserUserAgentInfo.h new file mode 100644 index 000000000..feebaedcf --- /dev/null +++ b/MoPubSDK/Internal/MPWebBrowserUserAgentInfo.h @@ -0,0 +1,23 @@ +// +// MPWebBrowserUserAgentInfo.h +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface MPWebBrowserUserAgentInfo : NSObject + +/** + The current user agent as determined by @c WKWebView. + @returns The user agent. +*/ ++ (NSString *)userAgent; + +@end + +NS_ASSUME_NONNULL_END diff --git a/MoPubSDK/Internal/MPWebBrowserUserAgentInfo.m b/MoPubSDK/Internal/MPWebBrowserUserAgentInfo.m new file mode 100644 index 000000000..fc0af0a2f --- /dev/null +++ b/MoPubSDK/Internal/MPWebBrowserUserAgentInfo.m @@ -0,0 +1,74 @@ +// +// MPWebBrowserUserAgentInfo.m +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import +#import "MPLogging.h" +#import "MPWebBrowserUserAgentInfo.h" + +/** + Global variable for holding the user agent string. + */ +NSString *gUserAgent = nil; + +/** + Global variable for keeping `WKWebView` alive until the async call for user agent finishes. + Note: JavaScript evaluation will fail if the `WKWebView` is deallocated before completion. + */ +WKWebView *gWkWebView = nil; + +/** + The `UserDefaults` key for accessing the cached user agent value. + */ +NSString * const kUserDefaultsUserAgentKey = @"com.mopub.mopub-ios-sdk.user-agent"; + +@implementation MPWebBrowserUserAgentInfo + ++ (void)load { + // No need for "dispatch once" since `load` is called only once during app launch. + [self obtainUserAgentFromWebView]; +} + ++ (void)obtainUserAgentFromWebView { + NSString *cachedUserAgent = [NSUserDefaults.standardUserDefaults stringForKey:kUserDefaultsUserAgentKey]; + if (cachedUserAgent.length > 0) { + // Use the cached value before the async JavaScript evaluation is successful. + gUserAgent = cachedUserAgent; + } else { + /* + Use the composed value before the async JavaScript evaluation is successful. This composed + user agent value should be very close to the actual value like this one: + "Mozilla/5.0 (iPhone; CPU iPhone OS 12_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148" + The history of user agent is very long, complicated, and confusing. Please search online to + learn about why the user agent value looks like this. + */ + + NSString *systemVersion = [[UIDevice currentDevice].systemVersion stringByReplacingOccurrencesOfString:@"." withString:@"_"]; + NSString *deviceType = UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad ? @"iPad" : @"iPhone"; + gUserAgent = [NSString stringWithFormat:@"Mozilla/5.0 (%@; CPU %@ OS %@ like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148", + deviceType, deviceType, systemVersion]; + } + + dispatch_async(dispatch_get_main_queue(), ^{ + gWkWebView = [WKWebView new]; // `WKWebView` must be created in main thread + [gWkWebView evaluateJavaScript:@"navigator.userAgent" completionHandler:^(id _Nullable result, NSError * _Nullable error) { + if (error != nil) { + MPLogInfo(@"%@ error: %@", NSStringFromSelector(_cmd), error); + } else if ([result isKindOfClass:NSString.class]) { + gUserAgent = result; + [NSUserDefaults.standardUserDefaults setValue:result forKeyPath:kUserDefaultsUserAgentKey]; + } + gWkWebView = nil; + }]; + }); +} + ++ (NSString *)userAgent { + return gUserAgent; +} + +@end diff --git a/MoPubSDK/Internal/MRAID/MPForceableOrientationProtocol.h b/MoPubSDK/Internal/MRAID/MPForceableOrientationProtocol.h index 225424d31..16580a3a2 100644 --- a/MoPubSDK/Internal/MRAID/MPForceableOrientationProtocol.h +++ b/MoPubSDK/Internal/MRAID/MPForceableOrientationProtocol.h @@ -1,7 +1,7 @@ // // MPForceableOrientationProtocol.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/Internal/MRAID/MPMRAIDBannerCustomEvent.h b/MoPubSDK/Internal/MRAID/MPMRAIDBannerCustomEvent.h index f2d1b9644..735724496 100644 --- a/MoPubSDK/Internal/MRAID/MPMRAIDBannerCustomEvent.h +++ b/MoPubSDK/Internal/MRAID/MPMRAIDBannerCustomEvent.h @@ -1,7 +1,7 @@ // // MPMRAIDBannerCustomEvent.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -11,9 +11,6 @@ @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/MoPubSDK/Internal/MRAID/MPMRAIDBannerCustomEvent.m b/MoPubSDK/Internal/MRAID/MPMRAIDBannerCustomEvent.m index 25f6f9e7d..43d0be18e 100644 --- a/MoPubSDK/Internal/MRAID/MPMRAIDBannerCustomEvent.m +++ b/MoPubSDK/Internal/MRAID/MPMRAIDBannerCustomEvent.m @@ -1,7 +1,7 @@ // // MPMRAIDBannerCustomEvent.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -10,6 +10,7 @@ #import "MPLogging.h" #import "MPAdConfiguration.h" #import "MRController.h" +#import "MPError.h" #import "MPWebView.h" #import "MPViewabilityTracker.h" @@ -21,10 +22,15 @@ @interface MPMRAIDBannerCustomEvent () @implementation MPMRAIDBannerCustomEvent +// Explicitly `@synthesize` here to fix a "-Wobjc-property-synthesis" warning because super class `delegate` is +// `id` and this `delegate` is `id` +@synthesize delegate; + - (void)requestAdWithSize:(CGSize)size customEventInfo:(NSDictionary *)info { - MPLogInfo(@"Loading MoPub MRAID banner"); - MPAdConfiguration *configuration = [self.delegate configuration]; + MPAdConfiguration *configuration = self.delegate.configuration; + + MPLogAdEvent([MPLogEvent adLoadAttemptForAdapter:NSStringFromClass(configuration.customEventClass) dspCreativeId:configuration.dspCreativeId dspName:nil], self.adUnitId); CGRect adViewFrame = CGRectZero; if ([configuration hasPreferredSize]) { @@ -33,6 +39,7 @@ - (void)requestAdWithSize:(CGSize)size customEventInfo:(NSDictionary *)info } self.mraidController = [[MRController alloc] initWithAdViewFrame:adViewFrame + supportedOrientations:configuration.orientationType adPlacementType:MRAdViewPlacementTypeInline delegate:self]; [self.mraidController loadAdWithConfiguration:configuration]; @@ -62,14 +69,17 @@ - (UIViewController *)viewControllerForPresentingModalView - (void)adDidLoad:(UIView *)adView { - MPLogInfo(@"MoPub MRAID banner did load"); + MPLogAdEvent([MPLogEvent adLoadSuccessForAdapter:NSStringFromClass(self.class)], self.adUnitId); [self.delegate bannerCustomEvent:self didLoadAd:adView]; } - (void)adDidFailToLoad:(UIView *)adView { - MPLogInfo(@"MoPub MRAID banner did fail"); - [self.delegate bannerCustomEvent:self didFailToLoadAdWithError:nil]; + NSString * message = [NSString stringWithFormat:@"Failed to load creative:\n%@", self.adConfiguration.adResponseHTMLString]; + NSError * error = [NSError errorWithCode:MOPUBErrorAdapterFailedToLoadAd localizedDescription:message]; + + MPLogAdEvent([MPLogEvent adLoadFailedForAdapter:NSStringFromClass(self.class) error:error], self.adUnitId); + [self.delegate bannerCustomEvent:self didFailToLoadAdWithError:error]; } - (void)closeButtonPressed @@ -79,13 +89,11 @@ - (void)closeButtonPressed - (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]; } @@ -99,4 +107,18 @@ - (void)startViewabilityTracker [self.mraidController.viewabilityTracker startTracking]; } +- (void)adWillExpand:(UIView *)adView +{ + if ([self.delegate respondsToSelector:@selector(bannerCustomEventWillExpandAd:)]) { + [self.delegate bannerCustomEventWillExpandAd:self]; + } +} + +- (void)adDidCollapse:(UIView *)adView +{ + if ([self.delegate respondsToSelector:@selector(bannerCustomEventDidCollapseAd:)]) { + [self.delegate bannerCustomEventDidCollapseAd:self]; + } +} + @end diff --git a/MoPubSDK/Internal/MRAID/MPMRAIDInterstitialCustomEvent.h b/MoPubSDK/Internal/MRAID/MPMRAIDInterstitialCustomEvent.h index 81b10fabf..eadbb4d9f 100644 --- a/MoPubSDK/Internal/MRAID/MPMRAIDInterstitialCustomEvent.h +++ b/MoPubSDK/Internal/MRAID/MPMRAIDInterstitialCustomEvent.h @@ -1,20 +1,16 @@ // // MPMRAIDInterstitialCustomEvent.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // #import "MPInterstitialCustomEvent.h" -#import "MPMRAIDInterstitialViewController.h" #import "MPPrivateInterstitialCustomEventDelegate.h" -@interface MPMRAIDInterstitialCustomEvent : MPInterstitialCustomEvent +@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/MoPubSDK/Internal/MRAID/MPMRAIDInterstitialCustomEvent.m b/MoPubSDK/Internal/MRAID/MPMRAIDInterstitialCustomEvent.m index ee20030a6..697f33420 100644 --- a/MoPubSDK/Internal/MRAID/MPMRAIDInterstitialCustomEvent.m +++ b/MoPubSDK/Internal/MRAID/MPMRAIDInterstitialCustomEvent.m @@ -1,12 +1,15 @@ // // MPMRAIDInterstitialCustomEvent.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // #import "MPMRAIDInterstitialCustomEvent.h" +#import "MPMRAIDInterstitialViewController.h" +#import "MPAdConfiguration.h" +#import "MPError.h" #import "MPLogging.h" @interface MPMRAIDInterstitialCustomEvent () @@ -15,14 +18,21 @@ @interface MPMRAIDInterstitialCustomEvent () @end +@interface MPMRAIDInterstitialCustomEvent (MPInterstitialViewControllerDelegate) +@end + @implementation MPMRAIDInterstitialCustomEvent -@synthesize interstitial = _interstitial; +// Explicitly `@synthesize` here to fix a "-Wobjc-property-synthesis" warning because super class `delegate` is +// `id` and this `delegate` is `id` +@synthesize delegate; - (void)requestInterstitialWithCustomEventInfo:(NSDictionary *)info { - MPLogInfo(@"Loading MoPub MRAID interstitial"); - self.interstitial = [[MPMRAIDInterstitialViewController alloc] initWithAdConfiguration:[self.delegate configuration]]; + MPAdConfiguration * configuration = self.delegate.configuration; + MPLogAdEvent([MPLogEvent adLoadAttemptForAdapter:NSStringFromClass(self.class) dspCreativeId:configuration.dspCreativeId dspName:nil], self.adUnitId); + + self.interstitial = [[MPMRAIDInterstitialViewController alloc] initWithAdConfiguration:configuration]; self.interstitial.delegate = self; // The MRAID ad view will handle the close button so we don't need the MPInterstitialViewController's close button. @@ -32,10 +42,22 @@ - (void)requestInterstitialWithCustomEventInfo:(NSDictionary *)info - (void)showInterstitialFromRootViewController:(UIViewController *)controller { - [self.interstitial presentInterstitialFromViewController:controller]; + MPLogAdEvent([MPLogEvent adShowAttemptForAdapter:NSStringFromClass(self.class)], self.adUnitId); + [self.interstitial presentInterstitialFromViewController:controller complete:^(NSError * error) { + if (error != nil) { + MPLogAdEvent([MPLogEvent adShowFailedForAdapter:NSStringFromClass(self.class) error:error], self.adUnitId); + } + else { + MPLogAdEvent([MPLogEvent adShowSuccessForAdapter:NSStringFromClass(self.class)], self.adUnitId); + } + }]; } -#pragma mark - MPMRAIDInterstitialViewControllerDelegate +@end + +#pragma mark - MPInterstitialViewControllerDelegate + +@implementation MPMRAIDInterstitialCustomEvent (MPInterstitialViewControllerDelegate) - (CLLocation *)location { @@ -47,39 +69,38 @@ - (NSString *)adUnitId return [self.delegate adUnitId]; } -- (void)interstitialDidLoadAd:(MPInterstitialViewController *)interstitial +- (void)interstitialDidLoadAd:(id)interstitial { - MPLogInfo(@"MoPub MRAID interstitial did load"); + MPLogAdEvent([MPLogEvent adLoadSuccessForAdapter:NSStringFromClass(self.class)], self.adUnitId); [self.delegate interstitialCustomEvent:self didLoadAd:self.interstitial]; } -- (void)interstitialDidFailToLoadAd:(MPInterstitialViewController *)interstitial +- (void)interstitialDidFailToLoadAd:(id)interstitial { - MPLogInfo(@"MoPub MRAID interstitial did fail"); - [self.delegate interstitialCustomEvent:self didFailToLoadAdWithError:nil]; + NSString * message = [NSString stringWithFormat:@"Failed to load creative:\n%@", self.delegate.configuration.adResponseHTMLString]; + NSError * error = [NSError errorWithCode:MOPUBErrorAdapterFailedToLoadAd localizedDescription:message]; + + MPLogAdEvent([MPLogEvent adLoadFailedForAdapter:NSStringFromClass(self.class) error:error], self.adUnitId); + [self.delegate interstitialCustomEvent:self didFailToLoadAdWithError:error]; } -- (void)interstitialWillAppear:(MPInterstitialViewController *)interstitial +- (void)interstitialWillAppear:(id)interstitial { - MPLogInfo(@"MoPub MRAID interstitial will appear"); [self.delegate interstitialCustomEventWillAppear:self]; } -- (void)interstitialDidAppear:(MPInterstitialViewController *)interstitial +- (void)interstitialDidAppear:(id)interstitial { - MPLogInfo(@"MoPub MRAID interstitial did appear"); [self.delegate interstitialCustomEventDidAppear:self]; } -- (void)interstitialWillDisappear:(MPInterstitialViewController *)interstitial +- (void)interstitialWillDisappear:(id)interstitial { - MPLogInfo(@"MoPub MRAID interstitial will disappear"); [self.delegate interstitialCustomEventWillDisappear:self]; } -- (void)interstitialDidDisappear:(MPInterstitialViewController *)interstitial +- (void)interstitialDidDisappear:(id)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, @@ -88,15 +109,13 @@ - (void)interstitialDidDisappear:(MPInterstitialViewController *)interstitial self.interstitial = nil; } -- (void)interstitialDidReceiveTapEvent:(MPInterstitialViewController *)interstitial +- (void)interstitialDidReceiveTapEvent:(id)interstitial { - MPLogInfo(@"MoPub MRAID interstitial did receive tap event"); [self.delegate interstitialCustomEventDidReceiveTapEvent:self]; } -- (void)interstitialWillLeaveApplication:(MPInterstitialViewController *)interstitial +- (void)interstitialWillLeaveApplication:(id)interstitial { - MPLogInfo(@"MoPub MRAID interstitial will leave application"); [self.delegate interstitialCustomEventWillLeaveApplication:self]; } diff --git a/MoPubSDK/Internal/MRAID/MPMRAIDInterstitialViewController.h b/MoPubSDK/Internal/MRAID/MPMRAIDInterstitialViewController.h index 96b554460..c995f92aa 100644 --- a/MoPubSDK/Internal/MRAID/MPMRAIDInterstitialViewController.h +++ b/MoPubSDK/Internal/MRAID/MPMRAIDInterstitialViewController.h @@ -1,7 +1,7 @@ // // MPMRAIDInterstitialViewController.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -11,7 +11,6 @@ //////////////////////////////////////////////////////////////////////////////////////////////////// -@protocol MPMRAIDInterstitialViewControllerDelegate; @class MPAdConfiguration; //////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/MoPubSDK/Internal/MRAID/MPMRAIDInterstitialViewController.m b/MoPubSDK/Internal/MRAID/MPMRAIDInterstitialViewController.m index 9715d7e17..7b12e463c 100644 --- a/MoPubSDK/Internal/MRAID/MPMRAIDInterstitialViewController.m +++ b/MoPubSDK/Internal/MRAID/MPMRAIDInterstitialViewController.m @@ -1,7 +1,7 @@ // // MPMRAIDInterstitialViewController.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -32,6 +32,7 @@ - (id)initWithAdConfiguration:(MPAdConfiguration *)configuration CGFloat height = MAX(configuration.preferredSize.height, 1); CGRect frame = CGRectMake(0, 0, width, height); self.mraidController = [[MRController alloc] initWithAdViewFrame:frame + supportedOrientations:configuration.orientationType adPlacementType:MRAdViewPlacementTypeInterstitial delegate:self]; @@ -50,7 +51,7 @@ - (void)startLoading - (void)willPresentInterstitial { - [self.mraidController disableRequestHandling]; + [self.mraidController handleMRAIDInterstitialWillPresentWithViewController:self]; if ([self.delegate respondsToSelector:@selector(interstitialWillAppear:)]) { [self.delegate interstitialWillAppear:self]; } @@ -117,15 +118,13 @@ - (void)adDidLoad:(UIView *)adView self.interstitialView.frame = self.view.bounds; self.interstitialView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; [self.view addSubview:self.interstitialView]; - if (@available(iOS 9.0, *)) { - self.interstitialView.translatesAutoresizingMaskIntoConstraints = NO; - [NSLayoutConstraint activateConstraints:@[ - [self.interstitialView.topAnchor constraintEqualToAnchor:self.view.topAnchor], - [self.interstitialView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor], - [self.interstitialView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor], - [self.interstitialView.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor], - ]]; - } + self.interstitialView.translatesAutoresizingMaskIntoConstraints = NO; + [NSLayoutConstraint activateConstraints:@[ + [self.interstitialView.topAnchor constraintEqualToAnchor:self.view.topAnchor], + [self.interstitialView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor], + [self.interstitialView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor], + [self.interstitialView.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor], + ]]; if ([self.delegate respondsToSelector:@selector(interstitialDidLoadAd:)]) { [self.delegate interstitialDidLoadAd:self]; @@ -177,14 +176,9 @@ - (void)rewardedVideoEnded #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; + return ([[UIApplication sharedApplication] mp_supportsOrientationMask:self.supportedOrientationMask]) ? self.supportedOrientationMask : [super supportedInterfaceOrientations]; } - (BOOL)shouldAutorotate diff --git a/MoPubSDK/Internal/MRAID/MRBridge.h b/MoPubSDK/Internal/MRAID/MRBridge.h index d6ef943df..aa6d23866 100644 --- a/MoPubSDK/Internal/MRAID/MRBridge.h +++ b/MoPubSDK/Internal/MRAID/MRBridge.h @@ -1,7 +1,7 @@ // // MRBridge.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/Internal/MRAID/MRBridge.m b/MoPubSDK/Internal/MRAID/MRBridge.m index f50f79528..cd3fc5642 100644 --- a/MoPubSDK/Internal/MRAID/MRBridge.m +++ b/MoPubSDK/Internal/MRAID/MRBridge.m @@ -1,7 +1,7 @@ // // MRBridge.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -13,12 +13,13 @@ #import "NSURL+MPAdditions.h" #import "MPGlobal.h" #import "MRBundleManager.h" -#import "UIWebView+MPAdditions.h" #import "MRError.h" #import "MRProperty.h" #import "MRNativeCommandHandler.h" static NSString * const kMraidURLScheme = @"mraid"; +static NSString * const kSMSURLScheme = @"sms"; +static NSString * const kTelURLScheme = @"tel"; @interface MRBridge () @@ -60,7 +61,6 @@ - (void)loadHTMLString:(NSString *)HTML baseURL:(NSURL *)baseURL // Execute the javascript in the web view directly. dispatch_async(dispatch_get_main_queue(), ^{ [self.webView evaluateJavaScript:[[MPCoreInstanceProvider sharedProvider] mraidJavascript] completionHandler:^(id result, NSError *error){ - [self.webView disableJavaScriptDialogs]; [self.webView loadHTMLString:HTML baseURL:baseURL]; }]; }); @@ -137,7 +137,7 @@ - (void)fireSetMaxSize:(CGSize)maxSize #pragma mark - -- (BOOL)webView:(MPWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType +- (BOOL)webView:(MPWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(WKNavigationType)navigationType { NSURL *url = [request URL]; NSMutableString *urlString = [NSMutableString stringWithString:[url absoluteString]]; @@ -159,7 +159,7 @@ - (BOOL)webView:(MPWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *) withString:@" " options:NSLiteralSearch range:NSMakeRange(0, [urlString length])]; - MPLogDebug(@"Web console: %@", urlString); + MPLogEvent([MPLogEvent javascriptConsoleLogWithMessage:urlString]); return NO; } @@ -167,21 +167,16 @@ - (BOOL)webView:(MPWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *) 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]; + BOOL safeToAutoloadLink = navigationType == WKNavigationTypeLinkActivated || userInteractedWithWebView || [url mp_isSafeForLoadingWithoutUserAction]; - if (!isLoading && (navigationType == UIWebViewNavigationTypeOther || navigationType == UIWebViewNavigationTypeLinkClicked)) { + if (!isLoading && (navigationType == WKNavigationTypeOther || navigationType == WKNavigationTypeLinkActivated)) { 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"]))) { + if (iframe && !((navigationType == WKNavigationTypeLinkActivated) && ([scheme isEqualToString:@"https"] || [scheme isEqualToString:@"http"]))) { return safeToAutoloadLink; } @@ -195,7 +190,7 @@ - (BOOL)webView:(MPWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *) - (void)webViewDidStartLoad:(MPWebView *)webView { - [webView disableJavaScriptDialogs]; + // no op } - (void)webViewDidFinishLoad:(MPWebView *)webView @@ -248,6 +243,16 @@ - (void)handleMRAIDSetOrientationPropertiesWithForceOrientationMask:(UIInterface - (void)handleMRAIDOpenCallForURL:(NSURL *)URL { + // sms:// and tel:// schemes are not supported by MoPub's MRAID system. + // The calls to these schemes via MRAID will be logged, but not allowed + // to execute. sms:// and tel:// schemes opened via normal HTML links + // will be handled by the OS per its default operating mode. + NSString *lowercasedScheme = URL.scheme.lowercaseString; + if ([lowercasedScheme isEqualToString:kSMSURLScheme] || [lowercasedScheme isEqualToString:kTelURLScheme]) { + MPLogDebug(@"mraidbridge.open() disallowed: %@ scheme is not supported", URL.scheme); + return; + } + [self.delegate bridge:self handleDisplayForDestinationURL:URL]; } diff --git a/MoPubSDK/Internal/MRAID/MRBundleManager.h b/MoPubSDK/Internal/MRAID/MRBundleManager.h index a7fb4d2a4..670a9e5c0 100644 --- a/MoPubSDK/Internal/MRAID/MRBundleManager.h +++ b/MoPubSDK/Internal/MRAID/MRBundleManager.h @@ -1,7 +1,7 @@ // // MRBundleManager.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/Internal/MRAID/MRBundleManager.m b/MoPubSDK/Internal/MRAID/MRBundleManager.m index eab667dda..17d1f1327 100644 --- a/MoPubSDK/Internal/MRAID/MRBundleManager.m +++ b/MoPubSDK/Internal/MRAID/MRBundleManager.m @@ -1,7 +1,7 @@ // // MRBundleManager.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/Internal/MRAID/MRCommand.h b/MoPubSDK/Internal/MRAID/MRCommand.h index da46c3332..f2de0e6db 100644 --- a/MoPubSDK/Internal/MRAID/MRCommand.h +++ b/MoPubSDK/Internal/MRAID/MRCommand.h @@ -1,7 +1,7 @@ // // MRCommand.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/Internal/MRAID/MRCommand.m b/MoPubSDK/Internal/MRAID/MRCommand.m index ec4453efe..c1ac9900d 100644 --- a/MoPubSDK/Internal/MRAID/MRCommand.m +++ b/MoPubSDK/Internal/MRAID/MRCommand.m @@ -1,7 +1,7 @@ // // MRCommand.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/Internal/MRAID/MRConstants.h b/MoPubSDK/Internal/MRAID/MRConstants.h index 24eadd2f8..36776e5ce 100644 --- a/MoPubSDK/Internal/MRAID/MRConstants.h +++ b/MoPubSDK/Internal/MRAID/MRConstants.h @@ -1,7 +1,7 @@ // // MRConstants.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/Internal/MRAID/MRConstants.m b/MoPubSDK/Internal/MRAID/MRConstants.m index cecb1a027..6b9de524e 100644 --- a/MoPubSDK/Internal/MRAID/MRConstants.m +++ b/MoPubSDK/Internal/MRAID/MRConstants.m @@ -1,7 +1,7 @@ // // MRConstants.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/Internal/MRAID/MRController.h b/MoPubSDK/Internal/MRAID/MRController.h index 43b1367f5..5887f8b23 100644 --- a/MoPubSDK/Internal/MRAID/MRController.h +++ b/MoPubSDK/Internal/MRAID/MRController.h @@ -1,7 +1,7 @@ // // MRController.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -30,10 +30,12 @@ @property (nonatomic, weak) id delegate; - (instancetype)initWithAdViewFrame:(CGRect)adViewFrame + supportedOrientations:(MPInterstitialOrientationType)orientationType adPlacementType:(MRAdViewPlacementType)placementType delegate:(id)delegate; - (void)loadAdWithConfiguration:(MPAdConfiguration *)configuration; +- (void)handleMRAIDInterstitialWillPresentWithViewController:(MPMRAIDInterstitialViewController *)viewController; - (void)handleMRAIDInterstitialDidPresentWithViewController:(MPMRAIDInterstitialViewController *)viewController; - (void)enableRequestHandling; - (void)disableRequestHandling; @@ -80,4 +82,10 @@ // Called after the rewarded video finishes playing - (void)rewardedVideoEnded; +// Called just before the ad will expand or resize +- (void)adWillExpand:(UIView *)adView; + +// Called after the ad collapsed from an expanded or resized state +- (void)adDidCollapse:(UIView *)adView; + @end diff --git a/MoPubSDK/Internal/MRAID/MRController.m b/MoPubSDK/Internal/MRAID/MRController.m index 9f4915c6d..40ec186e3 100644 --- a/MoPubSDK/Internal/MRAID/MRController.m +++ b/MoPubSDK/Internal/MRAID/MRController.m @@ -1,7 +1,7 @@ // // MRController.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -21,7 +21,6 @@ #import "MPTimer.h" #import "NSHTTPURLResponse+MPAdditions.h" #import "NSURL+MPAdditions.h" -#import "UIWebView+MPAdditions.h" #import "MPForceableOrientationProtocol.h" #import "MPAPIEndPoints.h" #import "MoPub.h" @@ -51,8 +50,8 @@ @interface MRController () )delegate { if (self = [super init]) { + _includeSafeAreaInsetsInCalculations = YES; _placementType = placementType; _currentState = MRAdViewStateDefault; - _forceOrientationMask = UIInterfaceOrientationMaskAll; + _forceOrientationMask = MPInterstitialOrientationTypeToUIInterfaceOrientationMask(orientationType); _isAnimatingAdSize = NO; - _firedReadyEventForDefaultAd = NO; + _didConfigureOrientationNotificationObservers = NO; _currentAdSize = CGSizeZero; + _isAppSuspended = NO; _mraidDefaultAdFrame = adViewFrame; - _adPropertyUpdateTimer = [[MPCoreInstanceProvider sharedProvider] buildMPTimerWithTimeInterval:kAdPropertyUpdateTimerInterval - target:self - selector:@selector(updateMRAIDProperties) - repeats:YES]; - _adPropertyUpdateTimer.runLoopMode = NSRunLoopCommonModes; + _adPropertyUpdateTimer = [MPTimer timerWithTimeInterval:kAdPropertyUpdateTimerInterval + target:self + selector:@selector(updateMRAIDProperties) + repeats:YES + runLoopMode:NSRunLoopCommonModes]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(viewEnteredBackground) @@ -123,6 +134,11 @@ - (instancetype)initWithAdViewFrame:(CGRect)adViewFrame _adAlertManagerTwoPart = [[MPCoreInstanceProvider sharedProvider] buildMPAdAlertManagerWithDelegate:self]; _delegate = delegate; + + _previousCurrentPosition = CGRectNull; + _previousDefaultPosition = CGRectNull; + _previousScreenSize = CGSizeZero; + _previousMaxSize = CGSizeZero; } return self; @@ -147,10 +163,8 @@ - (void)loadAdWithConfiguration:(MPAdConfiguration *)configuration self.isAdLoading = YES; self.adRequiresPrecaching = configuration.precacheRequired; self.isAdVastVideoPlayer = configuration.isVastVideoPlayer; - self.shouldUseUIWebView = self.isAdVastVideoPlayer; - self.mraidWebView = [self buildMRAIDWebViewWithFrame:self.mraidDefaultAdFrame - forceUIWebView:self.shouldUseUIWebView]; + self.mraidWebView = [self buildMRAIDWebViewWithFrame:self.mraidDefaultAdFrame]; self.mraidWebView.shouldConformToSafeArea = [self isInterstitialAd]; self.mraidBridge = [[MRBridge alloc] initWithWebView:self.mraidWebView delegate:self]; @@ -180,11 +194,19 @@ - (void)loadAdWithConfiguration:(MPAdConfiguration *)configuration } +- (void)handleMRAIDInterstitialWillPresentWithViewController:(MPMRAIDInterstitialViewController *)viewController +{ + self.interstitialViewController = viewController; + [self updateOrientation]; + [self willBeginAnimatingAdSize]; +} + - (void)handleMRAIDInterstitialDidPresentWithViewController:(MPMRAIDInterstitialViewController *)viewController { self.interstitialViewController = viewController; - [self enableRequestHandling]; - [self checkViewability]; + [self didEndAnimatingAdSize]; + [self updateMRAIDProperties]; + [self updateOrientation]; // If viewability tracking has been deferred (i.e., if this is a non-banner ad), start tracking here now that the // ad has been presented. If viewability tracking was not deferred, we're already tracking and there's no need to @@ -320,9 +342,9 @@ - (MRBridge *)bridgeForActiveAdView return bridge; } -- (MPWebView *)buildMRAIDWebViewWithFrame:(CGRect)frame forceUIWebView:(BOOL)forceUIWebView +- (MPWebView *)buildMRAIDWebViewWithFrame:(CGRect)frame { - MPWebView *webView = [[MPWebView alloc] initWithFrame:frame forceUIWebView:forceUIWebView]; + MPWebView *webView = [[MPWebView alloc] initWithFrame:frame]; webView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; webView.backgroundColor = [UIColor clearColor]; webView.clipsToBounds = YES; @@ -357,7 +379,7 @@ - (void)orientationDidChange:(NSNotification *)notification #pragma mark - Executing Javascript -- (void)initializeLoadedAdForBridge:(MRBridge *)bridge +- (void)configureMraidEnvironmentToShowAdForBridge:(MRBridge *)bridge { // Set up some initial properties so mraid can operate. MPLogDebug(@"Injecting initial JavaScript state."); @@ -382,68 +404,82 @@ - (void)fireChangeEventToBothBridgesForProperty:(MRProperty *)property #pragma mark - Resize Helpers -- (CGRect)adjustedFrameForFrame:(CGRect)frame allowOffscreen:(BOOL)allowOffscreen +/** + If the provided frame is not fully within the application safe area, to try to adjust it's origin so + that the provided frame can fit into the application safe area if possible. + + Note: Only the origin is adjusted. If the size doesn't fit, then the original frame is returned. + + @param frame The frame to adjust. + @param applicationSafeArea The frame of application safe area. + @return The adjusted frame. + */ ++ (CGRect)adjustedFrameForFrame:(CGRect)frame toFitIntoApplicationSafeArea:(CGRect)applicationSafeArea { - if (allowOffscreen) { + if (CGRectContainsRect(applicationSafeArea, frame)) { 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; + } else if (CGRectGetWidth(frame) <= CGRectGetWidth(applicationSafeArea) + && CGRectGetHeight(frame) <= CGRectGetHeight(applicationSafeArea)) { + // given the size is fitting, we only need to move the frame by changing its origin + if (CGRectGetMinX(frame) < CGRectGetMinX(applicationSafeArea)) { + frame.origin.x = CGRectGetMinX(applicationSafeArea); + } else if (CGRectGetMaxX(applicationSafeArea) < CGRectGetMaxX(frame)) { + frame.origin.x = CGRectGetMaxX(applicationSafeArea) - CGRectGetWidth(frame); } - 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; + if (CGRectGetMinY(frame) < CGRectGetMinY(applicationSafeArea)) { + frame.origin.y = CGRectGetMinY(applicationSafeArea); + } else if (CGRectGetMaxY(applicationSafeArea) < CGRectGetMaxY(frame)) { + frame.origin.y = CGRectGetMaxY(applicationSafeArea) - CGRectGetHeight(frame); } } 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; +/** + Check whether the provided @c frame is valid for a resized ad. + @param frame The ad frame to check + @param applicationSafeArea The safe area of this application + @param allowOffscreen Per MRAID spec https://www.iab.com/wp-content/uploads/2015/08/IAB_MRAID_v2_FINAL.pdf, + page 35, @c is for "whether or not it should allow the resized creative to be drawn fully/partially + offscreen". + @return @c YES if the provided @c frame is valid for a resized ad, and @c NO otherwise. + */ ++ (BOOL)isValidResizeFrame:(CGRect)frame + inApplicationSafeArea:(CGRect)applicationSafeArea + allowOffscreen:(BOOL)allowOffscreen +{ + if (CGRectGetWidth(frame) < kCloseRegionSize.width || CGRectGetHeight(frame) < kCloseRegionSize.height) { + /* + Per MRAID spec https://www.iab.com/wp-content/uploads/2015/08/IAB_MRAID_v2_FINAL.pdf, page 34, + "a resized ad must be at least 50x50 pixels, to ensure there is room on the resized creative + for the close event region." + */ + return false; + } else { + if (allowOffscreen) { + return YES; // any frame with a valid size is valid, even off screen + } else { + return CGRectContainsRect(applicationSafeArea, frame); + } } - - 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); +/** + Check whether the frame of Close button is valid. + @param closeButtonLocation The Close button location. + @param adFrame The ad frame that contains the Close button. + @param applicationSafeArea The safe area of this application. + @return @c YES if the frame of the Close button is valid, and @c NO otherwise. + */ ++ (BOOL)isValidCloseButtonPlacement:(MPClosableViewCloseButtonLocation)closeButtonLocation + inAdFrame:(CGRect)adFrame + inApplicationSafeArea:(CGRect)applicationSafeArea { + // Need to convert the corrdinate system of the Close button frame from "in the ad" to "in the window". + CGRect closeButtonFrameInAd = MPClosableViewCustomCloseButtonFrame(adFrame.size, closeButtonLocation); + CGRect closeButtonFrameInWindow = CGRectOffset(closeButtonFrameInAd, CGRectGetMinX(adFrame), CGRectGetMinY(adFrame)); + return CGRectContainsRect(applicationSafeArea, closeButtonFrameInWindow); } - (MPClosableViewCloseButtonLocation)adCloseButtonLocationFromString:(NSString *)closeButtonLocationString @@ -493,7 +529,6 @@ - (void)presentExpandModalViewControllerWithView:(MPClosableView *)view animated 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 @@ -565,8 +600,6 @@ - (void)closeFromExpandedState // 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; @@ -588,6 +621,11 @@ - (void)closeFromExpandedState // 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]; + + // Notify listeners that the expanded ad was collapsed. + if ([strongSelf.delegate respondsToSelector:@selector(adDidCollapse:)]) { + [strongSelf.delegate adDidCollapse:strongSelf.mraidAdView]; + } }]; } @@ -597,15 +635,25 @@ - (void)closeFromResizedState [self willBeginAnimatingAdSize]; + __weak __typeof__(self) weakSelf = self; [UIView animateWithDuration:kMRAIDResizeAnimationTimeInterval animations:^{ - self.mraidAdView.frame = self.mraidDefaultAdFrameInKeyWindow; + __typeof__(self) strongSelf = weakSelf; + + strongSelf.mraidAdView.frame = strongSelf.mraidDefaultAdFrameInKeyWindow; } completion:^(BOOL finished) { - [self.resizeBackgroundView removeFromSuperview]; - [self.originalSuperview addSubview:self.mraidAdView]; - self.mraidAdView.frame = self.mraidDefaultAdFrame; - [self changeStateTo:MRAdViewStateDefault]; - [self didEndAnimatingAdSize]; - [self adDidDismissModalView]; + __typeof__(self) strongSelf = weakSelf; + + [strongSelf.resizeBackgroundView removeFromSuperview]; + [strongSelf.originalSuperview addSubview:strongSelf.mraidAdView]; + strongSelf.mraidAdView.frame = strongSelf.mraidDefaultAdFrame; + [strongSelf changeStateTo:MRAdViewStateDefault]; + [strongSelf didEndAnimatingAdSize]; + [strongSelf adDidDismissModalView]; + + // Notify listeners that the expanded ad was collapsed. + if ([strongSelf.delegate respondsToSelector:@selector(adDidCollapse:)]) { + [strongSelf.delegate adDidCollapse:strongSelf.mraidAdView]; + } }]; } @@ -646,7 +694,7 @@ - (UIViewController *)viewControllerForPresentingModalView - (void)nativeCommandWillPresentModalView { - [self adWillPresentModalView]; + [self adWillPresentModalViewByExpanding:NO]; } - (void)nativeCommandDidDismissModalView @@ -665,8 +713,8 @@ - (void)bridge:(MRBridge *)bridge didFinishLoadingWebView:(MPWebView *)webView 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 + // We do not 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) { @@ -680,7 +728,7 @@ - (void)bridge:(MRBridge *)bridge didFinishLoadingWebView:(MPWebView *)webView // 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]; + [self configureMraidEnvironmentToShowAdForBridge:bridge]; } } } @@ -714,7 +762,7 @@ - (void)bridge:(MRBridge *)bridge performActionForMoPubSpecificURL:(NSURL *)url } else if (command == MPMoPubHostCommandRewardedVideoEnded) { [self.delegate rewardedVideoEnded]; } else { - MPLogWarn(@"MRController - unsupported MoPub URL: %@", [url absoluteString]); + MPLogInfo(@"MRController - unsupported MoPub URL: %@", [url absoluteString]); } } @@ -768,8 +816,10 @@ - (void)bridge:(MRBridge *)bridge handleNativeCommandSetOrientationPropertiesWit 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 we aren't expanded or showing an interstitial ad, save the force orientation in case the + // ad is expanded, but do not process further. if (!inExpandedState && self.placementType != MRAdViewPlacementTypeInterstitial) { + self.forceOrientationMask = forceOrientationMask; return; } @@ -795,12 +845,6 @@ - (void)bridge:(MRBridge *)bridge handleNativeCommandSetOrientationPropertiesWit 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]; @@ -841,7 +885,7 @@ - (void)bridge:(MRBridge *)bridge handleNativeCommandExpandWithURL:(NSURL *)url // 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]; + [self adWillPresentModalViewByExpanding:YES]; } else { [self.resizeBackgroundView removeFromSuperview]; } @@ -852,7 +896,7 @@ - (void)bridge:(MRBridge *)bridge handleNativeCommandExpandWithURL:(NSURL *)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]; + MPWebView *twoPartWebView = [self buildMRAIDWebViewWithFrame:twoPartFrame]; self.mraidBridgeTwoPart = [[MRBridge alloc] initWithWebView:twoPartWebView delegate:self]; self.mraidAdViewTwoPart = [[MPClosableView alloc] initWithFrame:twoPartFrame webView:twoPartWebView @@ -907,17 +951,29 @@ - (void)bridge:(MRBridge *)bridge handleNativeCommandResizeWithParameters:(NSDic 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]; + MPClosableViewCloseButtonLocation closeButtonLocation = [self adCloseButtonLocationFromString:customClosePositionString]; + CGRect applicationSafeArea = MPApplicationFrame(self.includeSafeAreaInsetsInCalculations); + CGRect newFrame = CGRectMake(CGRectGetMinX(applicationSafeArea) + offsetX, + CGRectGetMinY(applicationSafeArea) + offsetY, + width, + height); + if (!allowOffscreen) { // if `allowOffscreen` is YES, the frame doesn't need to be adjusted + newFrame = [[self class] adjustedFrameForFrame:newFrame toFitIntoApplicationSafeArea:applicationSafeArea]; + } - if (![self isValidResizeFrame:newFrame allowOffscreen:allowOffscreen]) { + if (![[self class] isValidResizeFrame:newFrame + inApplicationSafeArea:applicationSafeArea + 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 (![[self class] isValidCloseButtonPlacement:closeButtonLocation + inAdFrame:newFrame + inApplicationSafeArea:applicationSafeArea]) { + [self.mraidBridge fireErrorEventForAction:kMRAIDCommandResize withMessage:@"Custom close event region locates in invalid area."]; } else { + // Update the close button + self.mraidAdView.closeButtonType = MPClosableViewCloseButtonTypeTappableWithoutImage; + self.mraidAdView.closeButtonLocation = closeButtonLocation; + // 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) { @@ -925,12 +981,12 @@ - (void)bridge:(MRBridge *)bridge handleNativeCommandResizeWithParameters:(NSDic self.originalSuperview = self.mraidAdView.superview; self.mraidAdView.frame = self.mraidDefaultAdFrameInKeyWindow; - self.resizeBackgroundView.frame = MPApplicationFrame(); + self.resizeBackgroundView.frame = MPApplicationFrame(self.includeSafeAreaInsetsInCalculations); [MPKeyWindow().rootViewController.view addSubview:self.resizeBackgroundView]; [MPKeyWindow().rootViewController.view addSubview:self.mraidAdView]; - [self adWillPresentModalView]; + [self adWillPresentModalViewByExpanding:YES]; } [self animateViewFromDefaultStateToResizedState:self.mraidAdView withFrame:newFrame]; @@ -949,7 +1005,7 @@ - (void)closableView:(MPClosableView *)closableView didMoveToWindow:(UIWindow *) // Fire the ready event and initialize properties if the view has a window. MRBridge *bridge = [self bridgeForAdView:closableView]; - if (!self.firedReadyEventForDefaultAd && bridge == self.mraidBridge) { + if (!self.didConfigureOrientationNotificationObservers && 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 @@ -968,8 +1024,7 @@ - (void)closableView:(MPClosableView *)closableView didMoveToWindow:(UIWindow *) object:nil]; [self.adPropertyUpdateTimer scheduleNow]; - [self initializeLoadedAdForBridge:bridge]; - self.firedReadyEventForDefaultAd = YES; + self.didConfigureOrientationNotificationObservers = YES; } } } @@ -978,7 +1033,7 @@ - (void)closableView:(MPClosableView *)closableView didMoveToWindow:(UIWindow *) - (void)displayAgentWillPresentModal { - [self adWillPresentModalView]; + [self adWillPresentModalViewByExpanding:NO]; } - (void)displayAgentDidDismissModal @@ -1001,12 +1056,16 @@ - (void)updateMRAIDProperties // 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]; + + // Updating the Viewable state should be last because the creative may have a + // viewable event handler that relies upon the current position and sizes to be + // properly set. + [self checkViewability]; } } @@ -1014,16 +1073,18 @@ - (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; + // Full screen ads, including inline ads that are in an expanded state. + // The active area should be the full application frame residing in the + // safe area. + BOOL isExpandedInLineAd = (self.placementType == MRAdViewPlacementTypeInline && + self.currentState == MRAdViewStateExpanded); + if (self.placementType == MRAdViewPlacementTypeInterstitial || isExpandedInLineAd) { + visibleFrame = MPApplicationFrame(self.includeSafeAreaInsetsInCalculations); + } + // Inline ads that are not in an expanded state. + else if (self.placementType == MRAdViewPlacementTypeInline) { + UIWindow *keyWindow = MPKeyWindow(); + visibleFrame = [self.mraidAdView.superview convertRect:self.mraidAdView.frame toView:keyWindow.rootViewController.view]; } return visibleFrame; @@ -1051,22 +1112,42 @@ - (void)updateCurrentPosition { CGRect frame = [self activeAdFrameInScreenSpace]; - // Only fire to the active ad view. - MRBridge *activeBridge = [self bridgeForActiveAdView]; - [activeBridge fireSetCurrentPositionWithPositionRect:frame]; + @synchronized (self) { + // No need to update since nothing has changed. + if (CGRectEqualToRect(frame, self.previousCurrentPosition)) { + return; + } + + // Update previous value + self.previousCurrentPosition = frame; - MPLogTrace(@"Current Position: %@", NSStringFromCGRect(frame)); + // Only fire to the active ad view. + MRBridge *activeBridge = [self bridgeForActiveAdView]; + [activeBridge fireSetCurrentPositionWithPositionRect:frame]; + + MPLogDebug(@"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]; + @synchronized (self) { + // No need to update since nothing has changed. + if (CGRectEqualToRect(defaultFrame, self.previousDefaultPosition)) { + return; + } + + // Update previous value + self.previousDefaultPosition = defaultFrame; + + // 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)); + MPLogDebug(@"Default Position: %@", NSStringFromCGRect(defaultFrame)); + } } - (void)updateScreenSize @@ -1074,23 +1155,51 @@ - (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]; + @synchronized (self) { + // No need to update since nothing has changed. + if (CGSizeEqualToSize(screenSize, self.previousScreenSize)) { + return; + } + + // Update previous value + self.previousScreenSize = screenSize; + + // Fire to both ad views as it pertains to both views. + [self.mraidBridge fireSetScreenSize:screenSize]; + [self.mraidBridgeTwoPart fireSetScreenSize:screenSize]; - MPLogTrace(@"Screen Size: %@", NSStringFromCGSize(screenSize)); + MPLogDebug(@"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; + CGSize maxSize = MPApplicationFrame(self.includeSafeAreaInsetsInCalculations).size; + + @synchronized (self) { + // No need to update since nothing has changed. + if (CGSizeEqualToSize(maxSize, self.previousMaxSize)) { + return; + } + + // Update previous value + self.previousMaxSize = maxSize; + + // Fire to both ad views as it pertains to both views. + [self.mraidBridge fireSetMaxSize:maxSize]; + [self.mraidBridgeTwoPart fireSetMaxSize:maxSize]; + + MPLogDebug(@"Max Size: %@", NSStringFromCGSize(maxSize)); + } +} - // Fire to both ad views as it pertains to both views. - [self.mraidBridge fireSetMaxSize:maxSize]; - [self.mraidBridgeTwoPart fireSetMaxSize:maxSize]; +- (void)updateOrientation +{ + self.expandModalViewController.supportedOrientationMask = self.forceOrientationMask; + self.interstitialViewController.supportedOrientationMask = self.forceOrientationMask; - MPLogTrace(@"Max Size: %@", NSStringFromCGSize(maxSize)); + MPLogDebug(@"Orientation: %ud", (unsigned int)self.forceOrientationMask); } #pragma mark - MRAID events @@ -1165,6 +1274,9 @@ - (void)adAlertManagerDidTriggerAlert:(MPAdAlertManager *)manager - (void)adDidLoad { + // Configure environment and fire ready event when ad is finished loading. + [self configureMraidEnvironmentToShowAdForBridge:self.mraidBridge]; + if ([self.delegate respondsToSelector:@selector(adDidLoad:)]) { [self.delegate adDidLoad:self.mraidAdView]; } @@ -1191,12 +1303,18 @@ - (void)adDidClose } } -- (void)adWillPresentModalView +- (void)adWillPresentModalViewByExpanding:(BOOL)wasExpended { self.modalViewCount++; - if (self.modalViewCount == 1) { + if (self.modalViewCount >= 1 && !wasExpended) { [self appShouldSuspend]; } + + // Notify listeners that the ad is expanding or resizing to present + // a modal view. + if (wasExpended && [self.delegate respondsToSelector:@selector(adWillExpand:)]) { + [self.delegate adWillExpand:self.mraidAdView]; + } } - (void)adDidDismissModalView @@ -1209,6 +1327,12 @@ - (void)adDidDismissModalView - (void)appShouldSuspend { + // App is already suspended; do nothing. + if (self.isAppSuspended) { + return; + } + + self.isAppSuspended = YES; if ([self.delegate respondsToSelector:@selector(appShouldSuspendForAd:)]) { [self.delegate appShouldSuspendForAd:self.mraidAdView]; } @@ -1216,6 +1340,12 @@ - (void)appShouldSuspend - (void)appShouldResume { + // App is not suspended; do nothing. + if (!self.isAppSuspended) { + return; + } + + self.isAppSuspended = NO; if ([self.delegate respondsToSelector:@selector(appShouldResumeFromAd:)]) { [self.delegate appShouldResumeFromAd:self.mraidAdView]; } diff --git a/MoPubSDK/Internal/MRAID/MRError.h b/MoPubSDK/Internal/MRAID/MRError.h index 1ac3b3789..835a9390a 100644 --- a/MoPubSDK/Internal/MRAID/MRError.h +++ b/MoPubSDK/Internal/MRAID/MRError.h @@ -1,7 +1,7 @@ // // MRError.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/Internal/MRAID/MRError.m b/MoPubSDK/Internal/MRAID/MRError.m index 6b0efbca0..516ca6091 100644 --- a/MoPubSDK/Internal/MRAID/MRError.m +++ b/MoPubSDK/Internal/MRAID/MRError.m @@ -1,7 +1,7 @@ // // MRError.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/Internal/MRAID/MRExpandModalViewController.h b/MoPubSDK/Internal/MRAID/MRExpandModalViewController.h index 8b2ee4a32..41fa8144e 100644 --- a/MoPubSDK/Internal/MRAID/MRExpandModalViewController.h +++ b/MoPubSDK/Internal/MRAID/MRExpandModalViewController.h @@ -1,7 +1,7 @@ // // MRExpandModalViewController.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -24,19 +24,4 @@ */ - (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/MoPubSDK/Internal/MRAID/MRExpandModalViewController.m b/MoPubSDK/Internal/MRAID/MRExpandModalViewController.m index 17a613737..ef9ea50fd 100644 --- a/MoPubSDK/Internal/MRAID/MRExpandModalViewController.m +++ b/MoPubSDK/Internal/MRAID/MRExpandModalViewController.m @@ -1,7 +1,7 @@ // // MRExpandModalViewController.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -11,8 +11,6 @@ @interface MRExpandModalViewController () -@property (nonatomic, assign) BOOL statusBarHidden; -@property (nonatomic, assign) BOOL applicationHidesStatusBar; @property (nonatomic, assign) UIInterfaceOrientationMask supportedOrientationMask; @end @@ -23,6 +21,7 @@ - (instancetype)initWithOrientationMask:(UIInterfaceOrientationMask)orientationM { if (self = [super init]) { _supportedOrientationMask = orientationMask; + self.modalPresentationStyle = UIModalPresentationFullScreen; } return self; @@ -35,40 +34,9 @@ - (void)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; + return YES; } - (void)setSupportedOrientationMask:(UIInterfaceOrientationMask)supportedOrientationMask @@ -78,12 +46,7 @@ - (void)setSupportedOrientationMask:(UIInterfaceOrientationMask)supportedOrienta [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; } diff --git a/MoPubSDK/Internal/MRAID/MRNativeCommandHandler.h b/MoPubSDK/Internal/MRAID/MRNativeCommandHandler.h index 28f623194..efe3e8985 100644 --- a/MoPubSDK/Internal/MRAID/MRNativeCommandHandler.h +++ b/MoPubSDK/Internal/MRAID/MRNativeCommandHandler.h @@ -1,7 +1,7 @@ // // MRNativeCommandHandler.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/Internal/MRAID/MRNativeCommandHandler.m b/MoPubSDK/Internal/MRAID/MRNativeCommandHandler.m index 8566f0306..040bfa5be 100644 --- a/MoPubSDK/Internal/MRAID/MRNativeCommandHandler.m +++ b/MoPubSDK/Internal/MRAID/MRNativeCommandHandler.m @@ -1,7 +1,7 @@ // // MRNativeCommandHandler.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/Internal/MRAID/MRProperty.h b/MoPubSDK/Internal/MRAID/MRProperty.h index a37a6126a..65f2dd2e3 100644 --- a/MoPubSDK/Internal/MRAID/MRProperty.h +++ b/MoPubSDK/Internal/MRAID/MRProperty.h @@ -1,7 +1,7 @@ // // MRProperty.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/Internal/MRAID/MRProperty.m b/MoPubSDK/Internal/MRAID/MRProperty.m index 118ab7ba8..036b88458 100644 --- a/MoPubSDK/Internal/MRAID/MRProperty.m +++ b/MoPubSDK/Internal/MRAID/MRProperty.m @@ -1,7 +1,7 @@ // // MRProperty.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -26,8 +26,6 @@ - (NSString *)jsonString { @implementation MRHostSDKVersionProperty : MRProperty -@synthesize version = _version; - + (instancetype)defaultProperty { MRHostSDKVersionProperty *property = [[self alloc] init]; @@ -46,8 +44,6 @@ - (NSString *)description @implementation MRPlacementTypeProperty : MRProperty -@synthesize placementType = _placementType; - + (MRPlacementTypeProperty *)propertyWithType:(MRAdViewPlacementType)type { MRPlacementTypeProperty *property = [[self alloc] init]; property.placementType = type; @@ -71,8 +67,6 @@ - (NSString *)description { @implementation MRStateProperty -@synthesize state = _state; - + (MRStateProperty *)propertyWithState:(MRAdViewState)state { MRStateProperty *property = [[self alloc] init]; property.state = state; @@ -107,8 +101,6 @@ - (NSString *)description { @implementation MRScreenSizeProperty : MRProperty -@synthesize screenSize = _screenSize; - + (MRScreenSizeProperty *)propertyWithSize:(CGSize)size { MRScreenSizeProperty *property = [[self alloc] init]; property.screenSize = size; @@ -129,12 +121,9 @@ @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], @"sms", + [NSNumber numberWithBool:NO], @"tel", [NSNumber numberWithBool:NO], @"calendar", [NSNumber numberWithBool:NO], @"storePicture", [NSNumber numberWithBool:YES], @"inlineVideo", @@ -178,8 +167,6 @@ - (NSString *)javascriptBooleanStringFromBoolValue:(BOOL)value @implementation MRViewableProperty : MRProperty -@synthesize isViewable = _isViewable; - + (MRViewableProperty *)propertyWithViewable:(BOOL)viewable { MRViewableProperty *property = [[self alloc] init]; property.isViewable = viewable; diff --git a/MoPubSDK/Internal/MRAID/MRVideoPlayerManager.h b/MoPubSDK/Internal/MRAID/MRVideoPlayerManager.h index ae57806f3..47deff443 100644 --- a/MoPubSDK/Internal/MRAID/MRVideoPlayerManager.h +++ b/MoPubSDK/Internal/MRAID/MRVideoPlayerManager.h @@ -1,7 +1,7 @@ // // MRVideoPlayerManager.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/Internal/MRAID/MRVideoPlayerManager.m b/MoPubSDK/Internal/MRAID/MRVideoPlayerManager.m index 8482241d8..d8c741efb 100644 --- a/MoPubSDK/Internal/MRAID/MRVideoPlayerManager.m +++ b/MoPubSDK/Internal/MRAID/MRVideoPlayerManager.m @@ -1,33 +1,40 @@ // // MRVideoPlayerManager.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // -#import "MRVideoPlayerManager.h" +#import #import #import "MPGlobal.h" +#import "MRVideoPlayerManager.h" -@implementation MRVideoPlayerManager +@interface MRVideoPlayerManager () + +@property (nonatomic, strong) AVPlayerViewController *playerViewController; + +@end -@synthesize delegate = _delegate; +@implementation MRVideoPlayerManager - (id)initWithDelegate:(id)delegate { self = [super init]; if (self) { _delegate = delegate; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(moviePlayerPlaybackDidFinish:) + name:AVPlayerItemDidPlayToEndTimeNotification + object:nil]; } return self; } - (void)dealloc { - [[NSNotificationCenter defaultCenter] removeObserver:self - name:MPMoviePlayerPlaybackDidFinishNotification - object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self]; } - (void)playVideo:(NSURL *)url @@ -37,25 +44,23 @@ - (void)playVideo:(NSURL *)url return; } - MPMoviePlayerViewController *controller = [[MPMoviePlayerViewController alloc] initWithContentURL:url]; - + AVPlayerViewController *viewController = [AVPlayerViewController new]; + viewController.player = [AVPlayer playerWithURL:url]; + viewController.showsPlaybackControls = NO; + self.playerViewController = viewController; [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]; + [[self.delegate viewControllerForPresentingVideoPlayer] presentViewController:viewController + animated:MP_ANIMATED + completion:^{ + [viewController.player play]; + }]; } - (void)moviePlayerPlaybackDidFinish:(NSNotification *)notification { - [self.delegate videoPlayerManagerDidDismissVideo:self]; + [self.playerViewController dismissViewControllerAnimated:YES completion:^{ + [self.delegate videoPlayerManagerDidDismissVideo:self]; + }]; } @end diff --git a/MoPubSDK/Internal/Utility/Categories/MoPub+Utility.h b/MoPubSDK/Internal/Utility/Categories/MoPub+Utility.h new file mode 100644 index 000000000..d0c372a95 --- /dev/null +++ b/MoPubSDK/Internal/Utility/Categories/MoPub+Utility.h @@ -0,0 +1,60 @@ +// +// MoPub+Utility.h +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import +#import "MoPub.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface MoPub (Utility) + +/** + This is a simplified version of @c [MoPub openURL:options:completion:], which provides empty @c options + dictionary and nil @c completion. + + @param url A URL (Universal Resource Locator). + */ ++ (void)openURL:(NSURL*)url; + +/** + This is a wrapper method that picks the correct version of @c [UIApplication openURL:] (versus + @c [UIApplication openURL:options:completionHandler:]) base the iOS target. + + @param url A URL (Universal Resource Locator). + @param options A dictionary of options to use when opening the URL. + @param completion The block to execute with the results. + */ ++ (void)openURL:(NSURL*)url + options:(NSDictionary *)options + completion:(void (^ __nullable)(BOOL success))completion; + +/** + This method sends an impression @c NSNotification. + + @param ad the ad from which to send the notification, or @c nil + @param adUnitID the adunit ID of the ad that sent the notification + @param impressionData the impression data associated with the ad, or nil if no impression data + */ ++ (void)sendImpressionNotificationFromAd:(id _Nullable)ad + adUnitID:(NSString *)adUnitID + impressionData:(MPImpressionData * _Nullable)impressionData; + +/** + This method sends an impression @c NSNotification and notifies the @c ad's delegate of the impression. + + @param ad the ad from which to send the notification + @param adUnitID the adunit ID of the ad that sent the notification + @param impressionData the impression data associated with the ad, or nil if no impression data + */ ++ (void)sendImpressionDelegateAndNotificationFromAd:(id)ad + adUnitID:(NSString *)adUnitID + impressionData:(MPImpressionData * _Nullable)impressionData; + +@end + +NS_ASSUME_NONNULL_END diff --git a/MoPubSDK/Internal/Utility/Categories/MoPub+Utility.m b/MoPubSDK/Internal/Utility/Categories/MoPub+Utility.m new file mode 100644 index 000000000..06f1a85f1 --- /dev/null +++ b/MoPubSDK/Internal/Utility/Categories/MoPub+Utility.m @@ -0,0 +1,54 @@ +// +// MoPub+Utility.m +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import "MoPub+Utility.h" + +@implementation MoPub (Utility) + ++ (void)openURL:(NSURL*)url { + [self openURL:url options:@{} completion:nil]; +} + ++ (void)openURL:(NSURL*)url + options:(NSDictionary *)options + completion:(void (^ __nullable)(BOOL success))completion { + if (@available(iOS 10, *)) { + [[UIApplication sharedApplication] openURL:url options:options completionHandler:completion]; + } else { + completion([[UIApplication sharedApplication] openURL:url]); + } +} + ++ (void)sendImpressionNotificationFromAd:(id)ad + adUnitID:(NSString *)adUnitID + impressionData:(MPImpressionData * _Nullable)impressionData { + // This dictionary must always contain the adunit ID but may or may not include @c impressionData depending on if it's @c nil. + // If adding keys and objects in the future, put them above @c impressionData to avoid being skipped in the case of nil data. + NSDictionary * userInfo = [NSDictionary dictionaryWithObjectsAndKeys:adUnitID, + kMPImpressionTrackedInfoAdUnitIDKey, + impressionData, + kMPImpressionTrackedInfoImpressionDataKey, + nil]; + [[NSNotificationCenter defaultCenter] postNotificationName:kMPImpressionTrackedNotification + object:ad + userInfo:userInfo]; +} + ++ (void)sendImpressionDelegateAndNotificationFromAd:(id)ad + adUnitID:(NSString *)adUnitID + impressionData:(MPImpressionData * _Nullable)impressionData { + [self sendImpressionNotificationFromAd:ad + adUnitID:adUnitID + impressionData:impressionData]; + + if ([ad.delegate respondsToSelector:@selector(mopubAd:didTrackImpressionWithImpressionData:)]) { + [ad.delegate mopubAd:ad didTrackImpressionWithImpressionData:impressionData]; + } +} + +@end diff --git a/MoPubSDK/Internal/Utility/Categories/NSBundle+MPAdditions.h b/MoPubSDK/Internal/Utility/Categories/NSBundle+MPAdditions.h index c014c9315..01faf4a32 100644 --- a/MoPubSDK/Internal/Utility/Categories/NSBundle+MPAdditions.h +++ b/MoPubSDK/Internal/Utility/Categories/NSBundle+MPAdditions.h @@ -1,7 +1,7 @@ // // NSBundle+MPAdditions.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/Internal/Utility/Categories/NSBundle+MPAdditions.m b/MoPubSDK/Internal/Utility/Categories/NSBundle+MPAdditions.m index b3511ddf5..18dc86fa4 100644 --- a/MoPubSDK/Internal/Utility/Categories/NSBundle+MPAdditions.m +++ b/MoPubSDK/Internal/Utility/Categories/NSBundle+MPAdditions.m @@ -1,7 +1,7 @@ // // NSBundle+MPAdditions.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/Internal/Utility/Categories/NSDate+MPAdditions.h b/MoPubSDK/Internal/Utility/Categories/NSDate+MPAdditions.h index 011eb7b3d..55648fd6c 100644 --- a/MoPubSDK/Internal/Utility/Categories/NSDate+MPAdditions.h +++ b/MoPubSDK/Internal/Utility/Categories/NSDate+MPAdditions.h @@ -1,7 +1,7 @@ // // NSDate+MPAdditions.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/Internal/Utility/Categories/NSDate+MPAdditions.m b/MoPubSDK/Internal/Utility/Categories/NSDate+MPAdditions.m index 67dea93e1..074ff86e7 100644 --- a/MoPubSDK/Internal/Utility/Categories/NSDate+MPAdditions.m +++ b/MoPubSDK/Internal/Utility/Categories/NSDate+MPAdditions.m @@ -1,7 +1,7 @@ // // NSDate+MPAdditions.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/Internal/Utility/Categories/NSDictionary+MPAdditions.h b/MoPubSDK/Internal/Utility/Categories/NSDictionary+MPAdditions.h index 4e08cf646..4b6c85699 100644 --- a/MoPubSDK/Internal/Utility/Categories/NSDictionary+MPAdditions.h +++ b/MoPubSDK/Internal/Utility/Categories/NSDictionary+MPAdditions.h @@ -1,7 +1,7 @@ // // NSDictionary+MPAdditions.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/Internal/Utility/Categories/NSDictionary+MPAdditions.m b/MoPubSDK/Internal/Utility/Categories/NSDictionary+MPAdditions.m index d87134e85..46c79c6ca 100644 --- a/MoPubSDK/Internal/Utility/Categories/NSDictionary+MPAdditions.m +++ b/MoPubSDK/Internal/Utility/Categories/NSDictionary+MPAdditions.m @@ -1,7 +1,7 @@ // // NSDictionary+MPAdditions.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/Internal/Utility/Categories/NSError+MPAdditions.h b/MoPubSDK/Internal/Utility/Categories/NSError+MPAdditions.h index 582983cd7..c4e35dc36 100644 --- a/MoPubSDK/Internal/Utility/Categories/NSError+MPAdditions.h +++ b/MoPubSDK/Internal/Utility/Categories/NSError+MPAdditions.h @@ -1,7 +1,7 @@ // // NSError+MPAdditions.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/Internal/Utility/Categories/NSError+MPAdditions.m b/MoPubSDK/Internal/Utility/Categories/NSError+MPAdditions.m index 9e5cade32..597edccfe 100644 --- a/MoPubSDK/Internal/Utility/Categories/NSError+MPAdditions.m +++ b/MoPubSDK/Internal/Utility/Categories/NSError+MPAdditions.m @@ -1,7 +1,7 @@ // // NSError+MPAdditions.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -12,7 +12,7 @@ @implementation NSError (MPAdditions) - (BOOL)isAdRequestTimedOutError { - return ([self.domain isEqualToString:kMOPUBErrorDomain] && self.code == MOPUBErrorAdRequestTimedOut); + return ([self.domain isEqualToString:kNSErrorDomain] && self.code == MOPUBErrorAdRequestTimedOut); } @end @@ -24,7 +24,7 @@ + (NSError *)networkErrorWithHTTPStatusCode:(NSInteger)statusCode { // status code is 200 (not an error). It is up to the caller of this method to // determine if the status code is really an error or not. NSString * message = [NSHTTPURLResponse localizedStringForStatusCode:statusCode]; - NSError * error = [NSError errorWithDomain:kMOPUBErrorDomain code:MOPUBErrorHTTPResponseNot200 userInfo:@{ NSLocalizedDescriptionKey: message }]; + NSError * error = [NSError errorWithDomain:kNSErrorDomain code:MOPUBErrorHTTPResponseNot200 userInfo:@{ NSLocalizedDescriptionKey: message }]; return error; } diff --git a/MoPubSDK/Internal/Utility/Categories/NSHTTPURLResponse+MPAdditions.h b/MoPubSDK/Internal/Utility/Categories/NSHTTPURLResponse+MPAdditions.h index 53e7a1803..89d2b5946 100644 --- a/MoPubSDK/Internal/Utility/Categories/NSHTTPURLResponse+MPAdditions.h +++ b/MoPubSDK/Internal/Utility/Categories/NSHTTPURLResponse+MPAdditions.h @@ -1,7 +1,7 @@ // // NSHTTPURLResponse+MPAdditions.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/Internal/Utility/Categories/NSHTTPURLResponse+MPAdditions.m b/MoPubSDK/Internal/Utility/Categories/NSHTTPURLResponse+MPAdditions.m index 0c2535a1a..06101d0c5 100644 --- a/MoPubSDK/Internal/Utility/Categories/NSHTTPURLResponse+MPAdditions.m +++ b/MoPubSDK/Internal/Utility/Categories/NSHTTPURLResponse+MPAdditions.m @@ -1,7 +1,7 @@ // // NSHTTPURLResponse+MPAdditions.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -18,7 +18,7 @@ - (NSStringEncoding)stringEncodingFromContentType:(NSString *)contentType NSStringEncoding encoding = NSUTF8StringEncoding; if (![contentType length]) { - MPLogWarn(@"Attempting to set string encoding from nil %@", kMoPubHTTPHeaderContentType); + MPLogInfo(@"Attempting to set string encoding from nil %@", kMoPubHTTPHeaderContentType); return encoding; } diff --git a/MoPubSDK/Internal/Utility/Categories/NSJSONSerialization+MPAdditions.h b/MoPubSDK/Internal/Utility/Categories/NSJSONSerialization+MPAdditions.h index 8b85fa437..d56b30bea 100644 --- a/MoPubSDK/Internal/Utility/Categories/NSJSONSerialization+MPAdditions.h +++ b/MoPubSDK/Internal/Utility/Categories/NSJSONSerialization+MPAdditions.h @@ -1,7 +1,7 @@ // // NSJSONSerialization+MPAdditions.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/Internal/Utility/Categories/NSJSONSerialization+MPAdditions.m b/MoPubSDK/Internal/Utility/Categories/NSJSONSerialization+MPAdditions.m index f3aa95bcd..c5340493f 100644 --- a/MoPubSDK/Internal/Utility/Categories/NSJSONSerialization+MPAdditions.m +++ b/MoPubSDK/Internal/Utility/Categories/NSJSONSerialization+MPAdditions.m @@ -1,7 +1,7 @@ // // NSJSONSerialization+MPAdditions.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/Internal/Utility/Categories/NSMutableArray+MPAdditions.h b/MoPubSDK/Internal/Utility/Categories/NSMutableArray+MPAdditions.h index 105f7cbff..d87d8e5e6 100644 --- a/MoPubSDK/Internal/Utility/Categories/NSMutableArray+MPAdditions.h +++ b/MoPubSDK/Internal/Utility/Categories/NSMutableArray+MPAdditions.h @@ -1,7 +1,7 @@ // // NSMutableArray+MPAdditions.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/Internal/Utility/Categories/NSMutableArray+MPAdditions.m b/MoPubSDK/Internal/Utility/Categories/NSMutableArray+MPAdditions.m index 61fb93341..196ef426e 100644 --- a/MoPubSDK/Internal/Utility/Categories/NSMutableArray+MPAdditions.m +++ b/MoPubSDK/Internal/Utility/Categories/NSMutableArray+MPAdditions.m @@ -1,7 +1,7 @@ // // NSMutableArray+MPAdditions.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/Internal/Utility/Categories/NSString+MPAdditions.h b/MoPubSDK/Internal/Utility/Categories/NSString+MPAdditions.h index 98365f51c..36683953a 100644 --- a/MoPubSDK/Internal/Utility/Categories/NSString+MPAdditions.h +++ b/MoPubSDK/Internal/Utility/Categories/NSString+MPAdditions.h @@ -1,7 +1,7 @@ // // NSString+MPAdditions.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/Internal/Utility/Categories/NSString+MPAdditions.m b/MoPubSDK/Internal/Utility/Categories/NSString+MPAdditions.m index 03a497820..45278cfa9 100644 --- a/MoPubSDK/Internal/Utility/Categories/NSString+MPAdditions.m +++ b/MoPubSDK/Internal/Utility/Categories/NSString+MPAdditions.m @@ -1,7 +1,7 @@ // // NSString+MPAdditions.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -11,12 +11,9 @@ @implementation NSString (MPAdditions) - (NSString *)mp_URLEncodedString { - NSString *result = (NSString *)CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(NULL, - (CFStringRef)self, - NULL, - (CFStringRef)@"!*'();:@&=+$,/?%#[]<>", - kCFStringEncodingUTF8)); - return result; + NSString *charactersToEscape = @"!*'();:@&=+$,/?%#[]<>"; + NSCharacterSet *allowedCharacters = [[NSCharacterSet characterSetWithCharactersInString:charactersToEscape] invertedSet]; + return [self stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacters]; } - (NSNumber *)safeIntegerValue { diff --git a/MoPubSDK/Internal/Utility/Categories/NSString+MPConsentStatus.h b/MoPubSDK/Internal/Utility/Categories/NSString+MPConsentStatus.h index 3894b0736..e99a90ff2 100644 --- a/MoPubSDK/Internal/Utility/Categories/NSString+MPConsentStatus.h +++ b/MoPubSDK/Internal/Utility/Categories/NSString+MPConsentStatus.h @@ -1,7 +1,7 @@ // // NSString+MPConsentStatus.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/Internal/Utility/Categories/NSString+MPConsentStatus.m b/MoPubSDK/Internal/Utility/Categories/NSString+MPConsentStatus.m index 194d1b1f4..f56d2e1aa 100644 --- a/MoPubSDK/Internal/Utility/Categories/NSString+MPConsentStatus.m +++ b/MoPubSDK/Internal/Utility/Categories/NSString+MPConsentStatus.m @@ -1,7 +1,7 @@ // // NSString+MPConsentStatus.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/Internal/Utility/Categories/NSURL+MPAdditions.h b/MoPubSDK/Internal/Utility/Categories/NSURL+MPAdditions.h index 4b76e0ee2..02ff320ef 100644 --- a/MoPubSDK/Internal/Utility/Categories/NSURL+MPAdditions.h +++ b/MoPubSDK/Internal/Utility/Categories/NSURL+MPAdditions.h @@ -1,7 +1,7 @@ // // NSURL+MPAdditions.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -27,8 +27,6 @@ typedef enum { - (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; diff --git a/MoPubSDK/Internal/Utility/Categories/NSURL+MPAdditions.m b/MoPubSDK/Internal/Utility/Categories/NSURL+MPAdditions.m index 5eecc6928..db8d048c1 100644 --- a/MoPubSDK/Internal/Utility/Categories/NSURL+MPAdditions.m +++ b/MoPubSDK/Internal/Utility/Categories/NSURL+MPAdditions.m @@ -1,16 +1,13 @@ // // NSURL+MPAdditions.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // #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"; @@ -33,7 +30,7 @@ - (NSString *)mp_queryParameterForKey:(NSString *)key if (keyAndValue.count >= 2 && [[keyAndValue objectAtIndex:0] isEqualToString:key] && [[keyAndValue objectAtIndex:1] length] > 0) { - return [[keyAndValue objectAtIndex:1] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; + return [[keyAndValue objectAtIndex:1] stringByRemovingPercentEncoding]; } } return nil; @@ -48,7 +45,7 @@ - (NSArray *)mp_queryParametersForKey:(NSString *)key if (keyAndValue.count >= 2 && [[keyAndValue objectAtIndex:0] isEqualToString:key] && [[keyAndValue objectAtIndex:1] length] > 0) { - [matchingParameters addObject:[[keyAndValue objectAtIndex:1] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]; + [matchingParameters addObject:[[keyAndValue objectAtIndex:1] stringByRemovingPercentEncoding]]; } } return [NSArray arrayWithArray:matchingParameters]; @@ -63,23 +60,12 @@ - (NSDictionary *)mp_queryAsDictionary if (keyVal.count >= 2) { NSString *key = [keyVal objectAtIndex:0]; NSString *value = [keyVal objectAtIndex:1]; - [queryDict setObject:[value stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding] - forKey:key]; + [queryDict setObject:[value stringByRemovingPercentEncoding] 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"] || diff --git a/MoPubSDK/Internal/Utility/Categories/SKStoreProductViewController+MPAdditions.h b/MoPubSDK/Internal/Utility/Categories/SKStoreProductViewController+MPAdditions.h new file mode 100644 index 000000000..1e177b8ea --- /dev/null +++ b/MoPubSDK/Internal/Utility/Categories/SKStoreProductViewController+MPAdditions.h @@ -0,0 +1,24 @@ +// +// SKStoreProductViewController+MPAdditions.h +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface SKStoreProductViewController (MPAdditions) + +/** + @c SKStoreProductViewController can crash the app if used under the wrong conditions (e.g., + in the case of an orientation mismatch), so this property reports whether it's safe to use + the view controller. + */ +@property (class, nonatomic, readonly) BOOL canUseStoreProductViewController; + +@end + +NS_ASSUME_NONNULL_END diff --git a/MoPubSDK/Internal/Utility/Categories/SKStoreProductViewController+MPAdditions.m b/MoPubSDK/Internal/Utility/Categories/SKStoreProductViewController+MPAdditions.m new file mode 100644 index 000000000..d4eebe3a6 --- /dev/null +++ b/MoPubSDK/Internal/Utility/Categories/SKStoreProductViewController+MPAdditions.m @@ -0,0 +1,35 @@ +// +// SKStoreProductViewController+MPAdditions.m +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import "SKStoreProductViewController+MPAdditions.h" + +@implementation SKStoreProductViewController (MPAdditions) + ++ (BOOL)canUseStoreProductViewController { + // @c SKStoreProductViewController cannot be used in an app environment that only + // supports landscape -- portrait is required, or presenting the view controller + // will produce an app crash -- so query the usable orientations for the app and + // report whether @c SKStoreProductViewController is usable. + + // Compute this once and use forever because the application's supported orientations + // will not change in the app lifetime. + + static BOOL canUseStoreProductViewController = NO; + + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + UIWindow * keyWindow = [UIApplication sharedApplication].keyWindow; + UIInterfaceOrientationMask appSupportedOrientations = [[UIApplication sharedApplication] supportedInterfaceOrientationsForWindow:keyWindow]; + + canUseStoreProductViewController = (appSupportedOrientations & UIInterfaceOrientationMaskPortrait) != 0; + }); + + return canUseStoreProductViewController; +} + +@end diff --git a/MoPubSDK/Internal/Utility/Categories/UIButton+MPAdditions.h b/MoPubSDK/Internal/Utility/Categories/UIButton+MPAdditions.h deleted file mode 100644 index e5c98aeb4..000000000 --- a/MoPubSDK/Internal/Utility/Categories/UIButton+MPAdditions.h +++ /dev/null @@ -1,15 +0,0 @@ -// -// UIButton+MPAdditions.h -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import - -@interface UIButton (MPAdditions) - -@property (nonatomic) UIEdgeInsets mp_TouchAreaInsets; - -@end diff --git a/MoPubSDK/Internal/Utility/Categories/UIButton+MPAdditions.m b/MoPubSDK/Internal/Utility/Categories/UIButton+MPAdditions.m deleted file mode 100644 index 4aafc2d76..000000000 --- a/MoPubSDK/Internal/Utility/Categories/UIButton+MPAdditions.m +++ /dev/null @@ -1,36 +0,0 @@ -// -// UIButton+MPAdditions.m -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#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/MoPubSDK/Internal/Utility/Categories/UIColor+MPAdditions.h b/MoPubSDK/Internal/Utility/Categories/UIColor+MPAdditions.h index c4242dcff..196091c31 100644 --- a/MoPubSDK/Internal/Utility/Categories/UIColor+MPAdditions.h +++ b/MoPubSDK/Internal/Utility/Categories/UIColor+MPAdditions.h @@ -1,7 +1,7 @@ // // UIColor+MPAdditions.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/Internal/Utility/Categories/UIColor+MPAdditions.m b/MoPubSDK/Internal/Utility/Categories/UIColor+MPAdditions.m index c0532bdc2..e92221acd 100644 --- a/MoPubSDK/Internal/Utility/Categories/UIColor+MPAdditions.m +++ b/MoPubSDK/Internal/Utility/Categories/UIColor+MPAdditions.m @@ -1,7 +1,7 @@ // // UIColor+MPAdditions.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/Internal/Utility/Categories/UIView+MPAdditions.h b/MoPubSDK/Internal/Utility/Categories/UIView+MPAdditions.h index 2129012d1..da6857574 100644 --- a/MoPubSDK/Internal/Utility/Categories/UIView+MPAdditions.h +++ b/MoPubSDK/Internal/Utility/Categories/UIView+MPAdditions.h @@ -1,7 +1,7 @@ // // UIView+MPAdditions.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -26,3 +26,21 @@ - (UIImage *)mp_snapshot:(BOOL)usePresentationLayer; @end + +/** + This @c MPSafeArea category is for reducing boilerplate code for Safe Area handling. + */ +@interface UIView (MPSafeArea) + +@property(nonatomic,readonly) NSLayoutXAxisAnchor *mp_safeLeadingAnchor; +@property(nonatomic,readonly) NSLayoutXAxisAnchor *mp_safeTrailingAnchor; +@property(nonatomic,readonly) NSLayoutXAxisAnchor *mp_safeLeftAnchor; +@property(nonatomic,readonly) NSLayoutXAxisAnchor *mp_safeRightAnchor; +@property(nonatomic,readonly) NSLayoutYAxisAnchor *mp_safeTopAnchor; +@property(nonatomic,readonly) NSLayoutYAxisAnchor *mp_safeBottomAnchor; +@property(nonatomic,readonly) NSLayoutDimension *mp_safeWidthAnchor; +@property(nonatomic,readonly) NSLayoutDimension *mp_safeHeightAnchor; +@property(nonatomic,readonly) NSLayoutXAxisAnchor *mp_safeCenterXAnchor; +@property(nonatomic,readonly) NSLayoutYAxisAnchor *mp_safeCenterYAnchor; + +@end diff --git a/MoPubSDK/Internal/Utility/Categories/UIView+MPAdditions.m b/MoPubSDK/Internal/Utility/Categories/UIView+MPAdditions.m index 8ecd51aa7..cf3144f86 100644 --- a/MoPubSDK/Internal/Utility/Categories/UIView+MPAdditions.m +++ b/MoPubSDK/Internal/Utility/Categories/UIView+MPAdditions.m @@ -1,7 +1,7 @@ // // UIView+MPAdditions.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -98,3 +98,87 @@ - (UIImage *)mp_snapshot:(BOOL)usePresentationLayer } @end + +@implementation UIView (MPSafeArea) + +- (NSLayoutXAxisAnchor *)mp_safeLeadingAnchor { + if (@available(iOS 11, *)) { + return self.safeAreaLayoutGuide.leadingAnchor; + } else { + return self.leadingAnchor; + } +} + +- (NSLayoutXAxisAnchor *)mp_safeTrailingAnchor { + if (@available(iOS 11, *)) { + return self.safeAreaLayoutGuide.trailingAnchor; + } else { + return self.trailingAnchor; + } +} + +- (NSLayoutXAxisAnchor *)mp_safeLeftAnchor { + if (@available(iOS 11, *)) { + return self.safeAreaLayoutGuide.leftAnchor; + } else { + return self.leftAnchor; + } +} + +- (NSLayoutXAxisAnchor *)mp_safeRightAnchor { + if (@available(iOS 11, *)) { + return self.safeAreaLayoutGuide.rightAnchor; + } else { + return self.rightAnchor; + } +} + +- (NSLayoutYAxisAnchor *)mp_safeTopAnchor { + if (@available(iOS 11, *)) { + return self.safeAreaLayoutGuide.topAnchor; + } else { + return self.topAnchor; + } +} + +- (NSLayoutYAxisAnchor *)mp_safeBottomAnchor { + if (@available(iOS 11, *)) { + return self.safeAreaLayoutGuide.bottomAnchor; + } else { + return self.bottomAnchor; + } +} + +- (NSLayoutDimension *)mp_safeWidthAnchor { + if (@available(iOS 11, *)) { + return self.safeAreaLayoutGuide.widthAnchor; + } else { + return self.widthAnchor; + } +} + +- (NSLayoutDimension *)mp_safeHeightAnchor { + if (@available(iOS 11, *)) { + return self.safeAreaLayoutGuide.heightAnchor; + } else { + return self.heightAnchor; + } +} + +- (NSLayoutXAxisAnchor *)mp_safeCenterXAnchor { + if (@available(iOS 11, *)) { + return self.safeAreaLayoutGuide.centerXAnchor; + } else { + return self.centerXAnchor; + } +} + +- (NSLayoutYAxisAnchor *)mp_safeCenterYAnchor { + if (@available(iOS 11, *)) { + return self.safeAreaLayoutGuide.centerYAnchor; + } else { + return self.centerYAnchor; + } +} + +@end diff --git a/MoPubSDK/Internal/Utility/Categories/UIWebView+MPAdditions.h b/MoPubSDK/Internal/Utility/Categories/UIWebView+MPAdditions.h deleted file mode 100644 index b8cb7b7e1..000000000 --- a/MoPubSDK/Internal/Utility/Categories/UIWebView+MPAdditions.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// UIWebView+MPAdditions.h -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import -#import - -extern NSString *const kJavaScriptDisableDialogSnippet; - -@interface UIWebView (MPAdditions) - -- (void)mp_setScrollable:(BOOL)scrollable; -- (void)disableJavaScriptDialogs; - -@end diff --git a/MoPubSDK/Internal/Utility/Categories/UIWebView+MPAdditions.m b/MoPubSDK/Internal/Utility/Categories/UIWebView+MPAdditions.m deleted file mode 100644 index 111d15a4b..000000000 --- a/MoPubSDK/Internal/Utility/Categories/UIWebView+MPAdditions.m +++ /dev/null @@ -1,49 +0,0 @@ -// -// UIWebView+MPAdditions.m -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#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 ([self respondsToSelector:@selector(scrollView)]) - { - UIScrollView *scrollView = self.scrollView; - scrollView.scrollEnabled = scrollable; - scrollView.bounces = scrollable; - } - else - { - 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/MoPubSDK/Internal/Utility/MOPUBExperimentProvider.h b/MoPubSDK/Internal/Utility/MOPUBExperimentProvider.h index 73d11abf3..dbc8bca17 100644 --- a/MoPubSDK/Internal/Utility/MOPUBExperimentProvider.h +++ b/MoPubSDK/Internal/Utility/MOPUBExperimentProvider.h @@ -1,7 +1,7 @@ // // MOPUBExperimentProvider.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -10,8 +10,10 @@ @interface MOPUBExperimentProvider : NSObject -+ (void)setDisplayAgentType:(MOPUBDisplayAgentType)displayAgentType; -+ (void)setDisplayAgentFromAdServer:(MOPUBDisplayAgentType)displayAgentType; -+ (MOPUBDisplayAgentType)displayAgentType; +@property (nonatomic, assign) MOPUBDisplayAgentType displayAgentType; + ++ (instancetype)sharedInstance; + +- (void)setDisplayAgentFromAdServer:(MOPUBDisplayAgentType)displayAgentType; @end diff --git a/MoPubSDK/Internal/Utility/MOPUBExperimentProvider.m b/MoPubSDK/Internal/Utility/MOPUBExperimentProvider.m index 861d8718c..f5cc74e95 100644 --- a/MoPubSDK/Internal/Utility/MOPUBExperimentProvider.m +++ b/MoPubSDK/Internal/Utility/MOPUBExperimentProvider.m @@ -1,40 +1,48 @@ // // MOPUBExperimentProvider.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // #import "MOPUBExperimentProvider.h" +@interface MOPUBExperimentProvider () +@property (nonatomic, assign) BOOL isDisplayAgentOverriddenByClient; +@end + @implementation MOPUBExperimentProvider -static BOOL gIsDisplayAgentOverriddenByClient = NO; -static MOPUBDisplayAgentType gDisplayAgentType = MOPUBDisplayAgentTypeInApp; +@synthesize displayAgentType = _displayAgentType; -+ (void)setDisplayAgentType:(MOPUBDisplayAgentType)displayAgentType -{ - gIsDisplayAgentOverriddenByClient = YES; - gDisplayAgentType = displayAgentType; +- (instancetype)init { + self = [super init]; + if (self != nil) { + _isDisplayAgentOverriddenByClient = NO; + _displayAgentType = MOPUBDisplayAgentTypeInApp; + } + return self; } -+ (void)setDisplayAgentFromAdServer:(MOPUBDisplayAgentType)displayAgentType -{ - if (!gIsDisplayAgentOverriddenByClient) { - gDisplayAgentType = displayAgentType; - } ++ (instancetype)sharedInstance { + static dispatch_once_t once; + static id _sharedInstance; + dispatch_once(&once, ^{ + _sharedInstance = [self new]; + }); + return _sharedInstance; } -+ (MOPUBDisplayAgentType)displayAgentType -{ - return gDisplayAgentType; +- (void)setDisplayAgentType:(MOPUBDisplayAgentType)displayAgentType { + _isDisplayAgentOverriddenByClient = YES; + _displayAgentType = displayAgentType; } -// used in test only -+ (void)setDisplayAgentOverriddenByClientFlag:(BOOL)flag -{ - gIsDisplayAgentOverriddenByClient = flag; +- (void)setDisplayAgentFromAdServer:(MOPUBDisplayAgentType)displayAgentType { + if (!self.isDisplayAgentOverriddenByClient) { + _displayAgentType = displayAgentType; + } } @end diff --git a/MoPubSDK/Internal/Utility/MPAnalyticsTracker.h b/MoPubSDK/Internal/Utility/MPAnalyticsTracker.h index c9624ce20..76ed0a40f 100644 --- a/MoPubSDK/Internal/Utility/MPAnalyticsTracker.h +++ b/MoPubSDK/Internal/Utility/MPAnalyticsTracker.h @@ -1,7 +1,7 @@ // // MPAnalyticsTracker.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -9,13 +9,21 @@ #import @class MPAdConfiguration; +@class MPVASTTrackingEvent; + +@protocol MPAnalyticsTracker + +- (void)trackImpressionForConfiguration:(MPAdConfiguration *)configuration; +- (void)trackClickForConfiguration:(MPAdConfiguration *)configuration; +- (void)sendTrackingRequestForURLs:(NSArray *)URLs; + +@end @interface MPAnalyticsTracker : NSObject + (MPAnalyticsTracker *)sharedTracker; -- (void)trackImpressionForConfiguration:(MPAdConfiguration *)configuration; -- (void)trackClickForConfiguration:(MPAdConfiguration *)configuration; -- (void)sendTrackingRequestForURLs:(NSArray *)URLs; +@end +@interface MPAnalyticsTracker (MPAnalyticsTracker) @end diff --git a/MoPubSDK/Internal/Utility/MPAnalyticsTracker.m b/MoPubSDK/Internal/Utility/MPAnalyticsTracker.m index 5e0b86073..5032ccf6e 100644 --- a/MoPubSDK/Internal/Utility/MPAnalyticsTracker.m +++ b/MoPubSDK/Internal/Utility/MPAnalyticsTracker.m @@ -1,7 +1,7 @@ // // MPAnalyticsTracker.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -12,6 +12,7 @@ #import "MPHTTPNetworkSession.h" #import "MPLogging.h" #import "MPURLRequest.h" +#import "MPVASTTrackingEvent.h" @implementation MPAnalyticsTracker @@ -25,6 +26,10 @@ + (MPAnalyticsTracker *)sharedTracker return sharedTracker; } +@end + +@implementation MPAnalyticsTracker (MPAnalyticsTracker) + - (void)trackImpressionForConfiguration:(MPAdConfiguration *)configuration { // Take the @c impressionTrackingURLs array from @c configuration and use the @c sendTrackingRequestForURLs method @@ -40,7 +45,7 @@ - (void)trackClickForConfiguration:(MPAdConfiguration *)configuration [MPHTTPNetworkSession startTaskWithHttpRequest:request]; } -- (void)sendTrackingRequestForURLs:(NSArray *)URLs +- (void)sendTrackingRequestForURLs:(NSArray *)URLs { for (NSURL *URL in URLs) { MPURLRequest * trackingRequest = [[MPURLRequest alloc] initWithURL:URL]; diff --git a/MoPubSDK/Internal/Utility/MPError.h b/MoPubSDK/Internal/Utility/MPError.h index 6c805270a..e82e14465 100644 --- a/MoPubSDK/Internal/Utility/MPError.h +++ b/MoPubSDK/Internal/Utility/MPError.h @@ -1,14 +1,14 @@ // // MPError.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // #import -extern NSString * const kMOPUBErrorDomain; +extern NSString * const kNSErrorDomain; typedef enum { MOPUBErrorUnknown = -1, @@ -24,13 +24,53 @@ typedef enum { MOPUBErrorHTTPResponseNot200, MOPUBErrorNoNetworkData, MOPUBErrorSDKNotInitialized, + MOPUBErrorSDKInitializationInProgress, MOPUBErrorAdRequestTimedOut, MOPUBErrorNoRenderer, + MOPUBErrorAdLoadAlreadyInProgress, + MOPUBErrorInvalidCustomEventClass, + MOPUBErrorJSONSerializationFailed, + MOPUBErrorUnableToParseAdResponse, + MOPUBErrorConsentDialogAlreadyShowing, + MOPUBErrorNoConsentDialogLoaded, + MOPUBErrorAdapterFailedToLoadAd, + MOPUBErrorFullScreenAdAlreadyOnScreen, + MOPUBErrorTooManyRequests, + MOPUBErrorFrameWidthNotSetForFlexibleSize, + MOPUBErrorFrameHeightNotSetForFlexibleSize, } MOPUBErrorCode; -@interface MOPUBError : NSError +@interface NSError (MoPub) -+ (MOPUBError *)errorWithCode:(MOPUBErrorCode)code; -+ (MOPUBError *)errorWithCode:(MOPUBErrorCode)code localizedDescription:(NSString *)description; ++ (NSError *)errorWithCode:(MOPUBErrorCode)code; ++ (NSError *)errorWithCode:(MOPUBErrorCode)code localizedDescription:(NSString *)description; @end + +@interface NSError (Initialization) ++ (instancetype)sdkMinimumOsVersion:(int)osVersion; ++ (instancetype)sdkInitializationInProgress; +@end + +@interface NSError (AdLifeCycle) ++ (instancetype)adAlreadyLoading; ++ (instancetype)customEventClass:(Class)customEventClass doesNotInheritFrom:(Class)baseClass; ++ (instancetype)networkResponseIsNotHTTP; ++ (instancetype)networkResponseContainedNoData; ++ (instancetype)adLoadFailedBecauseSdkNotInitialized; ++ (instancetype)serializationOfJson:(NSDictionary *)json failedWithError:(NSError *)serializationError; ++ (instancetype)adResponseFailedToParseWithError:(NSError *)serializationError; ++ (instancetype)adResponsesNotFound; ++ (instancetype)fullscreenAdAlreadyOnScreen; ++ (instancetype)frameWidthNotSetForFlexibleSize; ++ (instancetype)frameHeightNotSetForFlexibleSize; +@end + +@interface NSError (Consent) ++ (instancetype)consentDialogAlreadyShowing; ++ (instancetype)noConsentDialogLoaded; +@end + +@interface NSError (RateLimit) ++ (instancetype)tooManyRequests; +@end diff --git a/MoPubSDK/Internal/Utility/MPError.m b/MoPubSDK/Internal/Utility/MPError.m index 9ba5e2e4b..363c92ce9 100644 --- a/MoPubSDK/Internal/Utility/MPError.m +++ b/MoPubSDK/Internal/Utility/MPError.m @@ -1,28 +1,109 @@ // // MPError.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // #import "MPError.h" -NSString * const kMOPUBErrorDomain = @"com.mopub.iossdk"; +NSString * const kNSErrorDomain = @"com.mopub.iossdk"; -@implementation MOPUBError +@implementation NSError (MoPub) -+ (MOPUBError *)errorWithCode:(MOPUBErrorCode)code { - return [MOPUBError errorWithCode:code localizedDescription:nil]; ++ (NSError *)errorWithCode:(MOPUBErrorCode)code { + return [NSError errorWithCode:code localizedDescription:nil]; } -+ (MOPUBError *)errorWithCode:(MOPUBErrorCode)code localizedDescription:(NSString *)description { ++ (NSError *)errorWithCode:(MOPUBErrorCode)code localizedDescription:(NSString *)description { NSDictionary * userInfo = nil; if (description != nil) { userInfo = @{ NSLocalizedDescriptionKey: description }; } - return [self errorWithDomain:kMOPUBErrorDomain code:code userInfo:userInfo]; + return [self errorWithDomain:kNSErrorDomain code:code userInfo:userInfo]; } @end + +@implementation NSError (Initialization) + ++ (instancetype)sdkMinimumOsVersion:(int)osVersion { + return [NSError errorWithCode:MOPUBErrorSDKNotInitialized localizedDescription:[NSString stringWithFormat:@"MoPub SDK requires iOS %d and up", osVersion]]; +} + ++ (instancetype)sdkInitializationInProgress { + return [NSError errorWithCode:MOPUBErrorSDKInitializationInProgress localizedDescription:@"Attempted to initialize the SDK while a prior SDK initialization is in progress."]; +} + +@end + +@implementation NSError (AdLifeCycle) + ++ (instancetype)adAlreadyLoading { + return [NSError errorWithCode:MOPUBErrorAdLoadAlreadyInProgress localizedDescription:@"An ad is already being loaded. Please wait for the previous load to finish."]; +} + ++ (instancetype)customEventClass:(Class)customEventClass doesNotInheritFrom:(Class)baseClass { + NSString * description = [NSString stringWithFormat:@"%@ is an invalid custom event class because it does not extend %@", NSStringFromClass(customEventClass), NSStringFromClass(baseClass)]; + return [NSError errorWithCode:MOPUBErrorInvalidCustomEventClass localizedDescription:description]; +} + ++ (instancetype)networkResponseIsNotHTTP { + return [NSError errorWithCode:MOPUBErrorUnexpectedNetworkResponse localizedDescription:@"Network response is not of type NSHTTPURLResponse"]; +} + ++ (instancetype)networkResponseContainedNoData { + return [NSError errorWithCode:MOPUBErrorNoNetworkData localizedDescription:@"No data found in the NSHTTPURLResponse"]; +} + ++ (instancetype)adLoadFailedBecauseSdkNotInitialized { + return [NSError errorWithCode:MOPUBErrorSDKNotInitialized localizedDescription:@"Ad prevented from loading. Error: Ad requested before initializing MoPub SDK. The MoPub SDK requires initializeSdkWithConfiguration:completion: to be called on MoPub.sharedInstance before attempting to load ads. Please update your integration."]; +} + ++ (instancetype)serializationOfJson:(NSDictionary *)json failedWithError:(NSError *)serializationError { + NSString * errorMessage = [NSString stringWithFormat:@"Failed to generate a JSON string from:\n%@\nReason: %@", json, serializationError.localizedDescription]; + return [NSError errorWithCode:MOPUBErrorJSONSerializationFailed localizedDescription:errorMessage]; +} + ++ (instancetype)adResponseFailedToParseWithError:(NSError *)serializationError { + NSString * errorMessage = [NSString stringWithFormat:@"Failed to parse ad response into JSON: %@", serializationError.localizedDescription]; + return [NSError errorWithCode:MOPUBErrorUnableToParseAdResponse localizedDescription:errorMessage]; +} + ++ (instancetype)adResponsesNotFound { + return [NSError errorWithCode:MOPUBErrorUnableToParseJSONAdResponse localizedDescription:@"No ad responses"]; +} + ++ (instancetype)fullscreenAdAlreadyOnScreen { + return [NSError errorWithCode:MOPUBErrorFullScreenAdAlreadyOnScreen localizedDescription:@"Cannot present a full screen ad that is already on-screen."]; +} + ++ (instancetype)frameWidthNotSetForFlexibleSize { + return [NSError errorWithCode:MOPUBErrorFrameWidthNotSetForFlexibleSize localizedDescription:@"Cannot determine a size for flexible width because the frame width is not set."]; +} + ++ (instancetype)frameHeightNotSetForFlexibleSize { + return [NSError errorWithCode:MOPUBErrorFrameHeightNotSetForFlexibleSize localizedDescription:@"Cannot determine a size for flexible height because the frame height is not set."]; +} + +@end + +@implementation NSError (Consent) + ++ (instancetype)consentDialogAlreadyShowing { + return [NSError errorWithCode:MOPUBErrorConsentDialogAlreadyShowing localizedDescription:@"Consent dialog is already being presented modally."]; +} + ++ (instancetype)noConsentDialogLoaded { + return [NSError errorWithCode:MOPUBErrorNoConsentDialogLoaded localizedDescription:@"Consent dialog has not been loaded."]; +} + +@end + +@implementation NSError (RateLimit) ++ (instancetype)tooManyRequests { + return [NSError errorWithCode:MOPUBErrorTooManyRequests localizedDescription:@"Could not perform ad request because too many requests have been sent to the server."]; +} +@end diff --git a/MoPubSDK/Internal/Utility/MPGeolocationProvider.h b/MoPubSDK/Internal/Utility/MPGeolocationProvider.h index 8e5ffeaff..f0f6cf505 100644 --- a/MoPubSDK/Internal/Utility/MPGeolocationProvider.h +++ b/MoPubSDK/Internal/Utility/MPGeolocationProvider.h @@ -1,7 +1,7 @@ // // MPGeolocationProvider.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/Internal/Utility/MPGeolocationProvider.m b/MoPubSDK/Internal/Utility/MPGeolocationProvider.m index 197f49c5b..0690057ef 100644 --- a/MoPubSDK/Internal/Utility/MPGeolocationProvider.m +++ b/MoPubSDK/Internal/Utility/MPGeolocationProvider.m @@ -1,7 +1,7 @@ // // MPGeolocationProvider.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -180,7 +180,10 @@ - (void)startRecurringLocationUpdates [self.locationManager startUpdatingLocation]; [self.locationUpdateDurationTimer invalidate]; - self.locationUpdateDurationTimer = [[MPCoreInstanceProvider sharedProvider] buildMPTimerWithTimeInterval:kMPLocationUpdateDuration target:self selector:@selector(currentLocationUpdateDidFinish) repeats:NO]; + self.locationUpdateDurationTimer = [MPTimer timerWithTimeInterval:kMPLocationUpdateDuration + target:self + selector:@selector(currentLocationUpdateDidFinish) + repeats:NO]; [self.locationUpdateDurationTimer scheduleNow]; } @@ -197,7 +200,10 @@ - (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 = [MPTimer timerWithTimeInterval:delay + target:self + selector:@selector(startRecurringLocationUpdates) + repeats:NO]; [self.nextLocationUpdateTimer scheduleNow]; } diff --git a/MoPubSDK/Internal/Utility/MPGlobal.h b/MoPubSDK/Internal/Utility/MPGlobal.h index 4514a41c0..709d950b0 100644 --- a/MoPubSDK/Internal/Utility/MPGlobal.h +++ b/MoPubSDK/Internal/Utility/MPGlobal.h @@ -1,7 +1,7 @@ // // MPGlobal.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -16,7 +16,7 @@ UIInterfaceOrientation MPInterfaceOrientation(void); UIWindow *MPKeyWindow(void); CGFloat MPStatusBarHeight(void); -CGRect MPApplicationFrame(void); +CGRect MPApplicationFrame(BOOL includeSafeAreaInsets); CGRect MPScreenBounds(void); CGSize MPScreenResolution(void); CGFloat MPDeviceScaleFactor(void); @@ -26,28 +26,6 @@ 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 //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -63,6 +41,8 @@ typedef NS_ENUM(NSUInteger, MPInterstitialOrientationType) { MPInterstitialOrientationTypeAll, }; +UIInterfaceOrientationMask MPInterstitialOrientationTypeToUIInterfaceOrientationMask(MPInterstitialOrientationType type); + //////////////////////////////////////////////////////////////////////////////////////////////////// @interface UIDevice (MPAdditions) @@ -75,8 +55,6 @@ typedef NS_ENUM(NSUInteger, MPInterstitialOrientationType) { @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; @@ -101,18 +79,3 @@ typedef NS_ENUM(NSUInteger, MPInterstitialOrientationType) { - (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/MoPubSDK/Internal/Utility/MPGlobal.m b/MoPubSDK/Internal/Utility/MPGlobal.m index 7b185bec1..5be46ae8b 100644 --- a/MoPubSDK/Internal/Utility/MPGlobal.m +++ b/MoPubSDK/Internal/Utility/MPGlobal.m @@ -1,7 +1,7 @@ // // MPGlobal.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -40,9 +40,25 @@ CGFloat MPStatusBarHeight() { return (width < height) ? width : height; } -CGRect MPApplicationFrame() +CGRect MPApplicationFrame(BOOL includeSafeAreaInsets) { - CGRect frame = MPScreenBounds(); + // Starting with iOS8, the orientation of the device is taken into account when + // requesting the key window's bounds. We are making the assumption that the + // key window is equivalent to the application frame. + CGRect frame = [UIApplication sharedApplication].keyWindow.frame; + + if (@available(iOS 11, *)) { + if (includeSafeAreaInsets) { + // Safe area insets include the status bar offset. + UIEdgeInsets safeInsets = UIApplication.sharedApplication.keyWindow.safeAreaInsets; + frame.origin.x = safeInsets.left; + frame.size.width -= (safeInsets.left + safeInsets.right); + frame.origin.y = safeInsets.top; + frame.size.height -= (safeInsets.top + safeInsets.bottom); + + return frame; + } + } frame.origin.y += MPStatusBarHeight(); frame.size.height -= MPStatusBarHeight(); @@ -52,23 +68,9 @@ CGRect MPApplicationFrame() 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; + // Starting with iOS8, the orientation of the device is taken into account when + // requesting the key window's bounds. + return [UIScreen mainScreen].bounds; } CGSize MPScreenResolution() @@ -96,8 +98,7 @@ CGFloat MPDeviceScaleFactor() NSArray *keyVal = [element componentsSeparatedByString:@"="]; NSString *key = [keyVal objectAtIndex:0]; NSString *value = [keyVal lastObject]; - [queryDict setObject:[value stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding] - forKey:key]; + [queryDict setObject:[value stringByRemovingPercentEncoding] forKey:key]; } return queryDict; } @@ -189,7 +190,7 @@ BOOL MPViewIntersectsParentWindowWithPercent(UIView *view, CGFloat percentVisibl if ([[NSBundle mainBundle] pathForResource:@"MoPub" ofType:@"bundle"] != nil) { return [@"MoPub.bundle" stringByAppendingPathComponent:resourceName]; } - else if ([[UIDevice currentDevice].systemVersion compare:@"8.0" options:NSNumericSearch] != NSOrderedAscending) { + else { // 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. @@ -197,14 +198,6 @@ BOOL MPViewIntersectsParentWindowWithPercent(UIView *view, CGFloat percentVisibl 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) @@ -223,6 +216,15 @@ BOOL MPViewIntersectsParentWindowWithPercent(UIView *view, CGFloat percentVisibl return urls; } +UIInterfaceOrientationMask MPInterstitialOrientationTypeToUIInterfaceOrientationMask(MPInterstitialOrientationType type) +{ + switch (type) { + case MPInterstitialOrientationTypePortrait: return UIInterfaceOrientationMaskPortrait; + case MPInterstitialOrientationTypeLandscape: return UIInterfaceOrientationMaskLandscape; + default: return UIInterfaceOrientationMaskAll; + } +} + //////////////////////////////////////////////////////////////////////////////////////////////////// @implementation UIDevice (MPAdditions) @@ -242,15 +244,6 @@ - (NSString *)mp_hardwareDeviceName @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"]; @@ -309,78 +302,3 @@ - (BOOL)mp_doesOrientation:(UIInterfaceOrientation)orientation matchOrientationM } @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/MoPubSDK/Internal/Utility/MPIdentityProvider.h b/MoPubSDK/Internal/Utility/MPIdentityProvider.h index d3e73648b..263cb58ac 100644 --- a/MoPubSDK/Internal/Utility/MPIdentityProvider.h +++ b/MoPubSDK/Internal/Utility/MPIdentityProvider.h @@ -1,7 +1,7 @@ // // MPIdentityProvider.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -25,6 +25,11 @@ */ + (NSString *)obfuscatedIdentifier; +/** + * Return the unobfuscated MoPub UUID, without the "mopub:" prefix. + */ ++ (NSString *)unobfuscatedMoPubIdentifier; + + (BOOL)advertisingTrackingEnabled; /** diff --git a/MoPubSDK/Internal/Utility/MPIdentityProvider.m b/MoPubSDK/Internal/Utility/MPIdentityProvider.m index b94fab45c..e420ea1ea 100644 --- a/MoPubSDK/Internal/Utility/MPIdentityProvider.m +++ b/MoPubSDK/Internal/Utility/MPIdentityProvider.m @@ -1,7 +1,7 @@ // // MPIdentityProvider.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -15,10 +15,12 @@ #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" +NSString *const mopubPrefix = @"mopub:"; static BOOL gFrequencyCappingIdUsageEnabled = YES; @interface MPIdentityProvider () +@property (class, nonatomic, readonly) NSCalendar * iso8601Calendar; + (NSString *)mopubIdentifier:(BOOL)obfuscate; @@ -26,6 +28,17 @@ + (NSString *)mopubIdentifier:(BOOL)obfuscate; @implementation MPIdentityProvider ++ (NSCalendar *)iso8601Calendar { + static dispatch_once_t onceToken; + static NSCalendar * _iso8601Calendar; + dispatch_once(&onceToken, ^{ + _iso8601Calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierISO8601]; + _iso8601Calendar.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0]; + }); + + return _iso8601Calendar; +} + + (NSString *)identifier { return [self _identifier:NO]; @@ -36,6 +49,14 @@ + (NSString *)obfuscatedIdentifier return [self _identifier:YES]; } ++ (NSString *)unobfuscatedMoPubIdentifier { + NSString *value = [self mopubIdentifier:NO]; + if ([value hasPrefix:mopubPrefix]) { + value = [value substringFromIndex:[mopubPrefix length]]; + } + return value; +} + + (NSString *)_identifier:(BOOL)obfuscate { if (MPIdentityProvider.advertisingTrackingEnabled && [MPConsentManager sharedManager].canCollectPersonalInfo) { @@ -73,26 +94,30 @@ + (NSString *)mopubIdentifier:(BOOL)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]; + // Compare the current timestamp to the timestamp of the last MoPub identifier generation. + NSDate * now = [NSDate date]; + NSDate * lastSetDate = [[NSUserDefaults standardUserDefaults] objectForKey:MOPUB_IDENTIFIER_LAST_SET_TIME_KEY]; + + // MoPub identifier has not been set before. Set the timestamp and let the identifer + // be generated. + if (lastSetDate == nil) { + [[NSUserDefaults standardUserDefaults] setObject:now 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]; - } + } + // Current day does not match the same day when the identifier was generated. + // Invalidate the current identifier so it can be regenerated. + else if (![MPIdentityProvider.iso8601Calendar isDate:now inSameDayAsDate:lastSetDate]) { + [[NSUserDefaults standardUserDefaults] setObject:now 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) { + NSString * identifier = [[NSUserDefaults standardUserDefaults] objectForKey:MOPUB_IDENTIFIER_DEFAULTS_KEY]; + if (identifier == nil) { CFUUIDRef uuidObject = CFUUIDCreate(kCFAllocatorDefault); NSString *uuidStr = (NSString *)CFBridgingRelease(CFUUIDCreateString(kCFAllocatorDefault, uuidObject)); CFRelease(uuidObject); - identifier = [NSString stringWithFormat:@"mopub:%@", [uuidStr uppercaseString]]; + identifier = [mopubPrefix stringByAppendingString:[uuidStr uppercaseString]]; [[NSUserDefaults standardUserDefaults] setObject:identifier forKey:MOPUB_IDENTIFIER_DEFAULTS_KEY]; [[NSUserDefaults standardUserDefaults] synchronize]; } diff --git a/MoPubSDK/Internal/Utility/MPInternalUtils.h b/MoPubSDK/Internal/Utility/MPInternalUtils.h deleted file mode 100644 index 9520286f1..000000000 --- a/MoPubSDK/Internal/Utility/MPInternalUtils.h +++ /dev/null @@ -1,26 +0,0 @@ -// -// MPInternalUtils.h -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#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/MoPubSDK/Internal/Utility/MPInternalUtils.m b/MoPubSDK/Internal/Utility/MPInternalUtils.m deleted file mode 100644 index be87981ee..000000000 --- a/MoPubSDK/Internal/Utility/MPInternalUtils.m +++ /dev/null @@ -1,33 +0,0 @@ -// -// MPInternalUtils.m -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#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/MoPubSDK/Internal/Utility/MPLogProvider.h b/MoPubSDK/Internal/Utility/MPLogProvider.h deleted file mode 100644 index d9caa2cf7..000000000 --- a/MoPubSDK/Internal/Utility/MPLogProvider.h +++ /dev/null @@ -1,28 +0,0 @@ -// -// MPLogProvider.h -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#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/MoPubSDK/Internal/Utility/MPLogProvider.m b/MoPubSDK/Internal/Utility/MPLogProvider.m deleted file mode 100644 index 58a063f20..000000000 --- a/MoPubSDK/Internal/Utility/MPLogProvider.m +++ /dev/null @@ -1,84 +0,0 @@ -// -// MPLogProvider.m -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#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/MoPubSDK/Internal/Utility/MPLogging.h b/MoPubSDK/Internal/Utility/MPLogging.h deleted file mode 100644 index 4d3cd5bd1..000000000 --- a/MoPubSDK/Internal/Utility/MPLogging.h +++ /dev/null @@ -1,43 +0,0 @@ -// -// MPLogging.h -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import -#import "MPConstants.h" -#import "MPLogLevel.h" - -extern NSString * const kMPClearErrorLogFormatWithAdUnitID; -extern NSString * const kMPWarmingUpErrorLogFormatWithAdUnitID; - -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/MoPubSDK/Internal/Utility/MPLogging.m b/MoPubSDK/Internal/Utility/MPLogging.m deleted file mode 100644 index 0e83b191c..000000000 --- a/MoPubSDK/Internal/Utility/MPLogging.m +++ /dev/null @@ -1,102 +0,0 @@ -// -// MPLogging.m -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#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/MoPubSDK/Internal/Utility/MPSessionTracker.h b/MoPubSDK/Internal/Utility/MPSessionTracker.h index c1da4db03..c813a45b9 100644 --- a/MoPubSDK/Internal/Utility/MPSessionTracker.h +++ b/MoPubSDK/Internal/Utility/MPSessionTracker.h @@ -1,7 +1,7 @@ // // MPSessionTracker.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/Internal/Utility/MPSessionTracker.m b/MoPubSDK/Internal/Utility/MPSessionTracker.m index 9f6bb737f..f8b79c97c 100644 --- a/MoPubSDK/Internal/Utility/MPSessionTracker.m +++ b/MoPubSDK/Internal/Utility/MPSessionTracker.m @@ -1,7 +1,7 @@ // // MPSessionTracker.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/Internal/Utility/MPStoreKitProvider.h b/MoPubSDK/Internal/Utility/MPStoreKitProvider.h deleted file mode 100644 index 84e45e7c5..000000000 --- a/MoPubSDK/Internal/Utility/MPStoreKitProvider.h +++ /dev/null @@ -1,23 +0,0 @@ -// -// MPStoreKitProvider.h -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import -#import "MPGlobal.h" -#import - -@class SKStoreProductViewController; - -@interface MPStoreKitProvider : NSObject - -+ (BOOL)deviceHasStoreKit; -+ (SKStoreProductViewController *)buildController; - -@end - -@protocol MPSKStoreProductViewControllerDelegate -@end diff --git a/MoPubSDK/Internal/Utility/MPStoreKitProvider.m b/MoPubSDK/Internal/Utility/MPStoreKitProvider.m deleted file mode 100644 index 5b6541dd0..000000000 --- a/MoPubSDK/Internal/Utility/MPStoreKitProvider.m +++ /dev/null @@ -1,51 +0,0 @@ -// -// MPStoreKitProvider.m -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#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/MoPubSDK/Internal/Utility/MPTimer.h b/MoPubSDK/Internal/Utility/MPTimer.h index 6bfba7c5a..7fc00d240 100644 --- a/MoPubSDK/Internal/Utility/MPTimer.h +++ b/MoPubSDK/Internal/Utility/MPTimer.h @@ -1,19 +1,30 @@ // // MPTimer.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // #import -/* - * MPTimer wraps an NSTimer and adds pause/resume functionality. +NS_ASSUME_NONNULL_BEGIN + +/** + * @c MPTimer is a thread safe @c NSTimer wrapper, with pause / resume functionality. */ @interface MPTimer : NSObject -@property (nonatomic, copy) NSString *runLoopMode; +/** + * Return NO is the timer is paused, and return YES otherwise. + */ +@property (nonatomic, readonly) BOOL isCountdownActive; + ++ (MPTimer *)timerWithTimeInterval:(NSTimeInterval)seconds + target:(id)target + selector:(SEL)aSelector + repeats:(BOOL)repeats + runLoopMode:(NSString *)runLoopMode; + (MPTimer *)timerWithTimeInterval:(NSTimeInterval)seconds target:(id)target @@ -22,10 +33,10 @@ - (BOOL)isValid; - (void)invalidate; -- (BOOL)isScheduled; -- (BOOL)scheduleNow; -- (BOOL)pause; -- (BOOL)resume; -- (NSTimeInterval)initialTimeInterval; +- (void)scheduleNow; +- (void)pause; +- (void)resume; @end + +NS_ASSUME_NONNULL_END diff --git a/MoPubSDK/Internal/Utility/MPTimer.m b/MoPubSDK/Internal/Utility/MPTimer.m index cf9b2ecc8..f2d121248 100644 --- a/MoPubSDK/Internal/Utility/MPTimer.m +++ b/MoPubSDK/Internal/Utility/MPTimer.m @@ -1,23 +1,21 @@ // // MPTimer.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // +#import // for `objc_msgSend` #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, assign) BOOL isRepeatingTimer; +@property (nonatomic, assign) BOOL isCountdownActive; @property (nonatomic, weak) id target; @property (nonatomic, assign) SEL selector; @@ -26,31 +24,50 @@ @interface MPTimer () @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 + runLoopMode:(NSString *)runLoopMode { 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.isCountdownActive = NO; + timer.isRepeatingTimer = repeats; timer.timeInterval = seconds; - timer.runLoopMode = NSDefaultRunLoopMode; + + // Use the main thread run loop to keep the timer alive. + // Note: `NSRunLoop` is not thread safe, so we have to access it from main thread only. + void (^mainThreadOperation)(void) = ^void(void) { + timer.timer = [NSTimer timerWithTimeInterval:seconds + target:timer + selector:@selector(timerDidFire) + userInfo:nil + repeats:repeats]; + [timer.timer setFireDate:[NSDate distantFuture]]; // do not fire until `scheduleNow` is called + [[NSRunLoop mainRunLoop] addTimer:timer.timer forMode:runLoopMode]; + }; + if ([NSThread isMainThread]) { + mainThreadOperation(); + } else { + dispatch_sync(dispatch_get_main_queue(), mainThreadOperation); + } + return timer; } ++ (MPTimer *)timerWithTimeInterval:(NSTimeInterval)seconds + target:(id)target + selector:(SEL)aSelector + repeats:(BOOL)repeats { + return [self timerWithTimeInterval:seconds + target:target + selector:aSelector + repeats:repeats + runLoopMode:NSDefaultRunLoopMode]; +} + - (void)dealloc { [self.timer invalidate]; @@ -58,9 +75,21 @@ - (void)dealloc - (void)timerDidFire { - SUPPRESS_PERFORM_SELECTOR_LEAK_WARNING( - [self.target performSelector:self.selector withObject:nil] - ); + @synchronized (self) { + if (!self.isRepeatingTimer) { + self.isCountdownActive = NO; // this is the last firing + } + + if (self.selector == nil) { + MPLogDebug(@"%s `selector` is unexpectedly nil. Return early to avoid crash.", __FUNCTION__); + return; + } + + // use `objc_msgSend` to avoid the potential memory leak issue of `performSelector:` + typedef void (*Message)(id, SEL, id); + Message func = (Message)objc_msgSend; + func(self.target, self.selector, self); + } } - (BOOL)isValid @@ -70,108 +99,73 @@ - (BOOL)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; + @synchronized (self) { + self.target = nil; + self.selector = nil; + [self.timer invalidate]; + self.timer = nil; + self.isCountdownActive = NO; } - - [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:self.runLoopMode]; - return YES; } -- (BOOL)pause +- (void)scheduleNow { - 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; - } + /* + Note: `MPLog` statements are commented out because during SDK init, the chain of calls + `MPConsentManager.sharedManager` -> `newNextUpdateTimer` -> `MPTimer.scheduleNow` -> + `MPLogDebug` -> `MPIdentityProvider.identifier` -> `MPConsentManager.sharedManager` will cause + a crash with EXC_BAD_INSTRUCTION: the same `dispatch_once` is called twice for + `MPConsentManager.sharedManager` in the same call stack. Uncomment the logs after + `MPIdentityProvider` is refactored. + */ + @synchronized (self) { + if (![self.timer isValid]) { +// MPLogDebug(@"Could not schedule invalidated MPTimer (%p).", self); + return; + } - if (![self isScheduled]) { - MPLogDebug(@"No-op: tried to pause an MPTimer (%p) that was never scheduled.", self); - return NO; - } + if (self.isCountdownActive) { +// MPLogDebug(@"Tried to schedule an MPTimer (%p) that is already ticking.",self); + return; + } - 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); + NSDate *newFireDate = [NSDate dateWithTimeInterval:self.timeInterval sinceDate:[NSDate date]]; + [self.timer setFireDate:newFireDate]; + self.isCountdownActive = YES; } - - // Pause the timer by setting its fire date far into the future. - [self.timer setFireDate:[NSDate distantFuture]]; - self.isPaused = YES; - - return YES; } -- (BOOL)resume +- (void)pause { - 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; - } + @synchronized (self) { + if (!self.isCountdownActive) { + MPLogDebug(@"No-op: tried to pause an MPTimer (%p) that was already paused.", self); + return; + } - MPLogDebug(@"Resumed MPTimer (%p), should fire in %.1f seconds.", self.timeInterval); + if (![self.timer isValid]) { + MPLogDebug(@"Cannot pause invalidated MPTimer (%p).", self); + return; + } - // Resume the timer. - NSDate *newFireDate = [NSDate dateWithTimeInterval:self.timeInterval sinceDate:[NSDate date]]; - [self.timer setFireDate:newFireDate]; + // `fireDate` is the date which the timer will fire. If the timer is no longer valid, `fireDate` + // is the last date at which the timer fired. + NSTimeInterval secondsLeft = [[self.timer fireDate] timeIntervalSinceDate:[NSDate date]]; + if (secondsLeft <= 0) { + MPLogInfo(@"An MPTimer was somehow paused after it was supposed to fire."); + } else { + MPLogDebug(@"Paused MPTimer (%p) %.1f seconds left before firing.", self, secondsLeft); + } - if (![self isScheduled]) { - [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:self.runLoopMode]; + // Pause the timer by setting its fire date far into the future. + [self.timer setFireDate:[NSDate distantFuture]]; + self.isCountdownActive = NO; } - - self.isPaused = NO; - return YES; } -- (NSTimeInterval)initialTimeInterval +- (void)resume { - return self.timeInterval; + [self scheduleNow]; } @end - diff --git a/MoPubSDK/Internal/Utility/MPUserInteractionGestureRecognizer.h b/MoPubSDK/Internal/Utility/MPUserInteractionGestureRecognizer.h index 8ff8c02b3..3e045dd13 100644 --- a/MoPubSDK/Internal/Utility/MPUserInteractionGestureRecognizer.h +++ b/MoPubSDK/Internal/Utility/MPUserInteractionGestureRecognizer.h @@ -1,7 +1,7 @@ // // MPUserInteractionGestureRecognizer.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/Internal/Utility/MPUserInteractionGestureRecognizer.m b/MoPubSDK/Internal/Utility/MPUserInteractionGestureRecognizer.m index 4f78fa75e..422d36423 100644 --- a/MoPubSDK/Internal/Utility/MPUserInteractionGestureRecognizer.m +++ b/MoPubSDK/Internal/Utility/MPUserInteractionGestureRecognizer.m @@ -1,7 +1,7 @@ // // MPUserInteractionGestureRecognizer.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/Internal/Utility/Protocols/MPMediaFileCache.h b/MoPubSDK/Internal/Utility/Protocols/MPMediaFileCache.h new file mode 100644 index 000000000..4a06a735b --- /dev/null +++ b/MoPubSDK/Internal/Utility/Protocols/MPMediaFileCache.h @@ -0,0 +1,52 @@ +// +// MPMediaFileCache.h +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#ifndef MPMediaFileCache_h +#define MPMediaFileCache_h + +#import +#import "MPVASTMediaFile.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + @c AVPlayer and related media player API requires the file extension (such as "mp4" and "3gpp") + being in the file name, otherwise the media file cannot be loaded. Problem is, the original design + of @c MPDiskLRUCache uses a SHA1 hash key for the file name of local cache file without the file + extension, and thus the cache file cannot be loaded into @c AVPlayer directly. This @c MPMediaFileCache + protocol is designed to solve this problem by preserving the original file extension in the cache + file. So, for @c AVPlayer relate media file access, use the API in this @c MediaFile category only. + */ +@protocol MPMediaFileCache + +/** + Determine whether a remote media file has been locally cached. + */ +- (BOOL)isRemoteFileCached:(NSURL *)remoteFileURL; + +/** + Move a file to the cache directory. + @param localFileURL The location of the file to move. Typically this source file is a temporary file + provided by the completion handler of a URL session download task. + @param remoteFileURL The original remote URL that the file was hosted. + */ +- (NSError *)moveLocalFileToCache:(NSURL *)localFileURL remoteSourceFileURL:(NSURL *)remoteFileURL; + +@optional + +/** + "Touch" (update with current date) @c NSFileModificationDate of the file for LRU tracking or other + purpose. @c NSFileModificationDate is updated because iOS doesn't provide "last opened date" access. + */ +- (void)touchCachedFileForRemoteFile:(NSURL *)remoteFileURL; + +@end + +NS_ASSUME_NONNULL_END + +#endif /* MPMediaFileCache_h */ diff --git a/MoPubSDK/Internal/VAST/MPVASTAd.h b/MoPubSDK/Internal/VAST/MPVASTAd.h index b4004928c..2b4c6be70 100644 --- a/MoPubSDK/Internal/VAST/MPVASTAd.h +++ b/MoPubSDK/Internal/VAST/MPVASTAd.h @@ -1,7 +1,7 @@ // // MPVASTAd.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/Internal/VAST/MPVASTAd.m b/MoPubSDK/Internal/VAST/MPVASTAd.m index d669ce2c2..41080d2a3 100644 --- a/MoPubSDK/Internal/VAST/MPVASTAd.m +++ b/MoPubSDK/Internal/VAST/MPVASTAd.m @@ -1,7 +1,7 @@ // // MPVASTAd.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -20,7 +20,7 @@ - (instancetype)initWithDictionary:(NSDictionary *)dictionary // 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 " + MPLogInfo(@"VAST element is not allowed to contain both an and a " @". The will be ignored."); _wrapper = nil; } diff --git a/MoPubSDK/Internal/VAST/MPVASTCompanionAd.h b/MoPubSDK/Internal/VAST/MPVASTCompanionAd.h index 08862746c..0ea3563ad 100644 --- a/MoPubSDK/Internal/VAST/MPVASTCompanionAd.h +++ b/MoPubSDK/Internal/VAST/MPVASTCompanionAd.h @@ -1,7 +1,7 @@ // // MPVASTCompanionAd.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -10,18 +10,47 @@ #import #import "MPVASTModel.h" +@class MPVASTResource; +@class MPVASTTrackingEvent; + @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, strong, readonly) NSString *identifier; // optional attribute @property (nonatomic, readonly) CGFloat width; +@property (nonatomic, readonly) CGFloat height; +@property (nonatomic, readonly) CGFloat assetHeight; // optional attribute +@property (nonatomic, readonly) CGFloat assetWidth; // optional attribute + +@property (nonatomic, strong, readonly) NSURL *clickThroughURL; +@property (nonatomic, strong, readonly) NSArray *clickTrackingURLs; + +/** Per VAST 3.0 spec 2.3.3.7 Tracking Details: + The element may contain one or more elements, but the only event + available for tracking under each Companion is the creativeView event. The creativeView event + tracks whether the Companion creative was viewed. This view does not count as an impression + because impressions are only counted for the Ad and the Companion is only one part of the Ad. + */ +@property (nonatomic, strong, readonly) NSArray *creativeViewTrackers; + +/** Per VAST 3.0 spec 2.3.3.2 Companion Resource Elements: + Companion resource types are described below: + • StaticResource: Describes non-html creative where an attribute for creativeType is used to + identify the creative resource platform. The video player uses the creativeType information to + determine how to display the resource: + o Image/gif,image/jpeg,image/png:displayedusingtheHTMLtagandthe resource URI as the src attribute. + o Application/x-javascript:displayedusingtheHTMLtag - - - - - - -
-
- -
-
- - diff --git a/MoPubSDK/Resources/MRAID.bundle/mraid.js b/MoPubSDK/Resources/MRAID.bundle/mraid.js index 5b0d84b9b..b2a7a4a54 100644 --- a/MoPubSDK/Resources/MRAID.bundle/mraid.js +++ b/MoPubSDK/Resources/MRAID.bundle/mraid.js @@ -913,4 +913,4 @@ If you wish to modify mraid.js, modify the version located at mopub-sdk-common/m } } }; -}()); \ No newline at end of file +}()); diff --git a/MoPubSDK/RewardedVideo/Internal/MPMoPubRewardedPlayableCustomEvent.h b/MoPubSDK/RewardedVideo/Internal/MPMoPubRewardedPlayableCustomEvent.h index bdc90dc51..430068c82 100644 --- a/MoPubSDK/RewardedVideo/Internal/MPMoPubRewardedPlayableCustomEvent.h +++ b/MoPubSDK/RewardedVideo/Internal/MPMoPubRewardedPlayableCustomEvent.h @@ -1,7 +1,7 @@ // // MPMoPubRewardedPlayableCustomEvent.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/RewardedVideo/Internal/MPMoPubRewardedPlayableCustomEvent.m b/MoPubSDK/RewardedVideo/Internal/MPMoPubRewardedPlayableCustomEvent.m index e35a29549..f43ae083a 100644 --- a/MoPubSDK/RewardedVideo/Internal/MPMoPubRewardedPlayableCustomEvent.m +++ b/MoPubSDK/RewardedVideo/Internal/MPMoPubRewardedPlayableCustomEvent.m @@ -1,7 +1,7 @@ // // MPMoPubRewardedPlayableCustomEvent.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -9,17 +9,24 @@ #import "MPMoPubRewardedPlayableCustomEvent.h" #import "MPMRAIDInterstitialViewController.h" #import "MPAdConfiguration.h" +#import "MPError.h" #import "MPLogging.h" #import "MPRewardedVideoError.h" #import "MPCountdownTimerView.h" +#import "UIView+MPAdditions.h" const NSTimeInterval kDefaultCountdownTimerIntervalInSeconds = 30; -@interface MPMoPubRewardedPlayableCustomEvent() +@interface MPMoPubRewardedPlayableCustomEvent() + @property (nonatomic, assign) BOOL adAvailable; @property (nonatomic, strong) MPMRAIDInterstitialViewController *interstitial; @property (nonatomic, strong) MPCountdownTimerView *timerView; @property (nonatomic, assign) BOOL userRewarded; + +@end + +@interface MPMoPubRewardedPlayableCustomEvent (MPInterstitialViewControllerDelegate) @end @implementation MPMoPubRewardedPlayableCustomEvent @@ -73,7 +80,9 @@ - (void)rewardUserWithConfiguration:(MPAdConfiguration *)configuration timerHasE @dynamic delegate; - (void)requestRewardedVideoWithCustomEventInfo:(NSDictionary *)info { - MPLogInfo(@"Loading MoPub rewarded playable"); + MPAdConfiguration * configuration = self.delegate.configuration; + MPLogAdEvent([MPLogEvent adLoadAttemptForAdapter:NSStringFromClass(configuration.customEventClass) dspCreativeId:configuration.dspCreativeId dspName:nil], self.adUnitId); + self.interstitial.delegate = self; [self.interstitial setCloseButtonStyle:MPInterstitialCloseButtonStyleAlwaysHidden]; @@ -93,56 +102,93 @@ - (void)handleCustomEventInvalidated { } - (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]; - - __weak __typeof__(self) 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]; + MPLogAdEvent([MPLogEvent adShowAttemptForAdapter:NSStringFromClass(self.class)], self.adUnitId); + + // Error handling block. + __typeof__(self) __weak weakSelf = self; + void (^onShowError)(NSError *) = ^(NSError * error) { + __typeof__(self) strongSelf = weakSelf; + if (strongSelf != nil) { + MPLogAdEvent([MPLogEvent adShowFailedForAdapter:NSStringFromClass(strongSelf.class) error:error], strongSelf.adUnitId); + + [strongSelf.delegate rewardedVideoDidFailToPlayForCustomEvent:strongSelf error:error]; + [strongSelf showCloseButton]; + } + }; + + // No ad available to show. + if (!self.hasAdAvailable) { + NSError * error = [NSError errorWithDomain:MoPubRewardedVideoAdsSDKDomain code:MPRewardedVideoAdErrorNoAdsAvailable userInfo:nil]; + onShowError(error); + return; } + + // Add the countdown timer to the interstitial and start the timer. + self.timerView = [[MPCountdownTimerView alloc] initWithDuration:self.countdownDuration timerCompletion:^(BOOL hasElapsed) { + __typeof__(self) strongSelf = weakSelf; + if (strongSelf != nil) { + [strongSelf rewardUserWithConfiguration:strongSelf.delegate.configuration timerHasElapsed:hasElapsed]; + [strongSelf showCloseButton]; + } + }]; + [self.interstitial.view addSubview:self.timerView]; + + NSArray *constraints = @[[self.timerView.topAnchor constraintEqualToAnchor:self.interstitial.view.mp_safeTopAnchor], + [self.timerView.rightAnchor constraintEqualToAnchor:self.interstitial.view.mp_safeRightAnchor]]; + [NSLayoutConstraint activateConstraints:constraints]; + self.timerView.translatesAutoresizingMaskIntoConstraints = NO; + + [self.timerView start]; + + [self.interstitial presentInterstitialFromViewController:viewController complete:^(NSError * error) { + if (error != nil) { + onShowError(error); + } + else { + MPLogAdEvent([MPLogEvent adShowSuccessForAdapter:NSStringFromClass(self.class)], self.adUnitId); + } + }]; } +@end + #pragma mark - MPInterstitialViewControllerDelegate -- (void)interstitialDidLoadAd:(MPInterstitialViewController *)interstitial { - MPLogInfo(@"MoPub rewarded playable did load"); +@implementation MPMoPubRewardedPlayableCustomEvent (MPInterstitialViewControllerDelegate) + +- (NSString *)adUnitId { + return [self.delegate adUnitId]; +} + +- (void)interstitialDidLoadAd:(id)interstitial { + MPLogAdEvent([MPLogEvent adLoadSuccessForAdapter:NSStringFromClass(self.class)], self.adUnitId); + self.adAvailable = YES; [self.delegate rewardedVideoDidLoadAdForCustomEvent:self]; } -- (void)interstitialDidAppear:(MPInterstitialViewController *)interstitial { - MPLogInfo(@"MoPub rewarded playable did appear"); +- (void)interstitialDidAppear:(id)interstitial { [self.delegate rewardedVideoDidAppearForCustomEvent:self]; } -- (void)interstitialWillAppear:(MPInterstitialViewController *)interstitial { - MPLogInfo(@"MoPub rewarded playable will appear"); +- (void)interstitialWillAppear:(id)interstitial { [self.delegate rewardedVideoWillAppearForCustomEvent:self]; } -- (void)interstitialDidFailToLoadAd:(MPInterstitialViewController *)interstitial { - MPLogInfo(@"MoPub rewarded playable failed to load"); +- (void)interstitialDidFailToLoadAd:(id)interstitial { + NSString * message = [NSString stringWithFormat:@"Failed to load creative:\n%@", self.delegate.configuration.adResponseHTMLString]; + NSError * error = [NSError errorWithCode:MOPUBErrorAdapterFailedToLoadAd localizedDescription:message]; + MPLogAdEvent([MPLogEvent adLoadFailedForAdapter:NSStringFromClass(self.class) error:error], self.adUnitId); + self.adAvailable = NO; [self.delegate rewardedVideoDidFailToLoadAdForCustomEvent:self error:nil]; } -- (void)interstitialWillDisappear:(MPInterstitialViewController *)interstitial { +- (void)interstitialWillDisappear:(id)interstitial { [self.delegate rewardedVideoWillDisappearForCustomEvent:self]; } -- (void)interstitialDidDisappear:(MPInterstitialViewController *)interstitial { +- (void)interstitialDidDisappear:(id)interstitial { self.adAvailable = NO; [self.timerView stopAndSignalCompletion:NO]; [self.delegate rewardedVideoDidDisappearForCustomEvent:self]; @@ -151,23 +197,13 @@ - (void)interstitialDidDisappear:(MPInterstitialViewController *)interstitial { self.interstitial = nil; } -- (void)interstitialDidReceiveTapEvent:(MPInterstitialViewController *)interstitial { - [self rewardUserWithConfiguration:self.configuration timerHasElapsed:NO]; +- (void)interstitialDidReceiveTapEvent:(id)interstitial { + [self rewardUserWithConfiguration:self.delegate.configuration timerHasElapsed:NO]; [self.delegate rewardedVideoDidReceiveTapEventForCustomEvent:self]; } -- (void)interstitialWillLeaveApplication:(MPInterstitialViewController *)interstitial { +- (void)interstitialWillLeaveApplication:(id)interstitial { [self.delegate rewardedVideoWillLeaveApplicationForCustomEvent:self]; } -#pragma mark - MPPrivateRewardedVideoCustomEventDelegate - -- (NSString *)adUnitId { - return [self.delegate adUnitId]; -} - -- (MPAdConfiguration *)configuration { - return [self.delegate configuration]; -} - @end diff --git a/MoPubSDK/RewardedVideo/Internal/MPMoPubRewardedVideoCustomEvent.h b/MoPubSDK/RewardedVideo/Internal/MPMoPubRewardedVideoCustomEvent.h index 469932adb..2041f5400 100644 --- a/MoPubSDK/RewardedVideo/Internal/MPMoPubRewardedVideoCustomEvent.h +++ b/MoPubSDK/RewardedVideo/Internal/MPMoPubRewardedVideoCustomEvent.h @@ -1,7 +1,7 @@ // // MPMoPubRewardedVideoCustomEvent.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/RewardedVideo/Internal/MPMoPubRewardedVideoCustomEvent.m b/MoPubSDK/RewardedVideo/Internal/MPMoPubRewardedVideoCustomEvent.m index f7841397f..89f1c6029 100644 --- a/MoPubSDK/RewardedVideo/Internal/MPMoPubRewardedVideoCustomEvent.m +++ b/MoPubSDK/RewardedVideo/Internal/MPMoPubRewardedVideoCustomEvent.m @@ -1,13 +1,14 @@ // // MPMoPubRewardedVideoCustomEvent.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // #import "MPMoPubRewardedVideoCustomEvent.h" #import "MPMRAIDInterstitialViewController.h" +#import "MPError.h" #import "MPLogging.h" #import "MPRewardedVideoReward.h" #import "MPAdConfiguration.h" @@ -15,21 +16,26 @@ #import "MPRewardedVideoReward.h" #import "MPRewardedVideoError.h" -@interface MPMoPubRewardedVideoCustomEvent() +@interface MPMoPubRewardedVideoCustomEvent() @property (nonatomic) MPMRAIDInterstitialViewController *interstitial; @property (nonatomic) BOOL adAvailable; @end +@interface MPMoPubRewardedVideoCustomEvent (MPInterstitialViewControllerDelegate) +@end + @implementation MPMoPubRewardedVideoCustomEvent @dynamic delegate; - (void)requestRewardedVideoWithCustomEventInfo:(NSDictionary *)info { - MPLogInfo(@"Loading MoPub rewarded video"); - self.interstitial = [[MPMRAIDInterstitialViewController alloc] initWithAdConfiguration:[self.delegate configuration]]; + MPAdConfiguration * configuration = self.delegate.configuration; + MPLogAdEvent([MPLogEvent adLoadAttemptForAdapter:NSStringFromClass(configuration.customEventClass) dspCreativeId:configuration.dspCreativeId dspName:nil], self.adUnitId); + + self.interstitial = [[MPMRAIDInterstitialViewController alloc] initWithAdConfiguration:configuration]; self.interstitial.delegate = self; [self.interstitial setCloseButtonStyle:MPInterstitialCloseButtonStyleAlwaysHidden]; @@ -53,49 +59,81 @@ - (void)handleCustomEventInvalidated - (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]; + MPLogAdEvent([MPLogEvent adShowAttemptForAdapter:NSStringFromClass(self.class)], self.adUnitId); + + // Error handling block. + __typeof__(self) __weak weakSelf = self; + void (^onShowError)(NSError *) = ^(NSError * error) { + __typeof__(self) strongSelf = weakSelf; + if (strongSelf != nil) { + MPLogAdEvent([MPLogEvent adShowFailedForAdapter:NSStringFromClass(strongSelf.class) error:error], strongSelf.adUnitId); + + [strongSelf.delegate rewardedVideoDidFailToPlayForCustomEvent:strongSelf error:error]; + } + }; + + // No ad available to show. + if (!self.hasAdAvailable) { + NSError * error = [NSError errorWithDomain:MoPubRewardedVideoAdsSDKDomain code:MPRewardedVideoAdErrorNoAdsAvailable userInfo:nil]; + onShowError(error); + return; } + + [self.interstitial presentInterstitialFromViewController:viewController complete:^(NSError * error) { + if (error != nil) { + onShowError(error); + } + else { + MPLogAdEvent([MPLogEvent adShowSuccessForAdapter:NSStringFromClass(self.class)], self.adUnitId); + } + }]; } -#pragma mark - MPMRAIDInterstitialViewControllerDelegate +@end + +#pragma mark - MPInterstitialViewControllerDelegate + +@implementation MPMoPubRewardedVideoCustomEvent (MPInterstitialViewControllerDelegate) -- (void)interstitialDidLoadAd:(MPInterstitialViewController *)interstitial +- (NSString *)adUnitId { - MPLogInfo(@"MoPub rewarded video did load"); + return [self.delegate adUnitId]; +} + +- (void)interstitialDidLoadAd:(id)interstitial +{ + MPLogAdEvent([MPLogEvent adLoadSuccessForAdapter:NSStringFromClass(self.class)], self.adUnitId); + self.adAvailable = YES; [self.delegate rewardedVideoDidLoadAdForCustomEvent:self]; } -- (void)interstitialDidAppear:(MPInterstitialViewController *)interstitial +- (void)interstitialDidAppear:(id)interstitial { - MPLogInfo(@"MoPub rewarded video did appear"); [self.delegate rewardedVideoDidAppearForCustomEvent:self]; } -- (void)interstitialWillAppear:(MPInterstitialViewController *)interstitial +- (void)interstitialWillAppear:(id)interstitial { - MPLogInfo(@"MoPub rewarded video will appear"); [self.delegate rewardedVideoWillAppearForCustomEvent:self]; } -- (void)interstitialDidFailToLoadAd:(MPInterstitialViewController *)interstitial +- (void)interstitialDidFailToLoadAd:(id)interstitial { - MPLogInfo(@"MoPub rewarded video failed to load"); + NSString * message = [NSString stringWithFormat:@"Failed to load creative:\n%@", self.delegate.configuration.adResponseHTMLString]; + NSError * error = [NSError errorWithCode:MOPUBErrorAdapterFailedToLoadAd localizedDescription:message]; + MPLogAdEvent([MPLogEvent adLoadFailedForAdapter:NSStringFromClass(self.class) error:error], self.adUnitId); + self.adAvailable = NO; [self.delegate rewardedVideoDidFailToLoadAdForCustomEvent:self error:nil]; } -- (void)interstitialWillDisappear:(MPInterstitialViewController *)interstitial +- (void)interstitialWillDisappear:(id)interstitial { [self.delegate rewardedVideoWillDisappearForCustomEvent:self]; } -- (void)interstitialDidDisappear:(MPInterstitialViewController *)interstitial +- (void)interstitialDidDisappear:(id)interstitial { self.adAvailable = NO; [self.delegate rewardedVideoDidDisappearForCustomEvent:self]; @@ -104,12 +142,12 @@ - (void)interstitialDidDisappear:(MPInterstitialViewController *)interstitial self.interstitial = nil; } -- (void)interstitialDidReceiveTapEvent:(MPInterstitialViewController *)interstitial +- (void)interstitialDidReceiveTapEvent:(id)interstitial { [self.delegate rewardedVideoDidReceiveTapEventForCustomEvent:self]; } -- (void)interstitialWillLeaveApplication:(MPInterstitialViewController *)interstitial +- (void)interstitialWillLeaveApplication:(id)interstitial { [self.delegate rewardedVideoWillLeaveApplicationForCustomEvent:self]; } @@ -117,18 +155,7 @@ - (void)interstitialWillLeaveApplication:(MPInterstitialViewController *)interst - (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]; + [self.delegate rewardedVideoShouldRewardUserForCustomEvent:self reward:self.delegate.configuration.selectedReward]; } @end diff --git a/MoPubSDK/RewardedVideo/Internal/MPPrivateRewardedVideoCustomEventDelegate.h b/MoPubSDK/RewardedVideo/Internal/MPPrivateRewardedVideoCustomEventDelegate.h index cb2a9253e..3833f7a26 100644 --- a/MoPubSDK/RewardedVideo/Internal/MPPrivateRewardedVideoCustomEventDelegate.h +++ b/MoPubSDK/RewardedVideo/Internal/MPPrivateRewardedVideoCustomEventDelegate.h @@ -1,7 +1,7 @@ // // MPPrivateRewardedVideoCustomEventDelegate.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/RewardedVideo/Internal/MPRewardedVideo+Internal.h b/MoPubSDK/RewardedVideo/Internal/MPRewardedVideo+Internal.h index c38ae4515..14fe73571 100644 --- a/MoPubSDK/RewardedVideo/Internal/MPRewardedVideo+Internal.h +++ b/MoPubSDK/RewardedVideo/Internal/MPRewardedVideo+Internal.h @@ -1,7 +1,7 @@ // // MPRewardedVideo+Internal.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdManager.h b/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdManager.h index ca6097c3d..1f24e7e62 100644 --- a/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdManager.h +++ b/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdManager.h @@ -1,13 +1,14 @@ // // MPRewardedVideoAdManager.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // #import #import "MPAdTargeting.h" +#import "MPImpressionData.h" @class MPRewardedVideoReward; @protocol MPRewardedVideoAdManagerDelegate; @@ -22,6 +23,8 @@ @property (nonatomic, readonly) NSString *adUnitID; @property (nonatomic, strong) NSArray *mediationSettings; @property (nonatomic, copy) NSString *customerId; +@property (nonatomic, readonly) NSString *dspCreativeId; +@property (nonatomic, readonly) NSString *lineItemId; @property (nonatomic, strong) MPAdTargeting *targeting; /** @@ -75,10 +78,15 @@ */ - (void)handleAdPlayedForCustomEventNetwork; +- (NSString *) getCreativeId; + @end @protocol MPRewardedVideoAdManagerDelegate +- (void)rewardedVideoWillStartAttemptForAdManager:(MPRewardedVideoAdManager *)manager; +- (void)rewardedVideoDidSucceedAttemptForAdManager:(MPRewardedVideoAdManager *)manager; +- (void)rewardedVideoDidFailAttemptForAdManager:(MPRewardedVideoAdManager *)manager error:(NSError*)error; - (void)rewardedVideoDidLoadForAdManager:(MPRewardedVideoAdManager *)manager; - (void)rewardedVideoDidFailToLoadForAdManager:(MPRewardedVideoAdManager *)manager error:(NSError *)error; - (void)rewardedVideoDidExpireForAdManager:(MPRewardedVideoAdManager *)manager; @@ -88,6 +96,7 @@ - (void)rewardedVideoWillDisappearForAdManager:(MPRewardedVideoAdManager *)manager; - (void)rewardedVideoDidDisappearForAdManager:(MPRewardedVideoAdManager *)manager; - (void)rewardedVideoDidReceiveTapEventForAdManager:(MPRewardedVideoAdManager *)manager; +- (void)rewardedVideoAdManager:(MPRewardedVideoAdManager *)manager didReceiveImpressionEventWithImpressionData:(MPImpressionData *)impressionData; - (void)rewardedVideoWillLeaveApplicationForAdManager:(MPRewardedVideoAdManager *)manager; - (void)rewardedVideoShouldRewardUserForAdManager:(MPRewardedVideoAdManager *)manager reward:(MPRewardedVideoReward *)reward; diff --git a/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdManager.m b/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdManager.m index 1d2b9cfe5..b17f31e5b 100644 --- a/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdManager.m +++ b/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdManager.m @@ -1,7 +1,7 @@ // // MPRewardedVideoAdManager.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -25,6 +25,7 @@ @interface MPRewardedVideoAdManager () *remainingConfigurations; +@property (nonatomic, strong) NSURL *mostRecentlyLoadedURL; // ADF-4286: avoid infinite ad reloads @property (nonatomic, assign) BOOL loading; @property (nonatomic, assign) BOOL playedAd; @property (nonatomic, assign) BOOL ready; @@ -65,6 +66,15 @@ - (Class)customEventClass return self.configuration.customEventClass; } +- (NSString*)dspCreativeId +{ + return self.configuration.dspCreativeId; +} + +- (NSString*)lineItemId { + return self.configuration.lineItemId; +} + - (BOOL)hasAdAvailable { //An Ad is not ready or has expired. @@ -81,6 +91,8 @@ - (BOOL)hasAdAvailable - (void)loadRewardedVideoAdWithCustomerId:(NSString *)customerId targeting:(MPAdTargeting *)targeting { + MPLogAdEvent(MPLogEvent.adLoadAttempt, self.adUnitID); + // 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. @@ -93,15 +105,14 @@ - (void)loadRewardedVideoAdWithCustomerId:(NSString *)customerId targeting:(MPAd // 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.targeting = targeting; - [self loadAdWithURL:[MPAdServerURLBuilder URLWithAdUnitID:self.adUnitID - keywords:targeting.keywords - userDataKeywords:targeting.userDataKeywords - location:targeting.location]]; + [self loadAdWithURL:[MPAdServerURLBuilder URLWithAdUnitID:self.adUnitID targeting:targeting]]; } } - (void)presentRewardedVideoAdFromViewController:(UIViewController *)viewController withReward:(MPRewardedVideoReward *)reward customData:(NSString *)customData { + MPLogAdEvent(MPLogEvent.adShowAttempt, self.adUnitID); + // Don't allow the ad to be shown if it isn't ready. if (!self.ready) { NSError *error = [NSError errorWithDomain:MoPubRewardedVideoAdsSDKDomain code:MPRewardedVideoAdErrorNoAdReady userInfo:@{ NSLocalizedDescriptionKey: @"Rewarded video ad view is not ready to be shown"}]; @@ -166,12 +177,12 @@ - (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."); + MPLogEvent([MPLogEvent error:NSError.adAlreadyLoading message:nil]); return; } self.loading = YES; + self.mostRecentlyLoadedURL = URL; [self.communicator loadURL:URL]; } @@ -209,6 +220,7 @@ - (void)fetchAdWithConfiguration:(MPAdConfiguration *)configuration { } self.adapter = adapter; + [self.delegate rewardedVideoWillStartAttemptForAdManager:self]; [self.adapter getAdWithConfiguration:configuration targeting:self.targeting]; } @@ -239,6 +251,14 @@ - (void)communicatorDidFailWithError:(NSError *)error [self.delegate rewardedVideoDidFailToLoadForAdManager:self error:error]; } +- (MPAdType)adTypeForAdServerCommunicator:(MPAdServerCommunicator *)adServerCommunicator { + return MPAdTypeFullscreen; +} + +- (NSString *)adUnitIDForAdServerCommunicator:(MPAdServerCommunicator *)adServerCommunicator { + return self.adUnitID; +} + #pragma mark - MPRewardedVideoAdapterDelegate - (id)instanceMediationSettingsForClass:(Class)aClass @@ -254,6 +274,7 @@ - (void)communicatorDidFailWithError:(NSError *)error - (void)rewardedVideoDidLoadForAdapter:(MPRewardedVideoAdapter *)adapter { + [self.delegate rewardedVideoDidSucceedAttemptForAdManager:self]; self.remainingConfigurations = nil; self.ready = YES; self.loading = NO; @@ -262,11 +283,14 @@ - (void)rewardedVideoDidLoadForAdapter:(MPRewardedVideoAdapter *)adapter NSTimeInterval duration = NSDate.now.timeIntervalSince1970 - self.adapterLoadStartTimestamp; [self.communicator sendAfterLoadUrlWithConfiguration:self.configuration adapterLoadDuration:duration adapterLoadResult:MPAfterLoadResultAdLoaded]; + MPLogAdEvent(MPLogEvent.adDidLoad, self.adUnitID); [self.delegate rewardedVideoDidLoadForAdManager:self]; } - (void)rewardedVideoDidFailToLoadForAdapter:(MPRewardedVideoAdapter *)adapter error:(NSError *)error { + [self.delegate rewardedVideoDidFailAttemptForAdManager:self error:error]; + // Record the end of the adapter load and send off the fire and forget after-load-url tracker // with the appropriate error code result. NSTimeInterval duration = NSDate.now.timeIntervalSince1970 - self.adapterLoadStartTimestamp; @@ -279,7 +303,8 @@ - (void)rewardedVideoDidFailToLoadForAdapter:(MPRewardedVideoAdapter *)adapter e [self fetchAdWithConfiguration:self.configuration]; } // No more configurations to try. Send new request to Ads server to get more Ads. - else if (self.configuration.nextURL != nil) { + else if (self.configuration.nextURL != nil + && [self.configuration.nextURL isEqual:self.mostRecentlyLoadedURL] == false) { self.ready = NO; self.loading = NO; [self loadAdWithURL:self.configuration.nextURL]; @@ -289,57 +314,78 @@ - (void)rewardedVideoDidFailToLoadForAdapter:(MPRewardedVideoAdapter *)adapter e self.ready = NO; self.loading = NO; - MPLogInfo(kMPClearErrorLogFormatWithAdUnitID, self.adUnitID); - NSError *error = [NSError errorWithDomain:MoPubRewardedVideoAdsSDKDomain code:MPRewardedVideoAdErrorNoAdsAvailable userInfo:nil]; - [self.delegate rewardedVideoDidFailToLoadForAdManager:self error:error]; + NSError * clearResponseError = [NSError errorWithDomain:MoPubRewardedVideoAdsSDKDomain code:MPRewardedVideoAdErrorNoAdsAvailable userInfo: @{ NSLocalizedDescriptionKey: [NSString stringWithFormat:kMPClearErrorLogFormatWithAdUnitID, self.adUnitID] }]; + MPLogAdEvent([MPLogEvent adFailedToLoadWithError:clearResponseError], self.adUnitID); + [self.delegate rewardedVideoDidFailToLoadForAdManager:self error:clearResponseError]; } } - (void)rewardedVideoDidExpireForAdapter:(MPRewardedVideoAdapter *)adapter { self.ready = NO; + + MPLogAdEvent([MPLogEvent adExpiredWithTimeInterval:MPConstants.adsExpirationInterval], self.adUnitID); [self.delegate rewardedVideoDidExpireForAdManager:self]; } - (void)rewardedVideoDidFailToPlayForAdapter:(MPRewardedVideoAdapter *)adapter error:(NSError *)error { + // Playback of the rewarded video failed; reset the internal played state + // so that a new rewarded video ad can be loaded. + self.ready = NO; + self.playedAd = NO; + + MPLogAdEvent([MPLogEvent adShowFailedWithError:error], self.adUnitID); [self.delegate rewardedVideoDidFailToPlayForAdManager:self error:error]; } - (void)rewardedVideoWillAppearForAdapter:(MPRewardedVideoAdapter *)adapter { + MPLogAdEvent(MPLogEvent.adWillAppear, self.adUnitID); [self.delegate rewardedVideoWillAppearForAdManager:self]; } - (void)rewardedVideoDidAppearForAdapter:(MPRewardedVideoAdapter *)adapter { + MPLogAdEvent(MPLogEvent.adDidAppear, self.adUnitID); [self.delegate rewardedVideoDidAppearForAdManager:self]; } - (void)rewardedVideoWillDisappearForAdapter:(MPRewardedVideoAdapter *)adapter { + MPLogAdEvent(MPLogEvent.adWillDisappear, self.adUnitID); [self.delegate rewardedVideoWillDisappearForAdManager:self]; } - (void)rewardedVideoDidDisappearForAdapter:(MPRewardedVideoAdapter *)adapter { + // Successful playback of the rewarded video; reset the internal played state. self.ready = NO; self.playedAd = YES; + + MPLogAdEvent(MPLogEvent.adDidDisappear, self.adUnitID); [self.delegate rewardedVideoDidDisappearForAdManager:self]; } - (void)rewardedVideoDidReceiveTapEventForAdapter:(MPRewardedVideoAdapter *)adapter { + MPLogAdEvent(MPLogEvent.adWillPresentModal, self.adUnitID); [self.delegate rewardedVideoDidReceiveTapEventForAdManager:self]; } +- (void)rewardedVideoDidReceiveImpressionEventForAdapter:(MPRewardedVideoAdapter *)adapter { + [self.delegate rewardedVideoAdManager:self didReceiveImpressionEventWithImpressionData:self.configuration.impressionData]; +} + - (void)rewardedVideoWillLeaveApplicationForAdapter:(MPRewardedVideoAdapter *)adapter { + MPLogAdEvent(MPLogEvent.adWillLeaveApplication, self.adUnitID); [self.delegate rewardedVideoWillLeaveApplicationForAdManager:self]; } - (void)rewardedVideoShouldRewardUserForAdapter:(MPRewardedVideoAdapter *)adapter reward:(MPRewardedVideoReward *)reward { + MPLogAdEvent([MPLogEvent adShouldRewardUserWithReward:reward], self.adUnitID); [self.delegate rewardedVideoShouldRewardUserForAdManager:self reward:reward]; } @@ -353,4 +399,9 @@ - (NSString *)rewardedVideoCustomerId return self.customerId; } +- (NSString *) getCreativeId +{ + return self.configuration.dspCreativeId; +} + @end diff --git a/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdapter.h b/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdapter.h index 50a3bea8e..bef501823 100644 --- a/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdapter.h +++ b/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdapter.h @@ -1,7 +1,7 @@ // // MPRewardedVideoAdapter.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -72,6 +72,7 @@ - (void)rewardedVideoWillDisappearForAdapter:(MPRewardedVideoAdapter *)adapter; - (void)rewardedVideoDidDisappearForAdapter:(MPRewardedVideoAdapter *)adapter; - (void)rewardedVideoDidReceiveTapEventForAdapter:(MPRewardedVideoAdapter *)adapter; +- (void)rewardedVideoDidReceiveImpressionEventForAdapter:(MPRewardedVideoAdapter *)adapter; - (void)rewardedVideoWillLeaveApplicationForAdapter:(MPRewardedVideoAdapter *)adapter; - (void)rewardedVideoShouldRewardUserForAdapter:(MPRewardedVideoAdapter *)adapter reward:(MPRewardedVideoReward *)reward; diff --git a/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdapter.m b/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdapter.m index 9d21d0696..583c3255f 100644 --- a/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdapter.m +++ b/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdapter.m @@ -1,7 +1,7 @@ // // MPRewardedVideoAdapter.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -75,8 +75,8 @@ - (void)getAdWithConfiguration:(MPAdConfiguration *)configuration targeting:(MPA self.configuration = configuration; MPRewardedVideoCustomEvent *customEvent = [[configuration.customEventClass alloc] init]; if (![customEvent isKindOfClass:[MPRewardedVideoCustomEvent class]]) { - MPLogError(@"**** Custom Event Class: %@ does not extend MPRewardedVideoCustomEvent ****", NSStringFromClass(configuration.customEventClass)); - NSError *error = [NSError errorWithDomain:MoPubRewardedVideoAdsSDKDomain code:MPRewardedVideoAdErrorInvalidCustomEvent userInfo:nil]; + NSError * error = [NSError customEventClass:configuration.customEventClass doesNotInheritFrom:MPRewardedVideoCustomEvent.class]; + MPLogEvent([MPLogEvent error:error message:nil]); [self.delegate rewardedVideoDidFailToLoadForAdapter:nil error:error]; return; } @@ -85,6 +85,7 @@ - (void)getAdWithConfiguration:(MPAdConfiguration *)configuration targeting:(MPA self.rewardedVideoCustomEvent = customEvent; [self startTimeoutTimer]; + [self.rewardedVideoCustomEvent requestRewardedVideoWithCustomEventInfo:configuration.customEventClassData adMarkup:configuration.advancedBidPayload]; } @@ -101,7 +102,7 @@ - (void)presentRewardedVideoFromViewController:(UIViewController *)viewControlle if (customDataLength > 0 && self.configuration.rewardedVideoCompletionUrl != nil) { // Warn about excessive custom data length, but allow the custom data to be sent anyway if (customDataLength > kExcessiveCustomDataLength) { - MPLogWarn(@"Custom data length %ld exceeds the receommended maximum length of %ld characters.", customDataLength, kExcessiveCustomDataLength); + MPLogInfo(@"Custom data length %lu exceeds the receommended maximum length of %lu characters.", (unsigned long)customDataLength, (unsigned long)kExcessiveCustomDataLength); } self.customData = customData; @@ -123,18 +124,17 @@ - (void)startTimeoutTimer 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 = [MPTimer timerWithTimeInterval:timeInterval + target:self + selector:@selector(timeout) + repeats:NO]; [self.timeoutTimer scheduleNow]; } } - (void)timeout { - NSError * error = [MOPUBError errorWithCode:MOPUBErrorAdRequestTimedOut localizedDescription:@"Rewarded video ad request timed out"]; + NSError * error = [NSError errorWithCode:MOPUBErrorAdRequestTimedOut localizedDescription:@"Rewarded video ad request timed out"]; [self.delegate rewardedVideoDidFailToLoadForAdapter:self error:error]; self.delegate = nil; } @@ -166,6 +166,7 @@ - (void)trackImpression [[MPAnalyticsTracker sharedTracker] trackImpressionForConfiguration:self.configuration]; self.hasTrackedImpression = YES; [self.expirationTimer invalidate]; + [self.delegate rewardedVideoDidReceiveImpressionEventForAdapter:self]; } - (void)trackClick diff --git a/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoConnection.h b/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoConnection.h index 5d28a62b1..b5f5c8066 100644 --- a/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoConnection.h +++ b/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoConnection.h @@ -1,7 +1,7 @@ // // MPRewardedVideoConnection.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoConnection.m b/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoConnection.m index 4e58f8708..1e8846f76 100644 --- a/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoConnection.m +++ b/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoConnection.m @@ -1,7 +1,7 @@ // // MPRewardedVideoConnection.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/RewardedVideo/MPRewardedVideo.h b/MoPubSDK/RewardedVideo/MPRewardedVideo.h index 99e58cac3..c87ee2e02 100644 --- a/MoPubSDK/RewardedVideo/MPRewardedVideo.h +++ b/MoPubSDK/RewardedVideo/MPRewardedVideo.h @@ -1,13 +1,14 @@ // // MPRewardedVideo.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // #import #import +#import "MPImpressionData.h" @class MPRewardedVideoReward; @class CLLocation; @@ -169,12 +170,39 @@ */ + (void)presentRewardedVideoAdForAdUnitID:(NSString *)adUnitID fromViewController:(UIViewController *)viewController withReward:(MPRewardedVideoReward *)reward customData:(NSString *)customData; ++ (NSString*) creativeIdForAdUnitID:(NSString *)adUnitID; + @end @protocol MPRewardedVideoDelegate @optional +/** + * This method is called before an ad attempts to load. + * + * @param adUnitID The ad unit ID of the ad associated with the event. + * @param customEventClass The MPCustomEvent class name to identify the AdNetwork. + * @param lineItemId The id of line item the ad belongs to. + */ +- (void)rewardedVideoWillStartAttemptForAdUnitID:(NSString *)adUnitID customEventClass:(NSString*)customEventClass withLineItemId:(NSString*)lineItemId; + +/** + * This method is called after an ad attempt succeeds to load. + * + * @param adUnitID The ad unit ID of the ad associated with the event. + * @param creativeId The id of the creative loaded. + */ +- (void)rewardedVideoDidSucceedAttemptForAdUnitID:(NSString *)adUnitID withCreativeId:(NSString*)creativeId; + +/** + * This method is called after an ad attempt fails to load. + * + * @param adUnitID The ad unit ID of the ad associated with the event. + * @param error The error that occurred during the load. + */ +- (void)rewardedVideoDidFailAttemptForAdUnitID:(NSString *)adUnitID error:(NSError *)error; + /** * This method is called after an ad loads successfully. * @@ -255,4 +283,12 @@ */ - (void)rewardedVideoAdShouldRewardForAdUnitID:(NSString *)adUnitID reward:(MPRewardedVideoReward *)reward; +/** + Called when an impression is fired on a Rewarded Video. Includes information about the impression if applicable. + + @param adUnitID The ad unit ID of the rewarded video that fired the impression. + @param impressionData Information about the impression, or @c nil if the server didn't return any information. + */ +- (void)didTrackImpressionWithAdUnitID:(NSString *)adUnitID impressionData:(MPImpressionData *)impressionData; + @end diff --git a/MoPubSDK/RewardedVideo/MPRewardedVideo.m b/MoPubSDK/RewardedVideo/MPRewardedVideo.m index 616260172..bf773a6df 100644 --- a/MoPubSDK/RewardedVideo/MPRewardedVideo.m +++ b/MoPubSDK/RewardedVideo/MPRewardedVideo.m @@ -1,19 +1,21 @@ // // MPRewardedVideo.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // #import "MPRewardedVideo.h" #import "MPAdTargeting.h" +#import "MPGlobal.h" +#import "MPImpressionTrackedNotification.h" #import "MPLogging.h" #import "MPRewardedVideoAdManager.h" #import "MPRewardedVideoError.h" #import "MPRewardedVideoConnection.h" #import "MPRewardedVideoCustomEvent.h" -#import "MPRewardedVideoCustomEvent+Caching.h" +#import "MoPub+Utility.h" static MPRewardedVideo *gSharedInstance = nil; @@ -118,7 +120,7 @@ + (void)loadRewardedVideoAdWithAdUnitID:(NSString *)adUnitID keywords:(NSString adManager.mediationSettings = mediationSettings; // Ad targeting options - MPAdTargeting * targeting = [[MPAdTargeting alloc] init]; + MPAdTargeting * targeting = [MPAdTargeting targetingWithCreativeSafeSize:MPApplicationFrame(YES).size]; targeting.keywords = keywords; targeting.location = location; targeting.localExtras = localExtras; @@ -157,21 +159,21 @@ + (void)presentRewardedVideoAdForAdUnitID:(NSString *)adUnitID fromViewControlle MPRewardedVideoAdManager *adManager = sharedInstance.rewardedVideoAdManagers[adUnitID]; if (!adManager) { - MPLogWarn(@"The rewarded video could not be shown: " + MPLogInfo(@"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: " + MPLogInfo(@"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."); + MPLogInfo(@"Attempting to present a rewarded video ad in non-key window. The ad may not render properly."); } [adManager presentRewardedVideoAdFromViewController:viewController withReward:reward customData:customData]; @@ -197,6 +199,31 @@ + (MPRewardedVideo *)sharedInstance #pragma mark - MPRewardedVideoAdManagerDelegate +- (void)rewardedVideoWillStartAttemptForAdManager:(MPRewardedVideoAdManager *)manager +{ + id delegate = [self.delegateTable objectForKey:manager.adUnitID]; + if ([delegate respondsToSelector:@selector(rewardedVideoWillStartAttemptForAdUnitID:customEventClass:withLineItemId:)]) { + NSString *customEventClass = NSStringFromClass([manager customEventClass]); + [delegate rewardedVideoWillStartAttemptForAdUnitID:manager.adUnitID customEventClass:customEventClass withLineItemId:[manager lineItemId]]; + } +} + +- (void)rewardedVideoDidSucceedAttemptForAdManager:(MPRewardedVideoAdManager *)manager +{ + id delegate = [self.delegateTable objectForKey:manager.adUnitID]; + if ([delegate respondsToSelector:@selector(rewardedVideoDidSucceedAttemptForAdUnitID:withCreativeId:)]) { + [delegate rewardedVideoDidSucceedAttemptForAdUnitID:manager.adUnitID withCreativeId:[manager dspCreativeId]]; + } +} + +- (void)rewardedVideoDidFailAttemptForAdManager:(MPRewardedVideoAdManager *)manager error:(NSError *)error +{ + id delegate = [self.delegateTable objectForKey:manager.adUnitID]; + if ([delegate respondsToSelector:@selector(rewardedVideoDidFailAttemptForAdUnitID:error:)]) { + [delegate rewardedVideoDidFailAttemptForAdUnitID:manager.adUnitID error:error]; + } +} + - (void)rewardedVideoDidLoadForAdManager:(MPRewardedVideoAdManager *)manager { id delegate = [self.delegateTable objectForKey:manager.adUnitID]; @@ -282,6 +309,18 @@ - (void)rewardedVideoDidReceiveTapEventForAdManager:(MPRewardedVideoAdManager *) } } +- (void)rewardedVideoAdManager:(MPRewardedVideoAdManager *)manager didReceiveImpressionEventWithImpressionData:(MPImpressionData *)impressionData +{ + [MoPub sendImpressionNotificationFromAd:nil + adUnitID:manager.adUnitID + impressionData:impressionData]; + + id delegate = [self.delegateTable objectForKey:manager.adUnitID]; + if ([delegate respondsToSelector:@selector(didTrackImpressionWithAdUnitID:impressionData:)]) { + [delegate didTrackImpressionWithAdUnitID:manager.adUnitID impressionData:impressionData]; + } +} + - (void)rewardedVideoWillLeaveApplicationForAdManager:(MPRewardedVideoAdManager *)manager { id delegate = [self.delegateTable objectForKey:manager.adUnitID]; @@ -314,4 +353,11 @@ - (void)rewardedVideoConnectionCompleted:(MPRewardedVideoConnection *)connection [self.rewardedVideoConnections removeObject:connection]; } ++ (NSString*) creativeIdForAdUnitID:(NSString *)adUnitID { + MPRewardedVideo *sharedInstance = [[self class] sharedInstance]; + MPRewardedVideoAdManager *adManager = sharedInstance.rewardedVideoAdManagers[adUnitID]; + + return [adManager getCreativeId]; +} + @end diff --git a/MoPubSDK/RewardedVideo/MPRewardedVideoCustomEvent+Caching.h b/MoPubSDK/RewardedVideo/MPRewardedVideoCustomEvent+Caching.h deleted file mode 100644 index 8da897876..000000000 --- a/MoPubSDK/RewardedVideo/MPRewardedVideoCustomEvent+Caching.h +++ /dev/null @@ -1,28 +0,0 @@ -// -// MPRewardedVideoCustomEvent+Caching.h -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import "MPRewardedVideoCustomEvent.h" - -/** - * Provides caching support for network SDK initialization parameters. - */ -@interface MPRewardedVideoCustomEvent (Caching) - -/** - * Updates the initialization parameters for the current network. - * @param params New set of initialization parameters. Nothing will be done if `nil` is passed in. - */ -- (void)setCachedInitializationParameters:(NSDictionary * _Nullable)params; - -/** - * Retrieves the initialization parameters for the current network (if any). - * @return The cached initialization parameters for the network. This may be `nil` if not parameters were found. - */ -- (NSDictionary * _Nullable)cachedInitializationParameters; - -@end diff --git a/MoPubSDK/RewardedVideo/MPRewardedVideoCustomEvent+Caching.m b/MoPubSDK/RewardedVideo/MPRewardedVideoCustomEvent+Caching.m deleted file mode 100644 index 6bf53bcdc..000000000 --- a/MoPubSDK/RewardedVideo/MPRewardedVideoCustomEvent+Caching.m +++ /dev/null @@ -1,23 +0,0 @@ -// -// MPRewardedVideoCustomEvent+Caching.m -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import "MPRewardedVideoCustomEvent+Caching.h" -#import "MPLogging.h" -#import "MPMediationManager.h" - -@implementation MPRewardedVideoCustomEvent (Caching) - -- (void)setCachedInitializationParameters:(NSDictionary * _Nullable)params { - [MPMediationManager.sharedManager setCachedInitializationParameters:params forNetwork:[self class]]; -} - -- (NSDictionary * _Nullable)cachedInitializationParameters { - return [MPMediationManager.sharedManager cachedInitializationParametersForNetwork:[self class]]; -} - -@end diff --git a/MoPubSDK/RewardedVideo/MPRewardedVideoCustomEvent.h b/MoPubSDK/RewardedVideo/MPRewardedVideoCustomEvent.h index 3fb5b91e7..7cb8e85c0 100644 --- a/MoPubSDK/RewardedVideo/MPRewardedVideoCustomEvent.h +++ b/MoPubSDK/RewardedVideo/MPRewardedVideoCustomEvent.h @@ -1,14 +1,13 @@ // // MPRewardedVideoCustomEvent.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // #import #import -#import "MPMediationSdkInitializable.h" @protocol MPRewardedVideoCustomEventDelegate; @protocol MPMediationSettingsProtocol; @@ -25,10 +24,10 @@ * 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 and `+initializeSdkWithParameters:` method. + * invoke its `-requestRewardedVideoWithCustomEventInfo:` method. */ -@interface MPRewardedVideoCustomEvent : NSObject +@interface MPRewardedVideoCustomEvent : NSObject /** * An optional dictionary containing extra local data. @@ -39,24 +38,6 @@ /** @name Requesting and Displaying a Rewarded Video Ad */ -/** - * Called when the MoPub SDK requires the underlying network SDK to be initialized. - * - * This method may be invoked either at SDK initialization time or on-demand when - * `requestRewardedVideoWithCustomEventInfo:` is invoked. - * - * The default implementation of this method does nothing. Subclasses must override this method and implement - * code to initialize the underlying SDK here. - * - * This method may be called multiple times during the lifetime of the app. As such - * it is recommended that the implementation is encapsulated by a `dispatch_once` - * block. - * - * @param parameters A dictionary containing any SDK-specific information needed for initialization, - * such as app IDs. - */ -- (void)initializeSdkWithParameters:(NSDictionary *)parameters; - /** * Called when the MoPub SDK requires a new rewarded video ad. * diff --git a/MoPubSDK/RewardedVideo/MPRewardedVideoCustomEvent.m b/MoPubSDK/RewardedVideo/MPRewardedVideoCustomEvent.m index b65f9bff6..ab351e075 100644 --- a/MoPubSDK/RewardedVideo/MPRewardedVideoCustomEvent.m +++ b/MoPubSDK/RewardedVideo/MPRewardedVideoCustomEvent.m @@ -1,7 +1,7 @@ // // MPRewardedVideoCustomEvent.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -11,12 +11,6 @@ @implementation MPRewardedVideoCustomEvent -- (void)initializeSdkWithParameters:(NSDictionary *)parameters -{ - // The default implementation of this method does nothing. Subclasses must override this method - // and implement code to initialize the underlying SDK here. -} - - (void)requestRewardedVideoWithCustomEventInfo:(NSDictionary *)info { // The default implementation of this method does nothing. Subclasses must override this method diff --git a/MoPubSDK/RewardedVideo/MPRewardedVideoError.h b/MoPubSDK/RewardedVideo/MPRewardedVideoError.h index c2ec0c663..3773ed9b9 100644 --- a/MoPubSDK/RewardedVideo/MPRewardedVideoError.h +++ b/MoPubSDK/RewardedVideo/MPRewardedVideoError.h @@ -1,7 +1,7 @@ // // MPRewardedVideoError.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/RewardedVideo/MPRewardedVideoError.m b/MoPubSDK/RewardedVideo/MPRewardedVideoError.m index 5b683f409..c6ec5248d 100644 --- a/MoPubSDK/RewardedVideo/MPRewardedVideoError.m +++ b/MoPubSDK/RewardedVideo/MPRewardedVideoError.m @@ -1,7 +1,7 @@ // // MPRewardedVideoError.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/RewardedVideo/MPRewardedVideoReward.h b/MoPubSDK/RewardedVideo/MPRewardedVideoReward.h index aa61a8e40..fbfd082c3 100644 --- a/MoPubSDK/RewardedVideo/MPRewardedVideoReward.h +++ b/MoPubSDK/RewardedVideo/MPRewardedVideoReward.h @@ -1,7 +1,7 @@ // // MPRewardedVideoReward.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/RewardedVideo/MPRewardedVideoReward.m b/MoPubSDK/RewardedVideo/MPRewardedVideoReward.m index 0806cad22..fbb600f38 100644 --- a/MoPubSDK/RewardedVideo/MPRewardedVideoReward.m +++ b/MoPubSDK/RewardedVideo/MPRewardedVideoReward.m @@ -1,7 +1,7 @@ // // MPRewardedVideoReward.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -28,4 +28,12 @@ - (instancetype)initWithCurrencyAmount:(NSNumber *)amount return [self initWithCurrencyType:kMPRewardedVideoRewardCurrencyTypeUnspecified amount:amount]; } +- (NSString *)description { + NSString * message = nil; + if (self.amount != nil && self.currencyType != nil) { + message = [NSString stringWithFormat:@"%@ %@", self.amount, self.currencyType]; + } + return message; +} + @end diff --git a/MoPubSDK/Viewability/Avid/MPViewabilityAdapterAvid.h b/MoPubSDK/Viewability/Avid/MPViewabilityAdapterAvid.h index 5be8efe15..1992e6a80 100644 --- a/MoPubSDK/Viewability/Avid/MPViewabilityAdapterAvid.h +++ b/MoPubSDK/Viewability/Avid/MPViewabilityAdapterAvid.h @@ -1,7 +1,7 @@ // // MPViewabilityAdapterAvid.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/Viewability/Avid/MPViewabilityAdapterAvid.m b/MoPubSDK/Viewability/Avid/MPViewabilityAdapterAvid.m index ed8306fac..c9c34fd43 100644 --- a/MoPubSDK/Viewability/Avid/MPViewabilityAdapterAvid.m +++ b/MoPubSDK/Viewability/Avid/MPViewabilityAdapterAvid.m @@ -1,7 +1,7 @@ // // MPViewabilityAdapterAvid.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -45,7 +45,7 @@ - (instancetype)initWithAdView:(UIView *)webView isVideo:(BOOL)isVideo startTrac if (startTracking) { _isTracking = YES; - MPLogInfo(@"[Viewability] IAS tracking started"); + MPLogInfo(@"IAS tracking started"); } #endif } @@ -61,7 +61,7 @@ - (void)startTracking { if (!self.isTracking && self.avidAdSession != nil) { [self.avidAdSession.avidDeferredAdSessionListener recordReadyEvent]; self.isTracking = YES; - MPLogInfo(@"[Viewability] IAS tracking started"); + MPLogInfo(@"IAS tracking started"); } #endif } @@ -73,7 +73,7 @@ - (void)stopTracking { if (self.isTracking) { [self.avidAdSession endSession]; if (self.avidAdSession) { - MPLogInfo(@"[Viewability] IAS tracking stopped"); + MPLogInfo(@"IAS tracking stopped"); } } diff --git a/MoPubSDK/Viewability/MOAT/MPViewabilityAdapterMoat.h b/MoPubSDK/Viewability/MOAT/MPViewabilityAdapterMoat.h index 811f322e7..8e2ef0810 100644 --- a/MoPubSDK/Viewability/MOAT/MPViewabilityAdapterMoat.h +++ b/MoPubSDK/Viewability/MOAT/MPViewabilityAdapterMoat.h @@ -1,7 +1,7 @@ // // MPViewabilityAdapterMoat.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/Viewability/MOAT/MPViewabilityAdapterMoat.m b/MoPubSDK/Viewability/MOAT/MPViewabilityAdapterMoat.m index 18f34a421..cb8b8a3f2 100644 --- a/MoPubSDK/Viewability/MOAT/MPViewabilityAdapterMoat.m +++ b/MoPubSDK/Viewability/MOAT/MPViewabilityAdapterMoat.m @@ -1,7 +1,7 @@ // // MPViewabilityAdapterMoat.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -62,7 +62,7 @@ - (instancetype)initWithAdView:(UIView *)webView isVideo:(BOOL)isVideo startTrac if (startTracking) { [_moatWebTracker startTracking]; _isTracking = YES; - MPLogInfo(@"[Viewability] MOAT tracking started"); + MPLogInfo(@"MOAT tracking started"); } #endif } @@ -78,7 +78,7 @@ - (void)startTracking { if (!self.isTracking && self.moatWebTracker != nil) { [self.moatWebTracker startTracking]; self.isTracking = YES; - MPLogInfo(@"[Viewability] MOAT tracking started"); + MPLogInfo(@"MOAT tracking started"); } #endif } @@ -91,7 +91,7 @@ - (void)stopTracking { void (^moatEndTrackingBlock)(void) = ^{ [self.moatWebTracker stopTracking]; if (self.moatWebTracker) { - MPLogInfo(@"[Viewability] MOAT tracking stopped"); + MPLogInfo(@"MOAT tracking stopped"); } }; // If video, as a safeguard, dispatch `AdStopped` event before we stop tracking. @@ -104,9 +104,8 @@ - (void)stopTracking { completionHandler:^(id result, NSError *error){ moatEndTrackingBlock(); }]; - } else if ([self.webView isKindOfClass:[UIWebView class]]) { - UIWebView *typedWebView = (UIWebView *)self.webView; - [typedWebView stringByEvaluatingJavaScriptFromString:kMOATSendAdStoppedJavascript]; + } else { + MPLogInfo(@"Unexpected web view class: %@", self.webView.class); moatEndTrackingBlock(); } } else { diff --git a/MoPubSDK/Viewability/MPViewabilityAdapter.h b/MoPubSDK/Viewability/MPViewabilityAdapter.h index f3b75c035..ee488d8da 100644 --- a/MoPubSDK/Viewability/MPViewabilityAdapter.h +++ b/MoPubSDK/Viewability/MPViewabilityAdapter.h @@ -1,7 +1,7 @@ // // MPViewabilityAdapter.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/Viewability/MPViewabilityOption.h b/MoPubSDK/Viewability/MPViewabilityOption.h index 34bf3b7b7..8e4d92ec7 100644 --- a/MoPubSDK/Viewability/MPViewabilityOption.h +++ b/MoPubSDK/Viewability/MPViewabilityOption.h @@ -1,7 +1,7 @@ // // MPViewabilityOption.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/Viewability/MPViewabilityTracker.h b/MoPubSDK/Viewability/MPViewabilityTracker.h index a8f551d21..6ae88391c 100644 --- a/MoPubSDK/Viewability/MPViewabilityTracker.h +++ b/MoPubSDK/Viewability/MPViewabilityTracker.h @@ -1,7 +1,7 @@ // // MPViewabilityTracker.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDK/Viewability/MPViewabilityTracker.m b/MoPubSDK/Viewability/MPViewabilityTracker.m index 1516cfcff..870b104d8 100644 --- a/MoPubSDK/Viewability/MPViewabilityTracker.m +++ b/MoPubSDK/Viewability/MPViewabilityTracker.m @@ -1,7 +1,7 @@ // // MPViewabilityTracker.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -52,7 +52,7 @@ + (void)initialize { NSString * adapterClassName = sSupportedAdapters[@(index)]; if (NSClassFromString(adapterClassName)) { sEnabledViewabilityVendors |= index; - MPLogInfo(@"[Viewability] %@ was found.", adapterClassName); + MPLogInfo(@"%@ was found.", adapterClassName); } } } @@ -69,7 +69,7 @@ - (instancetype)initWithAdView:(MPWebView *)webView // Invalid ad view if (view == nil) { - MPLogError(@"nil ad view passed into %s", __PRETTY_FUNCTION__); + MPLogInfo(@"nil ad view passed into %s", __PRETTY_FUNCTION__); return nil; } diff --git a/MoPubSDK/Viewability/MPWebView+Viewability.h b/MoPubSDK/Viewability/MPWebView+Viewability.h index 0a817b9e2..e68ae23d0 100644 --- a/MoPubSDK/Viewability/MPWebView+Viewability.h +++ b/MoPubSDK/Viewability/MPWebView+Viewability.h @@ -1,7 +1,7 @@ // // MPWebView+Viewability.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -10,12 +10,13 @@ @interface MPWebView (Viewability) -// Returns the UIWebView or WKWebView instance attached to this MPWebView. If using WKWebView, this property will return -// the WKWebView instance regardless of if it's currently in the view hierarchy of this MPWebView. Exposed for the -// purpose of having a reliable way to attach viewability SDKs to a web view. -// -// Note: Please do not alter the hierarchy of this view (i.e., don't ever call it with `addSubview` or -// `removeFromSuperview`). Call those methods on the MPWebView instance instead. +/** + * Returns the @c WKWebView instance attached to this @c MPWebView. Exposed for the purpose of having + * a reliable way to attach viewability SDKs to a web view. + * + * Note: Please do not alter the hierarchy of this view (i.e., don't ever call it with `addSubview` or + * `removeFromSuperview`). Call those methods on the MPWebView instance instead. + */ @property (nonatomic, readonly) UIView *containedWebView; @end diff --git a/MoPubSDK/Viewability/MPWebView+Viewability.m b/MoPubSDK/Viewability/MPWebView+Viewability.m index a37e1b4fc..73e4f5cb9 100644 --- a/MoPubSDK/Viewability/MPWebView+Viewability.m +++ b/MoPubSDK/Viewability/MPWebView+Viewability.m @@ -1,7 +1,7 @@ // // MPWebView+Viewability.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -11,7 +11,6 @@ @interface MPWebView () -- (UIWebView *)uiWebView; - (WKWebView *)wkWebView; @end @@ -19,7 +18,7 @@ - (WKWebView *)wkWebView; @implementation MPWebView (Viewability) - (UIView *)containedWebView { - return self.uiWebView ? self.uiWebView : self.wkWebView; + return self.wkWebView; } @end diff --git a/MoPubSDKFramework/Info.plist b/MoPubSDKFramework/Info.plist index 771b88ffd..523e899ef 100644 --- a/MoPubSDKFramework/Info.plist +++ b/MoPubSDKFramework/Info.plist @@ -15,9 +15,9 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 5.4.0 + 5.9.0 CFBundleVersion - 5.4.0 + 5.9.0 NSPrincipalClass diff --git a/MoPubSDKFramework/MoPubSDKFramework.modulemap b/MoPubSDKFramework/MoPubSDKFramework.modulemap index 402dd48ba..1f3fcbaec 100644 --- a/MoPubSDKFramework/MoPubSDKFramework.modulemap +++ b/MoPubSDKFramework/MoPubSDKFramework.modulemap @@ -1,3 +1,6 @@ framework module MoPubSDKFramework { umbrella header "MoPub.h" + + export * + module * { export * } } diff --git a/MoPubSDKTests/Fixtures/VastXML/VAST_3.0_linear_ad_comprehensive.xml b/MoPubSDKTests/Fixtures/VastXML/VAST_3.0_linear_ad_comprehensive.xml new file mode 100644 index 000000000..a50894e68 --- /dev/null +++ b/MoPubSDKTests/Fixtures/VastXML/VAST_3.0_linear_ad_comprehensive.xml @@ -0,0 +1,94 @@ + + + + MoPub + MoPub Test Ad + MoPub Description + + + + + + + + + 00:00:30 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Device + Install Now + Skip Now + + + + + diff --git a/MoPubSDKTests/Info.plist b/MoPubSDKTests/Info.plist index 952a37794..bab901746 100644 --- a/MoPubSDKTests/Info.plist +++ b/MoPubSDKTests/Info.plist @@ -15,8 +15,8 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 5.4.0 + 5.9.0 CFBundleVersion - 5.4.0 + 5.9.0 diff --git a/MoPubSDKTests/MOPUBExperimentProvider+Testing.h b/MoPubSDKTests/MOPUBExperimentProvider+Testing.h index 8a436952a..b9152c97c 100644 --- a/MoPubSDKTests/MOPUBExperimentProvider+Testing.h +++ b/MoPubSDKTests/MOPUBExperimentProvider+Testing.h @@ -1,7 +1,7 @@ // // MOPUBExperimentProvider+Testing.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -10,6 +10,6 @@ @interface MOPUBExperimentProvider (Testing) -+ (void)setDisplayAgentOverriddenByClientFlag:(BOOL)flag; +@property (nonatomic, assign) BOOL isDisplayAgentOverriddenByClient; @end diff --git a/MoPubSDKTests/MOPUBExperimentProviderTests.m b/MoPubSDKTests/MOPUBExperimentProviderTests.m index 125d6bc7c..f3bc3d807 100644 --- a/MoPubSDKTests/MOPUBExperimentProviderTests.m +++ b/MoPubSDKTests/MOPUBExperimentProviderTests.m @@ -1,7 +1,7 @@ // // MOPUBExperimentProviderTests.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -9,7 +9,7 @@ #import #import "MPAdConfiguration+Testing.h" #import "MOPUBExperimentProvider.h" -#import "MoPub.h" +#import "MoPub+Testing.h" #import "MPAdConfiguration.h" #import "MOPUBExperimentProvider+Testing.h" @@ -20,52 +20,64 @@ @interface MOPUBExperimentProviderTests : XCTestCase @implementation MOPUBExperimentProviderTests - (void)testClickthroughExperimentDefault { - [MOPUBExperimentProvider setDisplayAgentOverriddenByClientFlag:NO]; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:nil data:nil]; + MOPUBExperimentProvider *testSubject = [MOPUBExperimentProvider new]; + testSubject.isDisplayAgentOverriddenByClient = NO; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:nil + data:nil + adType:MPAdTypeFullscreen + experimentProvider:testSubject]; + XCTAssertEqual(config.clickthroughExperimentBrowserAgent, MOPUBDisplayAgentTypeInApp); - XCTAssertEqual([MOPUBExperimentProvider displayAgentType], MOPUBDisplayAgentTypeInApp); + XCTAssertEqual(testSubject.displayAgentType, MOPUBDisplayAgentTypeInApp); + XCTAssertFalse(testSubject.isDisplayAgentOverriddenByClient); } - (void)testClickthroughExperimentInApp { - [MOPUBExperimentProvider setDisplayAgentOverriddenByClientFlag:NO]; + // 0 is the raw value of MOPUBDisplayAgentTypeInApp NSDictionary * headers = @{ kClickthroughExperimentBrowserAgent: @"0"}; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; + MOPUBExperimentProvider *testSubject = [MOPUBExperimentProvider new]; + testSubject.isDisplayAgentOverriddenByClient = NO; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers + data:nil + adType:MPAdTypeFullscreen + experimentProvider:testSubject]; + XCTAssertEqual(config.clickthroughExperimentBrowserAgent, MOPUBDisplayAgentTypeInApp); - XCTAssertEqual([MOPUBExperimentProvider displayAgentType], MOPUBDisplayAgentTypeInApp); + XCTAssertEqual(testSubject.displayAgentType, MOPUBDisplayAgentTypeInApp); + XCTAssertFalse(testSubject.isDisplayAgentOverriddenByClient); } - (void)testClickthroughExperimentNativeBrowser { - [MOPUBExperimentProvider setDisplayAgentOverriddenByClientFlag:NO]; + // 1 is the raw value of MOPUBDisplayAgentTypeNativeSafari NSDictionary * headers = @{ kClickthroughExperimentBrowserAgent: @"1"}; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; - XCTAssertEqual(config.clickthroughExperimentBrowserAgent, MOPUBDisplayAgentTypeNativeSafari); - XCTAssertEqual([MOPUBExperimentProvider displayAgentType], MOPUBDisplayAgentTypeNativeSafari); -} + MOPUBExperimentProvider *testSubject = [MOPUBExperimentProvider new]; + testSubject.displayAgentType = MOPUBDisplayAgentTypeNativeSafari; + testSubject.isDisplayAgentOverriddenByClient = NO; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers + data:nil + adType:MPAdTypeFullscreen + experimentProvider:testSubject]; -- (void)testClickthroughExperimentSafariViewController { - [MOPUBExperimentProvider setDisplayAgentOverriddenByClientFlag:NO]; - NSDictionary * headers = @{ kClickthroughExperimentBrowserAgent: @"2"}; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; - XCTAssertEqual(config.clickthroughExperimentBrowserAgent, MOPUBDisplayAgentTypeSafariViewController); - XCTAssertEqual([MOPUBExperimentProvider displayAgentType], MOPUBDisplayAgentTypeSafariViewController); + XCTAssertEqual(config.clickthroughExperimentBrowserAgent, MOPUBDisplayAgentTypeNativeSafari); + XCTAssertEqual(testSubject.displayAgentType, MOPUBDisplayAgentTypeNativeSafari); + XCTAssertFalse(testSubject.isDisplayAgentOverriddenByClient); } - (void)testClickthroughClientOverride { - [[MoPub sharedInstance] setClickthroughDisplayAgentType:MOPUBDisplayAgentTypeInApp]; - - NSDictionary * headers = @{ kClickthroughExperimentBrowserAgent: @"2"}; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; - XCTAssertEqual(config.clickthroughExperimentBrowserAgent, MOPUBDisplayAgentTypeSafariViewController); - - XCTAssertEqual([MOPUBExperimentProvider displayAgentType], MOPUBDisplayAgentTypeInApp); - - // Display agent type is overridden to MOPUBDisplayAgentTypeSafariViewController - [[MoPub sharedInstance] setClickthroughDisplayAgentType:MOPUBDisplayAgentTypeSafariViewController]; - XCTAssertEqual([MOPUBExperimentProvider displayAgentType], MOPUBDisplayAgentTypeSafariViewController); + MOPUBExperimentProvider *testSubject = [MOPUBExperimentProvider new]; + MoPub *mopub = [[MoPub new] initWithExperimentProvider:testSubject]; + XCTAssertEqual(mopub.experimentProvider.displayAgentType, MOPUBDisplayAgentTypeInApp); // default + XCTAssertFalse(mopub.experimentProvider.isDisplayAgentOverriddenByClient); // Display agent type is overridden to MOPUBDisplayAgentTypeNativeSafari - [[MoPub sharedInstance] setClickthroughDisplayAgentType:MOPUBDisplayAgentTypeNativeSafari]; - XCTAssertEqual([MOPUBExperimentProvider displayAgentType], MOPUBDisplayAgentTypeNativeSafari); + [mopub setClickthroughDisplayAgentType:MOPUBDisplayAgentTypeNativeSafari]; + XCTAssertEqual(mopub.experimentProvider.displayAgentType, MOPUBDisplayAgentTypeNativeSafari); + XCTAssertTrue(mopub.experimentProvider.isDisplayAgentOverriddenByClient); + + // Display agent type is overridden to MOPUBDisplayAgentTypeInApp + [mopub setClickthroughDisplayAgentType:MOPUBDisplayAgentTypeInApp]; + XCTAssertEqual(mopub.experimentProvider.displayAgentType, MOPUBDisplayAgentTypeInApp); + XCTAssertTrue(mopub.experimentProvider.isDisplayAgentOverriddenByClient); } @end diff --git a/MoPubSDKTests/MOPUBNativeVideoAdAdapter+Testing.h b/MoPubSDKTests/MOPUBNativeVideoAdAdapter+Testing.h index aba764e24..e0dd2f62b 100644 --- a/MoPubSDKTests/MOPUBNativeVideoAdAdapter+Testing.h +++ b/MoPubSDKTests/MOPUBNativeVideoAdAdapter+Testing.h @@ -1,7 +1,7 @@ // // MOPUBNativeVideoAdAdapter+Testing.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/MOPUBNativeVideoAdAdapter+Testing.m b/MoPubSDKTests/MOPUBNativeVideoAdAdapter+Testing.m index 78235a1df..6aa629a5e 100644 --- a/MoPubSDKTests/MOPUBNativeVideoAdAdapter+Testing.m +++ b/MoPubSDKTests/MOPUBNativeVideoAdAdapter+Testing.m @@ -1,7 +1,7 @@ // // MOPUBNativeVideoAdAdapter+Testing.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/MOPUBNativeVideoAdAdapterTests.m b/MoPubSDKTests/MOPUBNativeVideoAdAdapterTests.m index 05b87bfd9..9b5d0fd6c 100644 --- a/MoPubSDKTests/MOPUBNativeVideoAdAdapterTests.m +++ b/MoPubSDKTests/MOPUBNativeVideoAdAdapterTests.m @@ -1,7 +1,7 @@ // // MOPUBNativeVideoAdAdapterTests.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/MOPUBNativeVideoAdConfigValuesTests.m b/MoPubSDKTests/MOPUBNativeVideoAdConfigValuesTests.m index 043f7de53..360f83a34 100644 --- a/MoPubSDKTests/MOPUBNativeVideoAdConfigValuesTests.m +++ b/MoPubSDKTests/MOPUBNativeVideoAdConfigValuesTests.m @@ -1,7 +1,7 @@ // // MOPUBNativeVideoAdConfigValuesTests.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/MPAdConfiguration+Testing.h b/MoPubSDKTests/MPAdConfiguration+Testing.h index 4f8119041..e4d540eb1 100644 --- a/MoPubSDKTests/MPAdConfiguration+Testing.h +++ b/MoPubSDKTests/MPAdConfiguration+Testing.h @@ -1,17 +1,27 @@ // // MPAdConfiguration+Testing.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // #import "MPAdConfiguration.h" +#import "MOPUBExperimentProvider+Testing.h" @interface MPAdConfiguration (Testing) @property (nonatomic) NSInteger clickthroughExperimentBrowserAgent; +@property (nonatomic, strong) MOPUBExperimentProvider *experimentProvider; +- (instancetype)initWithMetadata:(NSDictionary *)metadata + data:(NSData *)data + adType:(MPAdType)adType + experimentProvider:(MOPUBExperimentProvider *)experimentProvider; +- (void)commonInitWithMetadata:(NSDictionary *)metadata + data:(NSData *)data + adType:(MPAdType)adType + experimentProvider:(MOPUBExperimentProvider *)experimentProvider; - (NSArray *)URLsFromMetadata:(NSDictionary *)metadata forKey:(NSString *)key; - (NSArray *)URLStringsFromMetadata:(NSDictionary *)metadata forKey:(NSString *)key; diff --git a/MoPubSDKTests/MPAdConfiguration+Testing.m b/MoPubSDKTests/MPAdConfiguration+Testing.m index 6cbfe38b1..3ce457824 100644 --- a/MoPubSDKTests/MPAdConfiguration+Testing.m +++ b/MoPubSDKTests/MPAdConfiguration+Testing.m @@ -1,15 +1,33 @@ // // MPAdConfiguration+Testing.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // #import "MPAdConfiguration+Testing.h" +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wincomplete-implementation" +#pragma clang diagnostic ignored "-Wobjc-property-implementation" @implementation MPAdConfiguration (Testing) @dynamic clickthroughExperimentBrowserAgent; +- (instancetype)initWithMetadata:(NSDictionary *)metadata + data:(NSData *)data + adType:(MPAdType)adType + experimentProvider:(MOPUBExperimentProvider *)experimentProvider { + self = [super init]; + if (self) { + [self commonInitWithMetadata:metadata + data:data + adType:adType + experimentProvider:experimentProvider]; + } + return self; +} + @end +#pragma clang diagnostic pop diff --git a/MoPubSDKTests/MPAdConfigurationFactory.h b/MoPubSDKTests/MPAdConfigurationFactory.h index b40005afe..9692bf37c 100644 --- a/MoPubSDKTests/MPAdConfigurationFactory.h +++ b/MoPubSDKTests/MPAdConfigurationFactory.h @@ -1,7 +1,7 @@ // // MPAdConfigurationFactory.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/MPAdConfigurationFactory.m b/MoPubSDKTests/MPAdConfigurationFactory.m index 70ee19bf5..c31e5809a 100644 --- a/MoPubSDKTests/MPAdConfigurationFactory.m +++ b/MoPubSDKTests/MPAdConfigurationFactory.m @@ -1,7 +1,7 @@ // // MPAdConfigurationFactory.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -20,7 +20,7 @@ @implementation MPAdConfigurationFactory + (MPAdConfiguration *)clearResponse { NSDictionary * metadata = @{ kAdTypeMetadataKey: kAdTypeClear }; - return [[MPAdConfiguration alloc] initWithMetadata:metadata data:nil]; + return [[MPAdConfiguration alloc] initWithMetadata:metadata data:nil adType:MPAdTypeFullscreen]; } #pragma mark - Native @@ -87,7 +87,7 @@ + (MPAdConfiguration *)defaultNativeAdConfigurationWithHeaders:(NSDictionary *)d [allProperties addEntriesFromDictionary:properties]; } - return [[MPAdConfiguration alloc] initWithMetadata:headers data:[NSJSONSerialization dataWithJSONObject:allProperties options:NSJSONWritingPrettyPrinted error:nil]]; + return [[MPAdConfiguration alloc] initWithMetadata:headers data:[NSJSONSerialization dataWithJSONObject:allProperties options:NSJSONWritingPrettyPrinted error:nil] adType:MPAdTypeInline]; } #pragma mark - Banners @@ -141,7 +141,7 @@ + (MPAdConfiguration *)defaultBannerConfigurationWithHeaders:(NSDictionary *)dic HTMLString = HTMLString ? HTMLString : @"Publisher's Ad"; return [[MPAdConfiguration alloc] initWithMetadata:headers - data:[HTMLString dataUsingEncoding:NSUTF8StringEncoding]]; + data:[HTMLString dataUsingEncoding:NSUTF8StringEncoding] adType:MPAdTypeInline]; } #pragma mark - Interstitials @@ -224,7 +224,7 @@ + (MPAdConfiguration *)defaultInterstitialConfigurationWithHeaders:(NSDictionary HTMLString = HTMLString ? HTMLString : @"Publisher's Interstitial"; return [[MPAdConfiguration alloc] initWithMetadata:headers - data:[HTMLString dataUsingEncoding:NSUTF8StringEncoding]]; + data:[HTMLString dataUsingEncoding:NSUTF8StringEncoding] adType:MPAdTypeInline]; } #pragma mark - Rewarded Video @@ -263,7 +263,7 @@ + (NSMutableDictionary *)defaultNativeVideoHeadersWithTrackers + (MPAdConfiguration *)defaultRewardedVideoConfiguration { - MPAdConfiguration *adConfiguration = [[MPAdConfiguration alloc] initWithMetadata:[self defaultRewardedVideoHeaders] data:nil]; + MPAdConfiguration *adConfiguration = [[MPAdConfiguration alloc] initWithMetadata:[self defaultRewardedVideoHeaders] data:nil adType:MPAdTypeFullscreen]; return adConfiguration; } @@ -280,25 +280,25 @@ + (MPAdConfiguration *)defaultRewardedVideoConfigurationWithCustomEventClassName [metadata addEntriesFromDictionary:additionalMetadata]; } - MPAdConfiguration *adConfiguration = [[MPAdConfiguration alloc] initWithMetadata:metadata data:nil]; + MPAdConfiguration *adConfiguration = [[MPAdConfiguration alloc] initWithMetadata:metadata data:nil adType:MPAdTypeFullscreen]; return adConfiguration; } + (MPAdConfiguration *)defaultRewardedVideoConfigurationWithReward { - MPAdConfiguration *adConfiguration = [[MPAdConfiguration alloc] initWithMetadata:[self defaultRewardedVideoHeadersWithReward] data:nil]; + MPAdConfiguration *adConfiguration = [[MPAdConfiguration alloc] initWithMetadata:[self defaultRewardedVideoHeadersWithReward] data:nil adType:MPAdTypeFullscreen]; return adConfiguration; } + (MPAdConfiguration *)defaultRewardedVideoConfigurationServerToServer { - MPAdConfiguration *adConfiguration = [[MPAdConfiguration alloc] initWithMetadata:[self defaultRewardedVideoHeadersServerToServer] data:nil]; + MPAdConfiguration *adConfiguration = [[MPAdConfiguration alloc] initWithMetadata:[self defaultRewardedVideoHeadersServerToServer] data:nil adType:MPAdTypeFullscreen]; return adConfiguration; } + (MPAdConfiguration *)defaultNativeVideoConfigurationWithVideoTrackers { - MPAdConfiguration *adConfiguration = [[MPAdConfiguration alloc] initWithMetadata:[self defaultNativeVideoHeadersWithTrackers] data:nil]; + MPAdConfiguration *adConfiguration = [[MPAdConfiguration alloc] initWithMetadata:[self defaultNativeVideoHeadersWithTrackers] data:nil adType:MPAdTypeFullscreen]; return adConfiguration; } diff --git a/MoPubSDKTests/MPAdConfigurationTests.m b/MoPubSDKTests/MPAdConfigurationTests.m index 9f241f600..9b35b0dec 100644 --- a/MoPubSDKTests/MPAdConfigurationTests.m +++ b/MoPubSDKTests/MPAdConfigurationTests.m @@ -1,7 +1,7 @@ // // MPAdConfigurationTests.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -9,10 +9,10 @@ #import #import "MPAdConfiguration.h" #import "MPAdConfigurationFactory.h" -#import "MPVASTTrackingEvent.h" #import "MPRewardedVideoReward.h" #import "MOPUBExperimentProvider.h" #import "MPAdConfiguration+Testing.h" +#import "MPVASTTracking.h" #import "MPViewabilityTracker.h" #import "MPAdServerKeys.h" @@ -35,35 +35,35 @@ - (void)setUp { - (void)testRewardedPlayableDurationParseStringInputSuccess { NSDictionary * headers = @{ kRewardedPlayableDurationMetadataKey: @"30" }; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil adType:MPAdTypeFullscreen]; XCTAssertEqual(config.rewardedPlayableDuration, 30); } - (void)testRewardedPlayableDurationParseNumberInputSuccess { NSDictionary * headers = @{ kRewardedPlayableDurationMetadataKey: @(30) }; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil adType:MPAdTypeFullscreen]; XCTAssertEqual(config.rewardedPlayableDuration, 30); } - (void)testRewardedPlayableDurationParseNoHeader { NSDictionary * headers = @{ }; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil adType:MPAdTypeFullscreen]; XCTAssertEqual(config.rewardedPlayableDuration, -1); } - (void)testRewardedPlayableRewardOnClickParseSuccess { NSDictionary * headers = @{ kRewardedPlayableRewardOnClickMetadataKey: @"true" }; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil adType:MPAdTypeFullscreen]; XCTAssertEqual(config.rewardedPlayableShouldRewardOnClick, true); } - (void)testRewardedPlayableRewardOnClickParseNoHeader { NSDictionary * headers = @{ }; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil adType:MPAdTypeFullscreen]; XCTAssertEqual(config.rewardedPlayableShouldRewardOnClick, false); } @@ -72,7 +72,7 @@ - (void)testRewardedSingleCurrencyParseSuccess { NSDictionary * headers = @{ kRewardedVideoCurrencyNameMetadataKey: @"Diamonds", kRewardedVideoCurrencyAmountMetadataKey: @"3", }; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil adType:MPAdTypeFullscreen]; XCTAssertNotNil(config.availableRewards); XCTAssertNotNil(config.selectedReward); @@ -91,7 +91,7 @@ - (void)testRewardedMultiCurrencyParseSuccess { // ] // } NSDictionary * headers = @{ kRewardedCurrenciesMetadataKey: @{ @"rewards": @[ @{ @"name": @"Coins", @"amount": @(8) }, @{ @"name": @"Diamonds", @"amount": @(1) }, @{ @"name@": @"Energy", @"amount": @(20) } ] } }; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil adType:MPAdTypeFullscreen]; XCTAssertNotNil(config.availableRewards); XCTAssertNotNil(config.selectedReward); @@ -106,7 +106,7 @@ - (void)testRewardedMultiCurrencyParseFailure { // "rewards": [] // } NSDictionary * headers = @{ kRewardedCurrenciesMetadataKey: @{ @"rewards": @[] } }; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil adType:MPAdTypeFullscreen]; XCTAssertNotNil(config.availableRewards); XCTAssertNotNil(config.selectedReward); @@ -121,7 +121,7 @@ - (void)testRewardedMultiCurrencyParseFailureMalconfiguredReward { // "rewards": [ { "n": "Coins", "a": 8 } ] // } NSDictionary * headers = @{ kRewardedCurrenciesMetadataKey: @{ @"rewards": @[ @{ @"n": @"Coins", @"a": @(8) } ] } }; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil adType:MPAdTypeFullscreen]; XCTAssertNotNil(config.availableRewards); XCTAssertNotNil(config.selectedReward); @@ -136,7 +136,7 @@ - (void)testRewardedMultiCurrencyParseFailoverToSingleCurrencySuccess { kRewardedVideoCurrencyAmountMetadataKey: @"3", kRewardedCurrenciesMetadataKey: @{ } }; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil adType:MPAdTypeFullscreen]; XCTAssertNotNil(config.availableRewards); XCTAssertNotNil(config.selectedReward); @@ -162,40 +162,11 @@ - (void)testNaiveVideoTrackers { MPAdConfiguration *config = [MPAdConfigurationFactory defaultNativeVideoConfigurationWithVideoTrackers]; XCTAssertNotNil(config.nativeVideoTrackers); XCTAssertEqual(config.nativeVideoTrackers.count, 5); - XCTAssertEqual(((NSArray *)config.nativeVideoTrackers[MPVASTTrackingEventTypeStart]).count, 2); - XCTAssertEqual(((NSArray *)config.nativeVideoTrackers[MPVASTTrackingEventTypeFirstQuartile]).count, 2); - XCTAssertEqual(((NSArray *)config.nativeVideoTrackers[MPVASTTrackingEventTypeMidpoint]).count, 2); - XCTAssertEqual(((NSArray *)config.nativeVideoTrackers[MPVASTTrackingEventTypeThirdQuartile]).count, 2); - XCTAssertEqual(((NSArray *)config.nativeVideoTrackers[MPVASTTrackingEventTypeComplete]).count, 2); -} - -#pragma mark - Clickthrough experiments test - -- (void)testClickthroughExperimentDefault { - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:nil data:nil]; - XCTAssertEqual(config.clickthroughExperimentBrowserAgent, MOPUBDisplayAgentTypeInApp); - XCTAssertEqual([MOPUBExperimentProvider displayAgentType], MOPUBDisplayAgentTypeInApp); -} - -- (void)testClickthroughExperimentInApp { - NSDictionary * headers = @{ kClickthroughExperimentBrowserAgent: @"0"}; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; - XCTAssertEqual(config.clickthroughExperimentBrowserAgent, MOPUBDisplayAgentTypeInApp); - XCTAssertEqual([MOPUBExperimentProvider displayAgentType], MOPUBDisplayAgentTypeInApp); -} - -- (void)testClickthroughExperimentNativeBrowser { - NSDictionary * headers = @{ kClickthroughExperimentBrowserAgent: @"1"}; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; - XCTAssertEqual(config.clickthroughExperimentBrowserAgent, MOPUBDisplayAgentTypeNativeSafari); - XCTAssertEqual([MOPUBExperimentProvider displayAgentType], MOPUBDisplayAgentTypeNativeSafari); -} - -- (void)testClickthroughExperimentSafariViewController { - NSDictionary * headers = @{ kClickthroughExperimentBrowserAgent: @"2"}; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; - XCTAssertEqual(config.clickthroughExperimentBrowserAgent, MOPUBDisplayAgentTypeSafariViewController); - XCTAssertEqual([MOPUBExperimentProvider displayAgentType], MOPUBDisplayAgentTypeSafariViewController); + XCTAssertEqual(((NSArray *)config.nativeVideoTrackers[MPVideoEventStart]).count, 2); + XCTAssertEqual(((NSArray *)config.nativeVideoTrackers[MPVideoEventFirstQuartile]).count, 2); + XCTAssertEqual(((NSArray *)config.nativeVideoTrackers[MPVideoEventMidpoint]).count, 2); + XCTAssertEqual(((NSArray *)config.nativeVideoTrackers[MPVideoEventThirdQuartile]).count, 2); + XCTAssertEqual(((NSArray *)config.nativeVideoTrackers[MPVideoEventComplete]).count, 2); } #pragma mark - Viewability @@ -208,7 +179,7 @@ - (void)testDisableAllViewability { // "X-Disable-Viewability": 3 // } NSDictionary * headers = @{ kViewabilityDisableMetadataKey: @"3" }; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil adType:MPAdTypeFullscreen]; XCTAssertNotNil(config); @@ -224,7 +195,7 @@ - (void)testDisableNoViewability { // "X-Disable-Viewability": 0 // } NSDictionary * headers = @{ kViewabilityDisableMetadataKey: @"0" }; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil adType:MPAdTypeFullscreen]; XCTAssertNotNil(config); @@ -240,7 +211,7 @@ - (void)testEnableAlreadyDisabledViewability { // "X-Disable-Viewability": 3 // } NSDictionary * headers = @{ kViewabilityDisableMetadataKey: @"3" }; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil adType:MPAdTypeFullscreen]; XCTAssertNotNil(config); @@ -255,7 +226,7 @@ - (void)testEnableAlreadyDisabledViewability { // "X-Disable-Viewability": 0 // } headers = @{ kViewabilityDisableMetadataKey: @"0" }; - config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; + config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil adType:MPAdTypeFullscreen]; XCTAssertNotNil(config); @@ -271,7 +242,7 @@ - (void)testInvalidViewabilityHeaderValue { // "X-Disable-Viewability": 3aaaa // } NSDictionary * headers = @{ kViewabilityDisableMetadataKey: @"aaaa" }; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil adType:MPAdTypeFullscreen]; XCTAssertNotNil(config); @@ -287,7 +258,7 @@ - (void)testEmptyViewabilityHeaderValue { // "X-Disable-Viewability": "" // } NSDictionary * headers = @{ kViewabilityDisableMetadataKey: @"" }; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil adType:MPAdTypeFullscreen]; XCTAssertNotNil(config); @@ -299,42 +270,42 @@ - (void)testEmptyViewabilityHeaderValue { - (void)testMinVisiblePixelsParseSuccess { NSDictionary *headers = @{ kNativeImpressionMinVisiblePixelsMetadataKey: @"50" }; - MPAdConfiguration *config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; + MPAdConfiguration *config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil adType:MPAdTypeFullscreen]; XCTAssertEqual(config.nativeImpressionMinVisiblePixels, 50.0); } - (void)testMinVisiblePixelsParseNoHeader { NSDictionary *headers = @{}; - MPAdConfiguration *config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; + MPAdConfiguration *config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil adType:MPAdTypeFullscreen]; XCTAssertEqual(config.nativeImpressionMinVisiblePixels, -1.0); } - (void)testMinVisiblePercentParseSuccess { NSDictionary *headers = @{ kNativeImpressionMinVisiblePercentMetadataKey: @"50" }; - MPAdConfiguration *config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; + MPAdConfiguration *config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil adType:MPAdTypeFullscreen]; XCTAssertEqual(config.nativeImpressionMinVisiblePercent, 50); } - (void)testMinVisiblePercentParseNoHeader { NSDictionary *headers = @{}; - MPAdConfiguration *config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; + MPAdConfiguration *config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil adType:MPAdTypeFullscreen]; XCTAssertEqual(config.nativeImpressionMinVisiblePercent, -1); } - (void)testMinVisibleTimeIntervalParseSuccess { NSDictionary *headers = @{ kNativeImpressionVisibleMsMetadataKey: @"1500" }; - MPAdConfiguration *config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; + MPAdConfiguration *config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil adType:MPAdTypeFullscreen]; XCTAssertEqual(config.nativeImpressionMinVisibleTimeInterval, 1.5); } - (void)testMinVisibleTimeIntervalParseNoHeader { NSDictionary *headers = @{}; - MPAdConfiguration *config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; + MPAdConfiguration *config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil adType:MPAdTypeFullscreen]; XCTAssertEqual(config.nativeImpressionMinVisibleTimeInterval, -1); } @@ -343,26 +314,26 @@ - (void)testMinVisibleTimeIntervalParseNoHeader { - (void)testVisibleImpressionHeader { NSDictionary * headers = @{ kBannerImpressionVisableMsMetadataKey: @"0", kBannerImpressionMinPixelMetadataKey:@"1"}; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil adType:MPAdTypeFullscreen]; XCTAssertEqual(config.impressionMinVisiblePixels, 1); XCTAssertEqual(config.impressionMinVisibleTimeInSec, 0); } - (void)testVisibleImpressionEnabled { NSDictionary * headers = @{ kBannerImpressionVisableMsMetadataKey: @"0", kBannerImpressionMinPixelMetadataKey:@"1"}; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil adType:MPAdTypeFullscreen]; XCTAssertTrue(config.visibleImpressionTrackingEnabled); } - (void)testVisibleImpressionEnabledNoHeader { NSDictionary * headers = @{}; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil adType:MPAdTypeFullscreen]; XCTAssertFalse(config.visibleImpressionTrackingEnabled); } - (void)testVisibleImpressionNotEnabled { NSDictionary * headers = @{kBannerImpressionVisableMsMetadataKey: @"0", kBannerImpressionMinPixelMetadataKey:@"0"}; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil adType:MPAdTypeFullscreen]; XCTAssertFalse(config.visibleImpressionTrackingEnabled); } @@ -370,7 +341,7 @@ - (void)testVisibleImpressionNotEnabled { - (void)testMultipleImpressionTrackingURLs { NSDictionary * headers = @{ kImpressionTrackersMetadataKey: @[@"https://google.com", @"https://mopub.com", @"https://twitter.com"] }; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil adType:MPAdTypeFullscreen]; XCTAssert(config.impressionTrackingURLs.count == 3); XCTAssert([config.impressionTrackingURLs containsObject:[NSURL URLWithString:@"https://google.com"]]); @@ -380,7 +351,7 @@ - (void)testMultipleImpressionTrackingURLs { - (void)testSingleImpressionTrackingURLIsFunctional { NSDictionary * headers = @{ kImpressionTrackerMetadataKey: @"https://twitter.com" }; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil adType:MPAdTypeFullscreen]; XCTAssert(config.impressionTrackingURLs.count == 1); XCTAssert([config.impressionTrackingURLs containsObject:[NSURL URLWithString:@"https://twitter.com"]]); @@ -391,7 +362,7 @@ - (void)testMultipleImpressionTrackingURLsTakesPriorityOverSingleURL { kImpressionTrackersMetadataKey: @[@"https://google.com", @"https://mopub.com"], kImpressionTrackerMetadataKey: @"https://twitter.com" }; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil adType:MPAdTypeFullscreen]; XCTAssert(config.impressionTrackingURLs.count == 2); XCTAssert([config.impressionTrackingURLs containsObject:[NSURL URLWithString:@"https://google.com"]]); @@ -401,7 +372,7 @@ - (void)testMultipleImpressionTrackingURLsTakesPriorityOverSingleURL { - (void)testLackOfImpressionTrackingURLResultsInNilArray { NSDictionary * headers = @{}; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil adType:MPAdTypeFullscreen]; XCTAssertNil(config.impressionTrackingURLs); } @@ -410,7 +381,7 @@ - (void)testMalformedURLsAreNotIncludedInAdConfiguration { NSDictionary * headers = @{ kImpressionTrackersMetadataKey: @[@"https://google.com", @"https://mopub.com", @"https://mopub.com/%%FAKEMACRO%%", @"absolutely not a URL"], }; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil adType:MPAdTypeFullscreen]; XCTAssert(config.impressionTrackingURLs.count == 2); XCTAssert([config.impressionTrackingURLs containsObject:[NSURL URLWithString:@"https://google.com"]]); XCTAssert([config.impressionTrackingURLs containsObject:[NSURL URLWithString:@"https://mopub.com"]]); @@ -423,7 +394,7 @@ - (void)testSingleValidURL { NSString * key = @"url"; NSDictionary * metadata = @{ @"url": url }; - MPAdConfiguration * dummyConfig = [[MPAdConfiguration alloc] initWithMetadata:nil data:nil]; + MPAdConfiguration * dummyConfig = [[MPAdConfiguration alloc] initWithMetadata:nil data:nil adType:MPAdTypeFullscreen]; NSArray * strings = [dummyConfig URLStringsFromMetadata:metadata forKey:key]; @@ -440,7 +411,7 @@ - (void)testMultipleValidURLs { NSString * key = @"url"; NSDictionary * metadata = @{ @"url": urlStrings }; - MPAdConfiguration * dummyConfig = [[MPAdConfiguration alloc] initWithMetadata:nil data:nil]; + MPAdConfiguration * dummyConfig = [[MPAdConfiguration alloc] initWithMetadata:nil data:nil adType:MPAdTypeFullscreen]; NSArray * strings = [dummyConfig URLStringsFromMetadata:metadata forKey:key]; @@ -459,7 +430,7 @@ - (void)testMultipleInvalidItems { NSString * key = @"url"; NSDictionary * metadata = @{ @"url": urlStrings }; - MPAdConfiguration * dummyConfig = [[MPAdConfiguration alloc] initWithMetadata:nil data:nil]; + MPAdConfiguration * dummyConfig = [[MPAdConfiguration alloc] initWithMetadata:nil data:nil adType:MPAdTypeFullscreen]; NSArray * strings = [dummyConfig URLStringsFromMetadata:metadata forKey:key]; XCTAssertNil(strings); @@ -473,7 +444,7 @@ - (void)testMultipleValidURLsWithMultipleInvalidItems { NSString * key = @"url"; NSDictionary * metadata = @{ @"url": urlStrings }; - MPAdConfiguration * dummyConfig = [[MPAdConfiguration alloc] initWithMetadata:nil data:nil]; + MPAdConfiguration * dummyConfig = [[MPAdConfiguration alloc] initWithMetadata:nil data:nil adType:MPAdTypeFullscreen]; NSArray * strings = [dummyConfig URLStringsFromMetadata:metadata forKey:key]; @@ -492,7 +463,7 @@ - (void)testSingleInvalidItem { NSString * key = @"url"; NSDictionary * metadata = @{ @"url": urlStrings }; - MPAdConfiguration * dummyConfig = [[MPAdConfiguration alloc] initWithMetadata:nil data:nil]; + MPAdConfiguration * dummyConfig = [[MPAdConfiguration alloc] initWithMetadata:nil data:nil adType:MPAdTypeFullscreen]; NSArray * strings = [dummyConfig URLStringsFromMetadata:metadata forKey:key]; XCTAssertNil(strings); @@ -506,7 +477,7 @@ - (void)testEmptyString { NSString * key = @"url"; NSDictionary * metadata = @{ @"url": url }; - MPAdConfiguration * dummyConfig = [[MPAdConfiguration alloc] initWithMetadata:nil data:nil]; + MPAdConfiguration * dummyConfig = [[MPAdConfiguration alloc] initWithMetadata:nil data:nil adType:MPAdTypeFullscreen]; NSArray * strings = [dummyConfig URLStringsFromMetadata:metadata forKey:key]; XCTAssertNil(strings); @@ -520,7 +491,7 @@ - (void)testInvalidUrlStringWontConvert { NSString * key = @"url"; NSDictionary * metadata = @{ @"url": url }; - MPAdConfiguration * dummyConfig = [[MPAdConfiguration alloc] initWithMetadata:nil data:nil]; + MPAdConfiguration * dummyConfig = [[MPAdConfiguration alloc] initWithMetadata:nil data:nil adType:MPAdTypeFullscreen]; NSArray * urls = [dummyConfig URLsFromMetadata:metadata forKey:key]; XCTAssertNil(urls); @@ -530,7 +501,7 @@ - (void)testInvalidUrlStringWontConvert { - (void)testSingleDefaultUrlBackwardsCompatibility { NSDictionary * metadata = @{ kAfterLoadUrlMetadataKey: @"https://google.com" }; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:metadata data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:metadata data:nil adType:MPAdTypeFullscreen]; NSArray * urls = [config afterLoadUrlsWithLoadDuration:0.0 loadResult:MPAfterLoadResultAdLoaded]; @@ -543,7 +514,7 @@ - (void)testNoDefaultUrlAndSingleSuccessUrlAndSingleFailureUrlWithLoadResultAdLo kAfterLoadSuccessUrlMetadataKey: @"https://google.com", kAfterLoadFailureUrlMetadataKey: @"https://twitter.com", }; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:metadata data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:metadata data:nil adType:MPAdTypeFullscreen]; NSArray * urls = [config afterLoadUrlsWithLoadDuration:0.0 loadResult:MPAfterLoadResultAdLoaded]; @@ -556,7 +527,7 @@ - (void)testNoDefaultUrlAndSingleSuccessUrlAndSingleFailureUrlWithLoadResultErro kAfterLoadSuccessUrlMetadataKey: @"https://google.com", kAfterLoadFailureUrlMetadataKey: @"https://twitter.com", }; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:metadata data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:metadata data:nil adType:MPAdTypeFullscreen]; NSArray * urls = [config afterLoadUrlsWithLoadDuration:0.0 loadResult:MPAfterLoadResultError]; @@ -569,7 +540,7 @@ - (void)testNoDefaultUrlAndSingleSuccessUrlAndSingleFailureUrlWithLoadResultMiss kAfterLoadSuccessUrlMetadataKey: @"https://google.com", kAfterLoadFailureUrlMetadataKey: @"https://twitter.com", }; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:metadata data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:metadata data:nil adType:MPAdTypeFullscreen]; NSArray * urls = [config afterLoadUrlsWithLoadDuration:0.0 loadResult:MPAfterLoadResultMissingAdapter]; @@ -582,7 +553,7 @@ - (void)testNoDefaultUrlAndSingleSuccessUrlAndSingleFailureUrlWithLoadResultTime kAfterLoadSuccessUrlMetadataKey: @"https://google.com", kAfterLoadFailureUrlMetadataKey: @"https://twitter.com", }; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:metadata data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:metadata data:nil adType:MPAdTypeFullscreen]; NSArray * urls = [config afterLoadUrlsWithLoadDuration:0.0 loadResult:MPAfterLoadResultTimeout]; @@ -595,7 +566,7 @@ - (void)testSingleDefaultUrlAndSingleSuccessWithLoadResultAdLoaded { kAfterLoadUrlMetadataKey: @"https://google.com", kAfterLoadSuccessUrlMetadataKey: @"https://twitter.com", }; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:metadata data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:metadata data:nil adType:MPAdTypeFullscreen]; NSArray * urls = [config afterLoadUrlsWithLoadDuration:0.0 loadResult:MPAfterLoadResultAdLoaded]; @@ -609,7 +580,7 @@ - (void)testSingleDefaultUrlAndSingleSuccessWithLoadResultError { kAfterLoadUrlMetadataKey: @"https://google.com", kAfterLoadSuccessUrlMetadataKey: @"https://twitter.com", }; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:metadata data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:metadata data:nil adType:MPAdTypeFullscreen]; NSArray * urls = [config afterLoadUrlsWithLoadDuration:0.0 loadResult:MPAfterLoadResultError]; @@ -622,7 +593,7 @@ - (void)testSingleDefaultUrlAndSingleSuccessWithLoadResultMissingAdapter { kAfterLoadUrlMetadataKey: @"https://google.com", kAfterLoadSuccessUrlMetadataKey: @"https://twitter.com", }; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:metadata data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:metadata data:nil adType:MPAdTypeFullscreen]; NSArray * urls = [config afterLoadUrlsWithLoadDuration:0.0 loadResult:MPAfterLoadResultMissingAdapter]; @@ -635,7 +606,7 @@ - (void)testSingleDefaultUrlAndSingleSuccessWithLoadResultTimeout { kAfterLoadUrlMetadataKey: @"https://google.com", kAfterLoadSuccessUrlMetadataKey: @"https://twitter.com", }; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:metadata data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:metadata data:nil adType:MPAdTypeFullscreen]; NSArray * urls = [config afterLoadUrlsWithLoadDuration:0.0 loadResult:MPAfterLoadResultTimeout]; @@ -648,7 +619,7 @@ - (void)testSingleDefaultUrlAndSingleFailureWithLoadResultAdLoaded { kAfterLoadUrlMetadataKey: @"https://google.com", kAfterLoadFailureUrlMetadataKey: @"https://twitter.com", }; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:metadata data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:metadata data:nil adType:MPAdTypeFullscreen]; NSArray * urls = [config afterLoadUrlsWithLoadDuration:0.0 loadResult:MPAfterLoadResultAdLoaded]; @@ -661,7 +632,7 @@ - (void)testSingleDefaultUrlAndSingleFailureWithLoadResultError { kAfterLoadUrlMetadataKey: @"https://google.com", kAfterLoadFailureUrlMetadataKey: @"https://twitter.com", }; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:metadata data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:metadata data:nil adType:MPAdTypeFullscreen]; NSArray * urls = [config afterLoadUrlsWithLoadDuration:0.0 loadResult:MPAfterLoadResultError]; @@ -675,7 +646,7 @@ - (void)testSingleDefaultUrlAndSingleFailureWithLoadResultMissingAdapter { kAfterLoadUrlMetadataKey: @"https://google.com", kAfterLoadFailureUrlMetadataKey: @"https://twitter.com", }; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:metadata data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:metadata data:nil adType:MPAdTypeFullscreen]; NSArray * urls = [config afterLoadUrlsWithLoadDuration:0.0 loadResult:MPAfterLoadResultMissingAdapter]; @@ -689,7 +660,7 @@ - (void)testSingleDefaultUrlAndSingleFailureWithLoadResultTimeout { kAfterLoadUrlMetadataKey: @"https://google.com", kAfterLoadFailureUrlMetadataKey: @"https://twitter.com", }; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:metadata data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:metadata data:nil adType:MPAdTypeFullscreen]; NSArray * urls = [config afterLoadUrlsWithLoadDuration:0.0 loadResult:MPAfterLoadResultTimeout]; @@ -704,7 +675,7 @@ - (void)testSingleDefaultUrlAndSingleSuccessUrlAndSingleFailureUrlWithLoadResult kAfterLoadSuccessUrlMetadataKey: @"https://testurl.com", kAfterLoadFailureUrlMetadataKey: @"https://twitter.com", }; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:metadata data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:metadata data:nil adType:MPAdTypeFullscreen]; NSArray * urls = [config afterLoadUrlsWithLoadDuration:0.0 loadResult:MPAfterLoadResultAdLoaded]; @@ -720,7 +691,7 @@ - (void)testSingleDefaultUrlAndSingleSuccessUrlAndSingleFailureUrlWithLoadResult kAfterLoadSuccessUrlMetadataKey: @"https://testurl.com", kAfterLoadFailureUrlMetadataKey: @"https://twitter.com", }; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:metadata data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:metadata data:nil adType:MPAdTypeFullscreen]; NSArray * urls = [config afterLoadUrlsWithLoadDuration:0.0 loadResult:MPAfterLoadResultError]; @@ -736,7 +707,7 @@ - (void)testSingleDefaultUrlAndSingleSuccessUrlAndSingleFailureUrlWithLoadResult kAfterLoadSuccessUrlMetadataKey: @"https://testurl.com", kAfterLoadFailureUrlMetadataKey: @"https://twitter.com", }; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:metadata data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:metadata data:nil adType:MPAdTypeFullscreen]; NSArray * urls = [config afterLoadUrlsWithLoadDuration:0.0 loadResult:MPAfterLoadResultMissingAdapter]; @@ -752,7 +723,7 @@ - (void)testSingleDefaultUrlAndSingleSuccessUrlAndSingleFailureUrlWithLoadResult kAfterLoadSuccessUrlMetadataKey: @"https://testurl.com", kAfterLoadFailureUrlMetadataKey: @"https://twitter.com", }; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:metadata data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:metadata data:nil adType:MPAdTypeFullscreen]; NSArray * urls = [config afterLoadUrlsWithLoadDuration:0.0 loadResult:MPAfterLoadResultTimeout]; @@ -767,7 +738,7 @@ - (void)testMultipleDefaultUrlsAndMultipleSuccessUrlsWithLoadResultAdLoaded { kAfterLoadUrlMetadataKey: @[@"https://google.com", @"https://test.com"], kAfterLoadSuccessUrlMetadataKey: @[@"https://twitter.com", @"https://test2.com"], }; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:metadata data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:metadata data:nil adType:MPAdTypeFullscreen]; NSArray * urls = [config afterLoadUrlsWithLoadDuration:0.0 loadResult:MPAfterLoadResultAdLoaded]; @@ -783,7 +754,7 @@ - (void)testMultipleDefaultUrlsAndMultipleSuccessUrlsWithLoadResultError { kAfterLoadUrlMetadataKey: @[@"https://google.com", @"https://test.com"], kAfterLoadSuccessUrlMetadataKey: @[@"https://twitter.com", @"https://test2.com"], }; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:metadata data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:metadata data:nil adType:MPAdTypeFullscreen]; NSArray * urls = [config afterLoadUrlsWithLoadDuration:0.0 loadResult:MPAfterLoadResultError]; @@ -799,7 +770,7 @@ - (void)testMultipleDefaultUrlsAndMultipleSuccessUrlsWithLoadResultMissingAdapte kAfterLoadUrlMetadataKey: @[@"https://google.com", @"https://test.com"], kAfterLoadSuccessUrlMetadataKey: @[@"https://twitter.com", @"https://test2.com"], }; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:metadata data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:metadata data:nil adType:MPAdTypeFullscreen]; NSArray * urls = [config afterLoadUrlsWithLoadDuration:0.0 loadResult:MPAfterLoadResultMissingAdapter]; @@ -815,7 +786,7 @@ - (void)testMultipleDefaultUrlsAndMultipleSuccessUrlsWithLoadResultTimeout { kAfterLoadUrlMetadataKey: @[@"https://google.com", @"https://test.com"], kAfterLoadSuccessUrlMetadataKey: @[@"https://twitter.com", @"https://test2.com"], }; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:metadata data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:metadata data:nil adType:MPAdTypeFullscreen]; NSArray * urls = [config afterLoadUrlsWithLoadDuration:0.0 loadResult:MPAfterLoadResultTimeout]; @@ -831,7 +802,7 @@ - (void)testMultipleDefaultUrlsAndMultipleFailureUrlsWithLoadResultAdLoaded { kAfterLoadUrlMetadataKey: @[@"https://google.com", @"https://test.com"], kAfterLoadFailureUrlMetadataKey: @[@"https://twitter.com", @"https://test2.com"], }; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:metadata data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:metadata data:nil adType:MPAdTypeFullscreen]; NSArray * urls = [config afterLoadUrlsWithLoadDuration:0.0 loadResult:MPAfterLoadResultAdLoaded]; @@ -847,7 +818,7 @@ - (void)testMultipleDefaultUrlsAndMultipleFailureUrlsWithLoadResultError { kAfterLoadUrlMetadataKey: @[@"https://google.com", @"https://test.com"], kAfterLoadFailureUrlMetadataKey: @[@"https://twitter.com", @"https://test2.com"], }; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:metadata data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:metadata data:nil adType:MPAdTypeFullscreen]; NSArray * urls = [config afterLoadUrlsWithLoadDuration:0.0 loadResult:MPAfterLoadResultError]; @@ -863,7 +834,7 @@ - (void)testMultipleDefaultUrlsAndMultipleFailureUrlsWithLoadResultMissingAdapte kAfterLoadUrlMetadataKey: @[@"https://google.com", @"https://test.com"], kAfterLoadFailureUrlMetadataKey: @[@"https://twitter.com", @"https://test2.com"], }; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:metadata data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:metadata data:nil adType:MPAdTypeFullscreen]; NSArray * urls = [config afterLoadUrlsWithLoadDuration:0.0 loadResult:MPAfterLoadResultMissingAdapter]; @@ -879,7 +850,7 @@ - (void)testMultipleDefaultUrlsAndMultipleFailureUrlsWithLoadResultTimeout { kAfterLoadUrlMetadataKey: @[@"https://google.com", @"https://test.com"], kAfterLoadFailureUrlMetadataKey: @[@"https://twitter.com", @"https://test2.com"], }; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:metadata data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:metadata data:nil adType:MPAdTypeFullscreen]; NSArray * urls = [config afterLoadUrlsWithLoadDuration:0.0 loadResult:MPAfterLoadResultTimeout]; @@ -896,7 +867,7 @@ - (void)testMultipleDefaultUrlsAndMultipleSuccessUrlsAndMultipleFailureUrlsWithL kAfterLoadSuccessUrlMetadataKey: @[@"https://fakeurl.com", @"https://fakeurl2.com"], kAfterLoadFailureUrlMetadataKey: @[@"https://twitter.com", @"https://test2.com"], }; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:metadata data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:metadata data:nil adType:MPAdTypeFullscreen]; NSArray * urls = [config afterLoadUrlsWithLoadDuration:0.0 loadResult:MPAfterLoadResultAdLoaded]; @@ -915,7 +886,7 @@ - (void)testMultipleDefaultUrlsAndMultipleSuccessUrlsAndMultipleFailureUrlsWithL kAfterLoadSuccessUrlMetadataKey: @[@"https://fakeurl.com", @"https://fakeurl2.com"], kAfterLoadFailureUrlMetadataKey: @[@"https://twitter.com", @"https://test2.com"], }; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:metadata data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:metadata data:nil adType:MPAdTypeFullscreen]; NSArray * urls = [config afterLoadUrlsWithLoadDuration:0.0 loadResult:MPAfterLoadResultError]; @@ -934,7 +905,7 @@ - (void)testMultipleDefaultUrlsAndMultipleSuccessUrlsAndMultipleFailureUrlsWithL kAfterLoadSuccessUrlMetadataKey: @[@"https://fakeurl.com", @"https://fakeurl2.com"], kAfterLoadFailureUrlMetadataKey: @[@"https://twitter.com", @"https://test2.com"], }; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:metadata data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:metadata data:nil adType:MPAdTypeFullscreen]; NSArray * urls = [config afterLoadUrlsWithLoadDuration:0.0 loadResult:MPAfterLoadResultMissingAdapter]; @@ -953,7 +924,7 @@ - (void)testMultipleDefaultUrlsAndMultipleSuccessUrlsAndMultipleFailureUrlsWithL kAfterLoadSuccessUrlMetadataKey: @[@"https://fakeurl.com", @"https://fakeurl2.com"], kAfterLoadFailureUrlMetadataKey: @[@"https://twitter.com", @"https://test2.com"], }; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:metadata data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:metadata data:nil adType:MPAdTypeFullscreen]; NSArray * urls = [config afterLoadUrlsWithLoadDuration:0.0 loadResult:MPAfterLoadResultTimeout]; diff --git a/MoPubSDKTests/MPAdImpressionTimer+Testing.h b/MoPubSDKTests/MPAdImpressionTimer+Testing.h index 66318c533..1264725fb 100644 --- a/MoPubSDKTests/MPAdImpressionTimer+Testing.h +++ b/MoPubSDKTests/MPAdImpressionTimer+Testing.h @@ -1,7 +1,7 @@ // // MPAdImpressionTimer+Testing.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/MPAdImpressionTimer+Testing.m b/MoPubSDKTests/MPAdImpressionTimer+Testing.m index bb605a565..758978ff9 100644 --- a/MoPubSDKTests/MPAdImpressionTimer+Testing.m +++ b/MoPubSDKTests/MPAdImpressionTimer+Testing.m @@ -1,7 +1,7 @@ // // MPAdImpressionTimer+Testing.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/MPAdImpressionTimerTests.m b/MoPubSDKTests/MPAdImpressionTimerTests.m index d6953bbca..607159b0b 100644 --- a/MoPubSDKTests/MPAdImpressionTimerTests.m +++ b/MoPubSDKTests/MPAdImpressionTimerTests.m @@ -1,7 +1,7 @@ // // MPAdImpressionTimerTests.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/MPAdServerCommunicator+Testing.h b/MoPubSDKTests/MPAdServerCommunicator+Testing.h index 575c47dcb..abafbdcd1 100644 --- a/MoPubSDKTests/MPAdServerCommunicator+Testing.h +++ b/MoPubSDKTests/MPAdServerCommunicator+Testing.h @@ -1,18 +1,24 @@ // // MPAdServerCommunicator+Testing.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // #import "MPAdServerCommunicator.h" +#import "MPRealTimeTimer.h" + @interface MPAdServerCommunicator (Testing) @property (nonatomic, assign, readwrite) BOOL loading; +@property (nonatomic, readonly) BOOL isRateLimited; + // Expose private methods from `MPAdServerCommunicator` - (void)didFinishLoadingWithData:(NSData *)data; +- (void)didFailWithError:(NSError *)error; +- (NSArray *)getFlattenJsonResponses:(NSDictionary *)json keys:(NSArray *)keys; @end diff --git a/MoPubSDKTests/MPAdServerCommunicator+Testing.m b/MoPubSDKTests/MPAdServerCommunicator+Testing.m index 7e6da0c4d..6679de7cd 100644 --- a/MoPubSDKTests/MPAdServerCommunicator+Testing.m +++ b/MoPubSDKTests/MPAdServerCommunicator+Testing.m @@ -1,7 +1,7 @@ // // MPAdServerCommunicator+Testing.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -13,6 +13,7 @@ @implementation MPAdServerCommunicator (Testing) @dynamic loading; +@dynamic isRateLimited; @end #pragma clang diagnostic pop diff --git a/MoPubSDKTests/MPAdServerCommunicatorTests.m b/MoPubSDKTests/MPAdServerCommunicatorTests.m index 0c36f2111..952b53b1d 100644 --- a/MoPubSDKTests/MPAdServerCommunicatorTests.m +++ b/MoPubSDKTests/MPAdServerCommunicatorTests.m @@ -1,30 +1,35 @@ // // MPAdServerCommunicatorTests.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // #import #import "MPAdServerCommunicator.h" -#import "MPAdserverCommunicatorDelegateHandler.h" +#import "MPAdServerCommunicatorDelegateHandler.h" #import "MPAdServerCommunicator+Testing.h" #import "MPAdServerKeys.h" #import "MPConsentManager+Testing.h" #import "MPError.h" +#import "MPRateLimitManager.h" static NSTimeInterval const kTimeoutTime = 0.5; +static NSUInteger const kDefaultRateLimitTimeMs = 400; +static NSString * const kDefaultRateLimitReason = @"Reason"; // Constants are from `MPAdServerCommunicator.m` static NSString * const kAdResponsesKey = @"ad-responses"; static NSString * const kAdResonsesMetadataKey = @"metadata"; static NSString * const kAdResonsesContentKey = @"content"; +static NSString * const kIsWhitelistedUserDefaultsKey = @"com.mopub.mopub-ios-sdk.is.whitelisted"; + @interface MPAdServerCommunicatorTests : XCTestCase @property (nonatomic, strong) MPAdServerCommunicator *communicator; -@property (nonatomic, strong) MPAdserverCommunicatorDelegateHandler *communicatorDelegateHandler; +@property (nonatomic, strong) MPAdServerCommunicatorDelegateHandler *communicatorDelegateHandler; @end @@ -35,7 +40,7 @@ - (void)setUp { [[MPConsentManager sharedManager] setUpConsentManagerForTesting]; - self.communicatorDelegateHandler = [[MPAdserverCommunicatorDelegateHandler alloc] init]; + self.communicatorDelegateHandler = [[MPAdServerCommunicatorDelegateHandler alloc] init]; self.communicator = [[MPAdServerCommunicator alloc] initWithDelegate:self.communicatorDelegateHandler]; self.communicator.loading = YES; } @@ -47,6 +52,55 @@ - (void)tearDown { [super tearDown]; } +#pragma mark - JSON Flattening + +- (void)testJSONFlattening { + // The response data is a JSON payload conforming to the structure: + // { + // "ad-responses": [ + // { + // "metadata": { + // "adm": "some advanced bidding payload", + // "x-ad-timeout-ms": 5000, + // "x-adtype": "rewarded_video", + // }, + // "content": "Ad markup goes here" + // } + // ], + // "x-other-key": "some value", + // "x-next-url": "https:// ..." + // } + + // Set up a valid response with three configurations + NSDictionary * responseDataDict = @{ + kAdResponsesKey: @[ + @{ kAdResonsesMetadataKey: @{ @"adm": @"advanced bidding markup" }, kAdResonsesContentKey: @"mopub ad content" }, + @{ kAdResonsesMetadataKey: @{ @"x-adtype": @"banner" }, kAdResonsesContentKey: @"mopub ad content" }, + @{ kAdResonsesMetadataKey: @{ @"x-adtype": @"banner" }, kAdResonsesContentKey: @"mopub ad content" }, + ], + kNextUrlMetadataKey: @"https://www.mopub.com", + @"testing_1": @"testing_1", + @"testing_2": @"testing_2" + }; + + NSArray * topLevelJsonKeys = @[kNextUrlMetadataKey, @"testing_1", @"testing_2"]; + NSArray * responses = [self.communicator getFlattenJsonResponses:responseDataDict keys:topLevelJsonKeys]; + + XCTAssertNotNil(responses); + XCTAssert(responses.count == 3); + + for (NSDictionary * response in responses) { + XCTAssertNotNil(response); + + NSDictionary * metadata = response[kAdResonsesMetadataKey]; + XCTAssertNotNil(metadata); + + XCTAssert([metadata[kNextUrlMetadataKey] isEqualToString:@"https://www.mopub.com"]); + XCTAssert([metadata[@"testing_1"] isEqualToString:@"testing_1"]); + XCTAssert([metadata[@"testing_2"] isEqualToString:@"testing_2"]); + } +} + #pragma mark - Multiple Responses - (void)testMultipleAdResponses { @@ -294,6 +348,7 @@ - (void)testEmptyDataResponsesError { - (void)testParseInvalidateConsent { // Initially set consent state to consented + [NSUserDefaults.standardUserDefaults setBool:YES forKey:kIsWhitelistedUserDefaultsKey]; [MPConsentManager.sharedManager grantConsent]; XCTAssert(MPConsentManager.sharedManager.currentStatus == MPConsentStatusConsented); @@ -332,6 +387,7 @@ - (void)testParseInvalidateConsent { - (void)testParseReacquireConsent { // Initially set consent state to consented + [NSUserDefaults.standardUserDefaults setBool:YES forKey:kIsWhitelistedUserDefaultsKey]; [MPConsentManager.sharedManager grantConsent]; XCTAssert(MPConsentManager.sharedManager.currentStatus == MPConsentStatusConsented); XCTAssertFalse(MPConsentManager.sharedManager.isConsentNeeded); @@ -372,6 +428,7 @@ - (void)testParseReacquireConsent { - (void)testParseForceExplicitNoConsent { // Initially set consent state to consented + [NSUserDefaults.standardUserDefaults setBool:YES forKey:kIsWhitelistedUserDefaultsKey]; [MPConsentManager.sharedManager grantConsent]; XCTAssert(MPConsentManager.sharedManager.currentStatus == MPConsentStatusConsented); @@ -448,6 +505,7 @@ - (void)testParseForceGDPRApplies { - (void)testConsentForceExplicitNoTakesPriorityOverInvalidateConsent { // Initially set consent state to consented + [NSUserDefaults.standardUserDefaults setBool:YES forKey:kIsWhitelistedUserDefaultsKey]; [MPConsentManager.sharedManager grantConsent]; XCTAssert(MPConsentManager.sharedManager.currentStatus == MPConsentStatusConsented); @@ -487,6 +545,7 @@ - (void)testConsentForceExplicitNoTakesPriorityOverInvalidateConsent { - (void)testConsentForceExplicitNoDoesNothingWhenMalformed { // Initially set consent state to consented + [NSUserDefaults.standardUserDefaults setBool:YES forKey:kIsWhitelistedUserDefaultsKey]; [MPConsentManager.sharedManager grantConsent]; XCTAssert(MPConsentManager.sharedManager.currentStatus == MPConsentStatusConsented); @@ -525,6 +584,7 @@ - (void)testConsentForceExplicitNoDoesNothingWhenMalformed { - (void)testConsentInvalidateConsentDoesNothingWhenMalformed { // Initially set consent state to consented + [NSUserDefaults.standardUserDefaults setBool:YES forKey:kIsWhitelistedUserDefaultsKey]; [MPConsentManager.sharedManager grantConsent]; XCTAssert(MPConsentManager.sharedManager.currentStatus == MPConsentStatusConsented); @@ -561,4 +621,408 @@ - (void)testConsentInvalidateConsentDoesNothingWhenMalformed { XCTAssert(MPConsentManager.sharedManager.currentStatus == MPConsentStatusConsented); } +- (void)testAutomaticAdUnitIdPopulation { + // Garbage response data + NSDictionary * responseDataDict = @{ + kAdResponsesKey: @[ @{ + kAdResonsesMetadataKey: @{ + @"x-adtype": @"clear", + @"x-backfill": @"clear", + @"x-refreshtime": @(30), + }, + kAdResonsesContentKey: @"" + }, ] + }; + NSData * garbageResponseData = [NSJSONSerialization dataWithJSONObject:responseDataDict + options:0 + error:nil]; + + MPConsentManager * manager = MPConsentManager.sharedManager; + // Clear out any cached adunit state + [manager setUpConsentManagerForTesting]; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnonnull" + // Intentially set the explicitly marked `nonnull` property to `nil` to + // simulate an uninitialized state. + manager.adUnitIdUsedForConsent = nil; +#pragma clang diagnostic pop + XCTAssertNil(manager.adUnitIdUsedForConsent); + + // Simulate a successful ad load + NSString * adunitID = @"extremely not an actual adunit ID"; + + self.communicatorDelegateHandler.adUnitIdForAdServerCommunicator = ^NSString *(MPAdServerCommunicator *adServerCommunicator) { + return adunitID; + }; + + [self.communicator didFinishLoadingWithData:garbageResponseData]; + + // Check to make sure the adunit ID populated + XCTAssert([manager.adUnitIdUsedForConsent isEqualToString:adunitID]); + // Check to make sure the adunit ID is cached + NSString * cachedString = [NSUserDefaults.standardUserDefaults stringForKey:kAdUnitIdUsedForConsentStorageKey]; + XCTAssert([cachedString isEqualToString:adunitID]); +} + +- (void)testAutomaticAdUnitIdPopulationDoesNotOverwrite { + // Garbage response data + NSDictionary * responseDataDict = @{ + kAdResponsesKey: @[ @{ + kAdResonsesMetadataKey: @{ + @"x-adtype": @"clear", + @"x-backfill": @"clear", + @"x-refreshtime": @(30), + }, + kAdResonsesContentKey: @"" + }, ] + }; + NSData * garbageResponseData = [NSJSONSerialization dataWithJSONObject:responseDataDict + options:0 + error:nil]; + + MPConsentManager * manager = MPConsentManager.sharedManager; + // Clear out any cached adunit state + [manager setUpConsentManagerForTesting]; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnonnull" + // Intentially set the explicitly marked `nonnull` property to `nil` to + // simulate an uninitialized state. + manager.adUnitIdUsedForConsent = nil; +#pragma clang diagnostic pop + XCTAssertNil(manager.adUnitIdUsedForConsent); + + // Simulate a successful ad load + NSString * adunitID = @"extremely not an actual adunit ID"; + + self.communicatorDelegateHandler.adUnitIdForAdServerCommunicator = ^NSString *(MPAdServerCommunicator *adServerCommunicator) { + return adunitID; + }; + + [self.communicator didFinishLoadingWithData:garbageResponseData]; + + // Check to make sure the adunit ID populated + XCTAssert([manager.adUnitIdUsedForConsent isEqualToString:adunitID]); + // Check to make sure the adunit ID is cached + NSString * cachedString = [NSUserDefaults.standardUserDefaults stringForKey:kAdUnitIdUsedForConsentStorageKey]; + XCTAssert([cachedString isEqualToString:adunitID]); + + + // Make a new adunit ID and see if that gets set + NSString * newAdunitID = @"still not an adunit ID"; + self.communicatorDelegateHandler.adUnitIdForAdServerCommunicator = ^NSString *(MPAdServerCommunicator *adServerCommunicator) { + return newAdunitID; + }; + [self.communicator didFinishLoadingWithData:garbageResponseData]; + + // Check state + XCTAssertFalse([manager.adUnitIdUsedForConsent isEqualToString:newAdunitID]); + XCTAssert([manager.adUnitIdUsedForConsent isEqualToString:adunitID]); + XCTAssert([cachedString isEqualToString:adunitID]); +} + +- (void)testAutomaticAdUnitIDPopulationDoesNotOccurOnFailure { + MPConsentManager * manager = MPConsentManager.sharedManager; + // Clear out any cached adunit state + [manager setUpConsentManagerForTesting]; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnonnull" + // Intentially set the explicitly marked `nonnull` property to `nil` to + // simulate an uninitialized state. + manager.adUnitIdUsedForConsent = nil; +#pragma clang diagnostic pop + XCTAssertNil(manager.adUnitIdUsedForConsent); + + // Simulate an unsuccessful ad load + NSString * adunitID = @"extremely not an actual adunit ID"; + self.communicatorDelegateHandler.adUnitIdForAdServerCommunicator = ^NSString *(MPAdServerCommunicator *adServerCommunicator) { + return adunitID; + }; + [self.communicator didFailWithError:nil]; + + // Check to make sure the adunit ID is not populated + XCTAssertNil(manager.adUnitIdUsedForConsent); + // Check to make sure the adunit ID is cached + NSString * cachedString = [NSUserDefaults.standardUserDefaults stringForKey:kAdUnitIdUsedForConsentStorageKey]; + XCTAssertNil(cachedString); +} + +#pragma mark - Rate Limiting Tests + +- (void)testRateLimitTimerSuccessfullySetOnClearResponseWithBackoffKeyWithoutReason { + NSDictionary * responseDataDict = @{ + kBackoffMsKey: @(kDefaultRateLimitTimeMs), + kAdResponsesKey: @[ @{ + kAdResonsesMetadataKey: @{ + @"x-adtype": @"clear", + @"x-backfill": @"clear", + @"x-refreshtime": @(30), + }, + kAdResonsesContentKey: @"" + }, ] + }; + NSData * jsonData = [NSJSONSerialization dataWithJSONObject:responseDataDict + options:0 + error:nil]; + + XCTestExpectation * waitForRateLimit = [self expectationWithDescription:@"Wait for rate limit to end"]; + XCTestExpectation * waitForDelegate = [self expectationWithDescription:@"Wait for failure delegate"]; + + __block BOOL didFail = NO; + __block NSError * didFailError = nil; + + self.communicatorDelegateHandler.communicatorDidFailWithError = ^(NSError * error){ + didFail = YES; + didFailError = error; + + [waitForDelegate fulfill]; + }; + + self.communicatorDelegateHandler.adUnitIdForAdServerCommunicator = ^(MPAdServerCommunicator * communicator){ + return @"testRateLimitTimerSuccessfullySetOnClearResponseWithBackoffKeyWithoutReason"; + }; + + // Load data (set rate limit timer) + [self.communicator didFinishLoadingWithData:jsonData]; + + // Attempt URL request (see if rate limit timer blocks it) + [self.communicator loadURL:[NSURL URLWithString:@"https://google.com"]]; + + BOOL isRateLimited = self.communicator.isRateLimited; + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(kDefaultRateLimitTimeMs * NSEC_PER_MSEC)), dispatch_get_main_queue(), ^{ + [waitForRateLimit fulfill]; + + // Did the rate limit timer get set + XCTAssertTrue(isRateLimited); + XCTAssertEqual(kDefaultRateLimitTimeMs, [[MPRateLimitManager sharedInstance] lastRateLimitMillisecondsForAdUnitId:self.communicatorDelegateHandler.adUnitIdForAdServerCommunicator(nil)]); + XCTAssertNil([[MPRateLimitManager sharedInstance] lastRateLimitReasonForAdUnitId:self.communicatorDelegateHandler.adUnitIdForAdServerCommunicator(nil)]); + + // Did the attempt at a request fail + XCTAssertTrue(didFail); + XCTAssertEqual(didFailError.code, MOPUBErrorTooManyRequests); + }); + + [self waitForExpectations:@[waitForRateLimit, waitForDelegate] timeout:kTimeoutTime]; +} + +- (void)testRateLimitTimerSuccessfullySetOnClearResponseWithBackoffKeyWithReason { + NSDictionary * responseDataDict = @{ + kBackoffMsKey: @(kDefaultRateLimitTimeMs), + kBackoffReasonKey: kDefaultRateLimitReason, + kAdResponsesKey: @[ @{ + kAdResonsesMetadataKey: @{ + @"x-adtype": @"clear", + @"x-backfill": @"clear", + @"x-refreshtime": @(30), + }, + kAdResonsesContentKey: @"" + }, ] + }; + NSData * jsonData = [NSJSONSerialization dataWithJSONObject:responseDataDict + options:0 + error:nil]; + + XCTestExpectation * waitForRateLimit = [self expectationWithDescription:@"Wait for rate limit to end"]; + XCTestExpectation * waitForDelegate = [self expectationWithDescription:@"Wait for failure delegate"]; + + __block BOOL didFail = NO; + __block NSError * didFailError = nil; + + self.communicatorDelegateHandler.communicatorDidFailWithError = ^(NSError * error){ + didFail = YES; + didFailError = error; + + [waitForDelegate fulfill]; + }; + + self.communicatorDelegateHandler.adUnitIdForAdServerCommunicator = ^(MPAdServerCommunicator * communicator){ + return @"testRateLimitTimerSuccessfullySetOnClearResponseWithBackoffKeyWithReason"; + }; + + // Load data (set rate limit timer) + [self.communicator didFinishLoadingWithData:jsonData]; + + // Attempt URL request (see if rate limit timer blocks it) + [self.communicator loadURL:[NSURL URLWithString:@"https://google.com"]]; + + BOOL isRateLimited = self.communicator.isRateLimited; + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(kDefaultRateLimitTimeMs * NSEC_PER_MSEC)), dispatch_get_main_queue(), ^{ + [waitForRateLimit fulfill]; + + // Did the rate limit timer get set + XCTAssertTrue(isRateLimited); + XCTAssertEqual(kDefaultRateLimitTimeMs, [[MPRateLimitManager sharedInstance] lastRateLimitMillisecondsForAdUnitId:self.communicatorDelegateHandler.adUnitIdForAdServerCommunicator(nil)]); + XCTAssert([kDefaultRateLimitReason isEqualToString:[[MPRateLimitManager sharedInstance] lastRateLimitReasonForAdUnitId:self.communicatorDelegateHandler.adUnitIdForAdServerCommunicator(nil)]]); + + // Did the attempt at a request fail + XCTAssertTrue(didFail); + XCTAssertEqual(didFailError.code, MOPUBErrorTooManyRequests); + }); + + [self waitForExpectations:@[waitForRateLimit, waitForDelegate] timeout:kTimeoutTime]; +} + +- (void)testRateLimitTimerIsNotSetOnClearResponseWithNoBackoffKey { + NSDictionary *responseDataDict = @{ + kAdResponsesKey: @[ @{ + kAdResonsesMetadataKey: @{ + @"x-adtype": @"clear", + @"x-backfill": @"clear", + @"x-refreshtime": @(30), + }, + kAdResonsesContentKey: @"" + }, ] + }; + NSData *jsonData = [NSJSONSerialization dataWithJSONObject:responseDataDict + options:0 + error:nil]; + + self.communicatorDelegateHandler.adUnitIdForAdServerCommunicator = ^(MPAdServerCommunicator * communicator){ + return @"testRateLimitTimerIsNotSetOnClearResponseWithNoBackoffKey"; + }; + + [self.communicator didFinishLoadingWithData:jsonData]; + + XCTAssertFalse(self.communicator.isRateLimited); + XCTAssertEqual(0, [[MPRateLimitManager sharedInstance] lastRateLimitMillisecondsForAdUnitId:self.communicatorDelegateHandler.adUnitIdForAdServerCommunicator(nil)]); + XCTAssertNil([[MPRateLimitManager sharedInstance] lastRateLimitReasonForAdUnitId:self.communicatorDelegateHandler.adUnitIdForAdServerCommunicator(nil)]); +} + +- (void)testRateLimitTimerIsSetOnMraidResponseWithReason { + NSDictionary *responseDataDict = @{ + kBackoffMsKey: @(kDefaultRateLimitTimeMs), + kBackoffReasonKey: kDefaultRateLimitReason, + kAdResponsesKey: @[ @{ + kAdResonsesMetadataKey: @{ + @"x-adtype": @"mraid", + }, + kAdResonsesContentKey: @"" + }, ] + }; + NSData *jsonData = [NSJSONSerialization dataWithJSONObject:responseDataDict + options:0 + error:nil]; + + XCTestExpectation * waitForRateLimit = [self expectationWithDescription:@"Wait for rate limit to end"]; + XCTestExpectation * waitForDelegate = [self expectationWithDescription:@"Wait for failure delegate"]; + + __block BOOL didFail = NO; + __block NSError * didFailError = nil; + + self.communicatorDelegateHandler.communicatorDidFailWithError = ^(NSError * error){ + didFail = YES; + didFailError = error; + + [waitForDelegate fulfill]; + }; + + self.communicatorDelegateHandler.adUnitIdForAdServerCommunicator = ^(MPAdServerCommunicator * communicator){ + return @"testRateLimitTimerIsSetOnMraidResponseWithReason"; + }; + + // Load data (set rate limit timer) + [self.communicator didFinishLoadingWithData:jsonData]; + + // Attempt URL request (see if rate limit timer blocks it) + [self.communicator loadURL:[NSURL URLWithString:@"https://google.com"]]; + + BOOL isRateLimited = self.communicator.isRateLimited; + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(kDefaultRateLimitTimeMs * NSEC_PER_MSEC)), dispatch_get_main_queue(), ^{ + [waitForRateLimit fulfill]; + + // Did the rate limit timer get set + XCTAssertTrue(isRateLimited); + XCTAssertEqual(kDefaultRateLimitTimeMs, [[MPRateLimitManager sharedInstance] lastRateLimitMillisecondsForAdUnitId:self.communicatorDelegateHandler.adUnitIdForAdServerCommunicator(nil)]); + XCTAssert([kDefaultRateLimitReason isEqualToString:[[MPRateLimitManager sharedInstance] lastRateLimitReasonForAdUnitId:self.communicatorDelegateHandler.adUnitIdForAdServerCommunicator(nil)]]); + + // Did the attempt at a request fail + XCTAssertTrue(didFail); + XCTAssertEqual(didFailError.code, MOPUBErrorTooManyRequests); + }); + + [self waitForExpectations:@[waitForRateLimit, waitForDelegate] timeout:kTimeoutTime]; +} + +- (void)testRateLimitTimerIsSetOnMraidResponseWithoutReason { + NSDictionary *responseDataDict = @{ + kBackoffMsKey: @(kDefaultRateLimitTimeMs), + kAdResponsesKey: @[ @{ + kAdResonsesMetadataKey: @{ + @"x-adtype": @"mraid", + }, + kAdResonsesContentKey: @"" + }, ] + }; + NSData *jsonData = [NSJSONSerialization dataWithJSONObject:responseDataDict + options:0 + error:nil]; + + XCTestExpectation * waitForRateLimit = [self expectationWithDescription:@"Wait for rate limit to end"]; + XCTestExpectation * waitForDelegate = [self expectationWithDescription:@"Wait for failure delegate"]; + + __block BOOL didFail = NO; + __block NSError * didFailError = nil; + + self.communicatorDelegateHandler.communicatorDidFailWithError = ^(NSError * error){ + didFail = YES; + didFailError = error; + + [waitForDelegate fulfill]; + }; + + self.communicatorDelegateHandler.adUnitIdForAdServerCommunicator = ^(MPAdServerCommunicator * communicator){ + return @"testRateLimitTimerIsSetOnMraidResponseWithoutReason"; + }; + + // Load data (set rate limit timer) + [self.communicator didFinishLoadingWithData:jsonData]; + + // Attempt URL request (see if rate limit timer blocks it) + [self.communicator loadURL:[NSURL URLWithString:@"https://google.com"]]; + + BOOL isRateLimited = self.communicator.isRateLimited; + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(kDefaultRateLimitTimeMs * NSEC_PER_MSEC)), dispatch_get_main_queue(), ^{ + [waitForRateLimit fulfill]; + + // Did the rate limit timer get set + XCTAssertTrue(isRateLimited); + XCTAssertEqual(kDefaultRateLimitTimeMs, [[MPRateLimitManager sharedInstance] lastRateLimitMillisecondsForAdUnitId:self.communicatorDelegateHandler.adUnitIdForAdServerCommunicator(nil)]); + XCTAssertNil([[MPRateLimitManager sharedInstance] lastRateLimitReasonForAdUnitId:self.communicatorDelegateHandler.adUnitIdForAdServerCommunicator(nil)]); + + // Did the attempt at a request fail + XCTAssertTrue(didFail); + XCTAssertEqual(didFailError.code, MOPUBErrorTooManyRequests); + }); + + [self waitForExpectations:@[waitForRateLimit, waitForDelegate] timeout:kTimeoutTime]; +} + +- (void)testRateLimitTimerIsNotSetOnMraidResponseWithNoBackoffKey { + NSDictionary *responseDataDict = @{ + kAdResponsesKey: @[ @{ + kAdResonsesMetadataKey: @{ + @"x-adtype": @"mraid", + }, + kAdResonsesContentKey: @"" + }, ] + }; + + self.communicatorDelegateHandler.adUnitIdForAdServerCommunicator = ^(MPAdServerCommunicator * communicator){ + return @"testRateLimitTimerIsNotSetOnMraidResponseWithNoBackoffKey"; + }; + + NSData *jsonData = [NSJSONSerialization dataWithJSONObject:responseDataDict + options:0 + error:nil]; + + [self.communicator didFinishLoadingWithData:jsonData]; + + XCTAssertFalse(self.communicator.isRateLimited); + XCTAssertEqual(0, [[MPRateLimitManager sharedInstance] lastRateLimitMillisecondsForAdUnitId:self.communicatorDelegateHandler.adUnitIdForAdServerCommunicator(nil)]); + XCTAssertNil([[MPRateLimitManager sharedInstance] lastRateLimitReasonForAdUnitId:self.communicatorDelegateHandler.adUnitIdForAdServerCommunicator(nil)]); +} + @end diff --git a/MoPubSDKTests/MPAdServerURLBuilder+Testing.h b/MoPubSDKTests/MPAdServerURLBuilder+Testing.h index d61401c27..9533cb3be 100644 --- a/MoPubSDKTests/MPAdServerURLBuilder+Testing.h +++ b/MoPubSDKTests/MPAdServerURLBuilder+Testing.h @@ -1,7 +1,7 @@ // // MPAdServerURLBuilder+Testing.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -11,5 +11,6 @@ @interface MPAdServerURLBuilder (Testing) + (NSString *)advancedBiddingValue; ++ (NSDictionary *)adapterInformation; @end diff --git a/MoPubSDKTests/MPAdServerURLBuilder+Testing.m b/MoPubSDKTests/MPAdServerURLBuilder+Testing.m index 9ae8f34bc..f136ba95a 100644 --- a/MoPubSDKTests/MPAdServerURLBuilder+Testing.m +++ b/MoPubSDKTests/MPAdServerURLBuilder+Testing.m @@ -1,7 +1,7 @@ // // MPAdServerURLBuilder+Testing.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/MPAdServerURLBuilderTests.m b/MoPubSDKTests/MPAdServerURLBuilderTests.m index d31350994..e3ecf54e3 100644 --- a/MoPubSDKTests/MPAdServerURLBuilderTests.m +++ b/MoPubSDKTests/MPAdServerURLBuilderTests.m @@ -1,29 +1,29 @@ // // MPAdServerURLBuilderTests.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // #import +#import "MPAdServerKeys.h" #import "MPAdServerURLBuilder+Testing.h" -#import "MPAdvancedBiddingManager+Testing.h" #import "MPAPIEndpoints.h" #import "MPConsentManager.h" +#import "MPEngineInfo.h" +#import "MPIdentityProvider.h" +#import "MPMediationManager.h" +#import "MPMediationManager+Testing.h" +#import "MPURL.h" #import "MPViewabilityTracker.h" -#import "NSURLComponents+Testing.h" -#import "MPStubAdvancedBidder.h" #import "NSString+MPConsentStatus.h" #import "NSString+MPAdditions.h" -#import "MPAdServerKeys.h" -#import "MPStubAdvancedBidder.h" -#import "MPIdentityProvider.h" -#import "MPURL.h" +#import "NSURLComponents+Testing.h" +#import "MPRateLimitManager.h" -static NSString *const kTestAdUnitId = @""; -static NSString *const kTestKeywords = @""; -static NSTimeInterval const kTestTimeout = 4; +static NSString * const kTestAdUnitId = @""; +static NSString * const kTestKeywords = @""; static NSString * const kGDPRAppliesStorageKey = @"com.mopub.mopub-ios-sdk.gdpr.applies"; static NSString * const kConsentedIabVendorListStorageKey = @"com.mopub.mopub-ios-sdk.consented.iab.vendor.list"; static NSString * const kConsentedPrivacyPolicyVersionStorageKey = @"com.mopub.mopub-ios-sdk.consented.privacy.policy.version"; @@ -47,13 +47,19 @@ - (void)setUp { [defaults setObject:nil forKey:kConsentedVendorListVersionStorageKey]; [defaults setObject:nil forKey:kLastChangedMsStorageKey]; [defaults synchronize]; + + // Reset engine info + MPAdServerURLBuilder.engineInformation = nil; } #pragma mark - Viewability - (void)testViewabilityPresentInPOSTData { // By default, IAS should be enabled - MPURL * url = [MPAdServerURLBuilder URLWithAdUnitID:kTestAdUnitId keywords:kTestKeywords userDataKeywords:nil location:nil]; + MPAdTargeting * targeting = [MPAdTargeting targetingWithCreativeSafeSize:CGSizeZero]; + targeting.keywords = kTestKeywords; + + MPURL * url = [MPAdServerURLBuilder URLWithAdUnitID:kTestAdUnitId targeting:targeting]; XCTAssertNotNil(url); NSString * viewabilityValue = [url stringForPOSTDataKey:kViewabilityStatusKey]; @@ -64,7 +70,10 @@ - (void)testViewabilityDisabled { // By default, IAS should be enabled so we should disable all vendors [MPViewabilityTracker disableViewability:(MPViewabilityOptionIAS | MPViewabilityOptionMoat)]; - MPURL * url = [MPAdServerURLBuilder URLWithAdUnitID:kTestAdUnitId keywords:kTestKeywords userDataKeywords:nil location:nil]; + MPAdTargeting * targeting = [MPAdTargeting targetingWithCreativeSafeSize:CGSizeZero]; + targeting.keywords = kTestKeywords; + + MPURL * url = [MPAdServerURLBuilder URLWithAdUnitID:kTestAdUnitId targeting:targeting]; XCTAssertNotNil(url); NSString * viewabilityValue = [url stringForPOSTDataKey:kViewabilityStatusKey]; @@ -74,29 +83,12 @@ - (void)testViewabilityDisabled { #pragma mark - Advanced Bidding - (void)testAdvancedBiddingNotInitialized { - MPAdvancedBiddingManager.sharedManager.bidders = [NSMutableDictionary dictionary]; - MPAdvancedBiddingManager.sharedManager.advancedBiddingEnabled = YES; - NSString * queryParam = [MPAdServerURLBuilder advancedBiddingValue]; - + MPMediationManager.sharedManager.adapters = [NSMutableDictionary dictionary]; + NSDictionary * queryParam = [MPAdServerURLBuilder adapterInformation]; XCTAssertNil(queryParam); -} - -- (void)testAdvancedBiddingDisabled { - MPAdvancedBiddingManager.sharedManager.bidders = [NSMutableDictionary dictionary]; - MPAdvancedBiddingManager.sharedManager.advancedBiddingEnabled = NO; - XCTestExpectation * expectation = [self expectationWithDescription:@"Expect advanced bidders to initialize"]; - [MPAdvancedBiddingManager.sharedManager initializeBidders:@[MPStubAdvancedBidder.class] complete:^{ - [expectation fulfill]; - }]; - - [self waitForExpectationsWithTimeout:kTestTimeout handler:^(NSError *error) { - XCTAssertNil(error); - }]; - - NSString * queryParam = [MPAdServerURLBuilder advancedBiddingValue]; - - XCTAssertNil(queryParam); + NSString * tokens = [MPAdServerURLBuilder advancedBiddingValue]; + XCTAssertNil(tokens); } #pragma mark - Open Endpoint @@ -152,7 +144,7 @@ - (void)testConsentStatusInAdRequest { NSString * consentStatus = [NSString stringFromConsentStatus:MPConsentManager.sharedManager.currentStatus]; XCTAssertNotNil(consentStatus); - MPURL * request = [MPAdServerURLBuilder URLWithAdUnitID:@"1234" keywords:nil userDataKeywords:nil location:nil]; + MPURL * request = [MPAdServerURLBuilder URLWithAdUnitID:@"1234" targeting:nil]; XCTAssertNotNil(request); NSString * consentValue = [request stringForPOSTDataKey:kCurrentConsentStatusKey]; @@ -234,4 +226,104 @@ - (NSString *)queryParameterValueForKey:(NSString *)key inUrl:(NSString *)url { return value; } +#pragma mark - Rate Limiting + +- (void)testFilledReasonWithNonZeroRateLimitValue { + [[MPRateLimitManager sharedInstance] setRateLimitTimerWithAdUnitId:@"fake_adunit" milliseconds:10 reason:@"Reason"]; + + MPURL * url = [MPAdServerURLBuilder URLWithAdUnitID:@"fake_adunit" targeting:nil]; + + NSNumber * value = [url numberForPOSTDataKey:kBackoffMsKey]; + XCTAssertEqual([value integerValue], 10); + XCTAssert([[url stringForPOSTDataKey:kBackoffReasonKey] isEqualToString:@"Reason"]); +} + +- (void)testZeroRateLimitValueDoesntShow { + [[MPRateLimitManager sharedInstance] setRateLimitTimerWithAdUnitId:@"fake_adunit" milliseconds:0 reason:nil]; + + MPURL * url = [MPAdServerURLBuilder URLWithAdUnitID:@"fake_adunit" targeting:nil]; + + NSNumber * value = [url numberForPOSTDataKey:kBackoffMsKey]; + XCTAssertNil(value); + XCTAssertNil([url stringForPOSTDataKey:kBackoffReasonKey]); +} + +- (void)testNilReasonWithNonZeroRateLimitValue { + [[MPRateLimitManager sharedInstance] setRateLimitTimerWithAdUnitId:@"fake_adunit" milliseconds:10 reason:nil]; + + MPURL * url = [MPAdServerURLBuilder URLWithAdUnitID:@"fake_adunit" targeting:nil]; + + NSNumber * value = [url numberForPOSTDataKey:kBackoffMsKey]; + XCTAssertEqual([value integerValue], 10); + XCTAssertNil([url stringForPOSTDataKey:kBackoffReasonKey]); +} + +#pragma mark - Targeting + +- (void)testCreativeSafeSizeTargetingValuesPresent { + CGFloat width = 300.0f; + CGFloat height = 250.0f; + + MPAdTargeting * targeting = [MPAdTargeting targetingWithCreativeSafeSize:CGSizeMake(width, height)]; + + MPURL * url = [MPAdServerURLBuilder URLWithAdUnitID:@"fake_adunit" targeting:targeting]; + XCTAssertNotNil(url); + + NSNumber * sc = [url numberForPOSTDataKey:kScaleFactorKey]; + NSNumber * cw = [url numberForPOSTDataKey:kCreativeSafeWidthKey]; + NSNumber * ch = [url numberForPOSTDataKey:kCreativeSafeHeightKey]; + + XCTAssertNotNil(sc); + XCTAssertNotNil(cw); + XCTAssertNotNil(ch); + XCTAssertEqual([cw floatValue], [sc floatValue] * width); + XCTAssertEqual([ch floatValue], [sc floatValue] * height); +} + +#pragma mark - Parameter + +- (void)testMoPubID { + MPAdTargeting * targeting = [MPAdTargeting targetingWithCreativeSafeSize:CGSizeZero]; + MPURL * url = [MPAdServerURLBuilder URLWithAdUnitID:@"fake_ad_unit" targeting:targeting]; + XCTAssertNotNil(url); + XCTAssertNotNil([url stringForPOSTDataKey:kMoPubIDKey]); + XCTAssertNotEqual([url stringForPOSTDataKey:kMoPubIDKey], @""); + XCTAssertTrue([[url stringForPOSTDataKey:kMoPubIDKey] isEqualToString:[MPIdentityProvider unobfuscatedMoPubIdentifier]]); +} + +#pragma mark - Engine Information + +- (void)testNoEngineInformation { + // Verify that the engine information is present for the base URL for all + // Ad Server requests + MPAdTargeting * targeting = [MPAdTargeting targetingWithCreativeSafeSize:CGSizeZero]; + MPURL * url = [MPAdServerURLBuilder URLWithAdUnitID:@"fake_ad_unit" targeting:targeting]; + XCTAssertNotNil(url); + + NSString * name = [url stringForPOSTDataKey:kSDKEngineNameKey]; + XCTAssertNil(name); + + NSString * version = [url stringForPOSTDataKey:kSDKEngineVersionKey]; + XCTAssertNil(version); +} + +- (void)testEngineInformationPresent { + // Set the engine information. + MPAdServerURLBuilder.engineInformation = [MPEngineInfo named:@"unity" version:@"2017.1.2f2"]; + + // Verify that the engine information is present for the base URL for all + // Ad Server requests + MPAdTargeting * targeting = [MPAdTargeting targetingWithCreativeSafeSize:CGSizeZero]; + MPURL * url = [MPAdServerURLBuilder URLWithAdUnitID:@"fake_ad_unit" targeting:targeting]; + XCTAssertNotNil(url); + + NSString * name = [url stringForPOSTDataKey:kSDKEngineNameKey]; + XCTAssertNotNil(name); + XCTAssert([name isEqualToString:@"unity"]); + + NSString * version = [url stringForPOSTDataKey:kSDKEngineVersionKey]; + XCTAssertNotNil(version); + XCTAssert([version isEqualToString:@"2017.1.2f2"]); +} + @end diff --git a/MoPubSDKTests/MPAdView+Testing.h b/MoPubSDKTests/MPAdView+Testing.h index e54280d1f..f7931da09 100644 --- a/MoPubSDKTests/MPAdView+Testing.h +++ b/MoPubSDKTests/MPAdView+Testing.h @@ -1,7 +1,7 @@ // // MPAdView+Testing.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -11,4 +11,5 @@ @interface MPAdView (Testing) @property (nonatomic, strong) MPBannerAdManager *adManager; +- (void)impressionDidFireWithImpressionData:(MPImpressionData *)impressionData; @end diff --git a/MoPubSDKTests/MPAdView+Testing.m b/MoPubSDKTests/MPAdView+Testing.m index 742081a1d..f68fbb7fe 100644 --- a/MoPubSDKTests/MPAdView+Testing.m +++ b/MoPubSDKTests/MPAdView+Testing.m @@ -1,13 +1,17 @@ // // MPAdView+Testing.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // #import "MPAdView+Testing.h" +// Suppress warning of accessing private implementation `impressionDidFireWithImpressionData:` +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wincomplete-implementation" @implementation MPAdView (Testing) @dynamic adManager; @end +#pragma clang diagnostic pop diff --git a/MoPubSDKTests/MPAdViewTests.m b/MoPubSDKTests/MPAdViewTests.m index a56ad243b..1e7644aea 100644 --- a/MoPubSDKTests/MPAdViewTests.m +++ b/MoPubSDKTests/MPAdViewTests.m @@ -1,7 +1,7 @@ // // MPAdViewTests.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -12,9 +12,16 @@ #import "MPAdView+Testing.h" #import "MPAPIEndpoints.h" #import "MPBannerAdManager+Testing.h" +#import "MPError.h" +#import "MPLogging.h" +#import "MPLoggingHandler.h" #import "MPMockAdServerCommunicator.h" #import "MPURL.h" #import "NSURLComponents+Testing.h" +#import "MPImpressionTrackedNotification.h" + +static NSTimeInterval const kTestTimeout = 0.5; +static NSTimeInterval const kDefaultTimeout = 10; @interface MPAdViewTests : XCTestCase @property (nonatomic, strong) MPAdView * adView; @@ -26,7 +33,7 @@ @implementation MPAdViewTests - (void)setUp { [super setUp]; - self.adView = [[MPAdView alloc] initWithAdUnitId:@"FAKE_AD_UNIT_ID" size:MOPUB_BANNER_SIZE]; + self.adView = [[MPAdView alloc] initWithAdUnitId:@"FAKE_AD_UNIT_ID"]; self.adView.adManager.communicator = ({ MPMockAdServerCommunicator * mock = [[MPMockAdServerCommunicator alloc] initWithDelegate:self.adView.adManager]; self.mockAdServerCommunicator = mock; @@ -34,21 +41,24 @@ - (void)setUp { }); } -- (void)tearDown { - // Put teardown code here. This method is called after the invocation of each test method in the class. - [super tearDown]; -} - #pragma mark - Viewability - (void)testViewabilityQueryParameter { + MPAdView * adView = [[MPAdView alloc] initWithAdUnitId:@"FAKE_AD_UNIT_ID"]; + MPMockAdServerCommunicator * mockAdServerCommunicator = nil; + adView.adManager.communicator = ({ + MPMockAdServerCommunicator * mock = [[MPMockAdServerCommunicator alloc] initWithDelegate:adView.adManager]; + mockAdServerCommunicator = mock; + mock; + }); + // Banner ads should send a viewability query parameter. - [self.adView loadAd]; + [adView loadAdWithMaxAdSize:kMPPresetMaxAdSize50Height]; - XCTAssertNotNil(self.mockAdServerCommunicator); - XCTAssertNotNil(self.mockAdServerCommunicator.lastUrlLoaded); + XCTAssertNotNil(mockAdServerCommunicator); + XCTAssertNotNil(mockAdServerCommunicator.lastUrlLoaded); - MPURL * url = [self.mockAdServerCommunicator.lastUrlLoaded isKindOfClass:[MPURL class]] ? (MPURL *)self.mockAdServerCommunicator.lastUrlLoaded : nil; + MPURL * url = [mockAdServerCommunicator.lastUrlLoaded isKindOfClass:[MPURL class]] ? (MPURL *)mockAdServerCommunicator.lastUrlLoaded : nil; XCTAssertNotNil(url); NSString * viewabilityValue = [url stringForPOSTDataKey:kViewabilityStatusKey]; @@ -56,5 +66,215 @@ - (void)testViewabilityQueryParameter { XCTAssertTrue([viewabilityValue isEqualToString:@"1"]); } +#pragma mark - Ad Sizing + +- (void)testRequestMatchFrameWithNoFrameWarningOnLoad { + NSString * adUnitId = @"testRequestMatchFrameWithNoFrameWarningOnLoad"; + NSString * expectedWidthErrorMessage = NSError.frameWidthNotSetForFlexibleSize.localizedDescription; + NSString * expectedHeightErrorMessage = NSError.frameHeightNotSetForFlexibleSize.localizedDescription; + + XCTestExpectation * expectation = [self expectationWithDescription:@"Wait for banner warnings on load"]; + expectation.expectedFulfillmentCount = 2; // 2 warnings on load + + MPLoggingHandler * handler = [MPLoggingHandler handler:^(NSString * _Nullable message) { + if (![message containsString:adUnitId]) { + return; + } + + if ([message containsString:expectedWidthErrorMessage]) { + [expectation fulfill]; + } + + if ([message containsString:expectedHeightErrorMessage]) { + [expectation fulfill]; + } + }]; + + [MPLogging addLogger:handler]; + + MPAdView * noFrameBanner = [[MPAdView alloc] initWithAdUnitId:adUnitId]; + MPMockAdServerCommunicator * mockAdServerCommunicator = nil; + noFrameBanner.adManager.communicator = ({ + MPMockAdServerCommunicator * mock = [[MPMockAdServerCommunicator alloc] initWithDelegate:noFrameBanner.adManager]; + mockAdServerCommunicator = mock; + mock; + }); + + [noFrameBanner loadAdWithMaxAdSize:kMPPresetMaxAdSizeMatchFrame]; + + [self waitForExpectationsWithTimeout:kDefaultTimeout handler:^(NSError * _Nullable error) { + if (error != nil) { + XCTFail(@"Timed out"); + } + }]; + + [MPLogging removeLogger:handler]; + + MPURL * url = [mockAdServerCommunicator.lastUrlLoaded isKindOfClass:[MPURL class]] ? (MPURL *)mockAdServerCommunicator.lastUrlLoaded : nil; + XCTAssertNotNil(url); + NSNumber * cw = [url numberForPOSTDataKey:kCreativeSafeWidthKey]; + NSNumber * ch = [url numberForPOSTDataKey:kCreativeSafeHeightKey]; + XCTAssert(cw.floatValue == 0.0); + XCTAssert(ch.floatValue == 0.0); +} + +- (void)testRequestMatchFrameWithPartialFrameWarningOnLoad { + NSString * adUnitId = @"testRequestMatchFrameWithPartialFrameWarningOnLoad"; + NSString * expectedHeightErrorMessage = NSError.frameHeightNotSetForFlexibleSize.localizedDescription; + + XCTestExpectation * expectation = [self expectationWithDescription:@"Wait for banner warning on partial frame"]; + expectation.expectedFulfillmentCount = 1; // 1 warning on load + + MPLoggingHandler * handler = [MPLoggingHandler handler:^(NSString * _Nullable message) { + if (![message containsString:adUnitId]) { + return; + } + + if ([message containsString:expectedHeightErrorMessage]) { + [expectation fulfill]; + } + }]; + + [MPLogging addLogger:handler]; + + MPAdView * partialFrameBanner = [[MPAdView alloc] initWithAdUnitId:adUnitId]; + partialFrameBanner.frame = CGRectMake(0, 0, 475, 0); + MPMockAdServerCommunicator * mockAdServerCommunicator = nil; + partialFrameBanner.adManager.communicator = ({ + MPMockAdServerCommunicator * mock = [[MPMockAdServerCommunicator alloc] initWithDelegate:partialFrameBanner.adManager]; + mockAdServerCommunicator = mock; + mock; + }); + + [partialFrameBanner loadAdWithMaxAdSize:kMPPresetMaxAdSizeMatchFrame]; + + [self waitForExpectationsWithTimeout:kDefaultTimeout handler:^(NSError * _Nullable error) { + if (error != nil) { + XCTFail(@"Timed out"); + } + }]; + + [MPLogging removeLogger:handler]; + + MPURL * url = [mockAdServerCommunicator.lastUrlLoaded isKindOfClass:[MPURL class]] ? (MPURL *)mockAdServerCommunicator.lastUrlLoaded : nil; + XCTAssertNotNil(url); + NSNumber * sc = [url numberForPOSTDataKey:kScaleFactorKey]; + NSNumber * cw = [url numberForPOSTDataKey:kCreativeSafeWidthKey]; + NSNumber * ch = [url numberForPOSTDataKey:kCreativeSafeHeightKey]; + XCTAssert(cw.floatValue == 475.0 * sc.floatValue); + XCTAssert(ch.floatValue == 0.0); +} + +- (void)testRequestMatchFrameWithFrameNoWarningOnLoad { + NSString * adUnitId = @"testRequestMatchFrameWithFrameNoWarningOnLoad"; + NSString * expectedWidthErrorMessage = NSError.frameWidthNotSetForFlexibleSize.localizedDescription; + NSString * expectedHeightErrorMessage = NSError.frameHeightNotSetForFlexibleSize.localizedDescription; + + XCTestExpectation * expectation = [self expectationWithDescription:@"Wait for no banner warnings"]; + expectation.inverted = YES; + + MPLoggingHandler * handler = [MPLoggingHandler handler:^(NSString * _Nullable message) { + if (![message containsString:adUnitId]) { + return; + } + + if ([message containsString:expectedWidthErrorMessage]) { + [expectation fulfill]; + } + + if ([message containsString:expectedHeightErrorMessage]) { + [expectation fulfill]; + } + }]; + + [MPLogging addLogger:handler]; + + MPAdView * frameBanner = [[MPAdView alloc] initWithAdUnitId:adUnitId]; + frameBanner.frame = CGRectMake(0, 0, 475, 250); + MPMockAdServerCommunicator * mockAdServerCommunicator = nil; + frameBanner.adManager.communicator = ({ + MPMockAdServerCommunicator * mock = [[MPMockAdServerCommunicator alloc] initWithDelegate:frameBanner.adManager]; + mockAdServerCommunicator = mock; + mock; + }); + + [frameBanner loadAdWithMaxAdSize:kMPPresetMaxAdSizeMatchFrame]; + + [self waitForExpectationsWithTimeout:kDefaultTimeout handler:nil]; + + [MPLogging removeLogger:handler]; + + MPURL * url = [mockAdServerCommunicator.lastUrlLoaded isKindOfClass:[MPURL class]] ? (MPURL *)mockAdServerCommunicator.lastUrlLoaded : nil; + XCTAssertNotNil(url); + NSNumber * sc = [url numberForPOSTDataKey:kScaleFactorKey]; + NSNumber * cw = [url numberForPOSTDataKey:kCreativeSafeWidthKey]; + NSNumber * ch = [url numberForPOSTDataKey:kCreativeSafeHeightKey]; + XCTAssert(cw.floatValue == 475.0 * sc.floatValue); + XCTAssert(ch.floatValue == 250.0 * sc.floatValue); +} + +#pragma mark - Impression Level Revenue Data + +- (void)testImpressionNotificationWithImpressionData { + XCTestExpectation * notificationExpectation = [self expectationWithDescription:@"Wait for impression notification"]; + NSString * testAdUnitId = @"FAKE_AD_UNIT_ID"; + + // Make notification handler + id notificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMPImpressionTrackedNotification + object:nil + queue:[NSOperationQueue mainQueue] + usingBlock:^(NSNotification * note){ + [notificationExpectation fulfill]; + + MPImpressionData * impressionData = note.userInfo[kMPImpressionTrackedInfoImpressionDataKey]; + XCTAssert([note.object isEqual:self.adView]); + XCTAssertNotNil(impressionData); + XCTAssert([self.adView.adUnitId isEqualToString:note.userInfo[kMPImpressionTrackedInfoAdUnitIDKey]]); + XCTAssert([impressionData.adUnitID isEqualToString:testAdUnitId]); + }]; + + MPImpressionData * impressionData = [[MPImpressionData alloc] initWithDictionary:@{ + kImpressionDataAdUnitIDKey: testAdUnitId + }]; + + // Simulate impression + [self.adView impressionDidFireWithImpressionData:impressionData]; + + [self waitForExpectationsWithTimeout:kTestTimeout handler:^(NSError * _Nullable error) { + if (error != nil) { + XCTFail(@"Timed out"); + } + }]; + + [[NSNotificationCenter defaultCenter] removeObserver:notificationObserver]; +} + +- (void)testImpressionNotificationWithNoImpressionData { + XCTestExpectation * notificationExpectation = [self expectationWithDescription:@"Wait for impression notification"]; + + // Make notification handler + id notificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMPImpressionTrackedNotification + object:nil + queue:[NSOperationQueue mainQueue] + usingBlock:^(NSNotification * note){ + [notificationExpectation fulfill]; + + MPImpressionData * impressionData = note.userInfo[kMPImpressionTrackedInfoImpressionDataKey]; + XCTAssert([note.object isEqual:self.adView]); + XCTAssertNil(impressionData); + XCTAssert([self.adView.adUnitId isEqualToString:note.userInfo[kMPImpressionTrackedInfoAdUnitIDKey]]); + }]; + + // Simulate impression + [self.adView impressionDidFireWithImpressionData:nil]; + + [self waitForExpectationsWithTimeout:kTestTimeout handler:^(NSError * _Nullable error) { + if (error != nil) { + XCTFail(@"Timed out"); + } + }]; + + [[NSNotificationCenter defaultCenter] removeObserver:notificationObserver]; +} @end diff --git a/MoPubSDKTests/MPAdserverCommunicatorDelegateHandler.h b/MoPubSDKTests/MPAdserverCommunicatorDelegateHandler.h index 48fdadb0f..15a9ae01c 100644 --- a/MoPubSDKTests/MPAdserverCommunicatorDelegateHandler.h +++ b/MoPubSDKTests/MPAdserverCommunicatorDelegateHandler.h @@ -1,7 +1,7 @@ // -// MPAdserverCommunicatorDelegateHandler.h +// MPAdServerCommunicatorDelegateHandler.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -9,9 +9,11 @@ #import #import "MPAdServerCommunicator.h" -@interface MPAdserverCommunicatorDelegateHandler : NSObject +@interface MPAdServerCommunicatorDelegateHandler : NSObject @property (nonatomic, copy) void (^communicatorDidReceiveAdConfigurations)(NSArray *configurations); @property (nonatomic, copy) void (^communicatorDidFailWithError)(NSError *error); +@property (nonatomic, copy) MPAdType (^adTypeForAdServerCommunicator)(MPAdServerCommunicator *adServerCommunicator); +@property (nonatomic, copy) NSString * (^adUnitIdForAdServerCommunicator)(MPAdServerCommunicator *adServerCommunicator); @end diff --git a/MoPubSDKTests/MPAdserverCommunicatorDelegateHandler.m b/MoPubSDKTests/MPAdserverCommunicatorDelegateHandler.m index 849038710..1e8c87c16 100644 --- a/MoPubSDKTests/MPAdserverCommunicatorDelegateHandler.m +++ b/MoPubSDKTests/MPAdserverCommunicatorDelegateHandler.m @@ -1,16 +1,18 @@ // -// MPAdserverCommunicatorDelegateHandler.m +// MPAdServerCommunicatorDelegateHandler.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // -#import "MPAdserverCommunicatorDelegateHandler.h" +#import "MPAdServerCommunicatorDelegateHandler.h" -@implementation MPAdserverCommunicatorDelegateHandler +@implementation MPAdServerCommunicatorDelegateHandler - (void)communicatorDidReceiveAdConfigurations:(NSArray *)configurations { if (self.communicatorDidReceiveAdConfigurations) self.communicatorDidReceiveAdConfigurations(configurations); } - (void)communicatorDidFailWithError:(NSError *)error { if (self.communicatorDidFailWithError) self.communicatorDidFailWithError(error); } +- (MPAdType)adTypeForAdServerCommunicator:(MPAdServerCommunicator *)adServerCommunicator { if (self.adTypeForAdServerCommunicator) return self.adTypeForAdServerCommunicator(adServerCommunicator); else return MPAdTypeFullscreen; } +- (NSString *)adUnitIDForAdServerCommunicator:(MPAdServerCommunicator *)adServerCommunicator { if (self.adUnitIdForAdServerCommunicator) return self.adUnitIdForAdServerCommunicator(adServerCommunicator); else return @""; } @end diff --git a/MoPubSDKTests/MPAdvancedBiddingManager+Testing.h b/MoPubSDKTests/MPAdvancedBiddingManager+Testing.h deleted file mode 100644 index 3c8cc196d..000000000 --- a/MoPubSDKTests/MPAdvancedBiddingManager+Testing.h +++ /dev/null @@ -1,13 +0,0 @@ -// -// MPAdvancedBiddingManager+Testing.h -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import "MPAdvancedBiddingManager.h" - -@interface MPAdvancedBiddingManager (Testing) -@property (nonatomic, strong) NSMutableDictionary> * bidders; -@end diff --git a/MoPubSDKTests/MPAdvancedBiddingManager+Testing.m b/MoPubSDKTests/MPAdvancedBiddingManager+Testing.m deleted file mode 100644 index c1e07bc70..000000000 --- a/MoPubSDKTests/MPAdvancedBiddingManager+Testing.m +++ /dev/null @@ -1,13 +0,0 @@ -// -// MPAdvancedBiddingManager+Testing.m -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import "MPAdvancedBiddingManager+Testing.h" - -@implementation MPAdvancedBiddingManager (Testing) -@dynamic bidders; -@end diff --git a/MoPubSDKTests/MPAdvancedBiddingManagerTests.m b/MoPubSDKTests/MPAdvancedBiddingManagerTests.m deleted file mode 100644 index 0c2b72b29..000000000 --- a/MoPubSDKTests/MPAdvancedBiddingManagerTests.m +++ /dev/null @@ -1,102 +0,0 @@ -// -// MPAdvancedBiddingManagerTests.m -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import -#import "MPAdvancedBiddingManager+Testing.h" -#import "MPStubAdvancedBidder.h" - -static NSTimeInterval const kTestTimeout = 4; - -@interface MPAdvancedBiddingManagerTests : XCTestCase - -@end - -@implementation MPAdvancedBiddingManagerTests - -- (void)setUp { - [super setUp]; - - // Reset the state of the bidders. - MPAdvancedBiddingManager.sharedManager.bidders = [NSMutableDictionary dictionary]; - MPAdvancedBiddingManager.sharedManager.advancedBiddingEnabled = YES; -} - -- (void)testInitialization { - XCTestExpectation * expectation = [self expectationWithDescription:@"Expect advanced bidders to initialize"]; - - [MPAdvancedBiddingManager.sharedManager initializeBidders:@[MPStubAdvancedBidder.class] complete:^{ - [expectation fulfill]; - }]; - - [self waitForExpectationsWithTimeout:kTestTimeout handler:^(NSError *error) { - XCTAssertNil(error); - }]; - - NSDictionary * bidders = MPAdvancedBiddingManager.sharedManager.bidders; - XCTAssertNotNil(bidders[@"stub_bidder"]); - XCTAssert(bidders.allKeys.count == 1); - - NSString * json = MPAdvancedBiddingManager.sharedManager.bidderTokensJson; - XCTAssertNotNil(json); -} - -- (void)testReinitialization { - XCTestExpectation * expectation = [self expectationWithDescription:@"Expect advanced bidders to initialize"]; - [MPAdvancedBiddingManager.sharedManager initializeBidders:@[MPStubAdvancedBidder.class] complete:^{ - [expectation fulfill]; - }]; - - [self waitForExpectationsWithTimeout:kTestTimeout handler:^(NSError *error) { - XCTAssertNil(error); - }]; - - XCTestExpectation * expectationAgain = [self expectationWithDescription:@"Expect advanced bidders to initialize"]; - [MPAdvancedBiddingManager.sharedManager initializeBidders:@[MPStubAdvancedBidder.class] complete:^{ - [expectationAgain fulfill]; - }]; - - [self waitForExpectationsWithTimeout:kTestTimeout handler:^(NSError *error) { - XCTAssertNil(error); - }]; - - NSDictionary * bidders = MPAdvancedBiddingManager.sharedManager.bidders; - XCTAssertNotNil(bidders[@"stub_bidder"]); - XCTAssert(bidders.allKeys.count == 1); - - NSString * json = MPAdvancedBiddingManager.sharedManager.bidderTokensJson; - XCTAssertNotNil(json); -} - -- (void)testNoInitialization { - NSDictionary * bidders = MPAdvancedBiddingManager.sharedManager.bidders; - XCTAssert(bidders.allKeys.count == 0); - XCTAssertNil(MPAdvancedBiddingManager.sharedManager.bidderTokensJson); -} - -- (void)testDisabledAdvancedBidding { - MPAdvancedBiddingManager.sharedManager.advancedBiddingEnabled = NO; - - XCTestExpectation * expectation = [self expectationWithDescription:@"Expect advanced bidders to initialize"]; - - [MPAdvancedBiddingManager.sharedManager initializeBidders:@[MPStubAdvancedBidder.class] complete:^{ - [expectation fulfill]; - }]; - - [self waitForExpectationsWithTimeout:kTestTimeout handler:^(NSError *error) { - XCTAssertNil(error); - }]; - - NSDictionary * bidders = MPAdvancedBiddingManager.sharedManager.bidders; - XCTAssertNotNil(bidders[@"stub_bidder"]); - XCTAssert(bidders.allKeys.count == 1); - - // Expect no JSON since advanced bidding is disabled. - XCTAssertNil(MPAdvancedBiddingManager.sharedManager.bidderTokensJson); -} - -@end diff --git a/MoPubSDKTests/MPBannerAdManager+Testing.h b/MoPubSDKTests/MPBannerAdManager+Testing.h index 3eca1c0d9..e4817de45 100644 --- a/MoPubSDKTests/MPBannerAdManager+Testing.h +++ b/MoPubSDKTests/MPBannerAdManager+Testing.h @@ -1,7 +1,7 @@ // // MPBannerAdManager+Testing.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/MPBannerAdManager+Testing.m b/MoPubSDKTests/MPBannerAdManager+Testing.m index 27f7730ab..df7681dc2 100644 --- a/MoPubSDKTests/MPBannerAdManager+Testing.m +++ b/MoPubSDKTests/MPBannerAdManager+Testing.m @@ -1,7 +1,7 @@ // // MPBannerAdManager+Testing.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/MPBannerAdManagerDelegateHandler.h b/MoPubSDKTests/MPBannerAdManagerDelegateHandler.h index ad57faee0..6603a82c6 100644 --- a/MoPubSDKTests/MPBannerAdManagerDelegateHandler.h +++ b/MoPubSDKTests/MPBannerAdManagerDelegateHandler.h @@ -1,7 +1,7 @@ // // MPBannerAdManagerDelegateHandler.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -9,8 +9,11 @@ #import #import "MPBannerAdManager.h" #import "MPBannerAdManagerDelegate.h" +#import "MPImpressionData.h" typedef void(^MPBannerAdManagerDelegateHandlerBlock)(void); +typedef void(^MPBannerAdManagerDelegateHandlerImpressionBlock)(MPImpressionData * impressionData); +typedef void(^MPBannerAdManagerDelegateHandlerErrorBlock)(NSError * error); @interface MPBannerAdManagerDelegateHandler : NSObject @@ -25,9 +28,10 @@ typedef void(^MPBannerAdManagerDelegateHandlerBlock)(void); @property (nonatomic, strong) UIViewController * viewControllerForPresentingModalView; @property (nonatomic, copy) MPBannerAdManagerDelegateHandlerBlock didLoadAd; -@property (nonatomic, copy) MPBannerAdManagerDelegateHandlerBlock didFailToLoadAd; +@property (nonatomic, copy) MPBannerAdManagerDelegateHandlerErrorBlock didFailToLoadAd; @property (nonatomic, copy) MPBannerAdManagerDelegateHandlerBlock willBeginUserAction; @property (nonatomic, copy) MPBannerAdManagerDelegateHandlerBlock didEndUserAction; @property (nonatomic, copy) MPBannerAdManagerDelegateHandlerBlock willLeaveApplication; +@property (nonatomic, copy) MPBannerAdManagerDelegateHandlerImpressionBlock impressionDidFire; @end diff --git a/MoPubSDKTests/MPBannerAdManagerDelegateHandler.m b/MoPubSDKTests/MPBannerAdManagerDelegateHandler.m index 21aedc126..cf061b90c 100644 --- a/MoPubSDKTests/MPBannerAdManagerDelegateHandler.m +++ b/MoPubSDKTests/MPBannerAdManagerDelegateHandler.m @@ -1,14 +1,18 @@ // // MPBannerAdManagerDelegateHandler.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // #import "MPBannerAdManagerDelegateHandler.h" +// Warning: Class 'MPBannerAdManagerDelegateHandler' does not conform to protocol 'MPBannerAdManagerDelegate' +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wprotocol" @implementation MPBannerAdManagerDelegateHandler +#pragma clang diagnostic pop #pragma mark - MPBannerAdManagerDelegate @@ -20,8 +24,8 @@ - (void)managerDidLoadAd:(UIView *)ad { if (self.didLoadAd != nil) { self.didLoadAd(); } } -- (void)managerDidFailToLoadAd { - if (self.didFailToLoadAd != nil) { self.didFailToLoadAd(); } +- (void)managerDidFailToLoadAdWithError:(NSError *)error { + if (self.didFailToLoadAd != nil) { self.didFailToLoadAd(error); } } - (void)userActionWillBegin { @@ -36,4 +40,8 @@ - (void)userWillLeaveApplication { if (self.willLeaveApplication != nil) { self.willLeaveApplication(); } } +- (void)impressionDidFireWithImpressionData:(MPImpressionData *)impressionData { + if (self.impressionDidFire != nil) { self.impressionDidFire(impressionData); } +} + @end diff --git a/MoPubSDKTests/MPBannerAdManagerTests.m b/MoPubSDKTests/MPBannerAdManagerTests.m index c81fcc62a..2fe15de16 100644 --- a/MoPubSDKTests/MPBannerAdManagerTests.m +++ b/MoPubSDKTests/MPBannerAdManagerTests.m @@ -1,7 +1,7 @@ // // MPBannerAdManagerTests.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -16,6 +16,7 @@ #import "MPBannerCustomEventAdapter+Testing.h" #import "MPMockAdServerCommunicator.h" #import "MPMockBannerCustomEvent.h" +#import "MPAdServerKeys.h" static const NSTimeInterval kDefaultTimeout = 10; @@ -25,23 +26,13 @@ @interface MPBannerAdManagerTests : XCTestCase @implementation MPBannerAdManagerTests -- (void)setUp { - [super setUp]; - // Put setup code here. This method is called before the invocation of each test method in the class. -} - -- (void)tearDown { - // Put teardown code here. This method is called after the invocation of each test method in the class. - [super tearDown]; -} - #pragma mark - Networking - (void)testEmptyConfigurationArray { XCTestExpectation * expectation = [self expectationWithDescription:@"Wait for banner load"]; MPBannerAdManagerDelegateHandler * handler = [MPBannerAdManagerDelegateHandler new]; - handler.didFailToLoadAd = ^{ + handler.didFailToLoadAd = ^(NSError * error){ [expectation fulfill]; }; @@ -59,7 +50,7 @@ - (void)testNilConfigurationArray { XCTestExpectation * expectation = [self expectationWithDescription:@"Wait for banner load"]; MPBannerAdManagerDelegateHandler * handler = [MPBannerAdManagerDelegateHandler new]; - handler.didFailToLoadAd = ^{ + handler.didFailToLoadAd = ^(NSError * error){ [expectation fulfill]; }; @@ -80,7 +71,7 @@ - (void)testMultipleResponsesFirstSuccess { handler.didLoadAd = ^{ [expectation fulfill]; }; - handler.didFailToLoadAd = ^{ + handler.didFailToLoadAd = ^(NSError * error){ XCTFail(@"Encountered an unexpected load failure"); [expectation fulfill]; }; @@ -113,7 +104,7 @@ - (void)testMultipleResponsesMiddleSuccess { handler.didLoadAd = ^{ [expectation fulfill]; }; - handler.didFailToLoadAd = ^{ + handler.didFailToLoadAd = ^(NSError * error){ XCTFail(@"Encountered an unexpected load failure"); [expectation fulfill]; }; @@ -146,7 +137,7 @@ - (void)testMultipleResponsesLastSuccess { handler.didLoadAd = ^{ [expectation fulfill]; }; - handler.didFailToLoadAd = ^{ + handler.didFailToLoadAd = ^(NSError * error){ XCTFail(@"Encountered an unexpected load failure"); [expectation fulfill]; }; @@ -176,7 +167,7 @@ - (void)testMultipleResponsesFailOverToNextPage { XCTestExpectation * expectation = [self expectationWithDescription:@"Wait for banner load"]; MPBannerAdManagerDelegateHandler * handler = [MPBannerAdManagerDelegateHandler new]; - handler.didFailToLoadAd = ^{ + handler.didFailToLoadAd = ^(NSError * error){ [expectation fulfill]; }; @@ -206,7 +197,7 @@ - (void)testMultipleResponsesFailOverToNextPageClear { XCTestExpectation * expectation = [self expectationWithDescription:@"Wait for banner load"]; MPBannerAdManagerDelegateHandler * handler = [MPBannerAdManagerDelegateHandler new]; - handler.didFailToLoadAd = ^{ + handler.didFailToLoadAd = ^(NSError * error){ [expectation fulfill]; }; @@ -243,7 +234,7 @@ - (void)testLocalExtrasInCustomEvent { handler.didLoadAd = ^{ [expectation fulfill]; }; - handler.didFailToLoadAd = ^{ + handler.didFailToLoadAd = ^(NSError * error){ XCTFail(@"Encountered an unexpected load failure"); [expectation fulfill]; }; @@ -257,7 +248,7 @@ - (void)testLocalExtrasInCustomEvent { communicator.mockConfigurationsResponse = configurations; manager.communicator = communicator; - MPAdTargeting * targeting = [[MPAdTargeting alloc] init]; + MPAdTargeting * targeting = [[MPAdTargeting alloc] initWithCreativeSafeSize:CGSizeZero]; targeting.localExtras = @{ @"testing": @"YES" }; [manager loadAdWithTargeting:targeting]; @@ -277,4 +268,70 @@ - (void)testLocalExtrasInCustomEvent { XCTAssertTrue(customEvent.isLocalExtrasAvailableAtRequest); } +#pragma mark - Impression Level Revenue Data + +- (void)testImpressionDelegateFiresWithoutILRD { + XCTestExpectation * expectation = [self expectationWithDescription:@"Wait for impression"]; + + MPBannerAdManagerDelegateHandler * handler = [MPBannerAdManagerDelegateHandler new]; + handler.impressionDidFire = ^(MPImpressionData * impresionData) { + [expectation fulfill]; + + XCTAssertNil(impresionData); + }; + + // Generate the ad configurations + MPAdConfiguration * bannerThatShouldLoad = [MPAdConfigurationFactory defaultBannerConfigurationWithCustomEventClassName:@"MPMockBannerCustomEvent"]; + NSArray * configurations = @[bannerThatShouldLoad]; + + MPBannerAdManager * manager = [[MPBannerAdManager alloc] initWithDelegate:handler]; + MPMockAdServerCommunicator * communicator = [[MPMockAdServerCommunicator alloc] initWithDelegate:manager]; + communicator.mockConfigurationsResponse = configurations; + manager.communicator = communicator; + + MPAdTargeting * targeting = [[MPAdTargeting alloc] initWithCreativeSafeSize:CGSizeZero]; + [manager loadAdWithTargeting:targeting]; + + [self waitForExpectationsWithTimeout:kDefaultTimeout handler:^(NSError * _Nullable error) { + if (error != nil) { + XCTFail(@"Timed out"); + } + }]; +} + +- (void)testImpressionDelegateFiresWithILRD { + XCTestExpectation * expectation = [self expectationWithDescription:@"Wait for impression"]; + + NSString * adUnitIdSample = @"AD_UNIT_ID"; + + MPBannerAdManagerDelegateHandler * handler = [MPBannerAdManagerDelegateHandler new]; + handler.impressionDidFire = ^(MPImpressionData * impresionData) { + [expectation fulfill]; + + XCTAssertNotNil(impresionData); + XCTAssert(impresionData.adUnitID == adUnitIdSample); + }; + + // Generate the ad configurations + MPAdConfiguration * bannerThatShouldLoad = [MPAdConfigurationFactory defaultBannerConfigurationWithCustomEventClassName:@"MPMockBannerCustomEvent"]; + bannerThatShouldLoad.impressionData = [[MPImpressionData alloc] initWithDictionary:@{ + kImpressionDataAdUnitIDKey: adUnitIdSample + }]; + NSArray * configurations = @[bannerThatShouldLoad]; + + MPBannerAdManager * manager = [[MPBannerAdManager alloc] initWithDelegate:handler]; + MPMockAdServerCommunicator * communicator = [[MPMockAdServerCommunicator alloc] initWithDelegate:manager]; + communicator.mockConfigurationsResponse = configurations; + manager.communicator = communicator; + + MPAdTargeting * targeting = [[MPAdTargeting alloc] initWithCreativeSafeSize:CGSizeZero]; + [manager loadAdWithTargeting:targeting]; + + [self waitForExpectationsWithTimeout:kDefaultTimeout handler:^(NSError * _Nullable error) { + if (error != nil) { + XCTFail(@"Timed out"); + } + }]; +} + @end diff --git a/MoPubSDKTests/MPBannerAdapterDelegateHandler.h b/MoPubSDKTests/MPBannerAdapterDelegateHandler.h index 9544654a3..68699ca4a 100644 --- a/MoPubSDKTests/MPBannerAdapterDelegateHandler.h +++ b/MoPubSDKTests/MPBannerAdapterDelegateHandler.h @@ -1,7 +1,7 @@ // // MPBannerAdapterDelegateHandler.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -27,4 +27,7 @@ typedef void(^MPBannerAdapterDelegateHandlerFailureBlock)(NSError * error); @property (nonatomic, copy) MPBannerAdapterDelegateHandlerBlock willLeaveApplication; @property (nonatomic, copy) MPBannerAdapterDelegateHandlerBlock didTrackImpression; +@property (nonatomic, copy) MPBannerAdapterDelegateHandlerBlock willExpand; +@property (nonatomic, copy) MPBannerAdapterDelegateHandlerBlock didCollapse; + @end diff --git a/MoPubSDKTests/MPBannerAdapterDelegateHandler.m b/MoPubSDKTests/MPBannerAdapterDelegateHandler.m index de838e1f9..9de98d1cc 100644 --- a/MoPubSDKTests/MPBannerAdapterDelegateHandler.m +++ b/MoPubSDKTests/MPBannerAdapterDelegateHandler.m @@ -1,7 +1,7 @@ // // MPBannerAdapterDelegateHandler.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -30,8 +30,16 @@ - (void)userWillLeaveApplicationFromAdapter:(MPBaseBannerAdapter *)adapter { if (self.willLeaveApplication != nil) { self.willLeaveApplication(); } } -- (void)adapter:(MPBaseBannerAdapter *)adapter didTrackImpressionForAd:(UIView *)ad { +- (void)adapterDidTrackImpressionForAd:(MPBaseBannerAdapter *)adapter { if (self.didTrackImpression != nil) { self.didTrackImpression(); } } +- (void)adWillExpandForAdapter:(MPBaseBannerAdapter *)adapter { + if (self.willExpand != nil) { self.willExpand(); } +} + +- (void)adDidCollapseForAdapter:(MPBaseBannerAdapter *)adapter { + if (self.didCollapse != nil) { self.didCollapse(); } +} + @end diff --git a/MoPubSDKTests/MPBannerCustomEventAdapter+Testing.h b/MoPubSDKTests/MPBannerCustomEventAdapter+Testing.h index d4de53314..9ad36f72c 100644 --- a/MoPubSDKTests/MPBannerCustomEventAdapter+Testing.h +++ b/MoPubSDKTests/MPBannerCustomEventAdapter+Testing.h @@ -1,7 +1,7 @@ // // MPBannerCustomEventAdapter+Testing.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -17,6 +17,7 @@ - (void)loadAdWithConfiguration:(MPAdConfiguration *)configuration customEvent:(MPBannerCustomEvent *)customEvent; - (void)setHasTrackedImpression:(BOOL)hasTrackedImpression; +- (void)adViewWillLogImpression:(UIView *)adView; - (BOOL)shouldTrackImpressionOnDisplay; - (void)startTimeoutTimer; diff --git a/MoPubSDKTests/MPBannerCustomEventAdapter+Testing.m b/MoPubSDKTests/MPBannerCustomEventAdapter+Testing.m index 71848d67b..675b7a540 100644 --- a/MoPubSDKTests/MPBannerCustomEventAdapter+Testing.m +++ b/MoPubSDKTests/MPBannerCustomEventAdapter+Testing.m @@ -1,7 +1,7 @@ // // MPBannerCustomEventAdapter+Testing.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/MPBannerCustomEventAdapterTests.m b/MoPubSDKTests/MPBannerCustomEventAdapterTests.m index 93a4be2eb..c6bfb1841 100644 --- a/MoPubSDKTests/MPBannerCustomEventAdapterTests.m +++ b/MoPubSDKTests/MPBannerCustomEventAdapterTests.m @@ -1,7 +1,7 @@ // // MPBannerCustomEventAdapterTests.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -36,7 +36,7 @@ - (void)tearDown { // When an AD is in the imp tracking experiment, banner impressions (include all banner formats) are fired from SDK. - (void)testShouldTrackImpOnDisplayWhenExperimentEnabled { NSDictionary *headers = @{ kBannerImpressionVisableMsMetadataKey: @"0", kBannerImpressionMinPixelMetadataKey:@"1"}; - MPAdConfiguration *config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; + MPAdConfiguration *config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil adType:MPAdTypeInline]; MPBannerCustomEventAdapter *adapter = [MPBannerCustomEventAdapter new]; diff --git a/MoPubSDKTests/MPConsentDialogViewControllerDelegateHandler.h b/MoPubSDKTests/MPConsentDialogViewControllerDelegateHandler.h index 6a2f5be78..43bc4c682 100644 --- a/MoPubSDKTests/MPConsentDialogViewControllerDelegateHandler.h +++ b/MoPubSDKTests/MPConsentDialogViewControllerDelegateHandler.h @@ -1,7 +1,7 @@ // // MPConsentDialogViewControllerDelegateHandler.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/MPConsentDialogViewControllerDelegateHandler.m b/MoPubSDKTests/MPConsentDialogViewControllerDelegateHandler.m index 80908fb0e..4a46030af 100644 --- a/MoPubSDKTests/MPConsentDialogViewControllerDelegateHandler.m +++ b/MoPubSDKTests/MPConsentDialogViewControllerDelegateHandler.m @@ -1,7 +1,7 @@ // // MPConsentDialogViewControllerDelegateHandler.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/MPConsentDialogViewControllerTests.m b/MoPubSDKTests/MPConsentDialogViewControllerTests.m index 7b1389654..9d54c4b61 100644 --- a/MoPubSDKTests/MPConsentDialogViewControllerTests.m +++ b/MoPubSDKTests/MPConsentDialogViewControllerTests.m @@ -1,7 +1,7 @@ // // MPConsentDialogViewControllerTests.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/MPConsentManager+Testing.h b/MoPubSDKTests/MPConsentManager+Testing.h index d8292a935..49c24e394 100644 --- a/MoPubSDKTests/MPConsentManager+Testing.h +++ b/MoPubSDKTests/MPConsentManager+Testing.h @@ -1,7 +1,7 @@ // // MPConsentManager+Testing.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -9,6 +9,7 @@ #import "MPConsentManager.h" extern NSString * _Nonnull const kIfaForConsentStorageKey; +extern NSString * _Nonnull const kAdUnitIdUsedForConsentStorageKey; @interface MPConsentManager (Testing) - (BOOL)setCurrentStatus:(MPConsentStatus)currentStatus @@ -18,7 +19,8 @@ extern NSString * _Nonnull const kIfaForConsentStorageKey; - (NSURL * _Nullable)urlWithFormat:(NSString * _Nullable)urlFormat isoLanguageCode:(NSString * _Nullable)isoLanguageCode; - (void)setIsGDPRApplicable:(MPBool)isGDPRApplicable; @property (nonatomic, readonly) MPBool rawIsGDPRApplicable; - +- (void)didFinishSynchronizationWithData:(NSData * _Nonnull)data synchronizedStatus:(NSString * _Nonnull)synchronizedStatus completion:(void (^ _Nonnull)(NSError * _Nullable error))completion; +- (void)didFailSynchronizationWithError:(NSError * _Nullable)error completion:(void (^ _Nonnull)(NSError * _Nullable error))completion; // Reset consent manager state for testing - (void)setUpConsentManagerForTesting; @end diff --git a/MoPubSDKTests/MPConsentManager+Testing.m b/MoPubSDKTests/MPConsentManager+Testing.m index ec43e7d46..83b455346 100644 --- a/MoPubSDKTests/MPConsentManager+Testing.m +++ b/MoPubSDKTests/MPConsentManager+Testing.m @@ -1,25 +1,34 @@ // // MPConsentManager+Testing.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // #import "MPConsentManager+Testing.h" -// Copied from `MPConsentManager.m` +NSString * const kAdUnitIdUsedForConsentStorageKey = @"com.mopub.mopub-ios-sdk.consent.ad.unit.id"; static NSString * const kConsentedIabVendorListStorageKey = @"com.mopub.mopub-ios-sdk.consented.iab.vendor.list"; static NSString * const kConsentedPrivacyPolicyVersionStorageKey = @"com.mopub.mopub-ios-sdk.consented.privacy.policy.version"; static NSString * const kConsentedVendorListVersionStorageKey = @"com.mopub.mopub-ios-sdk.consented.vendor.list.version"; static NSString * const kConsentStatusStorageKey = @"com.mopub.mopub-ios-sdk.consent.status"; -static NSString * const kGDPRAppliesStorageKey = @"com.mopub.mopub-ios-sdk.gdpr.applies"; +static NSString * const kExtrasStorageKey = @"com.mopub.mopub-ios-sdk.extras"; +static NSString * const kIabVendorListStorageKey = @"com.mopub.mopub-ios-sdk.iab.vendor.list"; +static NSString * const kIabVendorListHashStorageKey = @"com.mopub.mopub-ios-sdk.iab.vendor.list.hash"; +NSString * const kIfaForConsentStorageKey = @"com.mopub.mopub-ios-sdk.ifa.for.consent"; static NSString * const kIsDoNotTrackStorageKey = @"com.mopub.mopub-ios-sdk.is.do.not.track"; +static NSString * const kIsWhitelistedStorageKey = @"com.mopub.mopub-ios-sdk.is.whitelisted"; +static NSString * const kGDPRAppliesStorageKey = @"com.mopub.mopub-ios-sdk.gdpr.applies"; +static NSString * const kForceGDPRAppliesStorageKey = @"com.mopub.mopub-ios-sdk.gdpr.force.applies.true"; static NSString * const kLastChangedMsStorageKey = @"com.mopub.mopub-ios-sdk.last.changed.ms"; static NSString * const kLastChangedReasonStorageKey = @"com.mopub.mopub-ios-sdk.last.changed.reason"; -NSString * const kIfaForConsentStorageKey = @"com.mopub.mopub-ios-sdk.ifa.for.consent"; +static NSString * const kLastSynchronizedConsentStatusStorageKey = @"com.mopub.mopub-ios-sdk.last.synchronized.consent.status"; +static NSString * const kPrivacyPolicyUrlStorageKey = @"com.mopub.mopub-ios-sdk.privacy.policy.url"; +static NSString * const kPrivacyPolicyVersionStorageKey = @"com.mopub.mopub-ios-sdk.privacy.policy.version"; static NSString * const kShouldReacquireConsentStorageKey = @"com.mopub.mopub-ios-sdk.should.reacquire.consent"; -static NSString * const kForceGDPRAppliesStorageKey = @"com.mopub.mopub-ios-sdk.gdpr.force.applies.true"; +static NSString * const kVendorListUrlStorageKey = @"com.mopub.mopub-ios-sdk.vendor.list.url"; +static NSString * const kVendorListVersionStorageKey = @"com.mopub.mopub-ios-sdk.vendor.list.version"; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wincomplete-implementation" @@ -36,6 +45,7 @@ - (void)synchronizeConsentWithCompletion:(void (^ _Nonnull)(NSError * error))com // Reset consent manager for testing - (void)setUpConsentManagerForTesting { NSUserDefaults * defaults = NSUserDefaults.standardUserDefaults; + [defaults setObject:nil forKey:kAdUnitIdUsedForConsentStorageKey]; [defaults setObject:nil forKey:kConsentedIabVendorListStorageKey]; [defaults setObject:nil forKey:kConsentedPrivacyPolicyVersionStorageKey]; [defaults setObject:nil forKey:kConsentedVendorListVersionStorageKey]; diff --git a/MoPubSDKTests/MPConsentManagerTests.m b/MoPubSDKTests/MPConsentManagerTests.m index 235af0e47..0a412c7f3 100644 --- a/MoPubSDKTests/MPConsentManagerTests.m +++ b/MoPubSDKTests/MPConsentManagerTests.m @@ -1,7 +1,7 @@ // // MPConsentManagerTests.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -1111,23 +1111,6 @@ - (void)testConsentSynchronizationUrlAfterTransitionSuccess { XCTAssert([[syncUrl stringForPOSTDataKey:kExtrasKey] isEqualToString:@"i'm extra!"]); } -- (void)testAutomaticAdUnitIdPopulation { - MPConsentManager * manager = MPConsentManager.sharedManager; - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wnonnull" - // Intentially set the explicitly marked `nonnull` property to `nil` to - // simulate an uninitialized state. - manager.adUnitIdUsedForConsent = nil; -#pragma clang diagnostic pop - XCTAssertNil(manager.adUnitIdUsedForConsent); - - MPURL * url = [MPAdServerURLBuilder URLWithAdUnitID:@"abc123" keywords:nil userDataKeywords:nil location:nil]; - XCTAssertNotNil(url); - XCTAssertNotNil(manager.adUnitIdUsedForConsent); - XCTAssert([manager.adUnitIdUsedForConsent isEqualToString:@"abc123"]); -} - #pragma mark - Macro Replacement - (void)testValidPrivacyPolicyPreferredLanguageReplacement { @@ -1285,5 +1268,234 @@ - (void)testDialogWontLoadIfNotSureIfGDPRApplies { XCTAssertFalse(MPConsentManager.sharedManager.isConsentDialogLoaded); } +#pragma mark - Adunit ID Caching Mechanism + +- (void)testAdunitIDNotOverwrittenIfAnotherIsCached { + MPConsentManager * manager = MPConsentManager.sharedManager; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnonnull" + // Intentially set the explicitly marked `nonnull` property to `nil` to + // simulate an uninitialized state. + manager.adUnitIdUsedForConsent = nil; +#pragma clang diagnostic pop + // Be sure the adunit ID is presently nil + XCTAssertNil(manager.adUnitIdUsedForConsent); + + NSString * cachedAdunitID = @"adunit ID"; + [NSUserDefaults.standardUserDefaults setObject:cachedAdunitID forKey:kAdUnitIdUsedForConsentStorageKey]; + + // Check to make sure the cached value is returned + XCTAssert([manager.adUnitIdUsedForConsent isEqualToString:cachedAdunitID]); + + // Attempt to set a different adunit ID + NSString * differentAdunitID = @"another adunit ID"; + manager.adUnitIdUsedForConsent = differentAdunitID; + + // Check to make sure the manager is still using the original adunit ID + XCTAssertFalse([manager.adUnitIdUsedForConsent isEqualToString:differentAdunitID]); + XCTAssert([manager.adUnitIdUsedForConsent isEqualToString:cachedAdunitID]); +} + +- (void)testIsKnownGoodWillCache { + MPConsentManager * manager = MPConsentManager.sharedManager; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnonnull" + // Intentially set the explicitly marked `nonnull` property to `nil` to + // simulate an uninitialized state. + manager.adUnitIdUsedForConsent = nil; +#pragma clang diagnostic pop + // Be sure the adunit ID is presently nil + XCTAssertNil(manager.adUnitIdUsedForConsent); + + // Make and set adunit ID as known good + NSString * adunitID = @"adunit ID"; + [manager setAdUnitIdUsedForConsent:adunitID isKnownGood:YES]; + + // Check to make sure the ID populated + XCTAssert([manager.adUnitIdUsedForConsent isEqualToString:adunitID]); + // Check to make sure it's cached + XCTAssert([adunitID isEqualToString:[NSUserDefaults.standardUserDefaults stringForKey:kAdUnitIdUsedForConsentStorageKey]]); +} + +- (void)testIsNotKnownGoodWillNotCache { + MPConsentManager * manager = MPConsentManager.sharedManager; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnonnull" + // Intentially set the explicitly marked `nonnull` property to `nil` to + // simulate an uninitialized state. + manager.adUnitIdUsedForConsent = nil; +#pragma clang diagnostic pop + // Be sure the adunit ID is presently nil + XCTAssertNil(manager.adUnitIdUsedForConsent); + + // Make and set adunit ID + NSString * adunitID = @"adunit ID"; + [manager setAdUnitIdUsedForConsent:adunitID isKnownGood:NO]; + + // Check to make sure the ID populated + XCTAssert([manager.adUnitIdUsedForConsent isEqualToString:adunitID]); + // Check to make sure it's not cached + XCTAssertNil([NSUserDefaults.standardUserDefaults stringForKey:kAdUnitIdUsedForConsentStorageKey]); +} + +- (void)testSettingDirectlyIsSameAsNotKnownGood { + MPConsentManager * manager = MPConsentManager.sharedManager; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnonnull" + // Intentially set the explicitly marked `nonnull` property to `nil` to + // simulate an uninitialized state. + manager.adUnitIdUsedForConsent = nil; +#pragma clang diagnostic pop + // Be sure the adunit ID is presently nil + XCTAssertNil(manager.adUnitIdUsedForConsent); + + // Make and set adunit ID + NSString * adunitID = @"adunit ID"; + manager.adUnitIdUsedForConsent = adunitID; + + // Check to make sure the ID populated + XCTAssert([manager.adUnitIdUsedForConsent isEqualToString:adunitID]); + // Check to make sure it's not cached + XCTAssertNil([NSUserDefaults.standardUserDefaults stringForKey:kAdUnitIdUsedForConsentStorageKey]); +} + +- (void)testSuccessfulSyncCachesAdunitID { + MPConsentManager * manager = MPConsentManager.sharedManager; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnonnull" + // Intentially set the explicitly marked `nonnull` property to `nil` to + // simulate an uninitialized state. + manager.adUnitIdUsedForConsent = nil; +#pragma clang diagnostic pop + // Be sure the adunit ID is presently nil + XCTAssertNil(manager.adUnitIdUsedForConsent); + + // Make and set adunit ID + NSString * adunitID = @"adunit ID"; + manager.adUnitIdUsedForConsent = adunitID; + + // Check to make sure the ID populated + XCTAssert([manager.adUnitIdUsedForConsent isEqualToString:adunitID]); + // Check to make sure it's not yet cached + XCTAssertNil([NSUserDefaults.standardUserDefaults stringForKey:kAdUnitIdUsedForConsentStorageKey]); + + // Simulate successful sync + [manager didFinishSynchronizationWithData:[NSData data] + synchronizedStatus:@"" + completion:^(NSError * error){}]; + + // Check to make sure the ID is still populated + XCTAssert([manager.adUnitIdUsedForConsent isEqualToString:adunitID]); + // Check to make sure it is now cached + XCTAssert([adunitID isEqualToString:[NSUserDefaults.standardUserDefaults stringForKey:kAdUnitIdUsedForConsentStorageKey]]); +} + +- (void)testUnsuccessfulSyncDoesNotCacheAdunitID { + MPConsentManager * manager = MPConsentManager.sharedManager; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnonnull" + // Intentially set the explicitly marked `nonnull` property to `nil` to + // simulate an uninitialized state. + manager.adUnitIdUsedForConsent = nil; +#pragma clang diagnostic pop + // Be sure the adunit ID is presently nil + XCTAssertNil(manager.adUnitIdUsedForConsent); + + // Make and set adunit ID + NSString * adunitID = @"adunit ID"; + manager.adUnitIdUsedForConsent = adunitID; + + // Check to make sure the ID populated + XCTAssert([manager.adUnitIdUsedForConsent isEqualToString:adunitID]); + // Check to make sure it's not yet cached + XCTAssertNil([NSUserDefaults.standardUserDefaults stringForKey:kAdUnitIdUsedForConsentStorageKey]); + + // Simulate successful sync + [manager didFailSynchronizationWithError:nil + completion:^(NSError * error){}]; + + // Check to make sure the ID is still populated + XCTAssert([manager.adUnitIdUsedForConsent isEqualToString:adunitID]); + // Check to make sure it is still not cached + XCTAssertNil([NSUserDefaults.standardUserDefaults stringForKey:kAdUnitIdUsedForConsentStorageKey]); +} + +- (void)testSuccessfulSyncDoesNotOverwritePreviouslyCachedAdunitID { + MPConsentManager * manager = MPConsentManager.sharedManager; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnonnull" + // Intentially set the explicitly marked `nonnull` property to `nil` to + // simulate an uninitialized state. + manager.adUnitIdUsedForConsent = nil; +#pragma clang diagnostic pop + // Be sure the adunit ID is presently nil + XCTAssertNil(manager.adUnitIdUsedForConsent); + + // Cache an adunit ID + NSString * cachedAdunitID = @"cached adunit ID"; + [manager setAdUnitIdUsedForConsent:cachedAdunitID isKnownGood:YES]; + + // Make and set adunit ID + NSString * adunitID = @"adunit ID"; + manager.adUnitIdUsedForConsent = adunitID; + + // Check to make sure the cached ID is populated + XCTAssert([manager.adUnitIdUsedForConsent isEqualToString:cachedAdunitID]); + // Check to make sure it's cached + XCTAssert([cachedAdunitID isEqualToString:[NSUserDefaults.standardUserDefaults stringForKey:kAdUnitIdUsedForConsentStorageKey]]); + + // Simulate successful sync + [manager didFinishSynchronizationWithData:[NSData data] + synchronizedStatus:@"" + completion:^(NSError * error){}]; + + // Check to make sure the cached ID is populated + XCTAssert([manager.adUnitIdUsedForConsent isEqualToString:cachedAdunitID]); + // Check to make sure it's cached + XCTAssert([cachedAdunitID isEqualToString:[NSUserDefaults.standardUserDefaults stringForKey:kAdUnitIdUsedForConsentStorageKey]]); +} + +- (void)testCachedAdunitIDClears { + /* + Cache a new adunit ID + */ + MPConsentManager * manager = MPConsentManager.sharedManager; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnonnull" + // Intentially set the explicitly marked `nonnull` property to `nil` to + // simulate an uninitialized state. + manager.adUnitIdUsedForConsent = nil; +#pragma clang diagnostic pop + // Be sure the adunit ID is presently nil + XCTAssertNil(manager.adUnitIdUsedForConsent); + + // Make and set adunit ID + NSString * adunitID = @"adunit ID"; + manager.adUnitIdUsedForConsent = adunitID; + + // Check to make sure the ID populated + XCTAssert([manager.adUnitIdUsedForConsent isEqualToString:adunitID]); + // Check to make sure it's not yet cached + XCTAssertNil([NSUserDefaults.standardUserDefaults stringForKey:kAdUnitIdUsedForConsentStorageKey]); + + // Simulate successful sync + [manager didFinishSynchronizationWithData:[NSData data] + synchronizedStatus:@"" + completion:^(NSError * error){}]; + + // Check to make sure the ID is still populated + XCTAssert([manager.adUnitIdUsedForConsent isEqualToString:adunitID]); + // Check to make sure it is now cached + XCTAssert([adunitID isEqualToString:[NSUserDefaults.standardUserDefaults stringForKey:kAdUnitIdUsedForConsentStorageKey]]); + + + /* + Clear the adunit ID and make sure it's totally cleared + */ + [manager clearAdUnitIdUsedForConsent]; + XCTAssertNil(manager.adUnitIdUsedForConsent); + XCTAssertNil([NSUserDefaults.standardUserDefaults stringForKey:kAdUnitIdUsedForConsentStorageKey]); +} + @end diff --git a/MoPubSDKTests/MPConstants+Testing.h b/MoPubSDKTests/MPConstants+Testing.h index 3a8c54046..7f5ee3d28 100644 --- a/MoPubSDKTests/MPConstants+Testing.h +++ b/MoPubSDKTests/MPConstants+Testing.h @@ -1,7 +1,7 @@ // // MPConstants+Testing.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/MPConstants+Testing.m b/MoPubSDKTests/MPConstants+Testing.m index 3a23bd88b..3b17c8e1c 100644 --- a/MoPubSDKTests/MPConstants+Testing.m +++ b/MoPubSDKTests/MPConstants+Testing.m @@ -1,7 +1,7 @@ // // MPConstants+Testing.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/MPCountdownTimerView+Testing.h b/MoPubSDKTests/MPCountdownTimerView+Testing.h new file mode 100644 index 000000000..ea3cba136 --- /dev/null +++ b/MoPubSDKTests/MPCountdownTimerView+Testing.h @@ -0,0 +1,22 @@ +// +// MPCountdownTimerView+Testing.h +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import +#import "MPCountdownTimerView.h" +#import "MPTimer.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface MPCountdownTimerView (Testing) + +@property (nonatomic, readonly) MPTimer * timer; +@property (nonatomic, strong) NSNotificationCenter *notificationCenter; // do not use `defaultCenter` for unit tests + +@end + +NS_ASSUME_NONNULL_END diff --git a/MoPubSDKTests/MPCountdownTimerViewTests.m b/MoPubSDKTests/MPCountdownTimerViewTests.m index bb24e101d..cd84dc6a4 100644 --- a/MoPubSDKTests/MPCountdownTimerViewTests.m +++ b/MoPubSDKTests/MPCountdownTimerViewTests.m @@ -1,16 +1,20 @@ // // MPCountdownTimerViewTests.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // #import +#import "MPCountdownTimerView+Testing.h" #import "MPCountdownTimerView.h" -static const NSTimeInterval kTestTimeout = 15; // seconds -static const NSTimeInterval kTimerDuration = 7; // seconds +static const NSTimeInterval kTimerDurationInSeconds = 1; + +// `NSTimer` is not totally accurate and it might be slower on build machines , thus we need some +// extra tolerance while waiting for the expections to be fulfilled. +static const NSTimeInterval kWaitTimeTolerance = 2; @interface MPCountdownTimerViewTests : XCTestCase @property (nonatomic, strong) MPCountdownTimerView * timerView; @@ -18,162 +22,191 @@ @interface MPCountdownTimerViewTests : XCTestCase @implementation MPCountdownTimerViewTests -#pragma mark - Setup - -- (void)setUp { - [super setUp]; - self.timerView = [[MPCountdownTimerView alloc] initWithFrame:CGRectMake(0, 0, 40, 40) duration:kTimerDuration]; -} - -- (void)tearDown { - self.timerView = nil; - [super tearDown]; -} - #pragma mark - Tests -// Tests that attempting to start an already running timer will do nothing. -- (void)testDoubleStart { - XCTestExpectation * expectation = [self expectationWithDescription:@"Wait for timer completion block to fire."]; - - __block BOOL completionFired = NO; - [self.timerView startWithTimerCompletion:^(BOOL hasElapsed) { - completionFired = YES; - [expectation fulfill]; - }]; - - [self.timerView startWithTimerCompletion:^(BOOL hasElapsed) { - XCTFail(@"This timer completion block should never have been invoked."); - }]; - - [self waitForExpectationsWithTimeout:kTestTimeout handler:^(NSError * _Nullable error) { - XCTAssertNil(error); - }]; - - XCTAssert(completionFired, @"Countdown timer completion block failed to fire."); +// Test initialization with valid and invalid durations. +- (void)testInitialization { + XCTAssertNotNil([[MPCountdownTimerView alloc] initWithDuration:100 timerCompletion:^(BOOL hasElapsed) {}]); + XCTAssertNotNil([[MPCountdownTimerView alloc] initWithDuration:1 timerCompletion:^(BOOL hasElapsed) {}]); + XCTAssertNil([[MPCountdownTimerView alloc] initWithDuration:0 timerCompletion:^(BOOL hasElapsed) {}]); + XCTAssertNil([[MPCountdownTimerView alloc] initWithDuration:-1 timerCompletion:^(BOOL hasElapsed) {}]); + XCTAssertNil([[MPCountdownTimerView alloc] initWithDuration:-100 timerCompletion:^(BOOL hasElapsed) {}]); } // Tests that the completion block for the timer executes after the timer has elapsed. - (void)testElapses { - XCTestExpectation * expectation = [self expectationWithDescription:@"Wait for timer completion block to fire."]; - - __block BOOL completionFired = NO; - [self.timerView startWithTimerCompletion:^(BOOL hasElapsed) { - completionFired = YES; - [expectation fulfill]; + XCTestExpectation * completionExpectation = [self expectationWithDescription:@"Wait for timer completion block to fire."]; + __block BOOL completionCount = 0; // expected to be exactly 1 + MPCountdownTimerView * timerView = [[MPCountdownTimerView alloc] initWithDuration:kTimerDurationInSeconds timerCompletion:^(BOOL hasElapsed) { + completionCount += 1; + [completionExpectation fulfill]; }]; - [self waitForExpectationsWithTimeout:kTestTimeout handler:^(NSError * _Nullable error) { + [timerView start]; + [self waitForExpectationsWithTimeout:kTimerDurationInSeconds * kWaitTimeTolerance handler:^(NSError * _Nullable error) { + XCTAssertEqual(completionCount, 1, "Countdown timer completion block is not fired exactly once"); XCTAssertNil(error); }]; - - XCTAssert(completionFired, @"Countdown timer completion block failed to fire."); } -// Tests initialization with an invalid duration. -- (void)testInvalidDuration { - self.timerView = [[MPCountdownTimerView alloc] initWithFrame:CGRectMake(0, 0, 40, 40) duration:-1]; - - XCTAssertNil(self.timerView); -} - -// Tests pausing a stopped timer does nothing. -- (void)testNoOpPause { - [self.timerView pause]; +// Test that attempting to start an already running timer before completion will do nothing. +- (void)testDoubleStartBeforeCompletion { + XCTestExpectation * completionExpectation = [self expectationWithDescription:@"Wait for timer completion block to fire."]; + __block BOOL completionCount = 0; // expected to be exactly 1 + MPCountdownTimerView * timerView = [[MPCountdownTimerView alloc] initWithDuration:kTimerDurationInSeconds timerCompletion:^(BOOL hasElapsed) { + completionCount += 1; + [completionExpectation fulfill]; + }]; - XCTAssertFalse(self.timerView.isPaused); -} + [timerView start]; + [timerView start]; // this redundant `start` should be ignore and has not effect + [self waitForExpectationsWithTimeout:kTimerDurationInSeconds * kWaitTimeTolerance handler:^(NSError * _Nullable error) { + XCTAssertEqual(completionCount, 1, "Countdown timer completion block is not fired exactly once"); + XCTAssertNil(error); + }]; -// Tests resuming a stopped timer does nothing. -- (void)testNoOpResume { - [self.timerView resume]; + // Now the completion is fired, wait a little longer to make sure the second `start` does nothing - XCTAssertFalse(self.timerView.isPaused); - XCTAssertFalse(self.timerView.isActive); + XCTestExpectation * emptyExpectation = [self expectationWithDescription:@"Nothing should happen, wait a little long to see"]; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(kTimerDurationInSeconds / 5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [emptyExpectation fulfill]; + }); + [self waitForExpectationsWithTimeout:kTimerDurationInSeconds * kWaitTimeTolerance handler:^(NSError * _Nullable error) { + XCTAssertEqual(completionCount, 1, "Countdown timer completion block is not fired exactly once"); + XCTAssertNil(error); + }]; } -// Tests stopping a stopped timer does nothing. -- (void)testNoOpStop { - [self.timerView stopAndSignalCompletion:NO]; +// Test that starting a completed timer will do nothing +- (void)testStartAgainAfterCompletion { + XCTestExpectation * completionExpectation = [self expectationWithDescription:@"Wait for timer completion block to fire."]; + __block BOOL completionCount = 0; // expected to be exactly 1 + MPCountdownTimerView * timerView = [[MPCountdownTimerView alloc] initWithDuration:kTimerDurationInSeconds timerCompletion:^(BOOL hasElapsed) { + completionCount += 1; + [completionExpectation fulfill]; + }]; - XCTAssertFalse(self.timerView.isActive); -} + [timerView start]; + [self waitForExpectationsWithTimeout:kTimerDurationInSeconds * kWaitTimeTolerance handler:^(NSError * _Nullable error) { + XCTAssertEqual(completionCount, 1, "Countdown timer completion block is not fired exactly once"); + XCTAssertNil(error); + }]; -// Tests that the timer has successfully paused operation. -- (void)testPause { - [self.timerView startWithTimerCompletion:nil]; - [self.timerView pause]; + // Now the completion is fired, calling `start` again should has no effect - XCTAssertTrue(self.timerView.isPaused); + XCTestExpectation * emptyExpectation = [self expectationWithDescription:@"Nothing should happen, wait a little long to see"]; + [timerView start]; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(kTimerDurationInSeconds * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [emptyExpectation fulfill]; + }); + [self waitForExpectationsWithTimeout:kTimerDurationInSeconds * kWaitTimeTolerance handler:^(NSError * _Nullable error) { + XCTAssertEqual(completionCount, 1, "Countdown timer completion block is not fired exactly once"); + XCTAssertNil(error); + }]; } -// Tests that the timer has resumed operation. -- (void)testResume { - XCTestExpectation * expectation = [self expectationWithDescription:@"Wait for timer completion block to fire."]; - - __block BOOL completionFired = NO; - [self.timerView startWithTimerCompletion:^(BOOL hasElapsed) { - completionFired = YES; - [expectation fulfill]; +// Test the timer can be paused and resumed multiple times by notifications +- (void)testPauseAndResume { + XCTestExpectation * completionExpectation = [self expectationWithDescription:@"Wait for timer completion block to fire."]; + __block BOOL completionCount = 0; // expected to be exactly 1 + MPCountdownTimerView * timerView = [[MPCountdownTimerView alloc] initWithDuration:kTimerDurationInSeconds timerCompletion:^(BOOL hasElapsed) { + completionCount += 1; + [completionExpectation fulfill]; }]; + timerView.notificationCenter = [NSNotificationCenter new]; + const NSTimeInterval kPauseSeconds = kTimerDurationInSeconds / 4; + + // nothing should happen before `start` + [timerView.notificationCenter postNotificationName:UIApplicationDidEnterBackgroundNotification object:nil]; + XCTAssertFalse(timerView.timer.isCountdownActive); + [timerView.notificationCenter postNotificationName:UIApplicationWillEnterForegroundNotification object:nil]; + XCTAssertFalse(timerView.timer.isCountdownActive); + + [timerView start]; + XCTAssertTrue(timerView.timer.isCountdownActive); + + // pause + [timerView.notificationCenter postNotificationName:UIApplicationDidEnterBackgroundNotification object:nil]; + XCTAssertFalse(timerView.timer.isCountdownActive); + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(kPauseSeconds * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + // resume + [timerView.notificationCenter postNotificationName:UIApplicationWillEnterForegroundNotification object:nil]; + XCTAssertTrue(timerView.timer.isCountdownActive); + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(kPauseSeconds * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + // pause again + [timerView.notificationCenter postNotificationName:UIApplicationDidEnterBackgroundNotification object:nil]; + XCTAssertFalse(timerView.timer.isCountdownActive); + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(kPauseSeconds * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + // resume again + [timerView.notificationCenter postNotificationName:UIApplicationWillEnterForegroundNotification object:nil]; + XCTAssertTrue(timerView.timer.isCountdownActive); + }); + }); + }); - // Pause the timer. - [self.timerView pause]; - XCTAssertTrue(self.timerView.isPaused); - - // Resume the timer. - [self.timerView resume]; - XCTAssertFalse(self.timerView.isPaused); - - [self waitForExpectationsWithTimeout:kTestTimeout handler:^(NSError * _Nullable error) { + NSTimeInterval totalTime = kTimerDurationInSeconds + kPauseSeconds * 2; // paused twice + [self waitForExpectationsWithTimeout:totalTime * kWaitTimeTolerance handler:^(NSError * _Nullable error) { + XCTAssertEqual(completionCount, 1, "Countdown timer completion block is not fired exactly once"); XCTAssertNil(error); }]; - XCTAssert(completionFired, @"Countdown timer completion block failed to fire."); -} + XCTAssertFalse(timerView.timer.isCountdownActive); -// Tests that the timer has stopped operation. -- (void)testStop { - XCTestExpectation * expectation = [self expectationWithDescription:@"Wait for timer completion block to fire."]; + // nothing should happen after completion + [timerView.notificationCenter postNotificationName:UIApplicationDidEnterBackgroundNotification object:nil]; + XCTAssertFalse(timerView.timer.isCountdownActive); + [timerView.notificationCenter postNotificationName:UIApplicationWillEnterForegroundNotification object:nil]; + XCTAssertFalse(timerView.timer.isCountdownActive); +} - __block BOOL completionFired = NO; - [self.timerView startWithTimerCompletion:^(BOOL hasElapsed) { - completionFired = YES; - [expectation fulfill]; +// Test that the timer can stop and signal the completion block. +- (void)testStopAndSignal { + XCTestExpectation * completionExpectation = [self expectationWithDescription:@"Wait for timer completion block to fire."]; + __block BOOL completionCount = 0; // expected to be exactly 1 + MPCountdownTimerView * timerView = [[MPCountdownTimerView alloc] initWithDuration:kTimerDurationInSeconds timerCompletion:^(BOOL hasElapsed) { + completionCount += 1; + [completionExpectation fulfill]; }]; - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - [self.timerView stopAndSignalCompletion:YES]; + [timerView start]; + + NSTimeInterval stopTime = kTimerDurationInSeconds / 2; // stop half way through + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(stopTime * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [timerView stopAndSignalCompletion:YES]; }); - [self waitForExpectationsWithTimeout:kTestTimeout handler:^(NSError * _Nullable error) { + [self waitForExpectationsWithTimeout:kTimerDurationInSeconds * kWaitTimeTolerance handler:^(NSError * _Nullable error) { + XCTAssertFalse(timerView.timer.isCountdownActive); + XCTAssertEqual(completionCount, 1, "Countdown timer completion block is not fired exactly once"); XCTAssertNil(error); }]; - - XCTAssert(completionFired, @"Countdown timer completion block failed to fire."); - XCTAssertFalse(self.timerView.isActive); } -// Tests that the timer has stopped operation without signaling the completion block. +// Test that the timer can stop without signaling the completion block. - (void)testStopAndNoSignal { - XCTestExpectation * expectation = [self expectationWithDescription:@"Wait for timer completion block to fire."]; - - __block BOOL completionFired = NO; - [self.timerView startWithTimerCompletion:^(BOOL hasElapsed) { - completionFired = YES; + XCTestExpectation * completionExpectation = [self expectationWithDescription:@"Wait for timer completion block to fire."]; + __block BOOL completionCount = 0; // expected to be exactly 1 + MPCountdownTimerView * timerView = [[MPCountdownTimerView alloc] initWithDuration:kTimerDurationInSeconds timerCompletion:^(BOOL hasElapsed) { + completionCount += 1; // should not happen + [completionExpectation fulfill]; // should not happen }]; - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - [self.timerView stopAndSignalCompletion:NO]; - [expectation fulfill]; + [timerView start]; + + NSTimeInterval stopTime = kTimerDurationInSeconds / 2; // stop half way through + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(stopTime * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [timerView stopAndSignalCompletion:NO]; + [completionExpectation fulfill]; }); - [self waitForExpectationsWithTimeout:kTestTimeout handler:^(NSError * _Nullable error) { + [self waitForExpectationsWithTimeout:kTimerDurationInSeconds * kWaitTimeTolerance handler:^(NSError * _Nullable error) { + XCTAssertFalse(timerView.timer.isCountdownActive); + XCTAssertEqual(completionCount, 0, "Countdown timer completion block is fired unexpectedly"); XCTAssertNil(error); }]; - - XCTAssertFalse(completionFired, @"Countdown timer completion block should not have fired."); - XCTAssertFalse(self.timerView.isActive); } @end diff --git a/MoPubSDKTests/MPDictionaryAdditionTests.m b/MoPubSDKTests/MPDictionaryAdditionTests.m index e4db81a7c..cefdf9b31 100644 --- a/MoPubSDKTests/MPDictionaryAdditionTests.m +++ b/MoPubSDKTests/MPDictionaryAdditionTests.m @@ -1,7 +1,7 @@ // // MPDictionaryAdditionTests.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/MPDiskLRUCacheTests.m b/MoPubSDKTests/MPDiskLRUCacheTests.m new file mode 100644 index 000000000..7e5efa4ac --- /dev/null +++ b/MoPubSDKTests/MPDiskLRUCacheTests.m @@ -0,0 +1,102 @@ +// +// MPDiskLRUCacheTests.m +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import +#import "MPDiskLRUCache.h" + +#pragma mark - Private API Exposure + +@interface MPDiskLRUCache (Testing) +- (id)initWithCachePath:(NSString *)cachePath fileManager:(NSFileManager *)fileManager; +- (NSString *)cacheFilePathForKey:(NSString *)key; +@end + +#pragma mark - Tests + +@interface MPDiskLRUCacheTests : XCTestCase + +@property (nonatomic, strong) MPDiskLRUCache *cache; + +@end + +@implementation MPDiskLRUCacheTests + +- (void)setUp { + if (self.cache == nil) { + self.cache = [[MPDiskLRUCache alloc] initWithCachePath:[[NSUUID UUID] UUIDString] + fileManager:[NSFileManager new]]; + } + [self.cache removeAllCachedFiles]; +} + +- (void)tearDown { + [self.cache removeAllCachedFiles]; +} + +/** + Test all public methods in the main API. + */ +- (void)testBasicDataIO { + NSString *testKey = @"test key"; + NSStringEncoding stringEncoding = NSUTF8StringEncoding; + NSData *testData = [testKey dataUsingEncoding:stringEncoding]; + + XCTAssertFalse([self.cache cachedDataExistsForKey:testKey]); + XCTAssertNil([self.cache retrieveDataForKey:testKey]); + + [self.cache storeData:testData forKey:testKey]; + NSData *data = [self.cache retrieveDataForKey:testKey]; + NSString *string = [[NSString alloc] initWithData:data encoding:stringEncoding]; + + XCTAssertTrue([self.cache cachedDataExistsForKey:testKey]); + XCTAssertNotNil(data); + XCTAssertTrue([testKey isEqualToString: string]); + + [self.cache removeAllCachedFiles]; + + XCTAssertFalse([self.cache cachedDataExistsForKey:testKey]); + XCTAssertNil([self.cache retrieveDataForKey:testKey]); +} + +/** + Test all public methods in the (MediaFile) category. + */ +- (void)testMediaFileIO { + // obtain a URL of the expected media file + NSURL *testURL = [NSURL URLWithString:@"https://someurl.url/test.mp4"]; + NSString *localCacheFilePath = [[self.cache cacheFilePathForKey:testURL.absoluteString] + stringByAppendingPathExtension:@"mp4"]; + NSURL *localCacheFileURL = [NSURL fileURLWithPath:localCacheFilePath]; + + // Typically the source file is a temporary file provided by a URL session download task completion handler. + // Here we mock the source file URL by appending `.source` to `localCacheFileURL`. + NSURL *sourceFileURL = [localCacheFileURL URLByAppendingPathExtension:@"source"]; + + XCTAssertNotNil(localCacheFileURL); + XCTAssertTrue([[localCacheFileURL absoluteString] hasPrefix:@"file://"]); + XCTAssertTrue([[localCacheFileURL pathExtension] isEqualToString:testURL.pathExtension]); + XCTAssertFalse([self.cache isRemoteFileCached:testURL]); + + // "touch" should not create a file nor throw an exception + [self.cache touchCachedFileForRemoteFile:testURL]; + XCTAssertFalse([self.cache isRemoteFileCached:testURL]); + + // create an empty file instead of moving a real media file to the destination + [[NSFileManager defaultManager] createFileAtPath:sourceFileURL.path contents:nil attributes:nil]; + NSError *moveFileError = [self.cache moveLocalFileToCache:sourceFileURL remoteSourceFileURL:testURL]; + [self.cache touchCachedFileForRemoteFile:testURL]; // should not crash or anything bad + XCTAssertNil(moveFileError); + XCTAssertTrue([self.cache isRemoteFileCached:testURL]); + + // "touch" should not create a file nor throw an exception + [self.cache removeAllCachedFiles]; + [self.cache touchCachedFileForRemoteFile:testURL]; + XCTAssertFalse([self.cache isRemoteFileCached:testURL]); +} + +@end diff --git a/MoPubSDKTests/MPGeolocationProviderTest.m b/MoPubSDKTests/MPGeolocationProviderTest.m index cf526e524..cc25e6038 100644 --- a/MoPubSDKTests/MPGeolocationProviderTest.m +++ b/MoPubSDKTests/MPGeolocationProviderTest.m @@ -1,7 +1,7 @@ // // MPGeolocationProviderTest.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -12,13 +12,22 @@ @interface MPGeolocationProviderTest : XCTestCase +@property (nonatomic, readonly) MoPub *mopubInstance; +@property (nonatomic, readonly) MPConsentManager *consentManager; + @end @implementation MPGeolocationProviderTest - (void)setUp { [super setUp]; - [[MPConsentManager sharedManager] setUpConsentManagerForTesting]; + if (self.mopubInstance == nil) { + _mopubInstance = [MoPub new]; + } + if (self.consentManager == nil) { + _consentManager = [MPConsentManager new]; + } + [self.consentManager setUpConsentManagerForTesting]; } - (void)tearDown { @@ -39,12 +48,11 @@ - (void)testLocationUpdatesEnabledNotGdprRegion { @"current_vendor_list_iab_hash": @"hash", }; // Update consent - MPConsentManager * manager = MPConsentManager.sharedManager; - BOOL success = [manager updateConsentStateWithParameters:response]; + BOOL success = [self.consentManager updateConsentStateWithParameters:response]; XCTAssertTrue(success); - [MoPub.sharedInstance setLocationUpdatesEnabled:YES]; - XCTAssertTrue(MoPub.sharedInstance.locationUpdatesEnabled); + [self.mopubInstance setLocationUpdatesEnabled:YES]; + XCTAssertTrue(self.mopubInstance.locationUpdatesEnabled); } - (void)testLocationUpdatesEnabledNotConsented { @@ -60,12 +68,11 @@ - (void)testLocationUpdatesEnabledNotConsented { @"current_vendor_list_iab_hash": @"hash", }; // Update consent - MPConsentManager * manager = MPConsentManager.sharedManager; - BOOL success = [manager updateConsentStateWithParameters:response]; + BOOL success = [self.consentManager updateConsentStateWithParameters:response]; XCTAssertTrue(success); - [MoPub.sharedInstance setLocationUpdatesEnabled:YES]; - XCTAssertFalse(MoPub.sharedInstance.locationUpdatesEnabled); + [self.mopubInstance setLocationUpdatesEnabled:YES]; + XCTAssertFalse(self.mopubInstance.locationUpdatesEnabled); } @end diff --git a/MoPubSDKTests/MPHTTPNetworkSession+Testing.h b/MoPubSDKTests/MPHTTPNetworkSession+Testing.h index d8ae8a1b9..e3cd58c83 100644 --- a/MoPubSDKTests/MPHTTPNetworkSession+Testing.h +++ b/MoPubSDKTests/MPHTTPNetworkSession+Testing.h @@ -1,7 +1,7 @@ // // MPHTTPNetworkSession+Testing.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -9,6 +9,8 @@ #import "MPHTTPNetworkSession.h" #import "MPHTTPNetworkTaskData.h" +NS_ASSUME_NONNULL_BEGIN + @interface MPHTTPNetworkSession (Testing) // Expose private methods @@ -20,3 +22,5 @@ - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(nullable NSError *)error; @end + +NS_ASSUME_NONNULL_END diff --git a/MoPubSDKTests/MPHTTPNetworkSession+Testing.m b/MoPubSDKTests/MPHTTPNetworkSession+Testing.m index f6f60167f..417b1d6c5 100644 --- a/MoPubSDKTests/MPHTTPNetworkSession+Testing.m +++ b/MoPubSDKTests/MPHTTPNetworkSession+Testing.m @@ -1,7 +1,7 @@ // // MPHTTPNetworkSession+Testing.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/MPHTTPNetworkSessionTests.m b/MoPubSDKTests/MPHTTPNetworkSessionTests.m index 73cfa958e..b2ebd0e05 100644 --- a/MoPubSDKTests/MPHTTPNetworkSessionTests.m +++ b/MoPubSDKTests/MPHTTPNetworkSessionTests.m @@ -1,7 +1,7 @@ // // MPHTTPNetworkSessionTests.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -48,7 +48,11 @@ - (void)testStatusCode200 { // Fake network completion [session setSessionData:taskData forTask:task]; + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnonnull" // intentional nil test [session URLSession:nil task:task didCompleteWithError:nil]; +#pragma clang diagnostic push XCTAssertFalse(didError); } @@ -80,7 +84,11 @@ - (void)testStatusCode302 { // Fake network completion [session setSessionData:taskData forTask:task]; + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnonnull" // intentional nil test [session URLSession:nil task:task didCompleteWithError:nil]; +#pragma clang diagnostic pop XCTAssertFalse(didError); } @@ -112,7 +120,11 @@ - (void)testStatusCode400Fail { // Fake network completion [session setSessionData:taskData forTask:task]; + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnonnull" // intentional nil test [session URLSession:nil task:task didCompleteWithError:nil]; +#pragma clang diagnostic pop XCTAssertTrue(didError); } @@ -144,7 +156,11 @@ - (void)testStatusCode500Fail { // Fake network completion [session setSessionData:taskData forTask:task]; + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnonnull" // intentional nil test [session URLSession:nil task:task didCompleteWithError:nil]; +#pragma clang diagnostic pop XCTAssertTrue(didError); } diff --git a/MoPubSDKTests/MPIdentityProvider+Testing.h b/MoPubSDKTests/MPIdentityProvider+Testing.h new file mode 100644 index 000000000..c4b5ca595 --- /dev/null +++ b/MoPubSDKTests/MPIdentityProvider+Testing.h @@ -0,0 +1,17 @@ +// +// MPIdentityProvider+Testing.h +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import "MPIdentityProvider.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface MPIdentityProvider (Testing) ++ (NSString *)mopubIdentifier:(BOOL)obfuscate; +@end + +NS_ASSUME_NONNULL_END diff --git a/MoPubSDKTests/MPIdentityProvider+Testing.m b/MoPubSDKTests/MPIdentityProvider+Testing.m new file mode 100644 index 000000000..0c5bd637b --- /dev/null +++ b/MoPubSDKTests/MPIdentityProvider+Testing.m @@ -0,0 +1,16 @@ +// +// MPIdentityProvider+Testing.m +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import "MPIdentityProvider+Testing.h" + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wincomplete-implementation" +@implementation MPIdentityProvider (Testing) + +@end +#pragma clang diagnostic pop diff --git a/MoPubSDKTests/MPIdentityProviderTests.m b/MoPubSDKTests/MPIdentityProviderTests.m new file mode 100644 index 000000000..b273e0dff --- /dev/null +++ b/MoPubSDKTests/MPIdentityProviderTests.m @@ -0,0 +1,99 @@ +// +// MPIdentityProviderTests.m +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import +#import "MPIdentityProvider.h" +#import "MPIdentityProvider+Testing.h" + +// These should match the constants with the same name in `MPIdentityProvider.m` +#define MOPUB_IDENTIFIER_DEFAULTS_KEY @"com.mopub.identifier" +#define MOPUB_IDENTIFIER_LAST_SET_TIME_KEY @"com.mopub.identifiertime" + +@interface MPIdentityProviderTests : XCTestCase +@property (nonatomic, strong) NSCalendar * calendar; +@end + +@implementation MPIdentityProviderTests + +- (void)setUp { + // Clear out the MoPub identifier + [NSUserDefaults.standardUserDefaults removeObjectForKey:MOPUB_IDENTIFIER_DEFAULTS_KEY]; + [NSUserDefaults.standardUserDefaults removeObjectForKey:MOPUB_IDENTIFIER_LAST_SET_TIME_KEY]; + [NSUserDefaults.standardUserDefaults synchronize]; + + // Setup calendar + self.calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierISO8601]; + self.calendar.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0]; +} + +#pragma mark - MoPub Identifier + +- (void)testGenerateMoPubIdentifier { + NSString * mopubId = [MPIdentityProvider mopubIdentifier:NO]; + XCTAssertNotNil(mopubId); + XCTAssert([mopubId hasPrefix:@"mopub:"]); + XCTAssertNotNil([NSUserDefaults.standardUserDefaults objectForKey:MOPUB_IDENTIFIER_DEFAULTS_KEY]); + XCTAssertNotNil([NSUserDefaults.standardUserDefaults objectForKey:MOPUB_IDENTIFIER_LAST_SET_TIME_KEY]); +} + +- (void)testMoPubIdentifierSameUtcDay { + NSString * mopubId = [MPIdentityProvider mopubIdentifier:NO]; + XCTAssertNotNil(mopubId); + XCTAssert([mopubId hasPrefix:@"mopub:"]); + XCTAssertNotNil([NSUserDefaults.standardUserDefaults objectForKey:MOPUB_IDENTIFIER_DEFAULTS_KEY]); + XCTAssertNotNil([NSUserDefaults.standardUserDefaults objectForKey:MOPUB_IDENTIFIER_LAST_SET_TIME_KEY]); + + NSString * secondTryMopubId = [MPIdentityProvider mopubIdentifier:NO]; + XCTAssertNotNil(secondTryMopubId); + XCTAssert([secondTryMopubId hasPrefix:@"mopub:"]); + XCTAssert([secondTryMopubId isEqualToString:mopubId]); +} + +- (void)testMoPubIdentifierNextUtcDay { + // Calculate previous UTC day + NSDate * now = [NSDate date]; + NSDate * yesterday = [self.calendar dateByAddingUnit:NSCalendarUnitDay value:-1 toDate:now options:0]; + XCTAssert([now timeIntervalSinceDate:yesterday] > 0); + + // Set a fake mopub ID with yesterday's timestamp. + NSString * const kFakeMoPubId = @"unittest:1234567890"; + [NSUserDefaults.standardUserDefaults setObject:kFakeMoPubId forKey:MOPUB_IDENTIFIER_DEFAULTS_KEY]; + [NSUserDefaults.standardUserDefaults setObject:yesterday forKey:MOPUB_IDENTIFIER_LAST_SET_TIME_KEY]; + [NSUserDefaults.standardUserDefaults synchronize]; + + // Retrieve the MoPub identifier. This should trigger a new MoPub identifier generation. + NSString * mopubId = [MPIdentityProvider mopubIdentifier:NO]; + XCTAssertNotNil(mopubId); + XCTAssert([mopubId hasPrefix:@"mopub:"]); + XCTAssert(![mopubId isEqualToString:kFakeMoPubId]); + XCTAssertNotNil([NSUserDefaults.standardUserDefaults objectForKey:MOPUB_IDENTIFIER_DEFAULTS_KEY]); + XCTAssertNotNil([NSUserDefaults.standardUserDefaults objectForKey:MOPUB_IDENTIFIER_LAST_SET_TIME_KEY]); +} + +- (void)testMoPubIdentifierNextUtcWeek { + // Calculate previous UTC week + NSDate * now = [NSDate date]; + NSDate * lastWeek = [self.calendar dateByAddingUnit:NSCalendarUnitDay value:-7 toDate:now options:0]; + XCTAssert([now timeIntervalSinceDate:lastWeek] > 0); + + // Set a fake mopub ID with last week's timestamp. + NSString * const kFakeMoPubId = @"unittest:1234567890"; + [NSUserDefaults.standardUserDefaults setObject:kFakeMoPubId forKey:MOPUB_IDENTIFIER_DEFAULTS_KEY]; + [NSUserDefaults.standardUserDefaults setObject:lastWeek forKey:MOPUB_IDENTIFIER_LAST_SET_TIME_KEY]; + [NSUserDefaults.standardUserDefaults synchronize]; + + // Retrieve the MoPub identifier. This should trigger a new MoPub identifier generation. + NSString * mopubId = [MPIdentityProvider mopubIdentifier:NO]; + XCTAssertNotNil(mopubId); + XCTAssert([mopubId hasPrefix:@"mopub:"]); + XCTAssert(![mopubId isEqualToString:kFakeMoPubId]); + XCTAssertNotNil([NSUserDefaults.standardUserDefaults objectForKey:MOPUB_IDENTIFIER_DEFAULTS_KEY]); + XCTAssertNotNil([NSUserDefaults.standardUserDefaults objectForKey:MOPUB_IDENTIFIER_LAST_SET_TIME_KEY]); +} + +@end diff --git a/MoPubSDKTests/MPImpressionDataTests.m b/MoPubSDKTests/MPImpressionDataTests.m new file mode 100644 index 000000000..c46e21fa3 --- /dev/null +++ b/MoPubSDKTests/MPImpressionDataTests.m @@ -0,0 +1,169 @@ +// +// MPImpressionDataTests.m +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import +#import "MPImpressionData.h" +#import "MPAdServerKeys.h" + +@interface MPImpressionDataTests : XCTestCase + +@property (nonatomic, strong) NSMutableDictionary * testImpressionData; + +@end + +@implementation MPImpressionDataTests + +- (void)setUp { + [super setUp]; + + self.testImpressionData = [@{ + kImpressionDataImpressionIDKey : @"abcd-abcd-abcd-abcd", + kImpressionDataPublisherRevenueKey : @(0.0000015), + kImpressionDataAdUnitIDKey : @"FAKE_AD_UNIT", + kImpressionDataAdUnitNameKey : @"Test Fullscreen (fake name)", + kImpressionDataAdUnitFormatKey : @"320x50", + kImpressionDataCurrencyKey : @"USD", + kImpressionDataAdGroupIDKey : @"Test Ad Group ID", + kImpressionDataAdGroupTypeKey : @"Test Ad Group Type", + kImpressionDataAdGroupNameKey : @"Test Ad Group Name", + kImpressionDataAdGroupPriorityKey : @(6), + kImpressionDataCountryKey : @"US", + kImpressionDataPrecisionKey : @"estimated", + kImpressionDataNetworkNameKey : @"Facebook", + kImpressionDataNetworkPlacementIDKey : @"Network Placement ID", + } mutableCopy]; +} + +- (void)testBasicValuesPipedThrough { + MPImpressionData * impData = [[MPImpressionData alloc] initWithDictionary:self.testImpressionData]; + + // Numbers + XCTAssert([impData.publisherRevenue isEqualToNumber:self.testImpressionData[kImpressionDataPublisherRevenueKey]]); + XCTAssert([impData.adGroupPriority isEqualToNumber:self.testImpressionData[kImpressionDataAdGroupPriorityKey]]); + + // Strings + XCTAssert([impData.impressionID isEqualToString:self.testImpressionData[kImpressionDataImpressionIDKey]]); + XCTAssert([impData.adUnitID isEqualToString:self.testImpressionData[kImpressionDataAdUnitIDKey]]); + XCTAssert([impData.adUnitName isEqualToString:self.testImpressionData[kImpressionDataAdUnitNameKey]]); + XCTAssert([impData.adUnitFormat isEqualToString:self.testImpressionData[kImpressionDataAdUnitFormatKey]]); + XCTAssert([impData.currency isEqualToString:self.testImpressionData[kImpressionDataCurrencyKey]]); + XCTAssert([impData.adGroupID isEqualToString:self.testImpressionData[kImpressionDataAdGroupIDKey]]); + XCTAssert([impData.adGroupName isEqualToString:self.testImpressionData[kImpressionDataAdGroupNameKey]]); + XCTAssert([impData.country isEqualToString:self.testImpressionData[kImpressionDataCountryKey]]); + XCTAssert([impData.networkName isEqualToString:self.testImpressionData[kImpressionDataNetworkNameKey]]); + XCTAssert([impData.networkPlacementID isEqualToString:self.testImpressionData[kImpressionDataNetworkPlacementIDKey]]); +} + +- (void)testBasicValuesPipedThroughWithNilValues { + self.testImpressionData[kImpressionDataNetworkNameKey] = nil; + self.testImpressionData[kImpressionDataNetworkPlacementIDKey] = nil; + self.testImpressionData[kImpressionDataCountryKey] = nil; + + MPImpressionData * impData = [[MPImpressionData alloc] initWithDictionary:self.testImpressionData]; + + // Numbers + XCTAssert([impData.publisherRevenue isEqualToNumber:self.testImpressionData[kImpressionDataPublisherRevenueKey]]); + XCTAssert([impData.adGroupPriority isEqualToNumber:self.testImpressionData[kImpressionDataAdGroupPriorityKey]]); + + // Strings + XCTAssert([impData.impressionID isEqualToString:self.testImpressionData[kImpressionDataImpressionIDKey]]); + XCTAssert([impData.adUnitID isEqualToString:self.testImpressionData[kImpressionDataAdUnitIDKey]]); + XCTAssert([impData.adUnitName isEqualToString:self.testImpressionData[kImpressionDataAdUnitNameKey]]); + XCTAssert([impData.adUnitFormat isEqualToString:self.testImpressionData[kImpressionDataAdUnitFormatKey]]); + XCTAssert([impData.currency isEqualToString:self.testImpressionData[kImpressionDataCurrencyKey]]); + XCTAssert([impData.adGroupID isEqualToString:self.testImpressionData[kImpressionDataAdGroupIDKey]]); + XCTAssert([impData.adGroupName isEqualToString:self.testImpressionData[kImpressionDataAdGroupNameKey]]); + XCTAssertNil(impData.country); + XCTAssertNil(impData.networkName); + XCTAssertNil(impData.networkPlacementID); +} + +- (void)testPrecisionEnum { + self.testImpressionData[kImpressionDataPrecisionKey] = nil; + MPImpressionData * impData = [[MPImpressionData alloc] initWithDictionary:self.testImpressionData]; + XCTAssert(impData.precision == MPImpressionDataPrecisionUnknown); + + self.testImpressionData[kImpressionDataPrecisionKey] = @"estimated"; + impData = [[MPImpressionData alloc] initWithDictionary:self.testImpressionData]; + XCTAssert(impData.precision == MPImpressionDataPrecisionEstimated); + + self.testImpressionData[kImpressionDataPrecisionKey] = @"publisher_defined"; + impData = [[MPImpressionData alloc] initWithDictionary:self.testImpressionData]; + XCTAssert(impData.precision == MPImpressionDataPrecisionPublisherDefined); + + self.testImpressionData[kImpressionDataPrecisionKey] = @"exact"; + impData = [[MPImpressionData alloc] initWithDictionary:self.testImpressionData]; + XCTAssert(impData.precision == MPImpressionDataPrecisionExact); + + self.testImpressionData[kImpressionDataPrecisionKey] = @"corrupt value"; + impData = [[MPImpressionData alloc] initWithDictionary:self.testImpressionData]; + XCTAssert(impData.precision == MPImpressionDataPrecisionUnknown); +} + +- (void)testJsonRepresentationWithNetworks { + MPImpressionData * impData = [[MPImpressionData alloc] initWithDictionary:self.testImpressionData]; + + NSDictionary * jsonDict = [NSJSONSerialization JSONObjectWithData:impData.jsonRepresentation + options:0 + error:nil]; + XCTAssert([self.testImpressionData isEqualToDictionary:jsonDict]); +} + +- (void)testJsonRepresentationWithoutNetworks { + self.testImpressionData[kImpressionDataNetworkNameKey] = nil; + self.testImpressionData[kImpressionDataNetworkPlacementIDKey] = nil; + MPImpressionData * impData = [[MPImpressionData alloc] initWithDictionary:self.testImpressionData]; + + NSDictionary * jsonDict = [NSJSONSerialization JSONObjectWithData:impData.jsonRepresentation + options:0 + error:nil]; + XCTAssert([self.testImpressionData isEqualToDictionary:jsonDict]); + XCTAssertNil(self.testImpressionData[kImpressionDataNetworkNameKey]); + XCTAssertNil(self.testImpressionData[kImpressionDataNetworkPlacementIDKey]); +} + +- (void)testJsonRepresentationWithExtraKeys { + NSString * testKey = @"test key"; + NSString * testValue = @"test value"; + self.testImpressionData[testKey] = testValue; + MPImpressionData * impData = [[MPImpressionData alloc] initWithDictionary:self.testImpressionData]; + + NSDictionary * jsonDict = [NSJSONSerialization JSONObjectWithData:impData.jsonRepresentation + options:0 + error:nil]; + XCTAssert([self.testImpressionData isEqualToDictionary:jsonDict]); + XCTAssert([testValue isEqualToString:jsonDict[testKey]]); +} + +- (void)testNullPublisherRevenueValue { + self.testImpressionData[kImpressionDataPublisherRevenueKey] = [NSNull null]; + + MPImpressionData * impData = [[MPImpressionData alloc] initWithDictionary:self.testImpressionData]; + + NSDictionary * jsonDict = [NSJSONSerialization JSONObjectWithData:impData.jsonRepresentation + options:0 + error:nil]; + + XCTAssertNil(impData.publisherRevenue); + XCTAssert([jsonDict[kImpressionDataPublisherRevenueKey] isKindOfClass:[NSNull class]]); +} + +- (void)testNullPrecisionValue { + self.testImpressionData[kImpressionDataPrecisionKey] = [NSNull null]; + + MPImpressionData * impData = [[MPImpressionData alloc] initWithDictionary:self.testImpressionData]; + + NSDictionary * jsonDict = [NSJSONSerialization JSONObjectWithData:impData.jsonRepresentation + options:0 + error:nil]; + + XCTAssert(impData.precision == MPImpressionDataPrecisionUnknown); + XCTAssert([jsonDict[kImpressionDataPrecisionKey] isKindOfClass:[NSNull class]]); +} + +@end diff --git a/MoPubSDKTests/MPInterstitialAdController+Testing.h b/MoPubSDKTests/MPInterstitialAdController+Testing.h index b02145326..141a19ae3 100644 --- a/MoPubSDKTests/MPInterstitialAdController+Testing.h +++ b/MoPubSDKTests/MPInterstitialAdController+Testing.h @@ -1,7 +1,7 @@ // // MPInterstitialAdController+Testing.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -11,4 +11,5 @@ @interface MPInterstitialAdController (Testing) @property (nonatomic, strong) MPInterstitialAdManager * manager; +- (void)interstitialAdManager:(MPInterstitialAdManager *)manager didReceiveImpressionEventWithImpressionData:(MPImpressionData *)impressionData; @end diff --git a/MoPubSDKTests/MPInterstitialAdController+Testing.m b/MoPubSDKTests/MPInterstitialAdController+Testing.m index a14e798f7..b608dec01 100644 --- a/MoPubSDKTests/MPInterstitialAdController+Testing.m +++ b/MoPubSDKTests/MPInterstitialAdController+Testing.m @@ -1,13 +1,17 @@ // // MPInterstitialAdController+Testing.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // #import "MPInterstitialAdController+Testing.h" +// Suppress warning of accessing private implementation `interstitialAdManager:didReceiveImpressionEventWithImpressionData:` +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wincomplete-implementation" @implementation MPInterstitialAdController (Testing) @dynamic manager; @end +#pragma clang diagnostic pop diff --git a/MoPubSDKTests/MPInterstitialAdControllerTests.m b/MoPubSDKTests/MPInterstitialAdControllerTests.m index ab8ce749b..a5c027136 100644 --- a/MoPubSDKTests/MPInterstitialAdControllerTests.m +++ b/MoPubSDKTests/MPInterstitialAdControllerTests.m @@ -1,7 +1,7 @@ // // MPInterstitialAdControllerTests.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -15,6 +15,9 @@ #import "MPMockAdServerCommunicator.h" #import "NSURLComponents+Testing.h" #import "MPURL.h" +#import "MPImpressionTrackedNotification.h" + +static NSTimeInterval const kTestTimeout = 0.5; @interface MPInterstitialAdControllerTests : XCTestCase @property (nonatomic, strong) MPInterstitialAdController * interstitial; @@ -33,11 +36,6 @@ - (void)setUp { }); } -- (void)tearDown { - // Put teardown code here. This method is called after the invocation of each test method in the class. - [super tearDown]; -} - #pragma mark - Viewability - (void)testViewabilityQueryParameter { @@ -55,4 +53,87 @@ - (void)testViewabilityQueryParameter { XCTAssertTrue([viewabilityValue isEqualToString:@"1"]); } +#pragma mark - Ad Sizing + +- (void)testFullscreenCreativeSizeSent { + [self.interstitial loadAd]; + + XCTAssertNotNil(self.mockAdServerCommunicator); + XCTAssertNotNil(self.mockAdServerCommunicator.lastUrlLoaded); + + MPURL * url = [self.mockAdServerCommunicator.lastUrlLoaded isKindOfClass:[MPURL class]] ? (MPURL *)self.mockAdServerCommunicator.lastUrlLoaded : nil; + XCTAssertNotNil(url); + + NSNumber * sc = [url numberForPOSTDataKey:kScaleFactorKey]; + NSNumber * cw = [url numberForPOSTDataKey:kCreativeSafeWidthKey]; + NSNumber * ch = [url numberForPOSTDataKey:kCreativeSafeHeightKey]; + CGRect frame = MPApplicationFrame(YES); + XCTAssert(cw.floatValue == frame.size.width * sc.floatValue); + XCTAssert(ch.floatValue == frame.size.height * sc.floatValue); +} + +# pragma mark - Impression Level Revenue Data + +- (void)testImpressionNotificationWithImpressionData { + XCTestExpectation * notificationExpectation = [self expectationWithDescription:@"Wait for impression notification"]; + NSString * testAdUnitId = @"FAKE_AD_UNIT_ID"; + + // Make notification handler + id notificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMPImpressionTrackedNotification + object:nil + queue:[NSOperationQueue mainQueue] + usingBlock:^(NSNotification * note){ + [notificationExpectation fulfill]; + + MPImpressionData * impressionData = note.userInfo[kMPImpressionTrackedInfoImpressionDataKey]; + XCTAssert([note.object isEqual:self.interstitial]); + XCTAssertNotNil(impressionData); + XCTAssert([self.interstitial.adUnitId isEqualToString:note.userInfo[kMPImpressionTrackedInfoAdUnitIDKey]]); + XCTAssert([impressionData.adUnitID isEqualToString:testAdUnitId]); + }]; + + MPImpressionData * impressionData = [[MPImpressionData alloc] initWithDictionary:@{ + kImpressionDataAdUnitIDKey: testAdUnitId + }]; + + // Simulate impression + [self.interstitial interstitialAdManager:nil didReceiveImpressionEventWithImpressionData:impressionData]; + + [self waitForExpectationsWithTimeout:kTestTimeout handler:^(NSError * _Nullable error) { + if (error != nil) { + XCTFail(@"Timed out"); + } + }]; + + [[NSNotificationCenter defaultCenter] removeObserver:notificationObserver]; +} + +- (void)testImpressionNotificationWithNoImpressionData { + XCTestExpectation * notificationExpectation = [self expectationWithDescription:@"Wait for impression notification"]; + + // Make notification handler + id notificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMPImpressionTrackedNotification + object:nil + queue:[NSOperationQueue mainQueue] + usingBlock:^(NSNotification * note){ + [notificationExpectation fulfill]; + + MPImpressionData * impressionData = note.userInfo[kMPImpressionTrackedInfoImpressionDataKey]; + XCTAssert([note.object isEqual:self.interstitial]); + XCTAssert([self.interstitial.adUnitId isEqualToString:note.userInfo[kMPImpressionTrackedInfoAdUnitIDKey]]); + XCTAssertNil(impressionData); + }]; + + // Simulate impression + [self.interstitial interstitialAdManager:nil didReceiveImpressionEventWithImpressionData:nil]; + + [self waitForExpectationsWithTimeout:kTestTimeout handler:^(NSError * _Nullable error) { + if (error != nil) { + XCTFail(@"Timed out"); + } + }]; + + [[NSNotificationCenter defaultCenter] removeObserver:notificationObserver]; +} + @end diff --git a/MoPubSDKTests/MPInterstitialAdManager+Testing.h b/MoPubSDKTests/MPInterstitialAdManager+Testing.h index 6f9ea7d27..fe2e60f9a 100644 --- a/MoPubSDKTests/MPInterstitialAdManager+Testing.h +++ b/MoPubSDKTests/MPInterstitialAdManager+Testing.h @@ -1,7 +1,7 @@ // // MPInterstitialAdManager+Testing.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/MPInterstitialAdManager+Testing.m b/MoPubSDKTests/MPInterstitialAdManager+Testing.m index 0eecea6c8..1937e5b30 100644 --- a/MoPubSDKTests/MPInterstitialAdManager+Testing.m +++ b/MoPubSDKTests/MPInterstitialAdManager+Testing.m @@ -1,7 +1,7 @@ // // MPInterstitialAdManager+Testing.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/MPInterstitialAdManagerDelegateHandler.h b/MoPubSDKTests/MPInterstitialAdManagerDelegateHandler.h index fe08e1c57..8a13fbb3e 100644 --- a/MoPubSDKTests/MPInterstitialAdManagerDelegateHandler.h +++ b/MoPubSDKTests/MPInterstitialAdManagerDelegateHandler.h @@ -1,7 +1,7 @@ // // MPInterstitialAdManagerDelegateHandler.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -9,12 +9,15 @@ #import #import "MPInterstitialAdManager.h" #import "MPInterstitialAdManagerDelegate.h" +#import "MPImpressionData.h" typedef void(^MPInterstitialAdManagerDelegateHandlerBlock)(void); +typedef void(^MPInterstitialAdManagerDelegateHandlerImpressionBlock)(MPImpressionData *); typedef void(^MPInterstitialAdManagerDelegateHandlerErrorBlock)(NSError *); @interface MPInterstitialAdManagerDelegateHandler : NSObject +@property (nonatomic, copy) NSString * adUnitId; @property (nonatomic, strong) MPInterstitialAdController * interstitialAdController; @property (nonatomic, strong) CLLocation * location; @property (nonatomic, weak) id interstitialDelegate; @@ -27,5 +30,6 @@ typedef void(^MPInterstitialAdManagerDelegateHandlerErrorBlock)(NSError *); @property (nonatomic, copy) MPInterstitialAdManagerDelegateHandlerBlock didDismiss; @property (nonatomic, copy) MPInterstitialAdManagerDelegateHandlerBlock didExpire; @property (nonatomic, copy) MPInterstitialAdManagerDelegateHandlerBlock didTap; +@property (nonatomic, copy) MPInterstitialAdManagerDelegateHandlerImpressionBlock didReceiveImpression; @end diff --git a/MoPubSDKTests/MPInterstitialAdManagerDelegateHandler.m b/MoPubSDKTests/MPInterstitialAdManagerDelegateHandler.m index a181e0bf8..7f4b6b787 100644 --- a/MoPubSDKTests/MPInterstitialAdManagerDelegateHandler.m +++ b/MoPubSDKTests/MPInterstitialAdManagerDelegateHandler.m @@ -1,7 +1,7 @@ // // MPInterstitialAdManagerDelegateHandler.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -42,4 +42,8 @@ - (void)managerDidReceiveTapEventFromInterstitial:(MPInterstitialAdManager *)man if (self.didTap != nil) { self.didTap(); } } +- (void)interstitialAdManager:(MPInterstitialAdManager *)manager didReceiveImpressionEventWithImpressionData:(MPImpressionData *)impressionData { + if (self.didReceiveImpression != nil) { self.didReceiveImpression(impressionData); } +} + @end diff --git a/MoPubSDKTests/MPInterstitialAdManagerTests.m b/MoPubSDKTests/MPInterstitialAdManagerTests.m index 48cb4fe56..8e9f56fcf 100644 --- a/MoPubSDKTests/MPInterstitialAdManagerTests.m +++ b/MoPubSDKTests/MPInterstitialAdManagerTests.m @@ -1,7 +1,7 @@ // // MPInterstitialAdManagerTests.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -15,6 +15,7 @@ #import "MPInterstitialCustomEventAdapter+Testing.h" #import "MPMockAdServerCommunicator.h" #import "MPMockInterstitialCustomEvent.h" +#import "MPAdServerKeys.h" static const NSTimeInterval kDefaultTimeout = 10; @@ -24,16 +25,6 @@ @interface MPInterstitialAdManagerTests : XCTestCase @implementation MPInterstitialAdManagerTests -- (void)setUp { - [super setUp]; - // Put setup code here. This method is called before the invocation of each test method in the class. -} - -- (void)tearDown { - // Put teardown code here. This method is called after the invocation of each test method in the class. - [super tearDown]; -} - - (void)testEmptyConfigurationArray { XCTestExpectation * expectation = [self expectationWithDescription:@"Wait for interstitial load"]; @@ -254,7 +245,7 @@ - (void)testLocalExtrasInCustomEvent { communicator.mockConfigurationsResponse = configurations; manager.communicator = communicator; - MPAdTargeting * targeting = [[MPAdTargeting alloc] init]; + MPAdTargeting * targeting = [[MPAdTargeting alloc] initWithCreativeSafeSize:CGSizeZero]; targeting.localExtras = @{ @"testing": @"YES" }; [manager loadInterstitialWithAdUnitID:@"TEST_ADUNIT_ID" targeting:targeting]; @@ -274,4 +265,82 @@ - (void)testLocalExtrasInCustomEvent { XCTAssertTrue(customEvent.isLocalExtrasAvailableAtRequest); } +#pragma mark - Impression Level Revenue Data + +- (void)testImpressionDelegateFiresWithoutILRD { + XCTestExpectation * expectation = [self expectationWithDescription:@"Wait for impression"]; + NSString * testAdUnitID = @"TEST_ADUNIT_ID"; + + MPInterstitialAdManagerDelegateHandler * handler = [MPInterstitialAdManagerDelegateHandler new]; + handler.didReceiveImpression = ^(MPImpressionData * impressionData) { + [expectation fulfill]; + + XCTAssertNil(impressionData); + }; + + // Generate the ad configurations + MPAdConfiguration * interstitialThatShouldLoad = [MPAdConfigurationFactory defaultInterstitialConfigurationWithCustomEventClassName:@"MPMockInterstitialCustomEvent"]; + NSArray * configurations = @[interstitialThatShouldLoad]; + + MPInterstitialAdManager * manager = [[MPInterstitialAdManager alloc] initWithDelegate:handler]; + MPMockAdServerCommunicator * communicator = [[MPMockAdServerCommunicator alloc] initWithDelegate:manager]; + communicator.mockConfigurationsResponse = configurations; + manager.communicator = communicator; + + handler.didLoadAd = ^{ + // Track the impression + MPInterstitialCustomEventAdapter * adapter = (MPInterstitialCustomEventAdapter *)manager.adapter; + [adapter trackImpression]; + }; + + MPAdTargeting * targeting = [[MPAdTargeting alloc] initWithCreativeSafeSize:CGSizeZero]; + [manager loadInterstitialWithAdUnitID:testAdUnitID targeting:targeting]; + + [self waitForExpectationsWithTimeout:kDefaultTimeout handler:^(NSError * _Nullable error) { + if (error != nil) { + XCTFail(@"Timed out"); + } + }]; +} + +- (void)testImpressionDelegateFiresWithILRD { + XCTestExpectation * expectation = [self expectationWithDescription:@"Wait for impression"]; + NSString * testAdUnitID = @"TEST_ADUNIT_ID"; + + MPInterstitialAdManagerDelegateHandler * handler = [MPInterstitialAdManagerDelegateHandler new]; + handler.didReceiveImpression = ^(MPImpressionData * impressionData) { + [expectation fulfill]; + + XCTAssertNotNil(impressionData); + XCTAssert([impressionData.adUnitID isEqualToString:testAdUnitID]); + }; + + // Generate the ad configurations + MPAdConfiguration * interstitialThatShouldLoad = [MPAdConfigurationFactory defaultInterstitialConfigurationWithCustomEventClassName:@"MPMockInterstitialCustomEvent"]; + interstitialThatShouldLoad.impressionData = [[MPImpressionData alloc] initWithDictionary:@{ + kImpressionDataAdUnitIDKey : testAdUnitID + }]; + NSArray * configurations = @[interstitialThatShouldLoad]; + + MPInterstitialAdManager * manager = [[MPInterstitialAdManager alloc] initWithDelegate:handler]; + MPMockAdServerCommunicator * communicator = [[MPMockAdServerCommunicator alloc] initWithDelegate:manager]; + communicator.mockConfigurationsResponse = configurations; + manager.communicator = communicator; + + handler.didLoadAd = ^{ + // Track the impression + MPInterstitialCustomEventAdapter * adapter = (MPInterstitialCustomEventAdapter *)manager.adapter; + [adapter trackImpression]; + }; + + MPAdTargeting * targeting = [[MPAdTargeting alloc] initWithCreativeSafeSize:CGSizeZero]; + [manager loadInterstitialWithAdUnitID:testAdUnitID targeting:targeting]; + + [self waitForExpectationsWithTimeout:kDefaultTimeout handler:^(NSError * _Nullable error) { + if (error != nil) { + XCTFail(@"Timed out"); + } + }]; +} + @end diff --git a/MoPubSDKTests/MPInterstitialAdapterDelegateHandler.h b/MoPubSDKTests/MPInterstitialAdapterDelegateHandler.h index 7d3e8f264..f10334a52 100644 --- a/MoPubSDKTests/MPInterstitialAdapterDelegateHandler.h +++ b/MoPubSDKTests/MPInterstitialAdapterDelegateHandler.h @@ -1,7 +1,7 @@ // // MPInterstitialAdapterDelegateHandler.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -26,6 +26,7 @@ typedef void(^MPInterstitialAdapterDelegateHandlerErrorBlock)(MPBaseInterstitial @property (nonatomic, copy) MPInterstitialAdapterDelegateHandlerBlock didDisppear; @property (nonatomic, copy) MPInterstitialAdapterDelegateHandlerBlock didExpire; @property (nonatomic, copy) MPInterstitialAdapterDelegateHandlerBlock didReceiveTapEvent; +@property (nonatomic, copy) MPInterstitialAdapterDelegateHandlerBlock didReceiveImpressionEvent; @property (nonatomic, copy) MPInterstitialAdapterDelegateHandlerBlock willLeaveApplication; @end diff --git a/MoPubSDKTests/MPInterstitialAdapterDelegateHandler.m b/MoPubSDKTests/MPInterstitialAdapterDelegateHandler.m index b81824d39..46082f08b 100644 --- a/MoPubSDKTests/MPInterstitialAdapterDelegateHandler.m +++ b/MoPubSDKTests/MPInterstitialAdapterDelegateHandler.m @@ -1,7 +1,7 @@ // // MPInterstitialAdapterDelegateHandler.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -19,5 +19,6 @@ - (void)interstitialDidDisappearForAdapter:(MPBaseInterstitialAdapter *)adapter - (void)interstitialDidExpireForAdapter:(MPBaseInterstitialAdapter *)adapter { if (self.didExpire) self.didExpire(adapter); } - (void)interstitialDidReceiveTapEventForAdapter:(MPBaseInterstitialAdapter *)adapter { if (self.didReceiveTapEvent) self.didReceiveTapEvent(adapter); } - (void)interstitialWillLeaveApplicationForAdapter:(MPBaseInterstitialAdapter *)adapter { if (self.willLeaveApplication) self.willLeaveApplication(adapter); } +- (void)interstitialDidReceiveImpressionEventForAdapter:(MPBaseInterstitialAdapter *)adapter { if (self.didReceiveTapEvent) self.didReceiveTapEvent(adapter); } @end diff --git a/MoPubSDKTests/MPInterstitialCustomEventAdapter+Testing.h b/MoPubSDKTests/MPInterstitialCustomEventAdapter+Testing.h index a68404ebd..65c80505e 100644 --- a/MoPubSDKTests/MPInterstitialCustomEventAdapter+Testing.h +++ b/MoPubSDKTests/MPInterstitialCustomEventAdapter+Testing.h @@ -1,7 +1,7 @@ // // MPInterstitialCustomEventAdapter+Testing.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/MPInterstitialCustomEventAdapter+Testing.m b/MoPubSDKTests/MPInterstitialCustomEventAdapter+Testing.m index c10a8d74d..b5c5d1349 100644 --- a/MoPubSDKTests/MPInterstitialCustomEventAdapter+Testing.m +++ b/MoPubSDKTests/MPInterstitialCustomEventAdapter+Testing.m @@ -1,7 +1,7 @@ // // MPInterstitialCustomEventAdapter+Testing.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/MPInterstitialCustomEventAdapterTests.m b/MoPubSDKTests/MPInterstitialCustomEventAdapterTests.m index d2834a920..d747d4d1a 100644 --- a/MoPubSDKTests/MPInterstitialCustomEventAdapterTests.m +++ b/MoPubSDKTests/MPInterstitialCustomEventAdapterTests.m @@ -1,7 +1,7 @@ // // MPInterstitialCustomEventAdapterTests.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/MPLoggingHandler.h b/MoPubSDKTests/MPLoggingHandler.h new file mode 100644 index 000000000..a8a0b8303 --- /dev/null +++ b/MoPubSDKTests/MPLoggingHandler.h @@ -0,0 +1,20 @@ +// +// MPLoggingHandler.h +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import +#import "MPBLogger.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface MPLoggingHandler : NSObject +@property (nonatomic, copy, nullable) void(^didLogEventHandler)(NSString * _Nullable message); + ++ (instancetype)handler:(void(^)(NSString * _Nullable message))didLogEventHandler; +@end + +NS_ASSUME_NONNULL_END diff --git a/MoPubSDKTests/MPLoggingHandler.m b/MoPubSDKTests/MPLoggingHandler.m new file mode 100644 index 000000000..b14ab7fd9 --- /dev/null +++ b/MoPubSDKTests/MPLoggingHandler.m @@ -0,0 +1,32 @@ +// +// MPLoggingHandler.m +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import "MPLoggingHandler.h" +#import "MPLogging.h" + +@implementation MPLoggingHandler + ++ (instancetype)handler:(void(^)(NSString * _Nullable message))didLogEventHandler { + MPLoggingHandler * handler = MPLoggingHandler.new; + handler.didLogEventHandler = didLogEventHandler; + return handler; +} + +#pragma mark - MPBLogger + +- (MPBLogLevel)logLevel { + return MPBLogLevelDebug; +} + +- (void)logMessage:(NSString * _Nullable)message { + if (self.didLogEventHandler != nil) { + self.didLogEventHandler(message); + } +} + +@end diff --git a/MoPubSDKTests/MPMediationManager+Testing.h b/MoPubSDKTests/MPMediationManager+Testing.h new file mode 100644 index 000000000..afeeca07c --- /dev/null +++ b/MoPubSDKTests/MPMediationManager+Testing.h @@ -0,0 +1,23 @@ +// +// MPMediationManager+Testing.h +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import "MPMediationManager.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface MPMediationManager (Testing) +@property (class, nonatomic, copy, nullable) NSString * adapterInformationProvidersFilePath; +@property (nonatomic, strong) NSMutableDictionary> * adapters; +@property (nonatomic, strong, readonly) NSSet> * certifiedAdapterClasses; + ++ (NSSet> * _Nonnull)certifiedAdapterInformationProviderClasses; +- (NSDictionary *)parametersForAdapter:(id)adapter + overrideConfiguration:(NSDictionary *)configuration; +@end + +NS_ASSUME_NONNULL_END diff --git a/MoPubSDKTests/MPMediationManager+Testing.m b/MoPubSDKTests/MPMediationManager+Testing.m new file mode 100644 index 000000000..678d1ffe8 --- /dev/null +++ b/MoPubSDKTests/MPMediationManager+Testing.m @@ -0,0 +1,32 @@ +// +// MPMediationManager+Testing.m +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import "MPMediationManager+Testing.h" + +static NSString * sAdaptersPath = nil; + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wincomplete-implementation" +@implementation MPMediationManager (Testing) +@dynamic adapters; +@dynamic certifiedAdapterClasses; + ++ (void)initialize { + sAdaptersPath = [[NSBundle bundleForClass:self.class] pathForResource:@"MPMockAdapters" ofType:@"plist"]; +} + ++ (NSString *)adapterInformationProvidersFilePath { + return sAdaptersPath; +} + ++ (void)setAdapterInformationProvidersFilePath:(NSString *)adapterInformationProvidersFilePath { + sAdaptersPath = adapterInformationProvidersFilePath; +} + +@end +#pragma clang diagnostic pop diff --git a/MoPubSDKTests/MPMediationManagerTests.m b/MoPubSDKTests/MPMediationManagerTests.m index 3a28b7e3d..c459e7492 100644 --- a/MoPubSDKTests/MPMediationManagerTests.m +++ b/MoPubSDKTests/MPMediationManagerTests.m @@ -1,7 +1,7 @@ // // MPMediationManagerTests.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -9,8 +9,10 @@ #import #import "MoPub.h" #import "MPMediationManager.h" -#import "MPStubCustomEvent.h" -#import "MPStubMediatedNetwork.h" +#import "MPMediationManager+Testing.h" +#import "MPMockAdColonyAdapterConfiguration.h" +#import "MPMockChartboostAdapterConfiguration.h" +#import "MPMockTapjoyAdapterConfiguration.h" static const NSTimeInterval kTestTimeout = 2; // seconds @@ -23,6 +25,9 @@ @implementation MPMediationManagerTests - (void)setUp { [super setUp]; [MPMediationManager.sharedManager clearCache]; + MPMockAdColonyAdapterConfiguration.isSdkInitialized = NO; + MPMockChartboostAdapterConfiguration.isSdkInitialized = NO; + MPMockTapjoyAdapterConfiguration.isSdkInitialized = NO; } - (void)tearDown { @@ -31,13 +36,65 @@ - (void)tearDown { #pragma mark - Network SDK Initialization +- (void)testInitialization { + XCTAssertFalse(MPMockAdColonyAdapterConfiguration.isSdkInitialized); + XCTAssertFalse(MPMockChartboostAdapterConfiguration.isSdkInitialized); + XCTAssertFalse(MPMockTapjoyAdapterConfiguration.isSdkInitialized); + + // Put data into the cache to simulate having been cache prior. + [MPMediationManager.sharedManager setCachedInitializationParameters:@{ @"appId": @"aaaa" } forNetwork:MPMockAdColonyAdapterConfiguration.class]; + [MPMediationManager.sharedManager setCachedInitializationParameters:@{ @"appId": @"bbbb" } forNetwork:MPMockChartboostAdapterConfiguration.class]; + [MPMediationManager.sharedManager setCachedInitializationParameters:@{ @"appId": @"cccc" } forNetwork:MPMockTapjoyAdapterConfiguration.class]; + + // Initialize + XCTestExpectation * expectation = [self expectationWithDescription:@"Mediation initialization"]; + [MPMediationManager.sharedManager initializeWithAdditionalProviders:[NSArray arrayWithObject:MPMockTapjoyAdapterConfiguration.class] configurations:nil requestOptions:nil complete:^(NSError * _Nullable error, NSArray> * _Nullable initializedAdapters) { + [expectation fulfill]; + }]; + + // Wait for SDKs to initialize + [self waitForExpectationsWithTimeout:kTestTimeout handler:^(NSError *error) { + XCTAssertNil(error); + }]; + + // Verify initialized adapters + XCTAssertTrue(MPMockAdColonyAdapterConfiguration.isSdkInitialized); + XCTAssertTrue(MPMockChartboostAdapterConfiguration.isSdkInitialized); + XCTAssertTrue(MPMockTapjoyAdapterConfiguration.isSdkInitialized); + + NSDictionary * adRequestPayload = MPMediationManager.sharedManager.adRequestPayload; + XCTAssertNotNil(adRequestPayload); + XCTAssertNotNil(adRequestPayload[@"mock_adcolony"]); + XCTAssertNotNil(adRequestPayload[@"mock_chartboost"]); + XCTAssertNotNil(adRequestPayload[@"mock_tapjoy"]); + + NSDictionary * advancedBiddingTokens = MPMediationManager.sharedManager.advancedBiddingTokens; + XCTAssertNotNil(advancedBiddingTokens); + XCTAssertNotNil(advancedBiddingTokens[@"mock_adcolony"]); + XCTAssertNotNil(advancedBiddingTokens[@"mock_chartboost"]); + XCTAssertNil(advancedBiddingTokens[@"mock_tapjoy"]); + XCTAssertNotNil(advancedBiddingTokens[@"mock_adcolony"][@"token"]); + XCTAssertNotNil(advancedBiddingTokens[@"mock_chartboost"][@"token"]); + XCTAssertNil(advancedBiddingTokens[@"mock_tapjoy"][@"token"]); +} + +- (void)testNoInitialization { + XCTAssertFalse(MPMockAdColonyAdapterConfiguration.isSdkInitialized); + XCTAssertFalse(MPMockChartboostAdapterConfiguration.isSdkInitialized); + XCTAssertFalse(MPMockTapjoyAdapterConfiguration.isSdkInitialized); + + MPMediationManager.sharedManager.adapters = [NSMutableDictionary dictionary]; + + NSDictionary * adapterPayload = MPMediationManager.sharedManager.adRequestPayload; + XCTAssertNil(adapterPayload); +} + - (void)testNetworkSDKInitializationNotInCache { - [MPStubCustomEvent resetInitialization]; - XCTAssertFalse([MPStubCustomEvent isInitialized]); + XCTAssertFalse(MPMockAdColonyAdapterConfiguration.isSdkInitialized); // Initialize XCTestExpectation * expectation = [self expectationWithDescription:@"Mediation initialization"]; - [MPMediationManager.sharedManager initializeMediatedNetworks:@[MPStubCustomEvent.class] completion:^(NSError * _Nullable error) { + [MPMediationManager.sharedManager initializeWithAdditionalProviders:nil configurations:nil requestOptions:nil complete:^(NSError * _Nullable error, NSArray> * _Nullable initializedAdapters) { [expectation fulfill]; }]; @@ -46,19 +103,18 @@ - (void)testNetworkSDKInitializationNotInCache { XCTAssertNil(error); }]; - XCTAssertFalse([MPStubCustomEvent isInitialized]); + XCTAssertFalse(MPMockAdColonyAdapterConfiguration.isSdkInitialized); } - (void)testNetworkSDKInitializationSuccess { - [MPStubCustomEvent resetInitialization]; - XCTAssertFalse([MPStubCustomEvent isInitialized]); + XCTAssertFalse(MPMockAdColonyAdapterConfiguration.isSdkInitialized); // Set an entry in the cache to indicate that it was previously initialized on-demand - [MPMediationManager.sharedManager setCachedInitializationParameters:@{ @"poop": @"poop" } forNetwork:MPStubCustomEvent.class]; + [MPMediationManager.sharedManager setCachedInitializationParameters:@{ @"poop": @"poop" } forNetwork:MPMockAdColonyAdapterConfiguration.class]; // Initialize XCTestExpectation * expectation = [self expectationWithDescription:@"Mediation initialization"]; - [MPMediationManager.sharedManager initializeMediatedNetworks:@[MPStubCustomEvent.class] completion:^(NSError * _Nullable error) { + [MPMediationManager.sharedManager initializeWithAdditionalProviders:nil configurations:nil requestOptions:nil complete:^(NSError * _Nullable error, NSArray> * _Nullable initializedAdapters) { [expectation fulfill]; }]; @@ -67,15 +123,19 @@ - (void)testNetworkSDKInitializationSuccess { XCTAssertNil(error); }]; - XCTAssertTrue([MPStubCustomEvent isInitialized]); + XCTAssertTrue(MPMockAdColonyAdapterConfiguration.isSdkInitialized); } - (void)testNoNetworkSDKInitialization { - [MPStubCustomEvent resetInitialization]; - XCTAssertFalse([MPStubCustomEvent isInitialized]); + XCTAssertFalse(MPMockAdColonyAdapterConfiguration.isSdkInitialized); + XCTAssertFalse(MPMockChartboostAdapterConfiguration.isSdkInitialized); + XCTAssertFalse(MPMockTapjoyAdapterConfiguration.isSdkInitialized); + + // Change the plist source to the production version so that nothing will load. + MPMediationManager.adapterInformationProvidersFilePath = @"MPAdapters.plist"; XCTestExpectation * expectation = [self expectationWithDescription:@"Mediation initialization"]; - [MPMediationManager.sharedManager initializeMediatedNetworks:nil completion:^(NSError * _Nullable error) { + [MPMediationManager.sharedManager initializeWithAdditionalProviders:nil configurations:nil requestOptions:nil complete:^(NSError * _Nullable error, NSArray> * _Nullable initializedAdapters) { [expectation fulfill]; }]; @@ -84,7 +144,9 @@ - (void)testNoNetworkSDKInitialization { XCTAssertNil(error); }]; - XCTAssertFalse([MPStubCustomEvent isInitialized]); + XCTAssertFalse(MPMockAdColonyAdapterConfiguration.isSdkInitialized); + XCTAssertFalse(MPMockChartboostAdapterConfiguration.isSdkInitialized); + XCTAssertFalse(MPMockTapjoyAdapterConfiguration.isSdkInitialized); } #pragma mark - Caching @@ -94,9 +156,9 @@ - (void)testSetCacheSuccess { @"zones": @[@"zone 1", @"zone 2"], }; - [MPMediationManager.sharedManager setCachedInitializationParameters:params forNetwork:MPStubMediatedNetwork.class]; + [MPMediationManager.sharedManager setCachedInitializationParameters:params forNetwork:MPMockTapjoyAdapterConfiguration.class]; - NSDictionary * cachedParams = [MPMediationManager.sharedManager cachedInitializationParametersForNetwork:MPStubMediatedNetwork.class]; + NSDictionary * cachedParams = [MPMediationManager.sharedManager cachedInitializationParametersForNetwork:MPMockTapjoyAdapterConfiguration.class]; XCTAssertNotNil(cachedParams); NSString * appId = cachedParams[@"appId"]; @@ -120,44 +182,94 @@ - (void)testSetCacheNoNetwork { [MPMediationManager.sharedManager setCachedInitializationParameters:params forNetwork:nil]; #pragma clang diagnostic pop - NSDictionary * cachedParams = [MPMediationManager.sharedManager cachedInitializationParametersForNetwork:MPStubMediatedNetworkTwo.class]; + NSDictionary * cachedParams = [MPMediationManager.sharedManager cachedInitializationParametersForNetwork:MPMockTapjoyAdapterConfiguration.class]; XCTAssertNil(cachedParams); } - (void)testSetCacheNoParams { - [MPMediationManager.sharedManager setCachedInitializationParameters:nil forNetwork:MPStubMediatedNetworkTwo.class]; + [MPMediationManager.sharedManager setCachedInitializationParameters:nil forNetwork:MPMockTapjoyAdapterConfiguration.class]; - NSDictionary * cachedParams = [MPMediationManager.sharedManager cachedInitializationParametersForNetwork:MPStubMediatedNetworkTwo.class]; + NSDictionary * cachedParams = [MPMediationManager.sharedManager cachedInitializationParametersForNetwork:MPMockTapjoyAdapterConfiguration.class]; XCTAssertNil(cachedParams); } - (void)testClearCache { NSDictionary * params = @{ @"appId": @"tapjpy_app_id" }; - [MPMediationManager.sharedManager setCachedInitializationParameters:params forNetwork:MPStubMediatedNetwork.class]; + [MPMediationManager.sharedManager setCachedInitializationParameters:params forNetwork:MPMockTapjoyAdapterConfiguration.class]; - NSDictionary * cachedParams = [MPMediationManager.sharedManager cachedInitializationParametersForNetwork:MPStubMediatedNetwork.class]; + NSDictionary * cachedParams = [MPMediationManager.sharedManager cachedInitializationParametersForNetwork:MPMockTapjoyAdapterConfiguration.class]; XCTAssertNotNil(cachedParams); [MPMediationManager.sharedManager clearCache]; - cachedParams = [MPMediationManager.sharedManager cachedInitializationParametersForNetwork:MPStubMediatedNetwork.class]; + cachedParams = [MPMediationManager.sharedManager cachedInitializationParametersForNetwork:MPMockTapjoyAdapterConfiguration.class]; XCTAssertNil(cachedParams); } -- (void)testSetCacheFromSubclassSuccess { - NSDictionary * params = @{ @"appId": @"vungle_app_id" }; +#pragma mark - Initialization Parameters - MPStubCustomEvent * testCustomEvent = [MPStubCustomEvent new]; +- (void)testCachedParametersNoOverrides { + // Put data into the cache to simulate having been cache prior. + [MPMediationManager.sharedManager setCachedInitializationParameters:@{ @"appId": @"aaaa" } forNetwork:MPMockAdColonyAdapterConfiguration.class]; - [testCustomEvent setCachedInitializationParameters:params]; + // Retrieve initialization parameters + MPMockAdColonyAdapterConfiguration * adapter = [MPMockAdColonyAdapterConfiguration new]; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnonnull" // intentional nil test + NSDictionary * params = [MPMediationManager.sharedManager parametersForAdapter:adapter overrideConfiguration:nil]; +#pragma clang diagnostic pop - NSDictionary * cachedParams = [testCustomEvent cachedInitializationParameters]; - XCTAssertNotNil(cachedParams); + XCTAssertNotNil(params); + XCTAssert([params[@"appId"] isEqualToString:@"aaaa"]); +} - NSString * appId = cachedParams[@"appId"]; - XCTAssertNotNil(appId); - XCTAssertTrue([appId isEqualToString:@"vungle_app_id"]); +- (void)testNoCachedParametersAndOverrides { + // Override parameters + NSDictionary * override = @{ @"animal": @"cat" }; + + // Retrieve initialization parameters + MPMockAdColonyAdapterConfiguration * adapter = [MPMockAdColonyAdapterConfiguration new]; + NSDictionary * params = [MPMediationManager.sharedManager parametersForAdapter:adapter overrideConfiguration:override]; + + XCTAssertNotNil(params); + XCTAssert([params[@"animal"] isEqualToString:@"cat"]); +} + +- (void)testCachedParametersAndOverrides { + // Put data into the cache to simulate having been cache prior. + [MPMediationManager.sharedManager setCachedInitializationParameters:@{ @"appId": @"aaaa", @"pid": @"999" } forNetwork:MPMockAdColonyAdapterConfiguration.class]; + + // Override parameters + NSDictionary * override = @{ @"animal": @"cat", @"pid": @"0" }; + + // Retrieve initialization parameters + MPMockAdColonyAdapterConfiguration * adapter = [MPMockAdColonyAdapterConfiguration new]; + NSDictionary * params = [MPMediationManager.sharedManager parametersForAdapter:adapter overrideConfiguration:override]; + + XCTAssertNotNil(params); + XCTAssert([params[@"appId"] isEqualToString:@"aaaa"]); + XCTAssert([params[@"animal"] isEqualToString:@"cat"]); + XCTAssert([params[@"pid"] isEqualToString:@"0"]); +} + +#pragma mark - Adapters Plist + +- (void)testMockAdaptersPlistExists { + MPMediationManager.adapterInformationProvidersFilePath = [[NSBundle bundleForClass:self.class] pathForResource:@"MPMockAdapters" ofType:@"plist"]; + + NSSet> * certifiedAdapters = MPMediationManager.certifiedAdapterInformationProviderClasses; + XCTAssert(certifiedAdapters.count == 2); +} + +- (void)testMissingAdaptersPlist { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnonnull" // intentional nil test + MPMediationManager.adapterInformationProvidersFilePath = nil; +#pragma clang diagnostic pop + + NSSet> * certifiedAdapters = MPMediationManager.certifiedAdapterInformationProviderClasses; + XCTAssert(certifiedAdapters.count == 0); } @end diff --git a/MoPubSDKTests/MPMemoryCacheTests.m b/MoPubSDKTests/MPMemoryCacheTests.m index 719c76986..e01278fa6 100644 --- a/MoPubSDKTests/MPMemoryCacheTests.m +++ b/MoPubSDKTests/MPMemoryCacheTests.m @@ -1,7 +1,7 @@ // // MPMemoryCacheTests.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/MPMoPubConfigurationTests.m b/MoPubSDKTests/MPMoPubConfigurationTests.m new file mode 100644 index 000000000..64bd3c37b --- /dev/null +++ b/MoPubSDKTests/MPMoPubConfigurationTests.m @@ -0,0 +1,44 @@ +// +// MPMoPubConfigurationTests.m +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import +#import "MPMoPubConfiguration.h" + +static NSString * const kAdUnitId = @"FAKE_ID"; + +@interface MPMoPubConfigurationTests : XCTestCase + +@end + +@implementation MPMoPubConfigurationTests + +#pragma mark - Network Configurations + +- (void)testSetNetworkConfigurationSuccess { + NSDictionary * params = @{ @"key": @"test" }; + NSString * const adapterName = @"MPMockAdColonyAdapterConfiguration"; + + MPMoPubConfiguration * config = [[MPMoPubConfiguration alloc] initWithAdUnitIdForAppInitialization:kAdUnitId]; + [config setNetworkConfiguration:params forMediationAdapter:adapterName]; + + XCTAssertNotNil(config.mediatedNetworkConfigurations); + XCTAssertNotNil(config.mediatedNetworkConfigurations[adapterName]); + XCTAssert([config.mediatedNetworkConfigurations[adapterName][@"key"] isEqualToString:@"test"]); +} + +- (void)testSetNetworkConfigurationFail { + NSDictionary * params = @{ @"key": @"test" }; + NSString * const adapterName = @"MPMockUnknownAdapterConfiguration"; + + MPMoPubConfiguration * config = [[MPMoPubConfiguration alloc] initWithAdUnitIdForAppInitialization:kAdUnitId]; + [config setNetworkConfiguration:params forMediationAdapter:adapterName]; + + XCTAssertNil(config.mediatedNetworkConfigurations); +} + +@end diff --git a/MoPubSDKTests/MPMoPubNativeAdAdapter+Testing.h b/MoPubSDKTests/MPMoPubNativeAdAdapter+Testing.h index 2b94ea6c2..a9222ff82 100644 --- a/MoPubSDKTests/MPMoPubNativeAdAdapter+Testing.h +++ b/MoPubSDKTests/MPMoPubNativeAdAdapter+Testing.h @@ -1,7 +1,7 @@ // // MPMoPubNativeAdAdapter+Testing.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/MPMoPubNativeAdAdapter+Testing.m b/MoPubSDKTests/MPMoPubNativeAdAdapter+Testing.m index 0976922c3..8ab7ece8b 100644 --- a/MoPubSDKTests/MPMoPubNativeAdAdapter+Testing.m +++ b/MoPubSDKTests/MPMoPubNativeAdAdapter+Testing.m @@ -1,7 +1,7 @@ // // MPMoPubNativeAdAdapter+Testing.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/MPMoPubNativeAdAdapterTests.m b/MoPubSDKTests/MPMoPubNativeAdAdapterTests.m index 2780c14f5..526704cba 100644 --- a/MoPubSDKTests/MPMoPubNativeAdAdapterTests.m +++ b/MoPubSDKTests/MPMoPubNativeAdAdapterTests.m @@ -1,7 +1,7 @@ // // MPMoPubNativeAdAdapterTests.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/MPMoPubRewardedPlayableCustomEvent+Testing.h b/MoPubSDKTests/MPMoPubRewardedPlayableCustomEvent+Testing.h index edcd5ec4c..d49f89f5f 100644 --- a/MoPubSDKTests/MPMoPubRewardedPlayableCustomEvent+Testing.h +++ b/MoPubSDKTests/MPMoPubRewardedPlayableCustomEvent+Testing.h @@ -1,7 +1,7 @@ // // MPMoPubRewardedPlayableCustomEvent+Testing.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/MPMoPubRewardedPlayableCustomEvent+Testing.m b/MoPubSDKTests/MPMoPubRewardedPlayableCustomEvent+Testing.m index e0a2f8fce..86939cf03 100644 --- a/MoPubSDKTests/MPMoPubRewardedPlayableCustomEvent+Testing.m +++ b/MoPubSDKTests/MPMoPubRewardedPlayableCustomEvent+Testing.m @@ -1,11 +1,12 @@ // // MPMoPubRewardedPlayableCustomEvent+Testing.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // +#import "MPCountdownTimerView+Testing.h" #import "MPMoPubRewardedPlayableCustomEvent+Testing.h" @implementation MPMoPubRewardedPlayableCustomEvent (Testing) @@ -21,7 +22,7 @@ - (instancetype)initWithInterstitial:(MPMRAIDInterstitialViewController *)inters } - (BOOL)isCountdownActive { - return self.timerView.isActive; + return self.timerView.timer.isCountdownActive; } @end diff --git a/MoPubSDKTests/MPMoPubRewardedPlayableCustomEventTests.m b/MoPubSDKTests/MPMoPubRewardedPlayableCustomEventTests.m index 255e31ac1..5c8eb7b09 100644 --- a/MoPubSDKTests/MPMoPubRewardedPlayableCustomEventTests.m +++ b/MoPubSDKTests/MPMoPubRewardedPlayableCustomEventTests.m @@ -1,7 +1,7 @@ // // MPMoPubRewardedPlayableCustomEventTests.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/MPMockAdColonyAdapterConfiguration.h b/MoPubSDKTests/MPMockAdColonyAdapterConfiguration.h new file mode 100644 index 000000000..2ed741694 --- /dev/null +++ b/MoPubSDKTests/MPMockAdColonyAdapterConfiguration.h @@ -0,0 +1,21 @@ +// +// MPMockAdColonyAdapterConfiguration.h +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import "MPBaseAdapterConfiguration.h" + +@interface MPMockAdColonyAdapterConfiguration : MPBaseAdapterConfiguration +@property (class, nonatomic, assign) BOOL isSdkInitialized; + +@property (nonatomic, copy, readonly) NSString * _Nonnull adapterVersion; +@property (nonatomic, copy, readonly) NSString * _Nullable biddingToken; +@property (nonatomic, copy, readonly) NSString * _Nonnull moPubNetworkName; +@property (nonatomic, copy, readonly) NSString * _Nonnull networkSdkVersion; + +- (void)initializeNetworkWithConfiguration:(NSDictionary * _Nullable)configuration + complete:(void(^ _Nullable)(NSError * _Nullable))complete; +@end diff --git a/MoPubSDKTests/MPMockAdColonyAdapterConfiguration.m b/MoPubSDKTests/MPMockAdColonyAdapterConfiguration.m new file mode 100644 index 000000000..7e52e3f3b --- /dev/null +++ b/MoPubSDKTests/MPMockAdColonyAdapterConfiguration.m @@ -0,0 +1,45 @@ +// +// MPMockAdColonyAdapterConfiguration.m +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import "MPMockAdColonyAdapterConfiguration.h" + +static BOOL gInitialized = NO; + +@implementation MPMockAdColonyAdapterConfiguration + ++ (BOOL)isSdkInitialized { + return gInitialized; +} + ++ (void)setIsSdkInitialized:(BOOL)isSdkInitialized { + gInitialized = isSdkInitialized; +} + +- (NSString *)adapterVersion { + return @"10.3.2.0"; +} + +- (NSString *)biddingToken { + return @"1"; +} + +- (NSString *)moPubNetworkName { + return @"mock_adcolony"; +} + +- (NSString *)networkSdkVersion { + return @"10.3.2"; +} + +- (void)initializeNetworkWithConfiguration:(NSDictionary * _Nullable)configuration + complete:(void(^ _Nullable)(NSError * _Nullable))complete { + MPMockAdColonyAdapterConfiguration.isSdkInitialized = (configuration != nil); + complete(nil); +} + +@end diff --git a/MoPubSDKTests/MPMockAdColonyRewardedVideoCustomEvent.h b/MoPubSDKTests/MPMockAdColonyRewardedVideoCustomEvent.h deleted file mode 100644 index 9e93ec3ad..000000000 --- a/MoPubSDKTests/MPMockAdColonyRewardedVideoCustomEvent.h +++ /dev/null @@ -1,14 +0,0 @@ -// -// MPMockAdColonyRewardedVideoCustomEvent.h -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import "MPRewardedVideoCustomEvent.h" - -@interface MPMockAdColonyRewardedVideoCustomEvent : MPRewardedVideoCustomEvent -+ (BOOL)isSdkInitialized; -+ (void)reset; -@end diff --git a/MoPubSDKTests/MPMockAdColonyRewardedVideoCustomEvent.m b/MoPubSDKTests/MPMockAdColonyRewardedVideoCustomEvent.m deleted file mode 100644 index 3891d495d..000000000 --- a/MoPubSDKTests/MPMockAdColonyRewardedVideoCustomEvent.m +++ /dev/null @@ -1,33 +0,0 @@ -// -// MPMockAdColonyRewardedVideoCustomEvent.m -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import "MPMockAdColonyRewardedVideoCustomEvent.h" -#import "MPRewardedVideoCustomEvent+Caching.h" - -static BOOL gInitialized = NO; - -@implementation MPMockAdColonyRewardedVideoCustomEvent - -+ (BOOL)isSdkInitialized { - return gInitialized; -} - -+ (void)reset { - gInitialized = NO; -} - -- (void)initializeSdkWithParameters:(NSDictionary *)parameters { - gInitialized = YES; -} - -- (void)requestRewardedVideoWithCustomEventInfo:(NSDictionary *)info { - [self setCachedInitializationParameters:info]; - [self.delegate rewardedVideoDidLoadAdForCustomEvent:self]; -} - -@end diff --git a/MoPubSDKTests/MPMockAdDestinationDisplayAgent.h b/MoPubSDKTests/MPMockAdDestinationDisplayAgent.h index a9e844114..f50fc01ca 100644 --- a/MoPubSDKTests/MPMockAdDestinationDisplayAgent.h +++ b/MoPubSDKTests/MPMockAdDestinationDisplayAgent.h @@ -1,7 +1,7 @@ // // MPMockAdDestinationDisplayAgent.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/MPMockAdDestinationDisplayAgent.m b/MoPubSDKTests/MPMockAdDestinationDisplayAgent.m index b04744e83..7a2f64837 100644 --- a/MoPubSDKTests/MPMockAdDestinationDisplayAgent.m +++ b/MoPubSDKTests/MPMockAdDestinationDisplayAgent.m @@ -1,7 +1,7 @@ // // MPMockAdDestinationDisplayAgent.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/MPMockAdServerCommunicator.h b/MoPubSDKTests/MPMockAdServerCommunicator.h index 186d46929..8bb075e72 100644 --- a/MoPubSDKTests/MPMockAdServerCommunicator.h +++ b/MoPubSDKTests/MPMockAdServerCommunicator.h @@ -1,7 +1,7 @@ // // MPMockAdServerCommunicator.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/MPMockAdServerCommunicator.m b/MoPubSDKTests/MPMockAdServerCommunicator.m index 3a21a8667..bd056d34c 100644 --- a/MoPubSDKTests/MPMockAdServerCommunicator.m +++ b/MoPubSDKTests/MPMockAdServerCommunicator.m @@ -1,7 +1,7 @@ // // MPMockAdServerCommunicator.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/MPMockAdapters.plist b/MoPubSDKTests/MPMockAdapters.plist new file mode 100644 index 000000000..7f85f9a46 --- /dev/null +++ b/MoPubSDKTests/MPMockAdapters.plist @@ -0,0 +1,9 @@ + + + + + MPMockAdColonyAdapterConfiguration + MPMockChartboostAdapterConfiguration + MPMockFacebookAdapterConfiguration + + diff --git a/MoPubSDKTests/MPMockBannerCustomEvent.h b/MoPubSDKTests/MPMockBannerCustomEvent.h index 5cef6004d..6306ac827 100644 --- a/MoPubSDKTests/MPMockBannerCustomEvent.h +++ b/MoPubSDKTests/MPMockBannerCustomEvent.h @@ -1,7 +1,7 @@ // // MPMockBannerCustomEvent.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/MPMockBannerCustomEvent.m b/MoPubSDKTests/MPMockBannerCustomEvent.m index 5a4f774d5..ea09ec19a 100644 --- a/MoPubSDKTests/MPMockBannerCustomEvent.m +++ b/MoPubSDKTests/MPMockBannerCustomEvent.m @@ -1,7 +1,7 @@ // // MPMockBannerCustomEvent.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/MPMockChartboostAdapterConfiguration.h b/MoPubSDKTests/MPMockChartboostAdapterConfiguration.h new file mode 100644 index 000000000..8bf9db8d0 --- /dev/null +++ b/MoPubSDKTests/MPMockChartboostAdapterConfiguration.h @@ -0,0 +1,21 @@ +// +// MPMockChartboostAdapterConfiguration.h +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import "MPBaseAdapterConfiguration.h" + +@interface MPMockChartboostAdapterConfiguration : MPBaseAdapterConfiguration +@property (class, nonatomic, assign) BOOL isSdkInitialized; + +@property (nonatomic, copy, readonly) NSString * _Nonnull adapterVersion; +@property (nonatomic, copy, readonly) NSString * _Nullable biddingToken; +@property (nonatomic, copy, readonly) NSString * _Nonnull moPubNetworkName; +@property (nonatomic, copy, readonly) NSString * _Nonnull networkSdkVersion; + +- (void)initializeNetworkWithConfiguration:(NSDictionary * _Nullable)configuration + complete:(void(^ _Nullable)(NSError * _Nullable))complete; +@end diff --git a/MoPubSDKTests/MPMockChartboostAdapterConfiguration.m b/MoPubSDKTests/MPMockChartboostAdapterConfiguration.m new file mode 100644 index 000000000..0aab9f313 --- /dev/null +++ b/MoPubSDKTests/MPMockChartboostAdapterConfiguration.m @@ -0,0 +1,45 @@ +// +// MPMockChartboostAdapterConfiguration.m +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import "MPMockChartboostAdapterConfiguration.h" + +static BOOL gInitialized = NO; + +@implementation MPMockChartboostAdapterConfiguration + ++ (BOOL)isSdkInitialized { + return gInitialized; +} + ++ (void)setIsSdkInitialized:(BOOL)isSdkInitialized { + gInitialized = isSdkInitialized; +} + +- (NSString *)adapterVersion { + return @"1.2.3.4"; +} + +- (NSString *)biddingToken { + return @"eJxFkVuP2yAQhf8Lz9gyGHCItA9gSENjY8uX7LpVhewoVbdK1qnS9KKq/73gbLWPM8z5zszhD7C6e6yaneuGWoM1gqDVbWsq64wCa0BUwlUm0ogkeBWRTK0iKRGNVJIyISjGSm8ABEWVi8LLwfHF9S14g3SmDG3GYp4ijJOEEoYZYX6kave6af0jQjHxdVNVnVbLCkZthH+QiCPKFY0IESwieSojSbGKtFRIM7HCiuVeWDoprNWN2+nBqwbMvxdn9OOw3T8XL/bL9CSvH57K24Tffxt+UzshiYbTa7/f/DyIh4ewsdq5XNRCmsJ0gfMxhQRSmEEOUQIRhohBlH0KhloZ0YXzWt3sTR4uDIS80dq6rTbvth1YM5ZBIOpa9qYIUaI48UOyt2pJ6jCf4/N8uU3xdTxfTsdrPE9fD3E517epXTricvGC3i7rJJ61qb1u5NOYjW92j0Z1W7BOMxoi9QPPVfgAb/w/3sW3FLvg6pmnoy/zqtHuDiRHPiYZ/+y7Stv2fjy+JxJw8zXIK6ULX/1aMcfIa1zBwKcQGDHnMQJ//wGFwpho"; +} + +- (NSString *)moPubNetworkName { + return @"mock_chartboost"; +} + +- (NSString *)networkSdkVersion { + return @"1.2.3"; +} + +- (void)initializeNetworkWithConfiguration:(NSDictionary * _Nullable)configuration + complete:(void(^ _Nullable)(NSError * _Nullable))complete { + MPMockChartboostAdapterConfiguration.isSdkInitialized = (configuration != nil); + complete(nil); +} + +@end diff --git a/MoPubSDKTests/MPMockChartboostRewardedVideoCustomEvent.h b/MoPubSDKTests/MPMockChartboostRewardedVideoCustomEvent.h index fef673170..f7f973025 100644 --- a/MoPubSDKTests/MPMockChartboostRewardedVideoCustomEvent.h +++ b/MoPubSDKTests/MPMockChartboostRewardedVideoCustomEvent.h @@ -1,14 +1,17 @@ // // MPMockChartboostRewardedVideoCustomEvent.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // #import "MPRewardedVideoCustomEvent.h" +NS_ASSUME_NONNULL_BEGIN + @interface MPMockChartboostRewardedVideoCustomEvent : MPRewardedVideoCustomEvent -+ (BOOL)isSdkInitialized; -+ (void)reset; + @end + +NS_ASSUME_NONNULL_END diff --git a/MoPubSDKTests/MPMockChartboostRewardedVideoCustomEvent.m b/MoPubSDKTests/MPMockChartboostRewardedVideoCustomEvent.m index 8c1014ef7..48cbae231 100644 --- a/MoPubSDKTests/MPMockChartboostRewardedVideoCustomEvent.m +++ b/MoPubSDKTests/MPMockChartboostRewardedVideoCustomEvent.m @@ -1,32 +1,18 @@ // // MPMockChartboostRewardedVideoCustomEvent.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // #import "MPMockChartboostRewardedVideoCustomEvent.h" -#import "MPRewardedVideoCustomEvent+Caching.h" - -static BOOL gInitialized = NO; +#import "MPMockChartboostAdapterConfiguration.h" @implementation MPMockChartboostRewardedVideoCustomEvent -+ (BOOL)isSdkInitialized { - return gInitialized; -} - -+ (void)reset { - gInitialized = NO; -} - -- (void)initializeSdkWithParameters:(NSDictionary *)parameters { - gInitialized = YES; -} - - (void)requestRewardedVideoWithCustomEventInfo:(NSDictionary *)info { - [self setCachedInitializationParameters:info]; + [MPMockChartboostAdapterConfiguration setCachedInitializationParameters:info]; [self.delegate rewardedVideoDidLoadAdForCustomEvent:self]; } diff --git a/MoPubSDKTests/MPMockInterstitialCustomEvent.h b/MoPubSDKTests/MPMockInterstitialCustomEvent.h index 3aa0756f5..8e5ac6b78 100644 --- a/MoPubSDKTests/MPMockInterstitialCustomEvent.h +++ b/MoPubSDKTests/MPMockInterstitialCustomEvent.h @@ -1,7 +1,7 @@ // // MPMockInterstitialCustomEvent.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/MPMockInterstitialCustomEvent.m b/MoPubSDKTests/MPMockInterstitialCustomEvent.m index 450f5b5fb..90a1ee640 100644 --- a/MoPubSDKTests/MPMockInterstitialCustomEvent.m +++ b/MoPubSDKTests/MPMockInterstitialCustomEvent.m @@ -1,7 +1,7 @@ // // MPMockInterstitialCustomEvent.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/MPMockLongLoadNativeCustomEvent.h b/MoPubSDKTests/MPMockLongLoadNativeCustomEvent.h index 06b228e06..562709059 100644 --- a/MoPubSDKTests/MPMockLongLoadNativeCustomEvent.h +++ b/MoPubSDKTests/MPMockLongLoadNativeCustomEvent.h @@ -1,7 +1,7 @@ // // MPMockLongLoadNativeCustomEvent.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/MPMockLongLoadNativeCustomEvent.m b/MoPubSDKTests/MPMockLongLoadNativeCustomEvent.m index 674be59c7..d1143b52c 100644 --- a/MoPubSDKTests/MPMockLongLoadNativeCustomEvent.m +++ b/MoPubSDKTests/MPMockLongLoadNativeCustomEvent.m @@ -1,7 +1,7 @@ // // MPMockLongLoadNativeCustomEvent.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/MPMockMRAIDInterstitialViewController.h b/MoPubSDKTests/MPMockMRAIDInterstitialViewController.h index da1cb8b9e..02d884d61 100644 --- a/MoPubSDKTests/MPMockMRAIDInterstitialViewController.h +++ b/MoPubSDKTests/MPMockMRAIDInterstitialViewController.h @@ -1,7 +1,7 @@ // // MPMockMRAIDInterstitialViewController.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/MPMockMRAIDInterstitialViewController.m b/MoPubSDKTests/MPMockMRAIDInterstitialViewController.m index 31b66fbd3..e016b5b1c 100644 --- a/MoPubSDKTests/MPMockMRAIDInterstitialViewController.m +++ b/MoPubSDKTests/MPMockMRAIDInterstitialViewController.m @@ -1,7 +1,7 @@ // // MPMockMRAIDInterstitialViewController.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/MPMockNativeCustomEvent.h b/MoPubSDKTests/MPMockNativeCustomEvent.h index d41493527..04d424291 100644 --- a/MoPubSDKTests/MPMockNativeCustomEvent.h +++ b/MoPubSDKTests/MPMockNativeCustomEvent.h @@ -1,7 +1,7 @@ // // MPMockNativeCustomEvent.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/MPMockNativeCustomEvent.m b/MoPubSDKTests/MPMockNativeCustomEvent.m index 190be6f0a..f49cae097 100644 --- a/MoPubSDKTests/MPMockNativeCustomEvent.m +++ b/MoPubSDKTests/MPMockNativeCustomEvent.m @@ -1,7 +1,7 @@ // // MPMockNativeCustomEvent.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/MPMockRewardedVideoAdapter.h b/MoPubSDKTests/MPMockRewardedVideoAdapter.h index 7b097689e..097afc230 100644 --- a/MoPubSDKTests/MPMockRewardedVideoAdapter.h +++ b/MoPubSDKTests/MPMockRewardedVideoAdapter.h @@ -1,7 +1,7 @@ // // MPMockRewardedVideoAdapter.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/MPMockRewardedVideoAdapter.m b/MoPubSDKTests/MPMockRewardedVideoAdapter.m index 70bee3670..7ba9f7be0 100644 --- a/MoPubSDKTests/MPMockRewardedVideoAdapter.m +++ b/MoPubSDKTests/MPMockRewardedVideoAdapter.m @@ -1,7 +1,7 @@ // // MPMockRewardedVideoAdapter.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/MPMockRewardedVideoCustomEvent.h b/MoPubSDKTests/MPMockRewardedVideoCustomEvent.h index cf677d131..b513e16aa 100644 --- a/MoPubSDKTests/MPMockRewardedVideoCustomEvent.h +++ b/MoPubSDKTests/MPMockRewardedVideoCustomEvent.h @@ -1,7 +1,7 @@ // // MPMockRewardedVideoCustomEvent.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/MPMockRewardedVideoCustomEvent.m b/MoPubSDKTests/MPMockRewardedVideoCustomEvent.m index a3718fd97..33eecfd0b 100644 --- a/MoPubSDKTests/MPMockRewardedVideoCustomEvent.m +++ b/MoPubSDKTests/MPMockRewardedVideoCustomEvent.m @@ -1,7 +1,7 @@ // // MPMockRewardedVideoCustomEvent.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/MPMockTapjoyAdapterConfiguration.h b/MoPubSDKTests/MPMockTapjoyAdapterConfiguration.h new file mode 100644 index 000000000..7dadf03ff --- /dev/null +++ b/MoPubSDKTests/MPMockTapjoyAdapterConfiguration.h @@ -0,0 +1,22 @@ +// +// MPMockTapjoyAdapterConfiguration.h +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import +#import "MPBaseAdapterConfiguration.h" + +@interface MPMockTapjoyAdapterConfiguration : MPBaseAdapterConfiguration +@property (class, nonatomic, assign) BOOL isSdkInitialized; + +@property (nonatomic, copy, readonly) NSString * _Nonnull adapterVersion; +@property (nonatomic, copy, readonly) NSString * _Nullable biddingToken; +@property (nonatomic, copy, readonly) NSString * _Nonnull moPubNetworkName; +@property (nonatomic, copy, readonly) NSString * _Nonnull networkSdkVersion; + +- (void)initializeNetworkWithConfiguration:(NSDictionary * _Nullable)configuration + complete:(void(^ _Nullable)(NSError * _Nullable))complete; +@end diff --git a/MoPubSDKTests/MPMockTapjoyAdapterConfiguration.m b/MoPubSDKTests/MPMockTapjoyAdapterConfiguration.m new file mode 100644 index 000000000..62c2e1355 --- /dev/null +++ b/MoPubSDKTests/MPMockTapjoyAdapterConfiguration.m @@ -0,0 +1,45 @@ +// +// MPMockTapjoyAdapterConfiguration.m +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import "MPMockTapjoyAdapterConfiguration.h" + +static BOOL gInitialized = NO; + +@implementation MPMockTapjoyAdapterConfiguration + ++ (BOOL)isSdkInitialized { + return gInitialized; +} + ++ (void)setIsSdkInitialized:(BOOL)isSdkInitialized { + gInitialized = isSdkInitialized; +} + +- (NSString *)adapterVersion { + return @"20.0.0.0"; +} + +- (NSString *)biddingToken { + return nil; +} + +- (NSString *)moPubNetworkName { + return @"mock_tapjoy"; +} + +- (NSString *)networkSdkVersion { + return @"20.0.0"; +} + +- (void)initializeNetworkWithConfiguration:(NSDictionary * _Nullable)configuration + complete:(void(^ _Nullable)(NSError * _Nullable))complete { + MPMockTapjoyAdapterConfiguration.isSdkInitialized = (configuration != nil); + complete(nil); +} + +@end diff --git a/MoPubSDKTests/MPMockViewabilityAdapterAvid.h b/MoPubSDKTests/MPMockViewabilityAdapterAvid.h index ff5f4de4c..d0123bc47 100644 --- a/MoPubSDKTests/MPMockViewabilityAdapterAvid.h +++ b/MoPubSDKTests/MPMockViewabilityAdapterAvid.h @@ -1,7 +1,7 @@ // // MPMockViewabilityAdapterAvid.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/MPMockViewabilityAdapterAvid.m b/MoPubSDKTests/MPMockViewabilityAdapterAvid.m index ab29da247..bfa60e280 100644 --- a/MoPubSDKTests/MPMockViewabilityAdapterAvid.m +++ b/MoPubSDKTests/MPMockViewabilityAdapterAvid.m @@ -1,7 +1,7 @@ // // MPMockViewabilityAdapterAvid.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/MPNativeAd+Testing.h b/MoPubSDKTests/MPNativeAd+Testing.h new file mode 100644 index 000000000..052c5dd69 --- /dev/null +++ b/MoPubSDKTests/MPNativeAd+Testing.h @@ -0,0 +1,15 @@ +// +// MPNativeAd+Testing.h +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import "MPNativeAd+Internal.h" + +@interface MPNativeAd (Testing) + +- (void)trackImpression; + +@end diff --git a/MoPubSDKTests/MPNativeAd+Testing.m b/MoPubSDKTests/MPNativeAd+Testing.m new file mode 100644 index 000000000..47b160fd3 --- /dev/null +++ b/MoPubSDKTests/MPNativeAd+Testing.m @@ -0,0 +1,17 @@ +// +// MPNativeAd+Testing.m +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import "MPNativeAd+Testing.h" + +// Suppress warning of accessing private implementation `trackImpression` +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wincomplete-implementation" +@implementation MPNativeAd (Testing) + +@end +#pragma clang diagnostic pop diff --git a/MoPubSDKTests/MPNativeAdConfigValuesTests.m b/MoPubSDKTests/MPNativeAdConfigValuesTests.m index ee86d4688..94b6a1532 100644 --- a/MoPubSDKTests/MPNativeAdConfigValuesTests.m +++ b/MoPubSDKTests/MPNativeAdConfigValuesTests.m @@ -1,7 +1,7 @@ // // MPNativeAdConfigValuesTests.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/MPNativeAdDelegateHandler.h b/MoPubSDKTests/MPNativeAdDelegateHandler.h new file mode 100644 index 000000000..51dece3d2 --- /dev/null +++ b/MoPubSDKTests/MPNativeAdDelegateHandler.h @@ -0,0 +1,24 @@ +// +// MPNativeAdDelegateHandler.h +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import +#import "MPNativeAdDelegate.h" + +typedef void(^MPNativeAdDelegateHandlerBlock)(MPNativeAd *); +typedef UIViewController *(^MPNativeAdDelegateHandlerPresentingViewControllerBlock)(void); +typedef void(^MPNativeAdDelegateHandlerImpressionDataBlock)(MPNativeAd *, MPImpressionData *); + +@interface MPNativeAdDelegateHandler : NSObject + +@property (nonatomic, copy) MPNativeAdDelegateHandlerBlock willPresentModal; +@property (nonatomic, copy) MPNativeAdDelegateHandlerBlock didPresentModal; +@property (nonatomic, copy) MPNativeAdDelegateHandlerBlock willLeaveApplication; +@property (nonatomic, copy) MPNativeAdDelegateHandlerPresentingViewControllerBlock viewControllerForModal; +@property (nonatomic, copy) MPNativeAdDelegateHandlerImpressionDataBlock didTrackImpression; + +@end diff --git a/MoPubSDKTests/MPNativeAdDelegateHandler.m b/MoPubSDKTests/MPNativeAdDelegateHandler.m new file mode 100644 index 000000000..8fd04ec74 --- /dev/null +++ b/MoPubSDKTests/MPNativeAdDelegateHandler.m @@ -0,0 +1,35 @@ +// +// MPNativeAdDelegateHandler.m +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import "MPNativeAdDelegateHandler.h" + +@implementation MPNativeAdDelegateHandler + +- (void)willPresentModalForNativeAd:(MPNativeAd *)nativeAd { + if (self.willPresentModal) { self.willPresentModal(nativeAd); } +} + +- (void)didDismissModalForNativeAd:(MPNativeAd *)nativeAd { + if (self.didPresentModal) { self.didPresentModal(nativeAd); } +} + +- (void)willLeaveApplicationFromNativeAd:(MPNativeAd *)nativeAd { + if (self.willLeaveApplication) { self.willLeaveApplication(nativeAd); }; +} + +- (UIViewController *)viewControllerForPresentingModalView { + if (self.viewControllerForModal) { return self.viewControllerForModal(); } + + return [[UIViewController alloc] init]; +} + +- (void)mopubAd:(id)ad didTrackImpressionWithImpressionData:(MPImpressionData *)impressionData { + if (self.didTrackImpression) { self.didTrackImpression((MPNativeAd *)ad, impressionData); } +} + +@end diff --git a/MoPubSDKTests/MPNativeAdRequest+Testing.h b/MoPubSDKTests/MPNativeAdRequest+Testing.h index c0dffb955..a33348046 100644 --- a/MoPubSDKTests/MPNativeAdRequest+Testing.h +++ b/MoPubSDKTests/MPNativeAdRequest+Testing.h @@ -1,7 +1,7 @@ // // MPNativeAdRequest+Testing.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/MPNativeAdRequest+Testing.m b/MoPubSDKTests/MPNativeAdRequest+Testing.m index 741f7a018..11471ec29 100644 --- a/MoPubSDKTests/MPNativeAdRequest+Testing.m +++ b/MoPubSDKTests/MPNativeAdRequest+Testing.m @@ -1,7 +1,7 @@ // // MPNativeAdRequest+Testing.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -10,6 +10,7 @@ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wprotocol" +#pragma clang diagnostic ignored "-Wincomplete-implementation" @implementation MPNativeAdRequest (Testing) @dynamic adConfiguration; diff --git a/MoPubSDKTests/MPNativeAdRequestTargetingTests.m b/MoPubSDKTests/MPNativeAdRequestTargetingTests.m new file mode 100644 index 000000000..fcb5b4a85 --- /dev/null +++ b/MoPubSDKTests/MPNativeAdRequestTargetingTests.m @@ -0,0 +1,24 @@ +// +// MPNativeAdRequestTargetingTests.m +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import +#import "MPNativeAdRequestTargeting.h" + +@interface MPNativeAdRequestTargetingTests : XCTestCase + +@end + +@implementation MPNativeAdRequestTargetingTests + +- (void)testCreativeSafeSizeZero { + MPNativeAdRequestTargeting * targeting = [MPNativeAdRequestTargeting targeting]; + XCTAssertNotNil(targeting); + XCTAssert(CGSizeEqualToSize(targeting.creativeSafeSize, CGSizeZero)); +} + +@end diff --git a/MoPubSDKTests/MPNativeAdRequestTests.m b/MoPubSDKTests/MPNativeAdRequestTests.m index 8900b7f0e..723902576 100644 --- a/MoPubSDKTests/MPNativeAdRequestTests.m +++ b/MoPubSDKTests/MPNativeAdRequestTests.m @@ -1,13 +1,14 @@ // // MPNativeAdRequestTests.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // #import #import "MPAdConfigurationFactory.h" +#import "MPAdServerKeys.h" #import "MPAPIEndpoints.h" #import "MPConstants.h" #import "MPError.h" @@ -19,7 +20,12 @@ #import "MPStaticNativeAdRenderer.h" #import "MPNativeAdRendererConfiguration.h" #import "MPStaticNativeAdRendererSettings.h" +#import "MPURL.h" #import "NSURLComponents+Testing.h" +#import "MPNativeAdDelegateHandler.h" +#import "MPNativeAd+Testing.h" +#import "MPAdServerKeys.h" +#import "MPImpressionTrackedNotification.h" static const NSTimeInterval kTestTimeout = 2; // seconds @@ -379,4 +385,162 @@ - (void)testLocalExtrasInCustomEvent { XCTAssertTrue(customEvent.isLocalExtrasAvailableAtRequest); } +#pragma mark - Impression Level Revenue Data + +- (void)testImpressionDelegateFiresWithoutILRD { + XCTestExpectation * delegateExpectation = [self expectationWithDescription:@"Wait for impression delegate"]; + XCTestExpectation * notificationExpectation = [self expectationWithDescription:@"Wait for impression notification"]; + NSString * testAdUnitId = @"FAKE_AD_UNIT_ID"; + + __block MPNativeAd * nativeAd = nil; + + // Make delegate handler + MPNativeAdDelegateHandler * handler = [[MPNativeAdDelegateHandler alloc] init]; + handler.didTrackImpression = ^(MPNativeAd * ad, MPImpressionData * impressionData) { + [delegateExpectation fulfill]; + + XCTAssertNil(impressionData); + }; + + // Make notification handler + id notificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMPImpressionTrackedNotification + object:nil + queue:[NSOperationQueue mainQueue] + usingBlock:^(NSNotification * note){ + [notificationExpectation fulfill]; + + XCTAssertNil(note.userInfo[kMPImpressionTrackedInfoImpressionDataKey]); + XCTAssert([nativeAd isEqual:note.object]); + XCTAssert([note.userInfo[kMPImpressionTrackedInfoAdUnitIDKey] isEqualToString:nativeAd.adUnitID]); + }]; + + // Generate the ad configurations + MPAdConfiguration * nativeAdThatShouldLoad = [MPAdConfigurationFactory defaultNativeAdConfigurationWithCustomEventClassName:@"MPMockNativeCustomEvent"]; + NSArray * configurations = @[nativeAdThatShouldLoad]; + + // Generate ad request + MPNativeAdRequest * nativeAdRequest = [MPNativeAdRequest requestWithAdUnitIdentifier:testAdUnitId rendererConfigurations:self.rendererConfigurations]; + MPMockAdServerCommunicator * communicator = [[MPMockAdServerCommunicator alloc] initWithDelegate:nativeAdRequest]; + communicator.mockConfigurationsResponse = configurations; + nativeAdRequest.communicator = communicator; + + nativeAdRequest.targeting = [MPNativeAdRequestTargeting targeting]; + [nativeAdRequest startWithCompletionHandler:^(MPNativeAdRequest *request, MPNativeAd *response, NSError *error) { + if (error != nil) { + XCTFail(@"Unexpected failure"); + } + + nativeAd = response; + response.delegate = handler; + [response trackImpression]; + }]; + + [self waitForExpectationsWithTimeout:kTestTimeout handler:^(NSError * _Nullable error) { + if (error != nil) { + XCTFail(@"Timed out"); + } + }]; + + [[NSNotificationCenter defaultCenter] removeObserver:notificationObserver]; +} + +- (void)testImpressionDelegateFiresWithILRD { + XCTestExpectation * delegateExpectation = [self expectationWithDescription:@"Wait for impression delegate"]; + XCTestExpectation * notificationExpectation = [self expectationWithDescription:@"Wait for impression notification"]; + NSString * testAdUnitId = @"FAKE_AD_UNIT_ID"; + + __block MPNativeAd * nativeAd = nil; + + // Make delegate handler + MPNativeAdDelegateHandler * handler = [[MPNativeAdDelegateHandler alloc] init]; + handler.didTrackImpression = ^(MPNativeAd * ad, MPImpressionData * impressionData) { + [delegateExpectation fulfill]; + + XCTAssertNotNil(impressionData); + XCTAssert([impressionData.adUnitID isEqualToString:testAdUnitId]); + }; + + // Make notification handler + id notificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMPImpressionTrackedNotification + object:nil + queue:[NSOperationQueue mainQueue] + usingBlock:^(NSNotification * note){ + [notificationExpectation fulfill]; + + MPImpressionData * impressionData = note.userInfo[kMPImpressionTrackedInfoImpressionDataKey]; + XCTAssertNotNil(impressionData); + XCTAssert([impressionData.adUnitID isEqualToString:testAdUnitId]); + XCTAssert([nativeAd isEqual:note.object]); + XCTAssert([note.userInfo[kMPImpressionTrackedInfoAdUnitIDKey] isEqualToString:nativeAd.adUnitID]); + }]; + + // Generate the ad configurations + MPAdConfiguration * nativeAdThatShouldLoad = [MPAdConfigurationFactory defaultNativeAdConfigurationWithCustomEventClassName:@"MPMockNativeCustomEvent"]; + nativeAdThatShouldLoad.impressionData = [[MPImpressionData alloc] initWithDictionary:@{ + kImpressionDataAdUnitIDKey: testAdUnitId + }]; + NSArray * configurations = @[nativeAdThatShouldLoad]; + + // Generate ad request + MPNativeAdRequest * nativeAdRequest = [MPNativeAdRequest requestWithAdUnitIdentifier:testAdUnitId rendererConfigurations:self.rendererConfigurations]; + MPMockAdServerCommunicator * communicator = [[MPMockAdServerCommunicator alloc] initWithDelegate:nativeAdRequest]; + communicator.mockConfigurationsResponse = configurations; + nativeAdRequest.communicator = communicator; + + nativeAdRequest.targeting = [MPNativeAdRequestTargeting targeting]; + [nativeAdRequest startWithCompletionHandler:^(MPNativeAdRequest *request, MPNativeAd *response, NSError *error) { + if (error != nil) { + XCTFail(@"Unexpected failure"); + } + + nativeAd = response; + response.delegate = handler; + [response trackImpression]; + }]; + + [self waitForExpectationsWithTimeout:kTestTimeout handler:^(NSError * _Nullable error) { + if (error != nil) { + XCTFail(@"Timed out"); + } + }]; + + [[NSNotificationCenter defaultCenter] removeObserver:notificationObserver]; +} + +#pragma mark - Ad Sizing + +- (void)testNativeCreativeSizeSentAsZero { + XCTestExpectation * expectation = [self expectationWithDescription:@"Wait for native load"]; + + // Generate ad request + MPNativeAdRequest * nativeAdRequest = [MPNativeAdRequest requestWithAdUnitIdentifier:@"FAKE_AD_UNIT_ID" rendererConfigurations:self.rendererConfigurations]; + MPMockAdServerCommunicator * communicator = [[MPMockAdServerCommunicator alloc] initWithDelegate:nativeAdRequest]; + communicator.mockConfigurationsResponse = @[]; + + nativeAdRequest.communicator = communicator; + [nativeAdRequest startWithCompletionHandler:^(MPNativeAdRequest *request, MPNativeAd *response, NSError *error) { + if (error == nil) { + XCTFail(@"Unexpected success"); + } + [expectation fulfill]; + }]; + + [self waitForExpectationsWithTimeout:kTestTimeout handler:^(NSError * _Nullable error) { + if (error != nil) { + XCTFail(@"Timed out"); + } + }]; + + XCTAssertTrue(communicator.numberOfBeforeLoadEventsFired == 0); + XCTAssertTrue(communicator.numberOfAfterLoadEventsFired == 0); + + MPURL * url = [communicator.lastUrlLoaded isKindOfClass:[MPURL class]] ? (MPURL *)communicator.lastUrlLoaded : nil; + XCTAssertNotNil(url); + + NSNumber * cw = [url numberForPOSTDataKey:kCreativeSafeWidthKey]; + NSNumber * ch = [url numberForPOSTDataKey:kCreativeSafeHeightKey]; + XCTAssert(cw.floatValue == 0.0); + XCTAssert(ch.floatValue == 0.0); +} + @end diff --git a/MoPubSDKTests/MPPrivateRewardedVideoCustomEventDelegateHandler.h b/MoPubSDKTests/MPPrivateRewardedVideoCustomEventDelegateHandler.h index 06eea760e..63de4c8aa 100644 --- a/MoPubSDKTests/MPPrivateRewardedVideoCustomEventDelegateHandler.h +++ b/MoPubSDKTests/MPPrivateRewardedVideoCustomEventDelegateHandler.h @@ -1,7 +1,7 @@ // // MPPrivateRewardedVideoCustomEventDelegateHandler.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/MPPrivateRewardedVideoCustomEventDelegateHandler.m b/MoPubSDKTests/MPPrivateRewardedVideoCustomEventDelegateHandler.m index b320476f0..cf9870b7e 100644 --- a/MoPubSDKTests/MPPrivateRewardedVideoCustomEventDelegateHandler.m +++ b/MoPubSDKTests/MPPrivateRewardedVideoCustomEventDelegateHandler.m @@ -1,7 +1,7 @@ // // MPPrivateRewardedVideoCustomEventDelegateHandler.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -85,11 +85,11 @@ - (void)trackClick { } - (NSString *)adUnitId { - return self.adUnitId; + return _adUnitId; } - (MPAdConfiguration *)configuration { - return self.adConfiguration; + return _adConfiguration; } @end diff --git a/MoPubSDKTests/MPRateLimitConfiguration+Testing.h b/MoPubSDKTests/MPRateLimitConfiguration+Testing.h new file mode 100644 index 000000000..6c0ca6886 --- /dev/null +++ b/MoPubSDKTests/MPRateLimitConfiguration+Testing.h @@ -0,0 +1,20 @@ +// +// MPRateLimitConfiguration+Testing.h +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import "MPRateLimitConfiguration.h" +#import "MPRealTimeTimer.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface MPRateLimitConfiguration (Testing) + +@property (nonatomic, strong, nullable) MPRealTimeTimer * timer; + +@end + +NS_ASSUME_NONNULL_END diff --git a/MoPubSDKTests/MPRateLimitConfiguration+Testing.m b/MoPubSDKTests/MPRateLimitConfiguration+Testing.m new file mode 100644 index 000000000..d7b988875 --- /dev/null +++ b/MoPubSDKTests/MPRateLimitConfiguration+Testing.m @@ -0,0 +1,13 @@ +// +// MPRateLimitConfiguration+Testing.m +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import "MPRateLimitConfiguration+Testing.h" + +@implementation MPRateLimitConfiguration (Testing) +@dynamic timer; +@end diff --git a/MoPubSDKTests/MPRateLimitConfigurationTests.m b/MoPubSDKTests/MPRateLimitConfigurationTests.m new file mode 100644 index 000000000..b887519ec --- /dev/null +++ b/MoPubSDKTests/MPRateLimitConfigurationTests.m @@ -0,0 +1,143 @@ +// +// MPRateLimitConfigurationTests.m +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import +#import "MPRateLimitConfiguration+Testing.h" + +static NSTimeInterval const kTimeoutTime = 0.7; + +@interface MPRateLimitConfigurationTests : XCTestCase + +@end + +@implementation MPRateLimitConfigurationTests + +- (void)testDefaults { + MPRateLimitConfiguration * config = [[MPRateLimitConfiguration alloc] init]; + + XCTAssertNil(config.lastRateLimitReason); + XCTAssertEqual(0, config.lastRateLimitMilliseconds); + XCTAssertFalse(config.isRateLimited); + XCTAssertNil(config.timer); +} + +- (void)testSettingTimerWithNoValueAndNoReason { + MPRateLimitConfiguration * config = [[MPRateLimitConfiguration alloc] init]; + + [config setRateLimitTimerWithMilliseconds:0 reason:nil]; + + XCTAssertNil(config.lastRateLimitReason); + XCTAssertEqual(0, config.lastRateLimitMilliseconds); + XCTAssertFalse(config.isRateLimited); + XCTAssertNil(config.timer); +} + +- (void)testSettingTimerWithNegativeValueAndNoReason { + MPRateLimitConfiguration * config = [[MPRateLimitConfiguration alloc] init]; + + [config setRateLimitTimerWithMilliseconds:-5 reason:nil]; // Should evaluate to 0 + + XCTAssertNil(config.lastRateLimitReason); + XCTAssertEqual(0, config.lastRateLimitMilliseconds); + XCTAssertFalse(config.isRateLimited); + XCTAssertNil(config.timer); +} + +- (void)testSettingTimerWithNoValueAndReason { + MPRateLimitConfiguration * config = [[MPRateLimitConfiguration alloc] init]; + + NSString * reason = @"Reason"; + + [config setRateLimitTimerWithMilliseconds:0 reason:reason]; + + XCTAssert([config.lastRateLimitReason isEqualToString:reason]); + XCTAssertEqual(0, config.lastRateLimitMilliseconds); + XCTAssertFalse(config.isRateLimited); + XCTAssertNil(config.timer); +} + +- (void)testSettingTimerWithValueAndReason { + MPRateLimitConfiguration * config = [[MPRateLimitConfiguration alloc] init]; + + NSInteger milliseconds = 400; + NSString * reason = @"Reason"; + + [config setRateLimitTimerWithMilliseconds:milliseconds reason:reason]; + XCTAssertTrue(config.isRateLimited); + XCTAssertNotNil(config.timer); + + XCTestExpectation * expectation = [self expectationWithDescription:@"Wait for rate limit to end"]; + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)((milliseconds + 50) * NSEC_PER_MSEC)), dispatch_get_main_queue(), ^{ + [expectation fulfill]; + + XCTAssertFalse(config.isRateLimited); + XCTAssertEqual(milliseconds, config.lastRateLimitMilliseconds); + XCTAssert([reason isEqualToString:config.lastRateLimitReason]); + XCTAssertNil(config.timer); + }); + + [self waitForExpectations:@[expectation] timeout:kTimeoutTime]; +} + +- (void)testSettingTimerWithValueAndNoReason { + MPRateLimitConfiguration * config = [[MPRateLimitConfiguration alloc] init]; + + NSInteger milliseconds = 400; + + [config setRateLimitTimerWithMilliseconds:milliseconds reason:nil]; + XCTAssertTrue(config.isRateLimited); + XCTAssertNotNil(config.timer); + + XCTestExpectation * expectation = [self expectationWithDescription:@"Wait for rate limit to end"]; + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)((milliseconds + 50) * NSEC_PER_MSEC)), dispatch_get_main_queue(), ^{ + [expectation fulfill]; + + XCTAssertFalse(config.isRateLimited); + XCTAssertEqual(milliseconds, config.lastRateLimitMilliseconds); + XCTAssertNil(config.lastRateLimitReason); + XCTAssertNil(config.timer); + }); + + [self waitForExpectations:@[expectation] timeout:kTimeoutTime]; +} + +- (void)testSettingTimerWithValueAndReasonThenOverwritingWithNoValueAndNoReason { + MPRateLimitConfiguration * config = [[MPRateLimitConfiguration alloc] init]; + + NSInteger milliseconds = 400; + NSString * reason = @"Reason"; + + [config setRateLimitTimerWithMilliseconds:milliseconds reason:reason]; + XCTAssertTrue(config.isRateLimited); + XCTAssertNotNil(config.timer); + + XCTestExpectation * expectation = [self expectationWithDescription:@"Wait for rate limit to end"]; + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)((milliseconds + 50) * NSEC_PER_MSEC)), dispatch_get_main_queue(), ^{ + [expectation fulfill]; + + XCTAssertFalse(config.isRateLimited); + XCTAssertEqual(milliseconds, config.lastRateLimitMilliseconds); + XCTAssert([reason isEqualToString:config.lastRateLimitReason]); + XCTAssertNil(config.timer); + + // set timer with no value and no reason and check + [config setRateLimitTimerWithMilliseconds:0 reason:nil]; + XCTAssertNil(config.lastRateLimitReason); + XCTAssertEqual(0, config.lastRateLimitMilliseconds); + XCTAssertFalse(config.isRateLimited); + XCTAssertNil(config.timer); + + }); + + [self waitForExpectations:@[expectation] timeout:kTimeoutTime]; +} + +@end diff --git a/MoPubSDKTests/MPRateLimitManager+Testing.h b/MoPubSDKTests/MPRateLimitManager+Testing.h new file mode 100644 index 000000000..3b7fc39cf --- /dev/null +++ b/MoPubSDKTests/MPRateLimitManager+Testing.h @@ -0,0 +1,20 @@ +// +// MPRateLimitManager+Testing.h +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import "MPRateLimitManager.h" +#import "MPRateLimitConfiguration.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface MPRateLimitManager (Testing) + +@property (nonatomic, copy) NSMutableDictionary * configurationDictionary; + +@end + +NS_ASSUME_NONNULL_END diff --git a/MoPubSDKTests/MPRateLimitManager+Testing.m b/MoPubSDKTests/MPRateLimitManager+Testing.m new file mode 100644 index 000000000..6c3b912b9 --- /dev/null +++ b/MoPubSDKTests/MPRateLimitManager+Testing.m @@ -0,0 +1,13 @@ +// +// MPRateLimitManager+Testing.m +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import "MPRateLimitManager+Testing.h" + +@implementation MPRateLimitManager (Testing) +@dynamic configurationDictionary; +@end diff --git a/MoPubSDKTests/MPRateLimitManagerTests.m b/MoPubSDKTests/MPRateLimitManagerTests.m new file mode 100644 index 000000000..8798ab5f7 --- /dev/null +++ b/MoPubSDKTests/MPRateLimitManagerTests.m @@ -0,0 +1,201 @@ +// +// MPRateLimitManagerTests.m +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import +#import "MPRateLimitManager+Testing.h" + +static NSTimeInterval const kTimeoutTime = 0.7; + +@interface MPRateLimitManagerTests : XCTestCase + +@end + +@implementation MPRateLimitManagerTests + +- (void)testSettingTimer { + MPRateLimitManager * manager = [MPRateLimitManager sharedInstance]; + + NSString * adUnitString = @"FAKE_ADUNIT"; + NSString * reasonString = @"Reason"; + NSInteger milliseconds = 400; + + [manager setRateLimitTimerWithAdUnitId:adUnitString + milliseconds:milliseconds + reason:reasonString]; + + XCTAssertTrue([manager isRateLimitedForAdUnitId:adUnitString]); + XCTAssertNotNil(manager.configurationDictionary[adUnitString]); + + XCTestExpectation * expectation = [self expectationWithDescription:@"Wait for rate limit to end"]; + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)((milliseconds + 50) * NSEC_PER_MSEC)), dispatch_get_main_queue(), ^{ + [expectation fulfill]; + + XCTAssertEqual(milliseconds, [manager lastRateLimitMillisecondsForAdUnitId:adUnitString]); + XCTAssert([reasonString isEqualToString:[manager lastRateLimitReasonForAdUnitId:adUnitString]]); + XCTAssertFalse([manager isRateLimitedForAdUnitId:adUnitString]); + }); + + [self waitForExpectations:@[expectation] timeout:kTimeoutTime]; +} + +- (void)testSettingMultipleSimultaneousTimers { + MPRateLimitManager * manager = [MPRateLimitManager sharedInstance]; + + NSString * adUnitString1 = @"FAKE_ADUNIT1"; + NSString * reasonString1 = @"Reason1"; + NSString * adUnitString2 = @"FAKE_ADUNIT2"; + NSString * reasonString2 = @"Reason2"; + NSInteger milliseconds = 400; + + [manager setRateLimitTimerWithAdUnitId:adUnitString1 + milliseconds:milliseconds + reason:reasonString1]; + + [manager setRateLimitTimerWithAdUnitId:adUnitString2 + milliseconds:milliseconds + reason:reasonString2]; + + XCTAssertTrue([manager isRateLimitedForAdUnitId:adUnitString1]); + XCTAssertNotNil(manager.configurationDictionary[adUnitString1]); + + XCTAssertTrue([manager isRateLimitedForAdUnitId:adUnitString2]); + XCTAssertNotNil(manager.configurationDictionary[adUnitString2]); + + XCTestExpectation * expectation = [self expectationWithDescription:@"Wait for rate limit to end"]; + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)((milliseconds + 50) * NSEC_PER_MSEC)), dispatch_get_main_queue(), ^{ + [expectation fulfill]; + + XCTAssertEqual(milliseconds, [manager lastRateLimitMillisecondsForAdUnitId:adUnitString1]); + XCTAssert([reasonString1 isEqualToString:[manager lastRateLimitReasonForAdUnitId:adUnitString1]]); + XCTAssertFalse([manager isRateLimitedForAdUnitId:adUnitString1]); + + XCTAssertEqual(milliseconds, [manager lastRateLimitMillisecondsForAdUnitId:adUnitString2]); + XCTAssert([reasonString2 isEqualToString:[manager lastRateLimitReasonForAdUnitId:adUnitString2]]); + XCTAssertFalse([manager isRateLimitedForAdUnitId:adUnitString2]); + }); + + [self waitForExpectations:@[expectation] timeout:kTimeoutTime]; +} + +- (void)testSettingOneTimerWithValueAndAnotherWithZero { + MPRateLimitManager * manager = [MPRateLimitManager sharedInstance]; + + NSString * adUnitString1 = @"FAKE_ADUNIT1"; + NSString * reasonString1 = @"Reason1"; + NSString * adUnitString2 = @"FAKE_ADUNIT2"; + NSString * reasonString2 = @"Reason2"; + NSInteger milliseconds = 400; + + [manager setRateLimitTimerWithAdUnitId:adUnitString1 + milliseconds:milliseconds + reason:reasonString1]; + + [manager setRateLimitTimerWithAdUnitId:adUnitString2 + milliseconds:0 + reason:reasonString2]; + + XCTAssertTrue([manager isRateLimitedForAdUnitId:adUnitString1]); + XCTAssertNotNil(manager.configurationDictionary[adUnitString1]); + + XCTAssertFalse([manager isRateLimitedForAdUnitId:adUnitString2]); + XCTAssertNotNil(manager.configurationDictionary[adUnitString2]); + + XCTestExpectation * expectation = [self expectationWithDescription:@"Wait for rate limit to end"]; + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)((milliseconds + 50) * NSEC_PER_MSEC)), dispatch_get_main_queue(), ^{ + [expectation fulfill]; + + XCTAssertEqual(milliseconds, [manager lastRateLimitMillisecondsForAdUnitId:adUnitString1]); + XCTAssert([reasonString1 isEqualToString:[manager lastRateLimitReasonForAdUnitId:adUnitString1]]); + XCTAssertFalse([manager isRateLimitedForAdUnitId:adUnitString1]); + + XCTAssertEqual(0, [manager lastRateLimitMillisecondsForAdUnitId:adUnitString2]); + XCTAssert([reasonString2 isEqualToString:[manager lastRateLimitReasonForAdUnitId:adUnitString2]]); + XCTAssertFalse([manager isRateLimitedForAdUnitId:adUnitString2]); + }); + + [self waitForExpectations:@[expectation] timeout:kTimeoutTime]; +} + +- (void)testSettingMultipleSimultaneousWithDifferentValues { + MPRateLimitManager * manager = [MPRateLimitManager sharedInstance]; + + NSString * adUnitString1 = @"FAKE_ADUNIT1"; + NSString * reasonString1 = @"Reason1"; + NSInteger milliseconds1 = 400; + NSString * adUnitString2 = @"FAKE_ADUNIT2"; + NSString * reasonString2 = @"Reason2"; + NSInteger milliseconds2 = 200; + + [manager setRateLimitTimerWithAdUnitId:adUnitString1 + milliseconds:milliseconds1 + reason:reasonString1]; + + [manager setRateLimitTimerWithAdUnitId:adUnitString2 + milliseconds:milliseconds2 + reason:reasonString2]; + + XCTAssertTrue([manager isRateLimitedForAdUnitId:adUnitString1]); + XCTAssertNotNil(manager.configurationDictionary[adUnitString1]); + + XCTAssertTrue([manager isRateLimitedForAdUnitId:adUnitString2]); + XCTAssertNotNil(manager.configurationDictionary[adUnitString2]); + + XCTestExpectation * expectation = [self expectationWithDescription:@"Wait for rate limit to end"]; + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)((milliseconds2 + 50) * NSEC_PER_MSEC)), dispatch_get_main_queue(), ^{ + XCTAssertTrue([manager isRateLimitedForAdUnitId:adUnitString1]); + + XCTAssertEqual(milliseconds2, [manager lastRateLimitMillisecondsForAdUnitId:adUnitString2]); + XCTAssert([reasonString2 isEqualToString:[manager lastRateLimitReasonForAdUnitId:adUnitString2]]); + XCTAssertFalse([manager isRateLimitedForAdUnitId:adUnitString2]); + }); + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)((milliseconds1 + 50) * NSEC_PER_MSEC)), dispatch_get_main_queue(), ^{ + [expectation fulfill]; + + XCTAssertEqual(milliseconds1, [manager lastRateLimitMillisecondsForAdUnitId:adUnitString1]); + XCTAssert([reasonString1 isEqualToString:[manager lastRateLimitReasonForAdUnitId:adUnitString1]]); + XCTAssertFalse([manager isRateLimitedForAdUnitId:adUnitString1]); + + XCTAssertEqual(milliseconds2, [manager lastRateLimitMillisecondsForAdUnitId:adUnitString2]); + XCTAssert([reasonString2 isEqualToString:[manager lastRateLimitReasonForAdUnitId:adUnitString2]]); + XCTAssertFalse([manager isRateLimitedForAdUnitId:adUnitString2]); + }); + + [self waitForExpectations:@[expectation] timeout:kTimeoutTime]; +} + +- (void)testForAdUnitThatWasNeverAdded { + MPRateLimitManager * manager = [MPRateLimitManager sharedInstance]; + + NSString * adUnitId = @"MPRateLimitManagerTests.m: Please don't use this ad unit ID in any other test"; + + XCTAssertFalse([manager isRateLimitedForAdUnitId:adUnitId]); + XCTAssertEqual(0, [manager lastRateLimitMillisecondsForAdUnitId:adUnitId]); + XCTAssertNil([manager lastRateLimitReasonForAdUnitId:adUnitId]); +} + +- (void)testNilAdUnitDoesNotCrash { + MPRateLimitManager * manager = [MPRateLimitManager sharedInstance]; + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnonnull" + // Intentially set the explicitly marked `nonnull` property to `nil` + [manager setRateLimitTimerWithAdUnitId:nil milliseconds:5.0 reason:@"hi"]; + XCTAssertFalse([manager isRateLimitedForAdUnitId:nil]); + XCTAssertNil([manager lastRateLimitReasonForAdUnitId:nil]); + XCTAssert([manager lastRateLimitMillisecondsForAdUnitId:nil] == 0.0); +#pragma clang diagnostic pop + + +} + +@end diff --git a/MoPubSDKTests/MPReachabilityTests.m b/MoPubSDKTests/MPReachabilityTests.m index 0d2dd0e64..33e678575 100644 --- a/MoPubSDKTests/MPReachabilityTests.m +++ b/MoPubSDKTests/MPReachabilityTests.m @@ -1,7 +1,7 @@ // // MPReachabilityTests.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/MPRealTimeTimer+Testing.h b/MoPubSDKTests/MPRealTimeTimer+Testing.h new file mode 100644 index 000000000..a1f77828a --- /dev/null +++ b/MoPubSDKTests/MPRealTimeTimer+Testing.h @@ -0,0 +1,20 @@ +// +// MPRealTimeTimer+Testing.h +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import "MPRealTimeTimer.h" +#import "MPTimer.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface MPRealTimeTimer (Testing) + +@property (strong, nonatomic) MPTimer * timer; + +@end + +NS_ASSUME_NONNULL_END diff --git a/MoPubSDKTests/MPRealTimeTimer+Testing.m b/MoPubSDKTests/MPRealTimeTimer+Testing.m new file mode 100644 index 000000000..b60abc2f2 --- /dev/null +++ b/MoPubSDKTests/MPRealTimeTimer+Testing.m @@ -0,0 +1,15 @@ +// +// MPRealTimeTimer+Testing.m +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import "MPRealTimeTimer+Testing.h" + +@implementation MPRealTimeTimer (Testing) + +@dynamic timer; + +@end diff --git a/MoPubSDKTests/MPRealTimeTimerTests.m b/MoPubSDKTests/MPRealTimeTimerTests.m index 4c8d9c502..885949d15 100644 --- a/MoPubSDKTests/MPRealTimeTimerTests.m +++ b/MoPubSDKTests/MPRealTimeTimerTests.m @@ -1,13 +1,13 @@ // // MPRealTimeTimerTests.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // #import -#import "MPRealTimeTimer.h" +#import "MPRealTimeTimer+Testing.h" static NSTimeInterval const kTestTimeout = 4; static NSTimeInterval const kTestLength = 2; @@ -144,4 +144,32 @@ - (void)testTimerStillFiresAfterBackgroundingWithNoTimeLeftUponForeground { XCTAssertFalse(self.timer.isScheduled); } +- (void)testTimerDoesNotGetResetOnNewWindow { + XCTestExpectation *fireExpectation = [self expectationWithDescription:@"Expect timer to fire"]; + + __block BOOL timerFired = NO; + self.timer = [[MPRealTimeTimer alloc] initWithInterval:kTestLength block:^(MPRealTimeTimer *timer){ + timerFired = YES; + [fireExpectation fulfill]; + }]; + [self.timer scheduleNow]; + MPTimer * backingTimer = self.timer.timer; + XCTAssertTrue(self.timer.isScheduled); + + XCTAssertFalse(timerFired); + + // Open new windows + [[NSNotificationCenter defaultCenter] postNotificationName:UIApplicationWillEnterForegroundNotification object:nil]; + [[NSNotificationCenter defaultCenter] postNotificationName:UIApplicationWillEnterForegroundNotification object:nil]; + + XCTAssertEqual(backingTimer, self.timer.timer); // Intentionally compare references to be sure the backing timer instance did not change + + [self waitForExpectationsWithTimeout:kTestTimeout handler:^(NSError *error) { + XCTAssertNil(error); + }]; + + XCTAssertTrue(timerFired); + XCTAssertFalse(self.timer.isScheduled); +} + @end diff --git a/MoPubSDKTests/MPRewardedVideo+Testing.h b/MoPubSDKTests/MPRewardedVideo+Testing.h index 33a4330f0..161edb24e 100644 --- a/MoPubSDKTests/MPRewardedVideo+Testing.h +++ b/MoPubSDKTests/MPRewardedVideo+Testing.h @@ -1,7 +1,7 @@ // // MPRewardedVideo+Testing.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -20,5 +20,8 @@ + (void)loadRewardedVideoAdWithAdUnitID:(NSString *)adUnitID withTestConfiguration:(MPAdConfiguration *)config; + (MPRewardedVideoAdManager *)adManagerForAdUnitId:(NSString *)adUnitID; ++ (MPRewardedVideoAdManager *)makeAdManagerForAdUnitId:(NSString *)adUnitId; + +- (void)rewardedVideoAdManager:(MPRewardedVideoAdManager *)manager didReceiveImpressionEventWithImpressionData:(MPImpressionData *)impressionData; @end diff --git a/MoPubSDKTests/MPRewardedVideo+Testing.m b/MoPubSDKTests/MPRewardedVideo+Testing.m index 32a80efef..866889260 100644 --- a/MoPubSDKTests/MPRewardedVideo+Testing.m +++ b/MoPubSDKTests/MPRewardedVideo+Testing.m @@ -1,7 +1,7 @@ // // MPRewardedVideo+Testing.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -91,6 +91,14 @@ + (MPRewardedVideoAdManager *)adManagerForAdUnitId:(NSString *)adUnitID { return adManager; } ++ (MPRewardedVideoAdManager *)makeAdManagerForAdUnitId:(NSString *)adUnitId { + MPRewardedVideoAdManager * manager = [[MPRewardedVideoAdManager alloc] initWithAdUnitID:adUnitId delegate:MPRewardedVideo.sharedInstance]; + MPRewardedVideo *sharedInstance = [MPRewardedVideo sharedInstance]; + sharedInstance.rewardedVideoAdManagers[adUnitId] = manager; + + return manager; +} + #pragma mark - Swizzles - (void)testing_startRewardedVideoConnectionWithUrl:(NSURL *)url { diff --git a/MoPubSDKTests/MPRewardedVideoAdManager+Testing.h b/MoPubSDKTests/MPRewardedVideoAdManager+Testing.h index c20b2f9da..59d5a2aab 100644 --- a/MoPubSDKTests/MPRewardedVideoAdManager+Testing.h +++ b/MoPubSDKTests/MPRewardedVideoAdManager+Testing.h @@ -1,7 +1,7 @@ // // MPRewardedVideoAdManager+Testing.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/MPRewardedVideoAdManager+Testing.m b/MoPubSDKTests/MPRewardedVideoAdManager+Testing.m index 057ecee0f..47f5407e2 100644 --- a/MoPubSDKTests/MPRewardedVideoAdManager+Testing.m +++ b/MoPubSDKTests/MPRewardedVideoAdManager+Testing.m @@ -1,7 +1,7 @@ // // MPRewardedVideoAdManager+Testing.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/MPRewardedVideoAdManagerTests.m b/MoPubSDKTests/MPRewardedVideoAdManagerTests.m index e69ae3e35..036b26aec 100644 --- a/MoPubSDKTests/MPRewardedVideoAdManagerTests.m +++ b/MoPubSDKTests/MPRewardedVideoAdManagerTests.m @@ -1,7 +1,7 @@ // // MPRewardedVideoAdManagerTests.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -20,6 +20,8 @@ #import "MPMockRewardedVideoCustomEvent.h" #import "MPURL.h" #import "NSURLComponents+Testing.h" +#import "MPRewardedVideo+Testing.h" +#import "MPImpressionTrackedNotification.h" static NSString * const kTestAdUnitId = @"967f82c7-c059-4ae8-8cb6-41c34265b1ef"; static const NSTimeInterval kTestTimeout = 2; // seconds @@ -37,7 +39,7 @@ - (void)testRewardedSingleCurrencyPresentationSuccess { NSDictionary * headers = @{ kRewardedVideoCurrencyNameMetadataKey: @"Diamonds", kRewardedVideoCurrencyAmountMetadataKey: @"3", }; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil adType:MPAdTypeFullscreen]; // Semaphore to wait for asynchronous method to finish before continuing the test. XCTestExpectation * expectation = [self expectationWithDescription:@"Wait for reward completion block to fire."]; @@ -70,7 +72,7 @@ - (void)testRewardedSingleItemInMultiCurrencyPresentationSuccess { // ] // } NSDictionary * headers = @{ kRewardedCurrenciesMetadataKey: @{ @"rewards": @[ @{ @"name": @"Coins", @"amount": @(8) } ] } }; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil adType:MPAdTypeFullscreen]; // Semaphore to wait for asynchronous method to finish before continuing the test. XCTestExpectation * expectation = [self expectationWithDescription:@"Wait for reward completion block to fire."]; @@ -105,7 +107,7 @@ - (void)testRewardedMultiCurrencyPresentationSuccess { // ] // } NSDictionary * headers = @{ kRewardedCurrenciesMetadataKey: @{ @"rewards": @[ @{ @"name": @"Coins", @"amount": @(8) }, @{ @"name": @"Diamonds", @"amount": @(1) }, @{ @"name": @"Energy", @"amount": @(20) } ] } }; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil adType:MPAdTypeFullscreen]; // Semaphore to wait for asynchronous method to finish before continuing the test. XCTestExpectation * expectation = [self expectationWithDescription:@"Wait for reward completion block to fire."]; @@ -140,7 +142,7 @@ - (void)testRewardedMultiCurrencyPresentationAutoSelectionFailure { // ] // } NSDictionary * headers = @{ kRewardedCurrenciesMetadataKey: @{ @"rewards": @[ @{ @"name": @"Coins", @"amount": @(8) }, @{ @"name": @"Diamonds", @"amount": @(1) }, @{ @"name": @"Energy", @"amount": @(20) } ] } }; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil adType:MPAdTypeFullscreen]; // Semaphore to wait for asynchronous method to finish before continuing the test. XCTestExpectation * expectation = [self expectationWithDescription:@"Wait for reward completion block to fire."]; @@ -182,7 +184,7 @@ - (void)testRewardedMultiCurrencyPresentationNilParameterAutoSelectionFailure { // ] // } NSDictionary * headers = @{ kRewardedCurrenciesMetadataKey: @{ @"rewards": @[ @{ @"name": @"Coins", @"amount": @(8) }, @{ @"name": @"Diamonds", @"amount": @(1) }, @{ @"name": @"Energy", @"amount": @(20) } ] } }; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil adType:MPAdTypeFullscreen]; // Semaphore to wait for asynchronous method to finish before continuing the test. XCTestExpectation * expectation = [self expectationWithDescription:@"Wait for reward completion block to fire."]; @@ -224,7 +226,7 @@ - (void)testRewardedMultiCurrencyPresentationUnknownSelectionFail { // ] // } NSDictionary * headers = @{ kRewardedCurrenciesMetadataKey: @{ @"rewards": @[ @{ @"name": @"Coins", @"amount": @(8) }, @{ @"name": @"Diamonds", @"amount": @(1) }, @{ @"name": @"Energy", @"amount": @(20) } ] } }; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil adType:MPAdTypeFullscreen]; // Semaphore to wait for asynchronous method to finish before continuing the test. XCTestExpectation * expectation = [self expectationWithDescription:@"Wait for reward completion block to fire."]; @@ -528,7 +530,7 @@ - (void)testLocalExtrasInCustomEvent { communicator.mockConfigurationsResponse = configurations; manager.communicator = communicator; - MPAdTargeting * targeting = [[MPAdTargeting alloc] init]; + MPAdTargeting * targeting = [[MPAdTargeting alloc] initWithCreativeSafeSize:CGSizeZero]; targeting.localExtras = @{ @"testing": @"YES" }; [manager loadRewardedVideoAdWithCustomerId:@"CUSTOMER_ID" targeting:targeting]; @@ -548,4 +550,126 @@ - (void)testLocalExtrasInCustomEvent { XCTAssertTrue(customEvent.isLocalExtrasAvailableAtRequest); } +#pragma mark - Impression Level Revenue Data + +- (void)testImpressionDelegateFiresWithoutILRD { + XCTestExpectation * delegateExpectation = [self expectationWithDescription:@"Wait for impression delegate"]; + XCTestExpectation * notificationExpectation = [self expectationWithDescription:@"Wait for impression notification"]; + + __block MPRewardedVideoAdManager * manager = nil; + + // Make delegate handler + MPRewardedVideoDelegateHandler * handler = [MPRewardedVideoDelegateHandler new]; + handler.didReceiveImpression = ^(MPImpressionData * impressionData) { + [delegateExpectation fulfill]; + + XCTAssertNil(impressionData); + }; + + // Make notification handler + id notificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMPImpressionTrackedNotification + object:nil + queue:[NSOperationQueue mainQueue] + usingBlock:^(NSNotification * note){ + [notificationExpectation fulfill]; + + MPImpressionData * impressionData = note.userInfo[kMPImpressionTrackedInfoImpressionDataKey]; + XCTAssertNil(impressionData); + XCTAssertNil(note.object); + XCTAssert([note.userInfo[kMPImpressionTrackedInfoAdUnitIDKey] isEqualToString:manager.adUnitID]); + }]; + + // Generate the ad configurations + MPAdConfiguration * rewardedVideoThatShouldLoad = [MPAdConfigurationFactory defaultRewardedVideoConfigurationWithCustomEventClassName:@"MPMockRewardedVideoCustomEvent"]; + NSArray * configurations = @[rewardedVideoThatShouldLoad]; + + manager = [[MPRewardedVideoAdManager alloc] initWithAdUnitID:kTestAdUnitId delegate:handler]; + MPMockAdServerCommunicator * communicator = [[MPMockAdServerCommunicator alloc] initWithDelegate:manager]; + communicator.mockConfigurationsResponse = configurations; + manager.communicator = communicator; + + handler.didLoadAd = ^{ + // Track impression + MPRewardedVideoAdapter * adapter = (MPRewardedVideoAdapter *)manager.adapter; + [adapter trackImpression]; + + // Simulate impression to @c MPRewardedVideo proper + [[MPRewardedVideo sharedInstance] rewardedVideoAdManager:manager didReceiveImpressionEventWithImpressionData:nil]; + }; + + MPAdTargeting * targeting = [[MPAdTargeting alloc] initWithCreativeSafeSize:CGSizeZero]; + [manager loadRewardedVideoAdWithCustomerId:@"CUSTOMER_ID" targeting:targeting]; + + [self waitForExpectationsWithTimeout:kTestTimeout handler:^(NSError * _Nullable error) { + if (error != nil) { + XCTFail(@"Timed out"); + } + }]; + + [[NSNotificationCenter defaultCenter] removeObserver:notificationObserver]; +} + +- (void)testImpressionDelegateFiresWithILRD { + XCTestExpectation * delegateExpectation = [self expectationWithDescription:@"Wait for impression delegate"]; + XCTestExpectation * notificationExpectation = [self expectationWithDescription:@"Wait for impression notification"]; + NSString * testAdUnitID = @"TEST_ADUNIT_ID"; + + __block MPRewardedVideoAdManager * manager = nil; + + // Make delegate handler + MPRewardedVideoDelegateHandler * handler = [MPRewardedVideoDelegateHandler new]; + handler.didReceiveImpression = ^(MPImpressionData * impressionData) { + [delegateExpectation fulfill]; + + XCTAssertNotNil(impressionData); + XCTAssert([impressionData.adUnitID isEqualToString:testAdUnitID]); + }; + + // Make notification handler + id notificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMPImpressionTrackedNotification + object:nil + queue:[NSOperationQueue mainQueue] + usingBlock:^(NSNotification * note){ + [notificationExpectation fulfill]; + + MPImpressionData * impressionData = note.userInfo[kMPImpressionTrackedInfoImpressionDataKey]; + XCTAssertNotNil(impressionData); + XCTAssert([impressionData.adUnitID isEqualToString:testAdUnitID]); + XCTAssertNil(note.object); + XCTAssert([note.userInfo[kMPImpressionTrackedInfoAdUnitIDKey] isEqualToString:manager.adUnitID]); + }]; + + // Generate the ad configurations + MPAdConfiguration * rewardedVideoThatShouldLoad = [MPAdConfigurationFactory defaultRewardedVideoConfigurationWithCustomEventClassName:@"MPMockRewardedVideoCustomEvent"]; + rewardedVideoThatShouldLoad.impressionData = [[MPImpressionData alloc] initWithDictionary:@{ + kImpressionDataAdUnitIDKey: testAdUnitID + }]; + NSArray * configurations = @[rewardedVideoThatShouldLoad]; + + manager = [[MPRewardedVideoAdManager alloc] initWithAdUnitID:kTestAdUnitId delegate:handler]; + MPMockAdServerCommunicator * communicator = [[MPMockAdServerCommunicator alloc] initWithDelegate:manager]; + communicator.mockConfigurationsResponse = configurations; + manager.communicator = communicator; + + handler.didLoadAd = ^{ + // Track impression + MPRewardedVideoAdapter * adapter = (MPRewardedVideoAdapter *)manager.adapter; + [adapter trackImpression]; + + // Simulate impression to @c MPRewardedVideo proper + [[MPRewardedVideo sharedInstance] rewardedVideoAdManager:manager didReceiveImpressionEventWithImpressionData:rewardedVideoThatShouldLoad.impressionData]; + }; + + MPAdTargeting * targeting = [[MPAdTargeting alloc] initWithCreativeSafeSize:CGSizeZero]; + [manager loadRewardedVideoAdWithCustomerId:@"CUSTOMER_ID" targeting:targeting]; + + [self waitForExpectationsWithTimeout:kTestTimeout handler:^(NSError * _Nullable error) { + if (error != nil) { + XCTFail(@"Timed out"); + } + }]; + + [[NSNotificationCenter defaultCenter] removeObserver:notificationObserver]; +} + @end diff --git a/MoPubSDKTests/MPRewardedVideoAdapter+Testing.h b/MoPubSDKTests/MPRewardedVideoAdapter+Testing.h index 8fcf1dff4..8a91c5062 100644 --- a/MoPubSDKTests/MPRewardedVideoAdapter+Testing.h +++ b/MoPubSDKTests/MPRewardedVideoAdapter+Testing.h @@ -1,7 +1,7 @@ // // MPRewardedVideoAdapter+Testing.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/MPRewardedVideoAdapter+Testing.m b/MoPubSDKTests/MPRewardedVideoAdapter+Testing.m index c0076cace..710ca9bbf 100644 --- a/MoPubSDKTests/MPRewardedVideoAdapter+Testing.m +++ b/MoPubSDKTests/MPRewardedVideoAdapter+Testing.m @@ -1,7 +1,7 @@ // // MPRewardedVideoAdapter+Testing.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/MPRewardedVideoAdapterDelegateHandler.h b/MoPubSDKTests/MPRewardedVideoAdapterDelegateHandler.h index f6d83aaef..58fbaa62e 100644 --- a/MoPubSDKTests/MPRewardedVideoAdapterDelegateHandler.h +++ b/MoPubSDKTests/MPRewardedVideoAdapterDelegateHandler.h @@ -1,7 +1,7 @@ // // MPRewardedVideoAdapterDelegateHandler.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -26,6 +26,7 @@ typedef id (^MPRewardedVideoAdapterDelegateHandlerI @property (nonatomic, copy) MPRewardedVideoAdapterDelegateHandlerBlock rewardedVideoWillDisappear; @property (nonatomic, copy) MPRewardedVideoAdapterDelegateHandlerBlock rewardedVideoDidDisappear; @property (nonatomic, copy) MPRewardedVideoAdapterDelegateHandlerBlock rewardedVideoDidReceiveTapEvent; +@property (nonatomic, copy) MPRewardedVideoAdapterDelegateHandlerBlock rewardedVideoDidReceiveImpressionEvent; @property (nonatomic, copy) MPRewardedVideoAdapterDelegateHandlerBlock rewardedVideoWillLeaveApplication; @property (nonatomic, copy) MPRewardedVideoAdapterDelegateHandlerRewardBlock rewardedVideoShouldRewardUser; diff --git a/MoPubSDKTests/MPRewardedVideoAdapterDelegateHandler.m b/MoPubSDKTests/MPRewardedVideoAdapterDelegateHandler.m index edc1e4d43..598120f65 100644 --- a/MoPubSDKTests/MPRewardedVideoAdapterDelegateHandler.m +++ b/MoPubSDKTests/MPRewardedVideoAdapterDelegateHandler.m @@ -1,7 +1,7 @@ // // MPRewardedVideoAdapterDelegateHandler.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -28,5 +28,6 @@ - (void)rewardedVideoDidDisappearForAdapter:(MPRewardedVideoAdapter *)adapter { - (void)rewardedVideoDidReceiveTapEventForAdapter:(MPRewardedVideoAdapter *)adapter { if (self.rewardedVideoDidReceiveTapEvent) self.rewardedVideoDidReceiveTapEvent(adapter); } - (void)rewardedVideoWillLeaveApplicationForAdapter:(MPRewardedVideoAdapter *)adapter { if (self.rewardedVideoWillLeaveApplication) self.rewardedVideoWillLeaveApplication(adapter); } - (void)rewardedVideoShouldRewardUserForAdapter:(MPRewardedVideoAdapter *)adapter reward:(MPRewardedVideoReward *)reward { if (self.rewardedVideoShouldRewardUser) self.rewardedVideoShouldRewardUser(adapter, reward); } +- (void)rewardedVideoDidReceiveImpressionEventForAdapter:(MPRewardedVideoAdapter *)adapter { if (self.rewardedVideoDidReceiveImpressionEvent) self.rewardedVideoDidReceiveImpressionEvent(adapter); } @end diff --git a/MoPubSDKTests/MPRewardedVideoAdapterTests.m b/MoPubSDKTests/MPRewardedVideoAdapterTests.m index 5e8acb778..45b1458d4 100644 --- a/MoPubSDKTests/MPRewardedVideoAdapterTests.m +++ b/MoPubSDKTests/MPRewardedVideoAdapterTests.m @@ -1,7 +1,7 @@ // // MPRewardedVideoAdapterTests.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/MPRewardedVideoDelegateHandler.h b/MoPubSDKTests/MPRewardedVideoDelegateHandler.h index 3c6cd2218..3b56c9174 100644 --- a/MoPubSDKTests/MPRewardedVideoDelegateHandler.h +++ b/MoPubSDKTests/MPRewardedVideoDelegateHandler.h @@ -1,7 +1,7 @@ // // MPRewardedVideoDelegateHandler.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -9,6 +9,7 @@ #import #import "MPRewardedVideoAdManager.h" #import "MPRewardedVideo.h" +#import "MPImpressionData.h" /** * Delegate capturing object used to handle the following protocols: @@ -25,6 +26,7 @@ @property (nonatomic, copy) void(^willDisappear)(void); @property (nonatomic, copy) void(^didDisappear)(void); @property (nonatomic, copy) void(^didReceiveTap)(void); +@property (nonatomic, copy) void(^didReceiveImpression)(MPImpressionData *); @property (nonatomic, copy) void(^willLeaveApp)(void); @property (nonatomic, copy) void(^shouldRewardUser)(MPRewardedVideoReward *); diff --git a/MoPubSDKTests/MPRewardedVideoDelegateHandler.m b/MoPubSDKTests/MPRewardedVideoDelegateHandler.m index b5fde001c..2543e1439 100644 --- a/MoPubSDKTests/MPRewardedVideoDelegateHandler.m +++ b/MoPubSDKTests/MPRewardedVideoDelegateHandler.m @@ -1,7 +1,7 @@ // // MPRewardedVideoDelegateHandler.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -20,6 +20,7 @@ - (void)resetHandlers { self.willDisappear = nil; self.didDisappear = nil; self.didReceiveTap = nil; + self.didReceiveImpression = nil; self.willLeaveApp = nil; self.shouldRewardUser = nil; } @@ -70,6 +71,10 @@ - (void)rewardedVideoShouldRewardUserForAdManager:(MPRewardedVideoAdManager *)ma if (self.shouldRewardUser != nil) { self.shouldRewardUser(reward); } } +- (void)rewardedVideoAdManager:(MPRewardedVideoAdManager *)manager didReceiveImpressionEventWithImpressionData:(MPImpressionData *)impressionData { + if (self.didReceiveImpression != nil) { self.didReceiveImpression(impressionData); } +} + #pragma mark - MPRewardedVideoDelegate - (void)rewardedVideoAdDidLoadForAdUnitID:(NSString *)adUnitID { @@ -108,6 +113,10 @@ - (void)rewardedVideoAdDidReceiveTapEventForAdUnitID:(NSString *)adUnitID { if (self.didReceiveTap != nil) { self.didReceiveTap(); } } +- (void)didTrackImpressionWithAdUnitID:(NSString *)adUnitID impressionData:(MPImpressionData *)impressionData { + if (self.didReceiveImpression != nil) { self.didReceiveImpression(impressionData); } +} + - (void)rewardedVideoAdWillLeaveApplicationForAdUnitID:(NSString *)adUnitID { if (self.willLeaveApp != nil) { self.willLeaveApp(); } } diff --git a/MoPubSDKTests/MPRewardedVideoRewardTests.m b/MoPubSDKTests/MPRewardedVideoRewardTests.m index 68449ebfb..681f69309 100644 --- a/MoPubSDKTests/MPRewardedVideoRewardTests.m +++ b/MoPubSDKTests/MPRewardedVideoRewardTests.m @@ -1,7 +1,7 @@ // // MPRewardedVideoRewardTests.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/MPRewardedVideoTests.m b/MoPubSDKTests/MPRewardedVideoTests.m index 2f7b35b3c..ab228169e 100644 --- a/MoPubSDKTests/MPRewardedVideoTests.m +++ b/MoPubSDKTests/MPRewardedVideoTests.m @@ -1,7 +1,7 @@ // // MPRewardedVideoTests.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -10,6 +10,7 @@ #import "MPAdConfiguration.h" #import "MPAdServerKeys.h" #import "MoPub.h" +#import "MPMockAdServerCommunicator.h" #import "MPRewardedVideo.h" #import "MPRewardedVideo+Testing.h" #import "MPRewardedVideoAdapter+Testing.h" @@ -32,9 +33,8 @@ - (void)setUp { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ MPMoPubConfiguration * config = [[MPMoPubConfiguration alloc] initWithAdUnitIdForAppInitialization:kTestAdUnitId]; - config.advancedBidders = nil; + config.additionalNetworks = nil; config.globalMediationSettings = nil; - config.mediatedNetworks = nil; [MoPub.sharedInstance initializeSdkWithConfiguration:config completion:nil]; }); } @@ -143,7 +143,7 @@ - (void)testRewardedSingleCurrencyPresentationSuccess { NSDictionary * headers = @{ kRewardedVideoCurrencyNameMetadataKey: @"Diamonds", kRewardedVideoCurrencyAmountMetadataKey: @"3", }; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil adType:MPAdTypeFullscreen]; // Semaphore to wait for asynchronous method to finish before continuing the test. XCTestExpectation * expectation = [self expectationWithDescription:@"Wait for reward completion block to fire."]; @@ -184,7 +184,7 @@ - (void)testRewardedSingleItemInMultiCurrencyPresentationSuccess { // ] // } NSDictionary * headers = @{ kRewardedCurrenciesMetadataKey: @{ @"rewards": @[ @{ @"name": @"Coins", @"amount": @(8) } ] } }; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil adType:MPAdTypeFullscreen]; // Semaphore to wait for asynchronous method to finish before continuing the test. XCTestExpectation * expectation = [self expectationWithDescription:@"Wait for reward completion block to fire."]; @@ -227,7 +227,7 @@ - (void)testRewardedSingleItemInMultiCurrencyPresentationS2SSuccess { NSDictionary * headers = @{ kRewardedVideoCompletionUrlMetadataKey: @"https://test.com?verifier=123", kRewardedCurrenciesMetadataKey: @{ @"rewards": @[ @{ @"name": @"Coins", @"amount": @(8) } ] } }; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil adType:MPAdTypeFullscreen]; // Semaphore to wait for asynchronous method to finish before continuing the test. XCTestExpectation * expectation = [self expectationWithDescription:@"Wait for reward completion block to fire."]; @@ -282,7 +282,7 @@ - (void)testRewardedMultiCurrencyPresentationSuccess { // ] // } NSDictionary * headers = @{ kRewardedCurrenciesMetadataKey: @{ @"rewards": @[ @{ @"name": @"Coins", @"amount": @(8) }, @{ @"name": @"Diamonds", @"amount": @(1) }, @{ @"name": @"Energy", @"amount": @(20) } ] } }; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil adType:MPAdTypeFullscreen]; // Semaphore to wait for asynchronous method to finish before continuing the test. XCTestExpectation * expectation = [self expectationWithDescription:@"Wait for reward completion block to fire."]; @@ -321,7 +321,7 @@ - (void)testRewardedMultiCurrencyPresentationNilParameterAutoSelectionFailure { // ] // } NSDictionary * headers = @{ kRewardedCurrenciesMetadataKey: @{ @"rewards": @[ @{ @"name": @"Coins", @"amount": @(8) }, @{ @"name": @"Diamonds", @"amount": @(1) }, @{ @"name": @"Energy", @"amount": @(20) } ] } }; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil adType:MPAdTypeFullscreen]; // Semaphore to wait for asynchronous method to finish before continuing the test. XCTestExpectation * expectation = [self expectationWithDescription:@"Wait for reward completion block to fire."]; @@ -366,7 +366,7 @@ - (void)testRewardedMultiCurrencyPresentationUnknownSelectionFail { // ] // } NSDictionary * headers = @{ kRewardedCurrenciesMetadataKey: @{ @"rewards": @[ @{ @"name": @"Coins", @"amount": @(8) }, @{ @"name": @"Diamonds", @"amount": @(1) }, @{ @"name": @"Energy", @"amount": @(20) } ] } }; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil adType:MPAdTypeFullscreen]; // Semaphore to wait for asynchronous method to finish before continuing the test. XCTestExpectation * expectation = [self expectationWithDescription:@"Wait for reward completion block to fire."]; @@ -416,7 +416,7 @@ - (void)testRewardedMultiCurrencyS2SPresentationSuccess { NSDictionary * headers = @{ kRewardedVideoCompletionUrlMetadataKey: @"https://test.com?verifier=123", kRewardedCurrenciesMetadataKey: @{ @"rewards": @[ @{ @"name": @"Coins", @"amount": @(8) }, @{ @"name": @"Diamonds", @"amount": @(1) }, @{ @"name": @"Energy", @"amount": @(20) } ] } }; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil adType:MPAdTypeFullscreen]; // Semaphore to wait for asynchronous method to finish before continuing the test. XCTestExpectation * expectation = [self expectationWithDescription:@"Wait for reward completion block to fire."]; @@ -459,7 +459,7 @@ - (void)testRewardedMultiCurrencyS2SPresentationSuccess { - (void)testRewardedS2SNoRewardSpecified { NSDictionary * headers = @{ kRewardedVideoCompletionUrlMetadataKey: @"https://test.com?verifier=123", }; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil adType:MPAdTypeFullscreen]; // Semaphore to wait for asynchronous method to finish before continuing the test. XCTestExpectation * expectation = [self expectationWithDescription:@"Wait for reward completion block to fire."]; @@ -509,7 +509,7 @@ - (void)testCustomDataNormalDataLength { kRewardedVideoCurrencyAmountMetadataKey: @"3", kRewardedVideoCompletionUrlMetadataKey: @"https://test.com?verifier=123", }; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil adType:MPAdTypeFullscreen]; // Semaphore to wait for asynchronous method to finish before continuing the test. XCTestExpectation * expectation = [self expectationWithDescription:@"Wait for reward completion block to fire."]; @@ -546,7 +546,7 @@ - (void)testCustomDataExcessiveDataLength { kRewardedVideoCurrencyAmountMetadataKey: @"3", kRewardedVideoCompletionUrlMetadataKey: @"https://test.com?verifier=123", }; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil adType:MPAdTypeFullscreen]; // Semaphore to wait for asynchronous method to finish before continuing the test. XCTestExpectation * expectation = [self expectationWithDescription:@"Wait for reward completion block to fire."]; @@ -582,7 +582,7 @@ - (void)testCustomDataNil { kRewardedVideoCurrencyAmountMetadataKey: @"3", kRewardedVideoCompletionUrlMetadataKey: @"https://test.com?verifier=123", }; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil adType:MPAdTypeFullscreen]; // Semaphore to wait for asynchronous method to finish before continuing the test. XCTestExpectation * expectation = [self expectationWithDescription:@"Wait for reward completion block to fire."]; @@ -613,7 +613,7 @@ - (void)testCustomDataEmpty { kRewardedVideoCurrencyAmountMetadataKey: @"3", kRewardedVideoCompletionUrlMetadataKey: @"https://test.com?verifier=123", }; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil adType:MPAdTypeFullscreen]; // Semaphore to wait for asynchronous method to finish before continuing the test. XCTestExpectation * expectation = [self expectationWithDescription:@"Wait for reward completion block to fire."]; @@ -647,7 +647,7 @@ - (void)testCustomDataInPOSTData { kRewardedVideoCurrencyAmountMetadataKey: @"3", kRewardedVideoCompletionUrlMetadataKey: @"https://test.com?verifier=123", }; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil adType:MPAdTypeFullscreen]; // Semaphore to wait for asynchronous method to finish before continuing the test. XCTestExpectation * expectation = [self expectationWithDescription:@"Wait for reward completion block to fire."]; @@ -683,7 +683,7 @@ - (void)testCustomDataLocalReward { NSDictionary * headers = @{ kRewardedVideoCurrencyNameMetadataKey: @"Diamonds", kRewardedVideoCurrencyAmountMetadataKey: @"3", }; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil adType:MPAdTypeFullscreen]; // Semaphore to wait for asynchronous method to finish before continuing the test. XCTestExpectation * expectation = [self expectationWithDescription:@"Wait for reward completion block to fire."]; @@ -730,7 +730,7 @@ - (void)testNetworkIdentifierInRewardCallback { kRewardedVideoCurrencyAmountMetadataKey: @"3", kRewardedVideoCompletionUrlMetadataKey: @"https://test.com?verifier=123", }; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil adType:MPAdTypeFullscreen]; // Semaphore to wait for asynchronous method to finish before continuing the test. XCTestExpectation * expectation = [self expectationWithDescription:@"Wait for reward completion block to fire."]; @@ -766,7 +766,7 @@ - (void)testMoPubNetworkIdentifierInRewardCallback { kRewardedVideoCurrencyAmountMetadataKey: @"3", kRewardedVideoCompletionUrlMetadataKey: @"https://test.com?verifier=123", }; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil adType:MPAdTypeFullscreen]; // Semaphore to wait for asynchronous method to finish before continuing the test. XCTestExpectation * expectation = [self expectationWithDescription:@"Wait for reward completion block to fire."]; @@ -794,4 +794,51 @@ - (void)testMoPubNetworkIdentifierInRewardCallback { XCTAssert([[s2sMoPubUrl stringForPOSTDataKey:kRewardedCustomEventNameKey] isEqualToString:@"MPMoPubRewardedVideoCustomEvent"]); } +#pragma mark - Ad Sizing + +- (void)testRewardedCreativeSizeSent { + // Semaphore to wait for asynchronous method to finish before continuing the test. + XCTestExpectation * expectation = [self expectationWithDescription:@"Wait for reward completion block to fire."]; + + // Configure delegate handler to listen for the reward event. + MPRewardedVideoDelegateHandler * delegateHandler = [MPRewardedVideoDelegateHandler new]; + delegateHandler.didFailToLoadAd = ^{ + // Expecting load failure due to no configuration response. + // This doesn't matter since we are just verifying the URL that + // is being sent to the Ad Server communicator. + [expectation fulfill]; + }; + + NSString * adUnitId = [NSString stringWithFormat:@"%@:%s", kTestAdUnitId, __FUNCTION__]; + + MPMockAdServerCommunicator * mockAdServerCommunicator = nil; + MPRewardedVideoAdManager * manager = [MPRewardedVideo makeAdManagerForAdUnitId:adUnitId]; + manager.communicator = ({ + MPMockAdServerCommunicator * mock = [[MPMockAdServerCommunicator alloc] initWithDelegate:manager]; + mockAdServerCommunicator = mock; + mock; + }); + [MPRewardedVideo setDelegate:delegateHandler forAdUnitId:adUnitId]; + [MPRewardedVideo loadRewardedVideoAdWithAdUnitID:adUnitId withMediationSettings:nil]; + + [self waitForExpectationsWithTimeout:kTestTimeout handler:^(NSError * _Nullable error) { + XCTAssertNil(error); + }]; + + [MPRewardedVideo removeDelegateForAdUnitId:adUnitId]; + + XCTAssertNotNil(mockAdServerCommunicator); + XCTAssertNotNil(mockAdServerCommunicator.lastUrlLoaded); + + MPURL * url = [mockAdServerCommunicator.lastUrlLoaded isKindOfClass:[MPURL class]] ? (MPURL *)mockAdServerCommunicator.lastUrlLoaded : nil; + XCTAssertNotNil(url); + + NSNumber * sc = [url numberForPOSTDataKey:kScaleFactorKey]; + NSNumber * cw = [url numberForPOSTDataKey:kCreativeSafeWidthKey]; + NSNumber * ch = [url numberForPOSTDataKey:kCreativeSafeHeightKey]; + CGRect frame = MPApplicationFrame(YES); + XCTAssert(cw.floatValue == frame.size.width * sc.floatValue); + XCTAssert(ch.floatValue == frame.size.height * sc.floatValue); +} + @end diff --git a/MoPubSDKTests/MPStubAdvancedBidder.h b/MoPubSDKTests/MPStubAdvancedBidder.h deleted file mode 100644 index 5dd3b9565..000000000 --- a/MoPubSDKTests/MPStubAdvancedBidder.h +++ /dev/null @@ -1,15 +0,0 @@ -// -// MPStubAdvancedBidder.h -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import -#import "MPAdvancedBidder.h" - -@interface MPStubAdvancedBidder : NSObject -@property (nonatomic, copy, readonly) NSString * _Nonnull creativeNetworkName; -@property (nonatomic, copy, readonly) NSString * _Nonnull token; -@end diff --git a/MoPubSDKTests/MPStubAdvancedBidder.m b/MoPubSDKTests/MPStubAdvancedBidder.m deleted file mode 100644 index 0d94d33c2..000000000 --- a/MoPubSDKTests/MPStubAdvancedBidder.m +++ /dev/null @@ -1,21 +0,0 @@ -// -// MPStubAdvancedBidder.m -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import "MPStubAdvancedBidder.h" - -@implementation MPStubAdvancedBidder - -- (NSString * _Nonnull)creativeNetworkName { - return @"stub_bidder"; -} - -- (NSString * _Nonnull)token { - return @"eJxFkVuP2yAQhf8Lz9gyGHCItA9gSENjY8uX7LpVhewoVbdK1qnS9KKq/73gbLWPM8z5zszhD7C6e6yaneuGWoM1gqDVbWsq64wCa0BUwlUm0ogkeBWRTK0iKRGNVJIyISjGSm8ABEWVi8LLwfHF9S14g3SmDG3GYp4ijJOEEoYZYX6kave6af0jQjHxdVNVnVbLCkZthH+QiCPKFY0IESwieSojSbGKtFRIM7HCiuVeWDoprNWN2+nBqwbMvxdn9OOw3T8XL/bL9CSvH57K24Tffxt+UzshiYbTa7/f/DyIh4ewsdq5XNRCmsJ0gfMxhQRSmEEOUQIRhohBlH0KhloZ0YXzWt3sTR4uDIS80dq6rTbvth1YM5ZBIOpa9qYIUaI48UOyt2pJ6jCf4/N8uU3xdTxfTsdrPE9fD3E517epXTricvGC3i7rJJ61qb1u5NOYjW92j0Z1W7BOMxoi9QPPVfgAb/w/3sW3FLvg6pmnoy/zqtHuDiRHPiYZ/+y7Stv2fjy+JxJw8zXIK6ULX/1aMcfIa1zBwKcQGDHnMQJ//wGFwpho"; -} - -@end diff --git a/MoPubSDKTests/MPStubCustomEvent.h b/MoPubSDKTests/MPStubCustomEvent.h index 7b26518ad..c78bed3d0 100644 --- a/MoPubSDKTests/MPStubCustomEvent.h +++ b/MoPubSDKTests/MPStubCustomEvent.h @@ -1,7 +1,7 @@ // // MPStubCustomEvent.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/MPStubCustomEvent.m b/MoPubSDKTests/MPStubCustomEvent.m index 5b260cac8..b3d25901f 100644 --- a/MoPubSDKTests/MPStubCustomEvent.m +++ b/MoPubSDKTests/MPStubCustomEvent.m @@ -1,7 +1,7 @@ // // MPStubCustomEvent.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/MPStubMediatedNetwork.h b/MoPubSDKTests/MPStubMediatedNetwork.h deleted file mode 100644 index 559ac15c6..000000000 --- a/MoPubSDKTests/MPStubMediatedNetwork.h +++ /dev/null @@ -1,23 +0,0 @@ -// -// MPStubMediatedNetwork.h -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import -#import "MPMediationSdkInitializable.h" - -@interface MPStubMediatedNetwork : NSObject - -- (void)initializeSdkWithParameters:(NSDictionary * _Nullable)parameters; - -@end - - -@interface MPStubMediatedNetworkTwo : NSObject - -- (void)initializeSdkWithParameters:(NSDictionary * _Nullable)parameters; - -@end diff --git a/MoPubSDKTests/MPStubMediatedNetwork.m b/MoPubSDKTests/MPStubMediatedNetwork.m deleted file mode 100644 index ad4ac557d..000000000 --- a/MoPubSDKTests/MPStubMediatedNetwork.m +++ /dev/null @@ -1,25 +0,0 @@ -// -// MPStubMediatedNetwork.m -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import "MPStubMediatedNetwork.h" - -@implementation MPStubMediatedNetwork - -- (void)initializeSdkWithParameters:(NSDictionary * _Nullable)parameters { - -} - -@end - -@implementation MPStubMediatedNetworkTwo - -- (void)initializeSdkWithParameters:(NSDictionary * _Nullable)parameters { - -} - -@end diff --git a/MoPubSDKTests/MPTimer+Testing.h b/MoPubSDKTests/MPTimer+Testing.h new file mode 100644 index 000000000..3972a93ad --- /dev/null +++ b/MoPubSDKTests/MPTimer+Testing.h @@ -0,0 +1,22 @@ +// +// MPTimer+Testing.h +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import +#import "MPTimer.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface MPTimer (Testing) +/** + * A title string injected to Objective C runtime as associated value. Typically the test name is + * used for timer title and unique identifier. + */ +@property (nonatomic, strong) NSString * associatedTitle; +@end + +NS_ASSUME_NONNULL_END diff --git a/MoPubSDKTests/MPTimer+Testing.m b/MoPubSDKTests/MPTimer+Testing.m new file mode 100644 index 000000000..5581d47df --- /dev/null +++ b/MoPubSDKTests/MPTimer+Testing.m @@ -0,0 +1,24 @@ +// +// MPTimer+Testing.m +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import +#import "MPTimer+Testing.h" + +@implementation MPTimer (Testing) + +@dynamic associatedTitle; + +- (void)setAssociatedTitle:(NSString *)title { + objc_setAssociatedObject(self, @selector(associatedTitle), title, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + +- (NSString *)associatedTitle { + return objc_getAssociatedObject(self, @selector(associatedTitle)); +} + +@end diff --git a/MoPubSDKTests/MPTimerTests.m b/MoPubSDKTests/MPTimerTests.m new file mode 100644 index 000000000..f7a4e5900 --- /dev/null +++ b/MoPubSDKTests/MPTimerTests.m @@ -0,0 +1,328 @@ +// +// MPTimerTests.m +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import +#import "MPTimer+Testing.h" +#import "MPTimer.h" + +static const NSTimeInterval kTimerRepeatIntervalInSeconds = 0.05; + +// `NSTimer` is not totally accurate and it might be slower on build machines, thus we need some +// extra tolerance while waiting for the expections to be fulfilled. +// ADF-4255: 2 is good enough in most cases, but sometimes it still fails... So, try 5 and see. +static const NSTimeInterval kWaitTimeTolerance = 5; + +/** + * This test make use of `MPTimer.associatedTitle` to identifier the timers in each test. + */ +@interface MPTimerTests : XCTestCase + +/** + * Key: (NSString *) name of the test + * Value: (NSNumber *) the number of times the timer is fired in the corresponding test + */ +@property NSMutableDictionary * testNameVsFiringCount; + +/** + * Key: (NSString *) name of the test + * Value: (XCTestExpectation *) the expectation to be fulfill after the timer is fired in the corresponding test + */ +@property NSMutableDictionary * testNameVsExpectation; + +@end + +@implementation MPTimerTests + +// Create the dictionaries as needed +- (void)setUp { + if (self.testNameVsFiringCount == nil) { + self.testNameVsFiringCount = [NSMutableDictionary dictionary]; + } + if (self.testNameVsExpectation == nil) { + self.testNameVsExpectation = [NSMutableDictionary dictionary]; + } +} + +// A helper for reducing code duplication. +- (MPTimer *)generateTestTimerWithTitle:(NSString *)title { + MPTimer * timer = [MPTimer timerWithTimeInterval:kTimerRepeatIntervalInSeconds + target:self + selector:@selector(timerHandler:) + repeats:YES]; + timer.associatedTitle = title; + return timer; +} + +// This is the method called by all the test timers. +- (void)timerHandler:(MPTimer *)timer { + // increment the firing count + NSNumber * timerCount = self.testNameVsFiringCount[timer.associatedTitle]; + self.testNameVsFiringCount[timer.associatedTitle] = [NSNumber numberWithInt:timerCount.intValue + 1]; + + // fulfill corresponding expection, if any + [((XCTestExpectation *)self.testNameVsExpectation[timer.associatedTitle]) fulfill]; + self.testNameVsExpectation[timer.associatedTitle] = nil; +} + +// Test invalidating the timer before firing. +- (void)testInvalidateAfterInstantiation { + NSString * testName = NSStringFromSelector(_cmd); + MPTimer * timer = [self generateTestTimerWithTitle:testName]; + + XCTAssertFalse(timer.isCountdownActive); + XCTAssertTrue(timer.isValid); + [timer invalidate]; + XCTAssertFalse(timer.isCountdownActive); + XCTAssertFalse(timer.isValid); +} + +// Test invalidating the timer after firing. +- (void)testInvalidateAfterStart { + NSString * testName = NSStringFromSelector(_cmd); + MPTimer * timer = [self generateTestTimerWithTitle:testName]; + XCTestExpectation * expectation = [self expectationWithDescription:@"Wait for timer to fire"]; + self.testNameVsExpectation[testName] = expectation; + + XCTAssertFalse(timer.isCountdownActive); + XCTAssertTrue(timer.isValid); + [timer scheduleNow]; + XCTAssertTrue(timer.isCountdownActive); + XCTAssertTrue(timer.isValid); + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(kTimerRepeatIntervalInSeconds * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + XCTAssertTrue(timer.isCountdownActive); + XCTAssertTrue(timer.isValid); + [timer invalidate]; + XCTAssertFalse(timer.isCountdownActive); + XCTAssertFalse(timer.isValid); + }); + [self waitForExpectationsWithTimeout:kTimerRepeatIntervalInSeconds * kWaitTimeTolerance handler:^(NSError * _Nullable error) { + XCTAssertEqual([(NSNumber *)self.testNameVsFiringCount[timer.associatedTitle] intValue], 1); + XCTAssertNil(error); + }]; +} + +// Test invalidating the timer after firing and then pause. +- (void)testInvalidateAfterStartedAndPause { + NSString * testName = NSStringFromSelector(_cmd); + MPTimer * timer = [self generateTestTimerWithTitle:testName]; + XCTestExpectation * expectation = [self expectationWithDescription:@"Wait for timer to fire"]; + self.testNameVsExpectation[testName] = expectation; + + XCTAssertFalse(timer.isCountdownActive); + XCTAssertTrue(timer.isValid); + [timer scheduleNow]; + XCTAssertTrue(timer.isCountdownActive); + XCTAssertTrue(timer.isValid); + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(kTimerRepeatIntervalInSeconds * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + XCTAssertTrue(timer.isCountdownActive); + XCTAssertTrue(timer.isValid); + [timer pause]; + XCTAssertFalse(timer.isCountdownActive); + XCTAssertTrue(timer.isValid); + [timer invalidate]; + XCTAssertFalse(timer.isCountdownActive); + XCTAssertFalse(timer.isValid); + }); + [self waitForExpectationsWithTimeout:kTimerRepeatIntervalInSeconds * kWaitTimeTolerance handler:^(NSError * _Nullable error) { + XCTAssertEqual([(NSNumber *)self.testNameVsFiringCount[timer.associatedTitle] intValue], 1); + XCTAssertNil(error); + }]; +} + +// Test pausing and resuming the timer at different timings (before & after firing & invalidating). +- (void)testPauseAndResume { + NSString * testName = NSStringFromSelector(_cmd); + MPTimer * timer = [self generateTestTimerWithTitle:testName]; + XCTestExpectation * expectation = [self expectationWithDescription:@"Wait for timer to fire"]; + self.testNameVsExpectation[testName] = expectation; + + XCTAssertFalse(timer.isCountdownActive); + XCTAssertTrue(timer.isValid); + [timer pause]; + XCTAssertFalse(timer.isCountdownActive); + XCTAssertTrue(timer.isValid); + [timer scheduleNow]; + XCTAssertTrue(timer.isCountdownActive); + XCTAssertTrue(timer.isValid); + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(kTimerRepeatIntervalInSeconds * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + XCTAssertTrue(timer.isCountdownActive); + XCTAssertTrue(timer.isValid); + [timer pause]; + XCTAssertFalse(timer.isCountdownActive); + XCTAssertTrue(timer.isValid); + [timer resume]; + XCTAssertTrue(timer.isCountdownActive); + XCTAssertTrue(timer.isValid); + [timer invalidate]; + XCTAssertFalse(timer.isCountdownActive); + XCTAssertFalse(timer.isValid); + [timer pause]; + XCTAssertFalse(timer.isCountdownActive); + XCTAssertFalse(timer.isValid); + [timer resume]; + XCTAssertFalse(timer.isCountdownActive); + XCTAssertFalse(timer.isValid); + }); + [self waitForExpectationsWithTimeout:kTimerRepeatIntervalInSeconds * kWaitTimeTolerance handler:^(NSError * _Nullable error) { + XCTAssertEqual([(NSNumber *)self.testNameVsFiringCount[timer.associatedTitle] intValue], 1); + XCTAssertNil(error); + }]; +} + +// Test whether the timer repeats firing as expected. +- (void)testRepeatingTimer { + NSString * testName = NSStringFromSelector(_cmd); + MPTimer * timer = [self generateTestTimerWithTitle:testName]; + XCTestExpectation * expectation = [self expectationWithDescription:@"Wait for timer to fire"]; + int firingCount = 10; + + XCTAssertFalse(timer.isCountdownActive); + XCTAssertTrue(timer.isValid); + [timer scheduleNow]; + XCTAssertTrue(timer.isCountdownActive); + XCTAssertTrue(timer.isValid); + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(kTimerRepeatIntervalInSeconds * firingCount * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [expectation fulfill]; + }); + + [self waitForExpectationsWithTimeout:kTimerRepeatIntervalInSeconds * firingCount * kWaitTimeTolerance handler:^(NSError * _Nullable error) { + XCTAssertEqual([(NSNumber *)self.testNameVsFiringCount[timer.associatedTitle] intValue], firingCount); + XCTAssertNil(error); + }]; + + XCTAssertTrue(timer.isCountdownActive); + XCTAssertTrue(timer.isValid); + [timer invalidate]; + XCTAssertFalse(timer.isCountdownActive); + XCTAssertFalse(timer.isValid); +} + +// Test whether redundant `scheduleNow` calls are safe. +- (void)testRedundantSchedules { + NSString * testName = NSStringFromSelector(_cmd); + MPTimer * timer = [self generateTestTimerWithTitle:testName]; + XCTestExpectation * expectation = [self expectationWithDescription:@"Wait for timer to fire"]; + self.testNameVsExpectation[testName] = expectation; + + XCTAssertFalse(timer.isCountdownActive); + XCTAssertTrue(timer.isValid); + [timer scheduleNow]; + XCTAssertTrue(timer.isCountdownActive); + XCTAssertTrue(timer.isValid); + [timer scheduleNow]; + XCTAssertTrue(timer.isCountdownActive); + XCTAssertTrue(timer.isValid); + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(kTimerRepeatIntervalInSeconds * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + XCTAssertTrue(timer.isCountdownActive); + XCTAssertTrue(timer.isValid); + [timer invalidate]; + XCTAssertFalse(timer.isCountdownActive); + XCTAssertFalse(timer.isValid); + [timer scheduleNow]; + XCTAssertFalse(timer.isCountdownActive); + XCTAssertFalse(timer.isValid); + }); + [self waitForExpectationsWithTimeout:kTimerRepeatIntervalInSeconds * kWaitTimeTolerance handler:^(NSError * _Nullable error) { + XCTAssertEqual([(NSNumber *)self.testNameVsFiringCount[timer.associatedTitle] intValue], 1); + XCTAssertNil(error); + }]; +} + +// Test thread safety of `MPTimer`. `MPTimer` wasn't thread safe in the past, and `scheduleNow` might +// crash if the internal `NSTimer` is set to `nil` by `invalidate` before `scheduleNow` completes. +// With a thread safety update, `MPTimer` should not crash for any call sequence (ADF-4128). +- (void)testMultiThreadSchedulingAndInvalidation { + uint32_t randomNumberUpperBound = 100; + int numberOfTimers = 10000; + + for (int i = 0; i < numberOfTimers; i++) { + NSString * timerTitle = [NSString stringWithFormat:@"%@ [%d]", NSStringFromSelector(_cmd), i]; + MPTimer * timer = [self generateTestTimerWithTitle:timerTitle]; + + dispatch_queue_t randomScheduleQueue; + switch (arc4random_uniform(randomNumberUpperBound) % 5) { + case 0: + randomScheduleQueue = dispatch_get_main_queue(); + break; + case 1: + randomScheduleQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0); + break; + case 2: + randomScheduleQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); + break; + case 3: + randomScheduleQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0); + break; + default: + randomScheduleQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0); + break; + } + + dispatch_queue_t randomInvalidateQueue; + switch (arc4random_uniform(randomNumberUpperBound) % 5) { + case 0: + randomInvalidateQueue = dispatch_get_main_queue(); + break; + case 1: + randomInvalidateQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0); + break; + case 2: + randomInvalidateQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); + break; + case 3: + randomInvalidateQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0); + break; + default: + randomInvalidateQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0); + break; + } + + // call `scheduleNow` and `invalidate` in random order in random queues (threads) + if (arc4random_uniform(randomNumberUpperBound) % 2 == 0) { + // `scheduleNow` and then `invalidate` + dispatch_async(randomScheduleQueue, ^{ + [timer scheduleNow]; + }); + dispatch_async(randomInvalidateQueue, ^{ + [timer invalidate]; + }); + } else { + // `invalidate` and then `scheduleNow` + dispatch_async(randomInvalidateQueue, ^{ + [timer invalidate]; + }); + dispatch_async(randomScheduleQueue, ^{ + [timer scheduleNow]; + }); + } + } + + // The last timer is for fulfilling the test expectation and finishing this test - previous timers + // are randomly invalidated and we cannot rely on them for fulfilling the test expection. + NSString * timerTitle = [NSString stringWithFormat:@"%@ %@", NSStringFromSelector(_cmd), @"ending timer"]; + XCTestExpectation * expectation = [self expectationWithDescription:timerTitle]; + MPTimer * endingTimer = [self generateTestTimerWithTitle:timerTitle]; + self.testNameVsExpectation[timerTitle] = expectation; + dispatch_async(dispatch_get_main_queue(), ^{ + [endingTimer scheduleNow]; + }); + + // The `for` loop might take a while if there are a large number of loops on slow machines, so + // use a long timeout and rely on the `endingTimer` to fulfill the test expectation early. On + // faster machines with 10000 loops, this test case takes about 0.25 second. + [self waitForExpectationsWithTimeout:60 handler:^(NSError * _Nullable error) { + XCTAssertNil(error); + }]; +} + +@end diff --git a/MoPubSDKTests/MPURLRequest+Testing.h b/MoPubSDKTests/MPURLRequest+Testing.h index 6f75b3497..050abd511 100644 --- a/MoPubSDKTests/MPURLRequest+Testing.h +++ b/MoPubSDKTests/MPURLRequest+Testing.h @@ -1,7 +1,7 @@ // // MPURLRequest+Testing.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/MPURLRequest+Testing.m b/MoPubSDKTests/MPURLRequest+Testing.m index 6a901df74..1eea5c6f7 100644 --- a/MoPubSDKTests/MPURLRequest+Testing.m +++ b/MoPubSDKTests/MPURLRequest+Testing.m @@ -1,13 +1,18 @@ // // MPURLRequest+Testing.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // #import "MPURLRequest+Testing.h" +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wincomplete-implementation" + @implementation MPURLRequest (Testing) @end + +#pragma clang diagnostic pop diff --git a/MoPubSDKTests/MPURLRequestTests.m b/MoPubSDKTests/MPURLRequestTests.m index b90406d9d..ae8e5e012 100644 --- a/MoPubSDKTests/MPURLRequestTests.m +++ b/MoPubSDKTests/MPURLRequestTests.m @@ -1,7 +1,7 @@ // // MPURLRequestTests.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -140,52 +140,6 @@ - (void)testDoNotCombineQueryParametersWithPOSTDataForOtherHost { XCTAssert([json[@"query2"] intValue] == 77); } -- (void)testUserAgentCanBeObtainedOnNonMainThread { - // reset user agent so MPURLRequest has to reobtain it - gUserAgent = nil; - - dispatch_queue_t nonMainQueue = dispatch_queue_create("test queue", NULL); - - XCTestExpectation * expectation = [self expectationWithDescription:@"Wait for user agent to fill on background thread"]; - - // This will crash if the user agent isn't obtained via the main thread. - __block NSString * userAgent = nil; - dispatch_async(nonMainQueue, ^{ - userAgent = [MPURLRequest userAgent]; - [expectation fulfill]; - }); - - [self waitForExpectations:@[expectation] timeout:5.0]; - - XCTAssertNotNil(userAgent); -} - -- (void)testUserAgentCanBeObtainedOnMainThread { - // reset user agent so MPURLRequest has to reobtain it - gUserAgent = nil; - - NSString * userAgent = [MPURLRequest userAgent]; - - XCTAssertNotNil(userAgent); -} - -- (void)testUserAgentCanBeObtainedOnMainQueue { - // reset user agent so MPURLRequest has to reobtain it - gUserAgent = nil; - - XCTestExpectation * expectation = [self expectationWithDescription:@"Wait for user agent to fill on background thread"]; - - __block NSString * userAgent = nil; - dispatch_async(dispatch_get_main_queue(), ^{ - userAgent = [MPURLRequest userAgent]; - [expectation fulfill]; - }); - - [self waitForExpectations:@[expectation] timeout:5.0]; - - XCTAssertNotNil(userAgent); -} - - (void)testJSONNotPrettyPrinted { NSDictionary * postData = @{ @"string": @"1" }; NSString * const exptectedJSON = @"{\"string\":\"1\"}"; diff --git a/MoPubSDKTests/MPURLResolverTests.m b/MoPubSDKTests/MPURLResolverTests.m index 812acef86..db040d8f0 100644 --- a/MoPubSDKTests/MPURLResolverTests.m +++ b/MoPubSDKTests/MPURLResolverTests.m @@ -1,7 +1,7 @@ // // MPURLResolverTests.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -14,7 +14,12 @@ @interface MPURLResolver (Testing) -- (BOOL)shouldEnableClickthroughExperiment; +@property (nonatomic, readonly) BOOL shouldOpenWithInAppWebBrowser; + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wincomplete-implementation" +- (NSDictionary *)appStoreProductParametersForURL:(NSURL *)URL; +#pragma clang diagnostic pop @end @@ -30,7 +35,49 @@ - (void)testResolverNonHttpNorHttps { [resolver start]; - XCTAssertFalse([resolver shouldEnableClickthroughExperiment]); + XCTAssertFalse(resolver.shouldOpenWithInAppWebBrowser); +} + +- (void)testResolverSupportedAppleSchemes { + NSArray * supportedAppleStoreSubdomains = @[@"apps", @"books", @"itunes", @"music"]; + + for (NSString *subdomain in supportedAppleStoreSubdomains) { + NSString *testUrlString = [NSString stringWithFormat:@"https://%@.apple.com/id123456789", subdomain]; + NSURL *testUrl = [NSURL URLWithString:testUrlString]; + XCTAssertNotNil(testUrl); + + MPURLResolver *resolver = [MPURLResolver resolverWithURL:testUrl completion:nil]; + [resolver start]; + XCTAssertFalse(resolver.shouldOpenWithInAppWebBrowser); + } +} + +- (void)testResolverRedirectedSupportedAppleSchemes { + NSArray * supportedAppleStoreSubdomains = @[@"apps", @"books", @"itunes", @"music"]; + + for (NSString *subdomain in supportedAppleStoreSubdomains) { + NSString *testUrlString = [NSString stringWithFormat:@"%@&r=https%%3A%%2F%%2F%@.apple.com%%2Fid123456789", kWebviewClickthroughURLBase, subdomain]; + NSURL *testUrl = [NSURL URLWithString:testUrlString]; + XCTAssertNotNil(testUrl); + + MPURLResolver *resolver = [MPURLResolver resolverWithURL:testUrl completion:nil]; + [resolver start]; + XCTAssertFalse(resolver.shouldOpenWithInAppWebBrowser); + } +} + +- (void)testResolverAppleDeeplinkSchemes { + NSArray * appleStoreDeeplinkSchemes = @[@"itms", @"itmss", @"itms-apps"]; + + for (NSString *scheme in appleStoreDeeplinkSchemes) { + NSString *testUrlString = [NSString stringWithFormat:@"%@://itunes.apple.com/app/apple-store/id123456789", scheme]; + NSURL *testUrl = [NSURL URLWithString:testUrlString]; + XCTAssertNotNil(testUrl); + + MPURLResolver *resolver = [MPURLResolver resolverWithURL:testUrl completion:nil]; + [resolver start]; + XCTAssertFalse(resolver.shouldOpenWithInAppWebBrowser); + } } - (void)testHttpRedirectWithNativeSafari { @@ -40,7 +87,7 @@ - (void)testHttpRedirectWithNativeSafari { MPURLResolver *resolver = [MPURLResolver resolverWithURL:url completion:nil]; [resolver start]; - XCTAssertTrue([resolver shouldEnableClickthroughExperiment]); + XCTAssertFalse(resolver.shouldOpenWithInAppWebBrowser); } - (void)testHttpRedirectWithInAppBrowser { @@ -50,7 +97,7 @@ - (void)testHttpRedirectWithInAppBrowser { MPURLResolver *resolver = [MPURLResolver resolverWithURL:url completion:nil]; [resolver start]; - XCTAssertFalse([resolver shouldEnableClickthroughExperiment]); + XCTAssertTrue(resolver.shouldOpenWithInAppWebBrowser); } - (void)testMopubnativebrowserRedirectWithNativeSafari { @@ -61,7 +108,7 @@ - (void)testMopubnativebrowserRedirectWithNativeSafari { MPURLResolver *resolver = [MPURLResolver resolverWithURL:url completion:nil]; [resolver start]; - XCTAssertFalse([resolver shouldEnableClickthroughExperiment]); + XCTAssertFalse(resolver.shouldOpenWithInAppWebBrowser); } - (void)testHttpNonRedirectWithNativeSafari { @@ -70,7 +117,7 @@ - (void)testHttpNonRedirectWithNativeSafari { MPURLResolver *resolver = [MPURLResolver resolverWithURL:url completion:nil]; [resolver start]; - XCTAssertFalse([resolver shouldEnableClickthroughExperiment]); + XCTAssertFalse(resolver.shouldOpenWithInAppWebBrowser); } - (void)testNonWebviewWithNativeSafari { @@ -80,7 +127,7 @@ - (void)testNonWebviewWithNativeSafari { MPURLResolver *resolver = [MPURLResolver resolverWithURL:url completion:nil]; [resolver start]; - XCTAssertTrue([resolver shouldEnableClickthroughExperiment]); + XCTAssertFalse(resolver.shouldOpenWithInAppWebBrowser); } - (void)testNonWebviewWithInappBrowser { @@ -90,7 +137,51 @@ - (void)testNonWebviewWithInappBrowser { MPURLResolver *resolver = [MPURLResolver resolverWithURL:url completion:nil]; [resolver start]; - XCTAssertFalse([resolver shouldEnableClickthroughExperiment]); + XCTAssertTrue(resolver.shouldOpenWithInAppWebBrowser); +} + +- (void)testValidAppleStoreURLParsing { + NSURL *url = [NSURL URLWithString:@"https://itunes.apple.com/us/album/fame-monster-deluxe-version/id902143901?mt=1&at=123456&app=itunes&ct=newsletter1"]; + MPURLResolver *resolver = [MPURLResolver resolverWithURL:url completion:nil]; + NSDictionary *parameters = [resolver appStoreProductParametersForURL:url]; + + XCTAssertNotNil(parameters); + XCTAssert([parameters[SKStoreProductParameterITunesItemIdentifier] isEqualToString:@"902143901"]); + XCTAssert([parameters[SKStoreProductParameterAffiliateToken] isEqualToString:@"123456"]); + XCTAssert([parameters[SKStoreProductParameterCampaignToken] isEqualToString:@"newsletter1"]); +} + +- (void)testValidAppleStoreURLParsingOnlyId { + NSURL *url = [NSURL URLWithString:@"https://itunes.apple.com/us/album/fame-monster-deluxe-version/902143901"]; + MPURLResolver *resolver = [MPURLResolver resolverWithURL:url completion:nil]; + NSDictionary *parameters = [resolver appStoreProductParametersForURL:url]; + + XCTAssertNotNil(parameters); + XCTAssert([parameters[SKStoreProductParameterITunesItemIdentifier] isEqualToString:@"902143901"]); +} + +- (void)testInvalidAppleStoreURLParsingMissingId { + NSURL *url = [NSURL URLWithString:@"https://itunes.apple.com/us/album/fame-monster-deluxe-version/?mt=1&at=123456&app=itunes&ct=newsletter1"]; + MPURLResolver *resolver = [MPURLResolver resolverWithURL:url completion:nil]; + NSDictionary *parameters = [resolver appStoreProductParametersForURL:url]; + + XCTAssertNil(parameters); +} + +- (void)testInvalidAppleStoreURLParsingBadId { + NSURL *url = [NSURL URLWithString:@"https://itunes.apple.com/us/album/fame-monster-deluxe-version/id902143901zzz?mt=1&at=123456&app=itunes&ct=newsletter1"]; + MPURLResolver *resolver = [MPURLResolver resolverWithURL:url completion:nil]; + NSDictionary *parameters = [resolver appStoreProductParametersForURL:url]; + + XCTAssertNil(parameters); +} + +- (void)testInvalidAppleStoreURLParsingNotAppleUrl { + NSURL *url = [NSURL URLWithString:@"https://www.google.com"]; + MPURLResolver *resolver = [MPURLResolver resolverWithURL:url completion:nil]; + NSDictionary *parameters = [resolver appStoreProductParametersForURL:url]; + + XCTAssertNil(parameters); } @end diff --git a/MoPubSDKTests/MPVASTLinearAdTests.m b/MoPubSDKTests/MPVASTLinearAdTests.m index 269a8d7ed..1f74988ba 100644 --- a/MoPubSDKTests/MPVASTLinearAdTests.m +++ b/MoPubSDKTests/MPVASTLinearAdTests.m @@ -1,7 +1,7 @@ // // MPVASTLinearAdTests.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -12,67 +12,19 @@ #import "MPVideoConfig.h" #import "XCTestCase+MPAddition.h" -static const NSTimeInterval kDefaultTimeout = 1; - @interface MPVASTLinearAdTests : XCTestCase - @end @implementation MPVASTLinearAdTests -- (void)setUp { - [super setUp]; - // Put setup code here. This method is called before the invocation of each test method in the class. -} - -- (void)tearDown { - // Put teardown code here. This method is called after the invocation of each test method in the class. - [super tearDown]; -} - -// Test that the highest bitrate supported media file is selected. -- (void)testHighestBitrateSelection { - XCTestExpectation *expectation = [self expectationWithDescription:@"Wait for fetching data from xml."]; - - __block NSData *vastData = [self dataFromXMLFileNamed:@"linear-mime-types" class:[self class]]; - __block MPVASTResponse *vastResponse; - [MPVASTManager fetchVASTWithData:vastData completion:^(MPVASTResponse *response, NSError *error) { - XCTAssertNil(error); - vastResponse = response; - [expectation fulfill]; - }]; - - [self waitForExpectationsWithTimeout:kDefaultTimeout handler:^(NSError * _Nullable error) { - XCTAssertNil(error); - }]; - - // linear-mime-types.xml has 10 media files, with the highest valid bitrate of 1458 and URL of - // https://gcdn.2mdn.net/videoplayback/id/883bff9aabab6c02/itag/344/source/doubleclick_dmm/ratebypass/yes/acao/yes/ip/0.0.0.0/ipbits/0/expire/3622019356/sparams/id,itag,source,ratebypass,acao,ip,ipbits,expire/signature/9464D3C90825A4EA90C4E1C2D4DC82A164CB2F5C.8BC8A2FC95DF6E4724B0FF5D23A7B8DA6AE11493/key/ck2/file/file.mp4 - MPVideoConfig *videoConfig = [[MPVideoConfig alloc] initWithVASTResponse:vastResponse additionalTrackers:nil]; - - XCTAssertTrue([videoConfig.mediaURL.absoluteString isEqualToString:@"https://gcdn.2mdn.net/videoplayback/id/883bff9aabab6c02/itag/344/source/doubleclick_dmm/ratebypass/yes/acao/yes/ip/0.0.0.0/ipbits/0/expire/3622019356/sparams/id,itag,source,ratebypass,acao,ip,ipbits,expire/signature/9464D3C90825A4EA90C4E1C2D4DC82A164CB2F5C.8BC8A2FC95DF6E4724B0FF5D23A7B8DA6AE11493/key/ck2/file/file.mp4"]); -} - - (void)testAllMediaFilesInvalid { - XCTestExpectation *expectation = [self expectationWithDescription:@"Wait for fetching data from xml."]; + MPVASTResponse *vastResponse = [self vastResponseFromXMLFile:@"linear-mime-types-all-invalid"]; - __block NSData *vastData = [self dataFromXMLFileNamed:@"linear-mime-types-all-invalid" class:[self class]]; - __block MPVASTResponse *vastResponse; - [MPVASTManager fetchVASTWithData:vastData completion:^(MPVASTResponse *response, NSError *error) { - XCTAssertNil(error); - vastResponse = response; - [expectation fulfill]; - }]; - - [self waitForExpectationsWithTimeout:kDefaultTimeout handler:^(NSError * _Nullable error) { - XCTAssertNil(error); - }]; - - // linear-mime-types-all-invalid.xml has 2 media files, all invalid + // linear-mime-types-all-invalid.xml has 2 media files, both are invalid since their mime type + // "video/flv" is not officially supported. `mediaFiles` still keeps both objects, but they will + // receive 0 score by the media selection algorithm. MPVideoConfig *videoConfig = [[MPVideoConfig alloc] initWithVASTResponse:vastResponse additionalTrackers:nil]; - - XCTAssertNil(videoConfig.mediaURL); + XCTAssertTrue(videoConfig.mediaFiles.count == 2); } - @end diff --git a/MoPubSDKTests/MPVASTMediaFileTests.m b/MoPubSDKTests/MPVASTMediaFileTests.m new file mode 100644 index 000000000..b4edc1102 --- /dev/null +++ b/MoPubSDKTests/MPVASTMediaFileTests.m @@ -0,0 +1,124 @@ +// +// MPVASTMediaFileTests.m +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import +#import "MPVASTManager.h" +#import "MPVASTMediaFile.h" +#import "MPVideoConfig.h" +#import "XCTestCase+MPAddition.h" + +#pragma mark - MPVASTMediaFile (Testing) + +@interface MPVASTMediaFile (Testing) + +- (CGFloat)formatScore; + +- (CGFloat)fitScoreForContainerSize:(CGSize)containerSize + containerScaleFactor:(CGFloat)containerScaleFactor; + +- (CGFloat)qualityScore; + +- (CGFloat)selectionScoreForContainerSize:(CGSize)containerSize + containerScaleFactor:(CGFloat)containerScaleFactor; + +@end + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wincomplete-implementation" +// Suppress warning of accessing private implementation +@implementation MPVASTMediaFile (Testing) +@end +#pragma clang diagnostic pop + +#pragma mark - MPVASTMediaFileTests + +@interface MPVASTMediaFileTests : XCTestCase +@property (nonatomic, strong) NSArray *sampleMediaFiles; +@end + +@implementation MPVASTMediaFileTests + +- (void)setUp { + if (self.sampleMediaFiles == nil) { + MPVASTResponse *vastResponse = [self vastResponseFromXMLFile:@"linear-mime-types"]; + MPVideoConfig *videoConfig = [[MPVideoConfig alloc] initWithVASTResponse:vastResponse additionalTrackers:nil]; + self.sampleMediaFiles = videoConfig.mediaFiles; + XCTAssertTrue(self.sampleMediaFiles.count == 10); + } +} + +- (void)testFormatScore { + XCTAssertEqual(0, self.sampleMediaFiles[0].formatScore); // "video/flv" + XCTAssertEqual(1, self.sampleMediaFiles[1].formatScore); // "video/3gpp" + XCTAssertEqual(0, self.sampleMediaFiles[2].formatScore); // "video/flv" + XCTAssertEqual(1.5, self.sampleMediaFiles[3].formatScore); // "video/mp4" + XCTAssertEqual(1.5, self.sampleMediaFiles[4].formatScore); // "video/mp4" + XCTAssertEqual(1.5, self.sampleMediaFiles[5].formatScore); // "video/mp4" + XCTAssertEqual(1.5, self.sampleMediaFiles[6].formatScore); // "video/mp4" + XCTAssertEqual(1.5, self.sampleMediaFiles[7].formatScore); // "video/mp4" + XCTAssertEqual(1.5, self.sampleMediaFiles[8].formatScore); // "video/mp4" + XCTAssertEqual(1.5, self.sampleMediaFiles[9].formatScore); // "video/mp4" +} + +- (void)testFitScoreInSmallContainer { + NSArray *testSizes = @[[NSValue valueWithCGSize:CGSizeMake(320, 480)], // small iPhone + [NSValue valueWithCGSize:CGSizeMake(1024, 768)], // small iPad + [NSValue valueWithCGSize:CGSizeMake(1920, 1080)]]; // large iPad + + for (int i = 0; i < testSizes.count; i++) { + CGFloat scaleFactor = (CGFloat)(i + 1); + CGSize size = [testSizes[i] CGSizeValue]; + + for (MPVASTMediaFile *file in self.sampleMediaFiles) { + CGFloat aspectRatioScore = ABS(size.width / size.height - file.width / file.height); + CGFloat widthScore = ABS((scaleFactor * size.width - file.width) + / (scaleFactor * size.width)); + CGFloat score = aspectRatioScore + widthScore; + XCTAssertEqual(score, [file fitScoreForContainerSize:size containerScaleFactor:scaleFactor]); + } + } +} + +- (void)testQualityScore { + const CGFloat lowBitrate = 700; + const CGFloat highBitrate = 1500; + + for (MPVASTMediaFile *file in self.sampleMediaFiles) { + if (lowBitrate <= file.bitrate && file.bitrate <= highBitrate) { + XCTAssertEqual(0, file.qualityScore); + } else { + CGFloat score = MIN(ABS(lowBitrate - file.bitrate) / lowBitrate, + ABS(highBitrate - file.bitrate) / highBitrate); + XCTAssertEqual(score, file.qualityScore); + } + } +} + +/** + Note: To avoid small discrepancy in floating point number comparison that mistakenly fails the test, + the scores in this test are multiplied with 1000 for int type comparison. + */ +- (void)testSelection { + // iPhone 8 config + CGSize size = CGSizeMake(375, 667); + CGFloat scaleFactor = 2; + NSArray *expectedScoresX1000 = @[@0, @284, @0, @550, @633, @496, @634, @634, @535, @666]; + + for (int i = 0; i < self.sampleMediaFiles.count; i++) { + MPVASTMediaFile *file = self.sampleMediaFiles[i]; + int scoreX1000 = expectedScoresX1000[i].intValue; + XCTAssertEqual(scoreX1000, (int)(1000 * [file selectionScoreForContainerSize:size containerScaleFactor:scaleFactor])); + } + + MPVASTMediaFile *bestFile = [MPVASTMediaFile bestMediaFileFromCandidates:self.sampleMediaFiles + forContainerSize:size + containerScaleFactor:scaleFactor]; + XCTAssertEqual(bestFile, self.sampleMediaFiles[9]); // this one has the highest score of 666 +} + +@end diff --git a/MoPubSDKTests/MPVASTModelTests.m b/MoPubSDKTests/MPVASTModelTests.m index c646f6e23..09412ddb0 100644 --- a/MoPubSDKTests/MPVASTModelTests.m +++ b/MoPubSDKTests/MPVASTModelTests.m @@ -1,7 +1,7 @@ // // MPVASTModelTests.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/MPVASTTrackingTests.m b/MoPubSDKTests/MPVASTTrackingTests.m new file mode 100644 index 000000000..2793d6681 --- /dev/null +++ b/MoPubSDKTests/MPVASTTrackingTests.m @@ -0,0 +1,223 @@ +// +// MPVASTTrackingTests.m +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import +#import "MPAnalyticsTracker.h" +#import "MPVASTTracking.h" +#import "XCTestCase+MPAddition.h" + +#pragma mark - MPVASTTracking + +@interface MPVASTTracking (Testing) +@property (nonatomic, strong) id analyticsTracker; +@end + +#pragma mark - MockAnalyticTracker + +@interface MockAnalyticTracker : MPAnalyticsTracker + +@property (nonatomic, strong) NSArray *mostRecentlySentURLs; + +- (void)reset; + +@end + +@implementation MockAnalyticTracker + +- (void)trackImpressionForConfiguration:(MPAdConfiguration *)configuration {} // no op for this test + +- (void)trackClickForConfiguration:(MPAdConfiguration *)configuration {} // no op for this test + +- (void)sendTrackingRequestForURLs:(NSArray *)URLs { + if (self.mostRecentlySentURLs == nil) { + self.mostRecentlySentURLs = URLs; + } else { + self.mostRecentlySentURLs = [self.mostRecentlySentURLs arrayByAddingObjectsFromArray:URLs]; + } +} + +- (void)reset { + self.mostRecentlySentURLs = nil; +} + +@end + +#pragma mark - MPVASTTrackingTests + +@interface MPVASTTrackingTests : XCTestCase +@property (nonatomic, readonly) NSArray *allTrackingEventNames; +@property (nonatomic, readonly) NSSet *oneOffEventTypes; +@property (nonatomic, readonly) NSDictionary *> *testData; +@end + +@implementation MPVASTTrackingTests + +- (void)setUp { + if (self.allTrackingEventNames == nil) { + _allTrackingEventNames = @[MPVideoEventClick, + MPVideoEventCloseLinear, + MPVideoEventCollapse, + MPVideoEventComplete, + MPVideoEventCreativeView, + MPVideoEventError, + MPVideoEventExitFullScreen, + MPVideoEventExpand, + MPVideoEventFirstQuartile, + MPVideoEventFullScreen, + MPVideoEventImpression, + MPVideoEventMidpoint, + MPVideoEventMute, + MPVideoEventPause, + MPVideoEventProgress, + MPVideoEventResume, + MPVideoEventSkip, + MPVideoEventStart, + MPVideoEventThirdQuartile, + MPVideoEventUnmute]; + } + + if (self.testData == nil) { + _testData = @{MPVideoEventClick: @[@"https://www.mopub.com/?q=videoClickTracking"], + MPVideoEventCloseLinear: @[@"https://www.mopub.com/?q=closeLinear"], + MPVideoEventCollapse: @[@"https://www.mopub.com/?q=collapse"], + MPVideoEventComplete: @[@"https://www.mopub.com/?q=complete"], + MPVideoEventCreativeView: @[@"https://www.mopub.com/?q=creativeView"], + MPVideoEventError: @[@"https://www.mopub.com/?q=error&errorcode=%5BERRORCODE%5D"], + MPVideoEventExitFullScreen: @[@"https://www.mopub.com/?q=exitFullscreen"], + MPVideoEventExpand: @[@"https://www.mopub.com/?q=expand"], + MPVideoEventFirstQuartile: @[@"https://www.mopub.com/?q=firstQuartile"], + MPVideoEventFullScreen: @[@"https://www.mopub.com/?q=fullscreen"], + MPVideoEventImpression: @[@"https://www.mopub.com/?q=impression", + @"https://www.mopub.com/?q=impression1", + @"https://www.mopub.com/?q=impression2", + @"https://www.mopub.com/?q=impression3"], + MPVideoEventMidpoint: @[@"https://www.mopub.com/?q=midpoint"], + MPVideoEventMute: @[@"https://www.mopub.com/?q=mute"], + MPVideoEventPause: @[@"https://www.mopub.com/?q=pause"], + MPVideoEventProgress: @[@"https://www.mopub.com/?q=progress00", + @"https://www.mopub.com/?q=progress05", + @"https://www.mopub.com/?q=progress10", + @"https://www.mopub.com/?q=progress15", + @"https://www.mopub.com/?q=progress20", + @"https://www.mopub.com/?q=progress25", + @"https://www.mopub.com/?q=progress30"], + MPVideoEventResume: @[@"https://www.mopub.com/?q=resume"], + MPVideoEventSkip: @[@"https://www.mopub.com/?q=skip"], + MPVideoEventStart: @[@"https://www.mopub.com/?q=start", + @"https://www.mopub.com/?q=start1"], + MPVideoEventThirdQuartile: @[@"https://www.mopub.com/?q=thirdQuartile"], + MPVideoEventUnmute: @[@"https://www.mopub.com/?q=unmute"]}; + } + + if (self.oneOffEventTypes == nil) { + _oneOffEventTypes = [NSSet setWithObjects: + MPVideoEventClick, + MPVideoEventCloseLinear, + MPVideoEventComplete, + MPVideoEventCreativeView, + MPVideoEventFirstQuartile, + MPVideoEventImpression, + MPVideoEventMidpoint, + MPVideoEventProgress, + MPVideoEventSkip, + MPVideoEventStart, + MPVideoEventThirdQuartile, + nil]; + } +} + +- (MPVASTTracking *)makeTestSubject { + MPVASTResponse *vastResponse = [self vastResponseFromXMLFile:@"VAST_3.0_linear_ad_comprehensive"]; + MPVideoConfig *videoConfig = [[MPVideoConfig alloc] initWithVASTResponse:vastResponse additionalTrackers:nil]; + MPVASTTracking *testSubject = [[MPVASTTracking alloc] initWithVideoConfig:videoConfig + videoURL:[NSURL URLWithString:@"https://any.thing"]];; + testSubject.analyticsTracker = [MockAnalyticTracker new]; + return testSubject; +} + +/** + Test the `handleVideoEvent:videoTimeOffset:` method. + */ +- (void)testHandlingEvents { + MPVASTTracking *testSubject = [self makeTestSubject]; + for (NSString *eventName in self.allTrackingEventNames) { + [testSubject handleVideoEvent:eventName videoTimeOffset:5]; // time offset does not matter + NSArray *urls = [(MockAnalyticTracker *)testSubject.analyticsTracker mostRecentlySentURLs]; + + if ([eventName isEqualToString:MPVideoEventProgress]) { + // nothing should happen since this should be handled by `handleVideoProgressEvent:videoDuration:` + XCTAssertEqual(urls.count, 0); + } else { + if (urls.count != self.testData[eventName].count) { + XCTFail(@"[%@] URL count %lu is not equal to expected %lu", + eventName, urls.count, self.testData[eventName].count); + } + } + [(MockAnalyticTracker *)testSubject.analyticsTracker reset]; + } + + // This loop fires all events again, and verify the one-off events are sent only once. + for (NSString *eventName in self.allTrackingEventNames) { + [testSubject handleVideoEvent:eventName videoTimeOffset:5]; // time offset does not matter + NSArray *urls = [(MockAnalyticTracker *)testSubject.analyticsTracker mostRecentlySentURLs]; + + if ([self.oneOffEventTypes containsObject:eventName] == NO) { + if (urls.count != self.testData[eventName].count) { + XCTFail(@"[%@] URL count %lu is not equal to expected %lu", + eventName, urls.count, self.testData[eventName].count); + } + } else { + XCTAssertEqual(urls.count, 0); + } + [(MockAnalyticTracker *)testSubject.analyticsTracker reset]; + } +} + +/** + Test the `handleVideoProgressEvent:videoDuration:` method. + */ +- (void)testHandlingProgressEvents { + MPVASTTracking *testSubject = [self makeTestSubject]; + NSArray *times = @[@0, @5, @10, @15, @20, @25, @30]; // defined in the original XML + + for (int i = 0; i < times.count; i++) { + [testSubject handleVideoProgressEvent:times[i].doubleValue videoDuration:30]; + NSArray *urls = [(MockAnalyticTracker *)testSubject.analyticsTracker mostRecentlySentURLs]; + if (times[i].intValue == 0) { + XCTAssertEqual(urls.count, 3); + XCTAssertTrue([urls[0].absoluteString isEqualToString:self.testData[MPVideoEventStart][0]]); + XCTAssertTrue([urls[1].absoluteString isEqualToString:self.testData[MPVideoEventStart][1]]); + XCTAssertTrue([urls[2].absoluteString isEqualToString:self.testData[MPVideoEventProgress][i]]); + } else if (times[i].intValue == 10) { + XCTAssertEqual(urls.count, 2); + XCTAssertTrue([urls[0].absoluteString isEqualToString:self.testData[MPVideoEventFirstQuartile][0]]); + XCTAssertTrue([urls[1].absoluteString isEqualToString:self.testData[MPVideoEventProgress][i]]); + } else if (times[i].intValue == 15) { + XCTAssertEqual(urls.count, 2); + XCTAssertTrue([urls[0].absoluteString isEqualToString:self.testData[MPVideoEventMidpoint][0]]); + XCTAssertTrue([urls[1].absoluteString isEqualToString:self.testData[MPVideoEventProgress][i]]); + } else if (times[i].intValue == 25) { + XCTAssertEqual(urls.count, 2); + XCTAssertTrue([urls[0].absoluteString isEqualToString:self.testData[MPVideoEventThirdQuartile][0]]); + XCTAssertTrue([urls[1].absoluteString isEqualToString:self.testData[MPVideoEventProgress][i]]); + } else { + XCTAssertEqual(urls.count, 1); + XCTAssertTrue([urls[0].absoluteString isEqualToString:self.testData[MPVideoEventProgress][i]]); + } + [(MockAnalyticTracker *)testSubject.analyticsTracker reset]; + } + + // This loop fires all progress events again, and verify they are sent only once. + for (int i = 0; i < times.count; i++) { + [testSubject handleVideoProgressEvent:times[i].doubleValue videoDuration:30]; + NSArray *urls = [(MockAnalyticTracker *)testSubject.analyticsTracker mostRecentlySentURLs]; + XCTAssertEqual(urls.count, 0); + } +} + +@end diff --git a/MoPubSDKTests/MPVideoConfigTests.m b/MoPubSDKTests/MPVideoConfigTests.m index 73ffe9944..c44acdff1 100644 --- a/MoPubSDKTests/MPVideoConfigTests.m +++ b/MoPubSDKTests/MPVideoConfigTests.m @@ -1,7 +1,7 @@ // // MPVideoConfigTests.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -10,10 +10,8 @@ #import "XCTestCase+MPAddition.h" #import "MPVASTManager.h" #import "MPVASTResponse.h" +#import "MPVASTTracking.h" #import "MPVideoConfig.h" -#import "MPVASTTrackingEvent.h" - -static const NSTimeInterval kDefaultTimeout = 1; static NSString * const kTrackerEventDictionaryKey = @"event"; static NSString * const kTrackerTextDictionaryKey = @"text"; @@ -49,146 +47,112 @@ - (void)tearDown { - (void)testEmptyVastEmptyAdditionalTrackers { // vast response is nil MPVideoConfig *videoConfig = [[MPVideoConfig alloc] initWithVASTResponse:nil additionalTrackers:nil]; - XCTAssertEqual(videoConfig.startTrackers.count, 0); - XCTAssertEqual(videoConfig.firstQuartileTrackers.count, 0); - XCTAssertEqual(videoConfig.midpointTrackers.count, 0); - XCTAssertEqual(videoConfig.thirdQuartileTrackers.count, 0); - XCTAssertEqual(videoConfig.completionTrackers.count, 0); + XCTAssertEqual([videoConfig trackingEventsForKey:MPVideoEventStart].count, 0); + XCTAssertEqual([videoConfig trackingEventsForKey:MPVideoEventFirstQuartile].count, 0); + XCTAssertEqual([videoConfig trackingEventsForKey:MPVideoEventMidpoint].count, 0); + XCTAssertEqual([videoConfig trackingEventsForKey:MPVideoEventThirdQuartile].count, 0); + XCTAssertEqual([videoConfig trackingEventsForKey:MPVideoEventComplete].count, 0); // vast response is not nil, but it doesn't have trackers. MPVideoConfig *videoConfig2 = [[MPVideoConfig alloc] initWithVASTResponse:[MPVASTResponse new] additionalTrackers:nil]; - XCTAssertEqual(videoConfig2.startTrackers.count, 0); - XCTAssertEqual(videoConfig2.firstQuartileTrackers.count, 0); - XCTAssertEqual(videoConfig2.midpointTrackers.count, 0); - XCTAssertEqual(videoConfig2.thirdQuartileTrackers.count, 0); - XCTAssertEqual(videoConfig2.completionTrackers.count, 0); + XCTAssertEqual([videoConfig2 trackingEventsForKey:MPVideoEventStart].count, 0); + XCTAssertEqual([videoConfig2 trackingEventsForKey:MPVideoEventFirstQuartile].count, 0); + XCTAssertEqual([videoConfig2 trackingEventsForKey:MPVideoEventMidpoint].count, 0); + XCTAssertEqual([videoConfig2 trackingEventsForKey:MPVideoEventThirdQuartile].count, 0); + XCTAssertEqual([videoConfig2 trackingEventsForKey:MPVideoEventComplete].count, 0); } // Test when there are trackers in vast but no trackers in additonalTrackers. This test also ensures that trackers with no URLs are not included in the video config - (void)testNonEmptyVastEmptyAdditionalTrackers { - XCTestExpectation *expectation = [self expectationWithDescription:@"Wait for fetching data from xml."]; - - __block NSData *vastData = [self dataFromXMLFileNamed:@"linear-tracking" class:[self class]]; - __block MPVASTResponse *vastResponse; - [MPVASTManager fetchVASTWithData:vastData completion:^(MPVASTResponse *response, NSError *error) { - XCTAssertNil(error); - vastResponse = response; - [expectation fulfill]; - }]; - - [self waitForExpectationsWithTimeout:kDefaultTimeout handler:^(NSError * _Nullable error) { - XCTAssertNil(error); - }]; + MPVASTResponse *vastResponse = [self vastResponseFromXMLFile:@"linear-tracking"]; // linear-tracking.xml has 1 for each of the following trackers: start, firstQuartile, midpoint, thirdQuartile, and complete. MPVideoConfig *videoConfig = [[MPVideoConfig alloc] initWithVASTResponse:vastResponse additionalTrackers:nil]; - XCTAssertEqual(videoConfig.creativeViewTrackers.count, 1); - XCTAssertEqual(videoConfig.startTrackers.count, 1); - XCTAssertEqual(videoConfig.firstQuartileTrackers.count, 1); - XCTAssertEqual(videoConfig.midpointTrackers.count, 1); - XCTAssertEqual(videoConfig.thirdQuartileTrackers.count, 1); - XCTAssertEqual(videoConfig.completionTrackers.count, 1); + XCTAssertEqual([videoConfig trackingEventsForKey:MPVideoEventCreativeView].count, 1); + XCTAssertEqual([videoConfig trackingEventsForKey:MPVideoEventStart].count, 1); + XCTAssertEqual([videoConfig trackingEventsForKey:MPVideoEventFirstQuartile].count, 1); + XCTAssertEqual([videoConfig trackingEventsForKey:MPVideoEventMidpoint].count, 1); + XCTAssertEqual([videoConfig trackingEventsForKey:MPVideoEventThirdQuartile].count, 1); + XCTAssertEqual([videoConfig trackingEventsForKey:MPVideoEventComplete].count, 1); // additionalTrackers are not nil but there is nothing inside MPVideoConfig *videoConfig2 = [[MPVideoConfig alloc] initWithVASTResponse:vastResponse additionalTrackers:[NSDictionary new]]; - XCTAssertEqual(videoConfig.creativeViewTrackers.count, 1); - XCTAssertEqual(videoConfig2.startTrackers.count, 1); - XCTAssertEqual(videoConfig2.firstQuartileTrackers.count, 1); - XCTAssertEqual(videoConfig2.midpointTrackers.count, 1); - XCTAssertEqual(videoConfig2.thirdQuartileTrackers.count, 1); - XCTAssertEqual(videoConfig2.completionTrackers.count, 1); + XCTAssertEqual([videoConfig2 trackingEventsForKey:MPVideoEventCreativeView].count, 1); + XCTAssertEqual([videoConfig2 trackingEventsForKey:MPVideoEventStart].count, 1); + XCTAssertEqual([videoConfig2 trackingEventsForKey:MPVideoEventFirstQuartile].count, 1); + XCTAssertEqual([videoConfig2 trackingEventsForKey:MPVideoEventMidpoint].count, 1); + XCTAssertEqual([videoConfig2 trackingEventsForKey:MPVideoEventThirdQuartile].count, 1); + XCTAssertEqual([videoConfig2 trackingEventsForKey:MPVideoEventComplete].count, 1); } // Test when VAST doesn't have any trackers and there is exactly one entry for each event type - (void)testSingleTrackeForEachEventInAdditionalTrackers { - // set up VAST response - XCTestExpectation *expectation = [self expectationWithDescription:@"Wait for fetching data from xml."]; - __block NSData *vastData = [self dataFromXMLFileNamed:@"linear-tracking-no-event" class:[self class]]; - __block MPVASTResponse *vastResponse; - [MPVASTManager fetchVASTWithData:vastData completion:^(MPVASTResponse *response, NSError *error) { - XCTAssertNil(error); - vastResponse = response; - [expectation fulfill]; - }]; - - [self waitForExpectationsWithTimeout:kDefaultTimeout handler:^(NSError * _Nullable error) { - XCTAssertNil(error); - }]; - - + MPVASTResponse *vastResponse = [self vastResponseFromXMLFile:@"linear-tracking-no-event"]; NSDictionary *additonalTrackersDict = [self getAdditionalTrackersWithOneEntryForEachEvent]; MPVideoConfig *videoConfig = [[MPVideoConfig alloc] initWithVASTResponse:vastResponse additionalTrackers:additonalTrackersDict]; - XCTAssertEqual(videoConfig.startTrackers.count, 1); - XCTAssertEqual(videoConfig.firstQuartileTrackers.count, 1); - XCTAssertEqual(videoConfig.midpointTrackers.count, 1); - XCTAssertEqual(videoConfig.thirdQuartileTrackers.count, 1); - XCTAssertEqual(videoConfig.completionTrackers.count, 1); + XCTAssertEqual([videoConfig trackingEventsForKey:MPVideoEventStart].count, 1); + XCTAssertEqual([videoConfig trackingEventsForKey:MPVideoEventFirstQuartile].count, 1); + XCTAssertEqual([videoConfig trackingEventsForKey:MPVideoEventMidpoint].count, 1); + XCTAssertEqual([videoConfig trackingEventsForKey:MPVideoEventThirdQuartile].count, 1); + XCTAssertEqual([videoConfig trackingEventsForKey:MPVideoEventComplete].count, 1); // verify type and url - XCTAssertEqual(((MPVASTTrackingEvent *)videoConfig.startTrackers.firstObject).eventType, MPVASTTrackingEventTypeStart); - XCTAssertEqualObjects(((MPVASTTrackingEvent *)videoConfig.startTrackers.firstObject).URL, [NSURL URLWithString:kFirstAdditionalStartTrackerUrl]); + MPVASTTrackingEvent *event = [videoConfig trackingEventsForKey:MPVideoEventStart].firstObject; + XCTAssertEqual(event.eventType, MPVideoEventStart); + XCTAssertEqualObjects(event.URL, [NSURL URLWithString:kFirstAdditionalStartTrackerUrl]); - XCTAssertEqual(((MPVASTTrackingEvent *)videoConfig.firstQuartileTrackers.firstObject).eventType, MPVASTTrackingEventTypeFirstQuartile); - XCTAssertEqualObjects(((MPVASTTrackingEvent *)videoConfig.firstQuartileTrackers.firstObject).URL, [NSURL URLWithString:kFirstAdditionalFirstQuartileTrackerUrl]); + event = [videoConfig trackingEventsForKey:MPVideoEventFirstQuartile].firstObject; + XCTAssertEqual(event.eventType, MPVideoEventFirstQuartile); + XCTAssertEqualObjects(event.URL, [NSURL URLWithString:kFirstAdditionalFirstQuartileTrackerUrl]); - XCTAssertEqual(((MPVASTTrackingEvent *)videoConfig.midpointTrackers.firstObject).eventType, MPVASTTrackingEventTypeMidpoint); - XCTAssertEqualObjects(((MPVASTTrackingEvent *)videoConfig.midpointTrackers.firstObject).URL, [NSURL URLWithString:kFirstAdditionalMidpointTrackerUrl]); + event = [videoConfig trackingEventsForKey:MPVideoEventMidpoint].firstObject; + XCTAssertEqual(event.eventType, MPVideoEventMidpoint); + XCTAssertEqualObjects(event.URL, [NSURL URLWithString:kFirstAdditionalMidpointTrackerUrl]); - XCTAssertEqual(((MPVASTTrackingEvent *)videoConfig.thirdQuartileTrackers.firstObject).eventType, MPVASTTrackingEventTypeThirdQuartile); - XCTAssertEqualObjects(((MPVASTTrackingEvent *)videoConfig.thirdQuartileTrackers.firstObject).URL, [NSURL URLWithString:kFirstAdditionalThirdQuartileTrackerUrl]); + event = [videoConfig trackingEventsForKey:MPVideoEventThirdQuartile].firstObject; + XCTAssertEqual(event.eventType, MPVideoEventThirdQuartile); + XCTAssertEqualObjects(event.URL, [NSURL URLWithString:kFirstAdditionalThirdQuartileTrackerUrl]); - XCTAssertEqual(((MPVASTTrackingEvent *)videoConfig.completionTrackers.firstObject).eventType, MPVASTTrackingEventTypeComplete); - XCTAssertEqualObjects(((MPVASTTrackingEvent *)videoConfig.completionTrackers.firstObject).URL, [NSURL URLWithString:kFirstAdditionalCompleteTrackerUrl]); + event = [videoConfig trackingEventsForKey:MPVideoEventComplete].firstObject; + XCTAssertEqual(event.eventType, MPVideoEventComplete); + XCTAssertEqualObjects(event.URL, [NSURL URLWithString:kFirstAdditionalCompleteTrackerUrl]); } - (void)testMergeTrackers { - XCTestExpectation *expectation = [self expectationWithDescription:@"Wait for fetching data from xml."]; - - __block NSData *vastData = [self dataFromXMLFileNamed:@"linear-tracking" class:[self class]]; - __block MPVASTResponse *vastResponse; - [MPVASTManager fetchVASTWithData:vastData completion:^(MPVASTResponse *response, NSError *error) { - XCTAssertNil(error); - vastResponse = response; - [expectation fulfill]; - }]; - - [self waitForExpectationsWithTimeout:kDefaultTimeout handler:^(NSError * _Nullable error) { - XCTAssertNil(error); - }]; - + MPVASTResponse *vastResponse = [self vastResponseFromXMLFile:@"linear-tracking"]; NSDictionary *additonalTrackersDict = [self getAdditionalTrackersWithTwoEntriesForEachEvent]; MPVideoConfig *videoConfig = [[MPVideoConfig alloc] initWithVASTResponse:vastResponse additionalTrackers:additonalTrackersDict]; // one tracker from vast, two from additonalTrackers - XCTAssertEqual(videoConfig.startTrackers.count, 3); - XCTAssertEqual(videoConfig.firstQuartileTrackers.count, 3); - XCTAssertEqual(videoConfig.midpointTrackers.count, 3); - XCTAssertEqual(videoConfig.thirdQuartileTrackers.count, 3); - XCTAssertEqual(videoConfig.completionTrackers.count, 3); + XCTAssertEqual([videoConfig trackingEventsForKey:MPVideoEventStart].count, 3); + XCTAssertEqual([videoConfig trackingEventsForKey:MPVideoEventFirstQuartile].count, 3); + XCTAssertEqual([videoConfig trackingEventsForKey:MPVideoEventMidpoint].count, 3); + XCTAssertEqual([videoConfig trackingEventsForKey:MPVideoEventThirdQuartile].count, 3); + XCTAssertEqual([videoConfig trackingEventsForKey:MPVideoEventComplete].count, 3); } - (NSDictionary *)getAdditionalTrackersWithOneEntryForEachEvent { NSMutableDictionary *addtionalTrackersDict = [NSMutableDictionary new]; - NSDictionary *startTrackerDict = @{kTrackerEventDictionaryKey:MPVASTTrackingEventTypeStart, kTrackerTextDictionaryKey:kFirstAdditionalStartTrackerUrl}; + NSDictionary *startTrackerDict = @{kTrackerEventDictionaryKey:MPVideoEventStart, kTrackerTextDictionaryKey:kFirstAdditionalStartTrackerUrl}; MPVASTTrackingEvent *startTracker = [[MPVASTTrackingEvent alloc] initWithDictionary:startTrackerDict]; - NSDictionary *firstQuartileTrackerDict = @{kTrackerEventDictionaryKey:MPVASTTrackingEventTypeFirstQuartile, kTrackerTextDictionaryKey:kFirstAdditionalFirstQuartileTrackerUrl}; + NSDictionary *firstQuartileTrackerDict = @{kTrackerEventDictionaryKey:MPVideoEventFirstQuartile, kTrackerTextDictionaryKey:kFirstAdditionalFirstQuartileTrackerUrl}; MPVASTTrackingEvent *firstQuartileTracker = [[MPVASTTrackingEvent alloc] initWithDictionary:firstQuartileTrackerDict]; - NSDictionary *midpointTrackerDict = @{kTrackerEventDictionaryKey:MPVASTTrackingEventTypeMidpoint, kTrackerTextDictionaryKey:kFirstAdditionalMidpointTrackerUrl}; + NSDictionary *midpointTrackerDict = @{kTrackerEventDictionaryKey:MPVideoEventMidpoint, kTrackerTextDictionaryKey:kFirstAdditionalMidpointTrackerUrl}; MPVASTTrackingEvent *midpointTracker = [[MPVASTTrackingEvent alloc] initWithDictionary:midpointTrackerDict]; - NSDictionary *thirdQuartileTrackerDict = @{kTrackerEventDictionaryKey:MPVASTTrackingEventTypeThirdQuartile, kTrackerTextDictionaryKey:kFirstAdditionalThirdQuartileTrackerUrl}; + NSDictionary *thirdQuartileTrackerDict = @{kTrackerEventDictionaryKey:MPVideoEventThirdQuartile, kTrackerTextDictionaryKey:kFirstAdditionalThirdQuartileTrackerUrl}; MPVASTTrackingEvent *thirdQuartileTracker = [[MPVASTTrackingEvent alloc] initWithDictionary:thirdQuartileTrackerDict]; - NSDictionary *completeTrackerDict = @{kTrackerEventDictionaryKey:MPVASTTrackingEventTypeComplete, kTrackerTextDictionaryKey:kFirstAdditionalCompleteTrackerUrl}; + NSDictionary *completeTrackerDict = @{kTrackerEventDictionaryKey:MPVideoEventComplete, kTrackerTextDictionaryKey:kFirstAdditionalCompleteTrackerUrl}; MPVASTTrackingEvent *completeTracker = [[MPVASTTrackingEvent alloc] initWithDictionary:completeTrackerDict]; - addtionalTrackersDict[MPVASTTrackingEventTypeStart] = @[startTracker]; - addtionalTrackersDict[MPVASTTrackingEventTypeFirstQuartile] = @[firstQuartileTracker]; - addtionalTrackersDict[MPVASTTrackingEventTypeMidpoint] = @[midpointTracker]; - addtionalTrackersDict[MPVASTTrackingEventTypeThirdQuartile] = @[thirdQuartileTracker]; - addtionalTrackersDict[MPVASTTrackingEventTypeComplete] = @[completeTracker]; + addtionalTrackersDict[MPVideoEventStart] = @[startTracker]; + addtionalTrackersDict[MPVideoEventFirstQuartile] = @[firstQuartileTracker]; + addtionalTrackersDict[MPVideoEventMidpoint] = @[midpointTracker]; + addtionalTrackersDict[MPVideoEventThirdQuartile] = @[thirdQuartileTracker]; + addtionalTrackersDict[MPVideoEventComplete] = @[completeTracker]; return addtionalTrackersDict; } @@ -198,49 +162,48 @@ - (NSDictionary *)getAdditionalTrackersWithTwoEntriesForEachEvent NSMutableDictionary *addtionalTrackersDict = [NSMutableDictionary new]; // start trackers - NSDictionary *startTrackerDict1 = @{kTrackerEventDictionaryKey:MPVASTTrackingEventTypeStart, kTrackerTextDictionaryKey:kFirstAdditionalStartTrackerUrl}; + NSDictionary *startTrackerDict1 = @{kTrackerEventDictionaryKey:MPVideoEventStart, kTrackerTextDictionaryKey:kFirstAdditionalStartTrackerUrl}; MPVASTTrackingEvent *startTracker1 = [[MPVASTTrackingEvent alloc] initWithDictionary:startTrackerDict1]; - NSDictionary *startTrackerDict2 = @{kTrackerEventDictionaryKey:MPVASTTrackingEventTypeStart, kTrackerTextDictionaryKey:kSecondAdditionalStartTrackerUrl}; + NSDictionary *startTrackerDict2 = @{kTrackerEventDictionaryKey:MPVideoEventStart, kTrackerTextDictionaryKey:kSecondAdditionalStartTrackerUrl}; MPVASTTrackingEvent *startTracker2 = [[MPVASTTrackingEvent alloc] initWithDictionary:startTrackerDict2]; // firstQuartile trackers - NSDictionary *firstQuartileTrackerDict1 = @{kTrackerEventDictionaryKey:MPVASTTrackingEventTypeFirstQuartile, kTrackerTextDictionaryKey:kSecondAdditionalFirstQuartileTrackerUrl}; + NSDictionary *firstQuartileTrackerDict1 = @{kTrackerEventDictionaryKey:MPVideoEventFirstQuartile, kTrackerTextDictionaryKey:kSecondAdditionalFirstQuartileTrackerUrl}; MPVASTTrackingEvent *firstQuartileTracker1 = [[MPVASTTrackingEvent alloc] initWithDictionary:firstQuartileTrackerDict1]; - NSDictionary *firstQuartileTrackerDict2 = @{kTrackerEventDictionaryKey:MPVASTTrackingEventTypeFirstQuartile, kTrackerTextDictionaryKey:kSecondAdditionalFirstQuartileTrackerUrl}; + NSDictionary *firstQuartileTrackerDict2 = @{kTrackerEventDictionaryKey:MPVideoEventFirstQuartile, kTrackerTextDictionaryKey:kSecondAdditionalFirstQuartileTrackerUrl}; MPVASTTrackingEvent *firstQuartileTracker2 = [[MPVASTTrackingEvent alloc] initWithDictionary:firstQuartileTrackerDict2]; // midpoint trackers - NSDictionary *midpointTrackerDict1 = @{kTrackerEventDictionaryKey:MPVASTTrackingEventTypeMidpoint, kTrackerTextDictionaryKey:kFirstAdditionalMidpointTrackerUrl}; + NSDictionary *midpointTrackerDict1 = @{kTrackerEventDictionaryKey:MPVideoEventMidpoint, kTrackerTextDictionaryKey:kFirstAdditionalMidpointTrackerUrl}; MPVASTTrackingEvent *midpointTracker1 = [[MPVASTTrackingEvent alloc] initWithDictionary:midpointTrackerDict1]; - NSDictionary *midpointTrackerDict2 = @{kTrackerEventDictionaryKey:MPVASTTrackingEventTypeMidpoint, kTrackerTextDictionaryKey:kSecondAdditionalMidpointTrackerUrl}; + NSDictionary *midpointTrackerDict2 = @{kTrackerEventDictionaryKey:MPVideoEventMidpoint, kTrackerTextDictionaryKey:kSecondAdditionalMidpointTrackerUrl}; MPVASTTrackingEvent *midpointTracker2 = [[MPVASTTrackingEvent alloc] initWithDictionary:midpointTrackerDict2]; // thirdQuartile trackers - NSDictionary *thirdQuartileTrackerDict1 = @{kTrackerEventDictionaryKey:MPVASTTrackingEventTypeThirdQuartile, kTrackerTextDictionaryKey:kFirstAdditionalThirdQuartileTrackerUrl}; + NSDictionary *thirdQuartileTrackerDict1 = @{kTrackerEventDictionaryKey:MPVideoEventThirdQuartile, kTrackerTextDictionaryKey:kFirstAdditionalThirdQuartileTrackerUrl}; MPVASTTrackingEvent *thirdQuartileTracker1 = [[MPVASTTrackingEvent alloc] initWithDictionary:thirdQuartileTrackerDict1]; - NSDictionary *thirdQuartileTrackerDict2 = @{kTrackerEventDictionaryKey:MPVASTTrackingEventTypeThirdQuartile, kTrackerTextDictionaryKey:kSecondAdditionalThirdQuartileTrackerUrl}; + NSDictionary *thirdQuartileTrackerDict2 = @{kTrackerEventDictionaryKey:MPVideoEventThirdQuartile, kTrackerTextDictionaryKey:kSecondAdditionalThirdQuartileTrackerUrl}; MPVASTTrackingEvent *thirdQuartileTracker2 = [[MPVASTTrackingEvent alloc] initWithDictionary:thirdQuartileTrackerDict2]; // complete trackers - NSDictionary *completeTrackerDict1 = @{kTrackerEventDictionaryKey:MPVASTTrackingEventTypeComplete, kTrackerTextDictionaryKey:kFirstAdditionalCompleteTrackerUrl}; + NSDictionary *completeTrackerDict1 = @{kTrackerEventDictionaryKey:MPVideoEventComplete, kTrackerTextDictionaryKey:kFirstAdditionalCompleteTrackerUrl}; MPVASTTrackingEvent *completeTracker1 = [[MPVASTTrackingEvent alloc] initWithDictionary:completeTrackerDict1]; - NSDictionary *completeTrackerDict2 = @{kTrackerEventDictionaryKey:MPVASTTrackingEventTypeComplete, kTrackerTextDictionaryKey:kSecondAdditionalCompleteTrackerUrl}; + NSDictionary *completeTrackerDict2 = @{kTrackerEventDictionaryKey:MPVideoEventComplete, kTrackerTextDictionaryKey:kSecondAdditionalCompleteTrackerUrl}; MPVASTTrackingEvent *completeTracker2 = [[MPVASTTrackingEvent alloc] initWithDictionary:completeTrackerDict2]; - addtionalTrackersDict[MPVASTTrackingEventTypeStart] = @[startTracker1, startTracker2]; - addtionalTrackersDict[MPVASTTrackingEventTypeFirstQuartile] = @[firstQuartileTracker1, firstQuartileTracker2]; - addtionalTrackersDict[MPVASTTrackingEventTypeMidpoint] = @[midpointTracker1, midpointTracker2]; - addtionalTrackersDict[MPVASTTrackingEventTypeThirdQuartile] = @[thirdQuartileTracker1, thirdQuartileTracker2]; - addtionalTrackersDict[MPVASTTrackingEventTypeComplete] = @[completeTracker1, completeTracker2]; + addtionalTrackersDict[MPVideoEventStart] = @[startTracker1, startTracker2]; + addtionalTrackersDict[MPVideoEventFirstQuartile] = @[firstQuartileTracker1, firstQuartileTracker2]; + addtionalTrackersDict[MPVideoEventMidpoint] = @[midpointTracker1, midpointTracker2]; + addtionalTrackersDict[MPVideoEventThirdQuartile] = @[thirdQuartileTracker1, thirdQuartileTracker2]; + addtionalTrackersDict[MPVideoEventComplete] = @[completeTracker1, completeTracker2]; return addtionalTrackersDict; } - @end diff --git a/MoPubSDKTests/MPViewabilityTracker+Testing.h b/MoPubSDKTests/MPViewabilityTracker+Testing.h index 7cb8fe836..5bc042f16 100644 --- a/MoPubSDKTests/MPViewabilityTracker+Testing.h +++ b/MoPubSDKTests/MPViewabilityTracker+Testing.h @@ -1,7 +1,7 @@ // // MPViewabilityTracker+Testing.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/MPViewabilityTracker+Testing.m b/MoPubSDKTests/MPViewabilityTracker+Testing.m index 005d8ac20..d1a12b157 100644 --- a/MoPubSDKTests/MPViewabilityTracker+Testing.m +++ b/MoPubSDKTests/MPViewabilityTracker+Testing.m @@ -1,7 +1,7 @@ // // MPViewabilityTracker+Testing.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/MPViewabilityTrackerTests.m b/MoPubSDKTests/MPViewabilityTrackerTests.m index 2332b9fe1..6ee96a3d5 100644 --- a/MoPubSDKTests/MPViewabilityTrackerTests.m +++ b/MoPubSDKTests/MPViewabilityTrackerTests.m @@ -1,7 +1,7 @@ // // MPViewabilityTrackerTests.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/MPWebBrowserUserAgentInfoTests.m b/MoPubSDKTests/MPWebBrowserUserAgentInfoTests.m new file mode 100644 index 000000000..08116790f --- /dev/null +++ b/MoPubSDKTests/MPWebBrowserUserAgentInfoTests.m @@ -0,0 +1,47 @@ +// +// MPWebBrowserUserAgentInfoTests.m +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import +#import "MPWebBrowserUserAgentInfo.h" + +@interface MPWebBrowserUserAgentInfoTests : XCTestCase + +@end + +@implementation MPWebBrowserUserAgentInfoTests + +/** + Sample of valid user agent: + "Mozilla/5.0 (iPhone; CPU iPhone OS 12_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148" + */ +- (BOOL)isUserAgentValid:(NSString *)userAgent { + return ([userAgent hasPrefix:@"Mozilla/5.0"] + && [userAgent containsString:@"like Mac OS X"] + && [userAgent containsString:@"AppleWebKit/"] + && [userAgent containsString:@"(KHTML, like Gecko)"] + && [userAgent containsString:@"Mobile/"]); +} + +- (void)testUserAgentValue { + // `MPWebBrowserUserAgentInfo.load` uses `WKWebView`to evaluate "navigator.userAgent" for user + // agent, and typically it takes about 0.8 ~ 1.5 seconds. + NSTimeInterval waitTime = 3; + XCTAssertTrue([self isUserAgentValid:MPWebBrowserUserAgentInfo.userAgent]); + + XCTestExpectation *expectation = [self expectationWithDescription:@"Expect JavaScript evaluation for user agent"]; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(waitTime * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + XCTAssertTrue([self isUserAgentValid:MPWebBrowserUserAgentInfo.userAgent]); + [expectation fulfill]; + }); + + [self waitForExpectationsWithTimeout:waitTime + 0.1 handler:^(NSError *error) { + XCTAssertNil(error); + }]; +} + +@end diff --git a/MoPubSDKTests/MPWebView+Testing.h b/MoPubSDKTests/MPWebView+Testing.h index 69ebb12cd..43d1e5256 100644 --- a/MoPubSDKTests/MPWebView+Testing.h +++ b/MoPubSDKTests/MPWebView+Testing.h @@ -1,7 +1,7 @@ // // MPWebView+Testing.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -11,5 +11,4 @@ @interface MPWebView (Testing) @property (weak, nonatomic) WKWebView *wkWebView; -@property (weak, nonatomic) UIWebView *uiWebView; @end diff --git a/MoPubSDKTests/MPWebView+Testing.m b/MoPubSDKTests/MPWebView+Testing.m index 64496a69b..20f10108e 100644 --- a/MoPubSDKTests/MPWebView+Testing.m +++ b/MoPubSDKTests/MPWebView+Testing.m @@ -1,7 +1,7 @@ // // MPWebView+Testing.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -10,5 +10,4 @@ @implementation MPWebView (Testing) @dynamic wkWebView; -@dynamic uiWebView; @end diff --git a/MoPubSDKTests/MPWebViewTests.m b/MoPubSDKTests/MPWebViewTests.m index 671d4cadb..1923310bb 100644 --- a/MoPubSDKTests/MPWebViewTests.m +++ b/MoPubSDKTests/MPWebViewTests.m @@ -1,7 +1,7 @@ // // MPWebViewTests.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -11,12 +11,11 @@ typedef void (^MPWebViewTestsDelegate)(void); typedef void (^MPWebViewTestsDidFailLoad)(NSError *error); -typedef BOOL (^MPWebViewTestsShouldStartLoad)(NSURLRequest *, UIWebViewNavigationType); +typedef BOOL (^MPWebViewTestsShouldStartLoad)(NSURLRequest *, WKNavigationType); @interface MPWebViewTests : XCTestCase @property (nonatomic) MPWebView *wkWebView; -@property (nonatomic) MPWebView *uiWebView; @property MPWebViewTestsDelegate didStartLoadBlock; @property MPWebViewTestsDelegate didFinishLoadBlock; @@ -32,14 +31,10 @@ - (void)setUp { self.wkWebView = [[MPWebView alloc] initWithFrame:CGRectZero]; self.wkWebView.delegate = self; - - self.uiWebView = [[MPWebView alloc] initWithFrame:CGRectZero forceUIWebView:YES]; - self.uiWebView.delegate = self; } - (void)tearDown { self.wkWebView = nil; - self.uiWebView = nil; self.didStartLoadBlock = nil; self.didFinishLoadBlock = nil; @@ -49,20 +44,20 @@ - (void)tearDown { [super tearDown]; } -// Often for testing, we will need to verify some information that comes to us through MPWebViewDelegate. -// Currently, any bridging between web view and native comes through this delegate. For convenience, -// MPWebViewTests holds a set of blocks (one for each delegate method) that get called as the delegates fire. -// You can set these blocks from within your test method to verify information that comes in through the delegate -// methods. Use them in combination with XCTestExpectations. They are nil'd in `tearDown`. +/** + Often for testing, we will need to verify some information that comes to us through MPWebViewDelegate. + Currently, any bridging between web view and native comes through this delegate. For convenience, + MPWebViewTests holds a set of blocks (one for each delegate method) that get called as the delegates fire. + You can set these blocks from within your test method to verify information that comes in through the delegate + methods. Use them in combination with XCTestExpectations. They are nil'd in `tearDown`. -// Note on test structure: to be aware of any differences between UIWebView and WKWebView, each test gets run twice -- -// once with the OS default (WKWebView on anything at all modern) and once with UIWebView forced. Each test is packaged -// into a non-test function with a web view parameter, and to each non-test function there are two tests that each call -// the non-test function with one of the two web view properties. I separate the test methods so it can be seen -// immediately which web view type is being problematic. + Each test is packaged into a non-test function with a web view parameter, and to each non-test function there are + two tests that each call the non-test function with one of the two web view properties. I separate the test methods + so it can be seen immediately which web view type is being problematic. -// In building MPWebView, we had a lot of issues with javascript not completely getting executed, and, in particular, -// redirects getting ignored. Make sure redirects always work. + In building MPWebView, we had a lot of issues with javascript not completely getting executed, and, in particular, + redirects getting ignored. Make sure redirects always work. +*/ static NSString *const gTestMopubSchemeRedirectURL = @"mopub://testredirect"; // (via `evaluateJavaScript:completionHandler:`) @@ -70,10 +65,6 @@ - (void)testJavaScriptMoPubURLRedirectViaEvaluateWKWebView { [self javaScriptMoPubURLRedirectViaEvaluateTestWithWebView:self.wkWebView]; } -- (void)testJavaScriptMoPubURLRedirectViaEvaluateUIWebView { - [self javaScriptMoPubURLRedirectViaEvaluateTestWithWebView:self.uiWebView]; -} - - (void)javaScriptMoPubURLRedirectViaEvaluateTestWithWebView:(MPWebView *)webView { NSString *javascriptSnippet = [NSString stringWithFormat:@"window.location=\"%@\"", gTestMopubSchemeRedirectURL]; @@ -87,7 +78,7 @@ - (void)javaScriptMoPubURLRedirectViaEvaluateTestWithWebView:(MPWebView *)webVie dispatch_group_t group = dispatch_group_create(); dispatch_group_enter(group); - self.shouldStartLoadBlock = ^(NSURLRequest *request, UIWebViewNavigationType type) { + self.shouldStartLoadBlock = ^(NSURLRequest *request, WKNavigationType type) { shouldStartLoadURL = request.URL.absoluteString; dispatch_group_leave(group); @@ -114,10 +105,6 @@ - (void)testJavaScriptMoPubURLRedirectViaLoadHTMLStringWKWebView { [self javaScriptMoPubURLRedirectViaLoadHTMLStringTestWithWebView:self.wkWebView]; } -- (void)testJavaScriptMoPubURLRedirectViaLoadHTMLStringUIWebView { - [self javaScriptMoPubURLRedirectViaLoadHTMLStringTestWithWebView:self.uiWebView]; -} - - (void)javaScriptMoPubURLRedirectViaLoadHTMLStringTestWithWebView:(MPWebView *)webView { NSString *htmlSnippet = [NSString stringWithFormat:@"", gTestMopubSchemeRedirectURL]; @@ -138,7 +125,7 @@ - (void)javaScriptMoPubURLRedirectViaLoadHTMLStringTestWithWebView:(MPWebView *) dispatch_group_enter(group); // Note: this block gets called twice -- once for the initial load and once to redirect. Wait on the // redirect rather than the initial load. - self.shouldStartLoadBlock = ^(NSURLRequest *request, UIWebViewNavigationType type) { + self.shouldStartLoadBlock = ^(NSURLRequest *request, WKNavigationType type) { if ([request.URL.scheme isEqualToString:@"mopub"]) { shouldStartLoadURL = request.URL.absoluteString; dispatch_group_leave(group); @@ -168,10 +155,6 @@ - (void)testPropertiesWKWebView { [self propertiesTestWithWebView:self.wkWebView]; } -- (void)testPropertiesUIWebView { - [self propertiesTestWithWebView:self.uiWebView]; -} - - (void)propertiesTestWithWebView:(MPWebView *)webView { // Default values XCTAssertTrue(webView.allowsInlineMediaPlayback); @@ -199,7 +182,7 @@ - (void)propertiesTestWithWebView:(MPWebView *)webView { #pragma mark - MPWebViewDelegate -- (BOOL)webView:(MPWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType { +- (BOOL)webView:(MPWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(WKNavigationType)navigationType { if (self.shouldStartLoadBlock) { return self.shouldStartLoadBlock(request, navigationType); } diff --git a/MoPubSDKTests/MRController+Testing.h b/MoPubSDKTests/MRController+Testing.h index 9284bb340..bb8c81761 100644 --- a/MoPubSDKTests/MRController+Testing.h +++ b/MoPubSDKTests/MRController+Testing.h @@ -1,14 +1,26 @@ // // MRController+Testing.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // +#import "MPClosableView.h" #import "MRController.h" #import "MPWebView.h" @interface MRController (Testing) @property (nonatomic, strong) MPWebView *mraidWebView; + ++ (BOOL)isValidResizeFrame:(CGRect)frame + inApplicationSafeArea:(CGRect)applicationSafeArea + allowOffscreen:(BOOL)allowOffscreen; + ++ (BOOL)isValidCloseButtonPlacement:(MPClosableViewCloseButtonLocation)closeButtonLocation + inAdFrame:(CGRect)adFrame + inApplicationSafeArea:(CGRect)applicationSafeArea; + ++ (CGRect)adjustedFrameForFrame:(CGRect)frame toFitIntoApplicationSafeArea:(CGRect)applicationSafeArea; + @end diff --git a/MoPubSDKTests/MRController+Testing.m b/MoPubSDKTests/MRController+Testing.m index 225c22b18..b00cf3ae6 100644 --- a/MoPubSDKTests/MRController+Testing.m +++ b/MoPubSDKTests/MRController+Testing.m @@ -1,13 +1,17 @@ // // MRController+Testing.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // #import "MRController+Testing.h" +// Suppress warning of accessing private implementation `adjustedFrameForFrame:toFitIntoApplicationSafeArea:` +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wincomplete-implementation" @implementation MRController (Testing) @dynamic mraidWebView; @end +#pragma clang diagnostic pop diff --git a/MoPubSDKTests/MRControllerTests.m b/MoPubSDKTests/MRControllerTests.m new file mode 100644 index 000000000..744266caa --- /dev/null +++ b/MoPubSDKTests/MRControllerTests.m @@ -0,0 +1,229 @@ +// +// MRControllerTests.m +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import +#import "MPClosableView.h" +#import "MRController+Testing.h" + +#pragma mark - Test Utility (Resize Ad Frame Validation) + +/** + This class is for the test subject of resize ad frame validation tests. + */ +@interface MRControllerResizeAdFrameValidationTestSubject : NSObject +@property (nonatomic, assign) CGRect resizeFrame; +@property (nonatomic, assign) CGRect applicationSafeArea; +@property (nonatomic, assign) BOOL allowOffscreen; +@property (nonatomic, assign) BOOL expectedToBeValid; +@end + +@implementation MRControllerResizeAdFrameValidationTestSubject + +- (instancetype)initWithResizeFrame:(CGRect)resizeFrame + applicationSafeArea:(CGRect)applicationSafeArea + allowOffscreen:(BOOL)allowOffscreen + expectedToBeValid:(BOOL)expectedToBeValid { + self = [super init]; + if (self) { + _resizeFrame = resizeFrame; + _applicationSafeArea = applicationSafeArea; + _allowOffscreen = allowOffscreen; + _expectedToBeValid = expectedToBeValid; + } + return self; +} + ++ (NSArray *)defaultTestSubjects { + CGRect appSafeArea = CGRectMake(0, 20, 320, 460); + CGRect invalidResizeFrame = CGRectZero; + CGRect validOnScreenFrame = CGRectMake(10, 20, 300, 400); + CGRect validOnScreenFrameSmallest = CGRectMake(100, 100, 50, 50); // MRAID spec indicates a minimal 50x50 ad size + CGRect validOnScreenFrameFullScreen = appSafeArea; + CGRect validFullyOffScreenFrame = CGRectMake(-200, -200, 100, 100); + CGRect validPartiallyOffScreenFrame = CGRectMake(-100, -100, 200, 200); + + return @[// resize frames with invalid size + [[MRControllerResizeAdFrameValidationTestSubject alloc] initWithResizeFrame:invalidResizeFrame + applicationSafeArea:appSafeArea + allowOffscreen:NO + expectedToBeValid:NO], + [[MRControllerResizeAdFrameValidationTestSubject alloc] initWithResizeFrame:invalidResizeFrame + applicationSafeArea:appSafeArea + allowOffscreen:YES + expectedToBeValid:NO], + + // on screen resize frames with valide size + [[MRControllerResizeAdFrameValidationTestSubject alloc] initWithResizeFrame:validOnScreenFrame + applicationSafeArea:appSafeArea + allowOffscreen:NO + expectedToBeValid:YES], + [[MRControllerResizeAdFrameValidationTestSubject alloc] initWithResizeFrame:validOnScreenFrame + applicationSafeArea:appSafeArea + allowOffscreen:YES + expectedToBeValid:YES], + + [[MRControllerResizeAdFrameValidationTestSubject alloc] initWithResizeFrame:validOnScreenFrameSmallest + applicationSafeArea:appSafeArea + allowOffscreen:NO + expectedToBeValid:YES], + [[MRControllerResizeAdFrameValidationTestSubject alloc] initWithResizeFrame:validOnScreenFrameSmallest + applicationSafeArea:appSafeArea + allowOffscreen:YES + expectedToBeValid:YES], + + [[MRControllerResizeAdFrameValidationTestSubject alloc] initWithResizeFrame:validOnScreenFrameFullScreen + applicationSafeArea:appSafeArea + allowOffscreen:NO + expectedToBeValid:YES], + [[MRControllerResizeAdFrameValidationTestSubject alloc] initWithResizeFrame:validOnScreenFrameFullScreen + applicationSafeArea:appSafeArea + allowOffscreen:YES + expectedToBeValid:YES], + + // off screen resize frames with valide size + [[MRControllerResizeAdFrameValidationTestSubject alloc] initWithResizeFrame:validFullyOffScreenFrame + applicationSafeArea:appSafeArea + allowOffscreen:NO + expectedToBeValid:NO], + [[MRControllerResizeAdFrameValidationTestSubject alloc] initWithResizeFrame:validFullyOffScreenFrame + applicationSafeArea:appSafeArea + allowOffscreen:YES + expectedToBeValid:YES], + + [[MRControllerResizeAdFrameValidationTestSubject alloc] initWithResizeFrame:validPartiallyOffScreenFrame + applicationSafeArea:appSafeArea + allowOffscreen:NO + expectedToBeValid:NO], + [[MRControllerResizeAdFrameValidationTestSubject alloc] initWithResizeFrame:validPartiallyOffScreenFrame + applicationSafeArea:appSafeArea + allowOffscreen:YES + expectedToBeValid:YES] + ]; +} + +@end + +#pragma mark - Test Utility (Close Button Frame Validation) + +/** + This class is for the test subject of Close button frame validation tests. + */ +@interface MRControllerCloseButtonFrameValidationTestSubject : NSObject +@property (nonatomic, assign) MPClosableViewCloseButtonLocation closeButtonLocation; +@property (nonatomic, assign) CGRect adFrame; +@property (nonatomic, assign) CGRect applicationSafeArea; +@property (nonatomic, assign) BOOL expectedToBeValid; +@end + +@implementation MRControllerCloseButtonFrameValidationTestSubject + +- (instancetype)initWithCloseButtonLocation:(MPClosableViewCloseButtonLocation)closeButtonLocation + adFrame:(CGRect)adFrame + applicationSafeArea:(CGRect)applicationSafeArea + expectedToBeValid:(BOOL)expectedToBeValid { + self = [super init]; + if (self) { + _closeButtonLocation = closeButtonLocation; + _adFrame = adFrame; + _applicationSafeArea = applicationSafeArea; + _expectedToBeValid = expectedToBeValid; + } + return self; +} + ++ (NSArray *)defaultTestSubjects { + CGRect appSafeArea = CGRectMake(0, 20, 320, 460); + + return @[// ad fully off screen (including its Close button) + [[MRControllerCloseButtonFrameValidationTestSubject alloc] initWithCloseButtonLocation:MPClosableViewCloseButtonLocationTopLeft + adFrame:CGRectOffset(appSafeArea, appSafeArea.size.width * 2, appSafeArea.size.height * 2) + applicationSafeArea:appSafeArea + expectedToBeValid:NO], + + // ad partially off screen, and its Close button fully off screen + [[MRControllerCloseButtonFrameValidationTestSubject alloc] initWithCloseButtonLocation:MPClosableViewCloseButtonLocationTopRight + adFrame:CGRectOffset(appSafeArea, kCloseRegionSize.width * 2, kCloseRegionSize.height * 2) + applicationSafeArea:appSafeArea + expectedToBeValid:NO], + + // ad partially off screen, and its Close button partially off screen + [[MRControllerCloseButtonFrameValidationTestSubject alloc] initWithCloseButtonLocation:MPClosableViewCloseButtonLocationBottomLeft + adFrame:CGRectOffset(appSafeArea, -kCloseRegionSize.width / 2, -kCloseRegionSize.height / 2) + applicationSafeArea:appSafeArea + expectedToBeValid:NO], + + // ad partially off screen, and its Close button full on screen + [[MRControllerCloseButtonFrameValidationTestSubject alloc] initWithCloseButtonLocation:MPClosableViewCloseButtonLocationBottomLeft + adFrame:CGRectOffset(appSafeArea, kCloseRegionSize.width, 0) + applicationSafeArea:appSafeArea + expectedToBeValid:YES], + + // ad fully on screen (including its Close button) + [[MRControllerCloseButtonFrameValidationTestSubject alloc] initWithCloseButtonLocation:MPClosableViewCloseButtonLocationCenter + adFrame:appSafeArea + applicationSafeArea:appSafeArea + expectedToBeValid:YES] + ]; +} + +@end + +#pragma mark - Tests + +@interface MRControllerTests : XCTestCase + +@end + +@implementation MRControllerTests + +/** + Test the result of [MRController isValidResizeFrame:inApplicationSafeArea:allowOffscreen:]. + */ +- (void)testResizeAdFrameValidation { + for (MRControllerResizeAdFrameValidationTestSubject * t in [MRControllerResizeAdFrameValidationTestSubject defaultTestSubjects]) { + XCTAssertEqual(t.expectedToBeValid, [MRController isValidResizeFrame:t.resizeFrame + inApplicationSafeArea:t.applicationSafeArea + allowOffscreen:t.allowOffscreen]); + } +} + +/** + Test the result of [MRController isValidCloseButtonPlacement:inAdFrame:inApplicationSafeArea:]. + */ +- (void)testCloseButtonFrameValidation { + for (MRControllerCloseButtonFrameValidationTestSubject * t in [MRControllerCloseButtonFrameValidationTestSubject defaultTestSubjects]) { + XCTAssertEqual(t.expectedToBeValid, [MRController isValidCloseButtonPlacement:t.closeButtonLocation + inAdFrame:t.adFrame + inApplicationSafeArea:t.applicationSafeArea]); + } +} + +/** + Test the result of [MRController adjustedFrameForFrame:toFitIntoApplicationSafeArea:]. + */ +- (void)testAdjustedAdFrame { + // Test a frame that already fits. + XCTAssertTrue(CGRectEqualToRect(CGRectMake(100, 100, 100, 100), + [MRController adjustedFrameForFrame:CGRectMake(100, 100, 100, 100) + toFitIntoApplicationSafeArea:CGRectMake(0, 0, 500, 500)])); + + // Test a frame that fits after adjustment. + XCTAssertTrue(CGRectEqualToRect(CGRectMake(0, 0, 100, 100), + [MRController adjustedFrameForFrame:CGRectMake(100, 100, 100, 100) + toFitIntoApplicationSafeArea:CGRectMake(0, 0, 100, 100)])); + XCTAssertTrue(CGRectEqualToRect(CGRectMake(200, 200, 100, 100), + [MRController adjustedFrameForFrame:CGRectMake(100, 100, 100, 100) + toFitIntoApplicationSafeArea:CGRectMake(200, 200, 100, 100)])); + + // Test a frame that cannot fit due to large size (thus is not adjusted). + XCTAssertTrue(CGRectEqualToRect(CGRectMake(0, 0, 100, 100), + [MRController adjustedFrameForFrame:CGRectMake(0, 0, 100, 100) + toFitIntoApplicationSafeArea:CGRectMake(10, 10, 10, 10)])); +} + +@end diff --git a/MoPubSDKTests/MoPub+Testing.h b/MoPubSDKTests/MoPub+Testing.h index 4ce599c82..39f69682f 100644 --- a/MoPubSDKTests/MoPub+Testing.h +++ b/MoPubSDKTests/MoPub+Testing.h @@ -1,22 +1,36 @@ // // MoPub+Testing.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // #import "MoPub.h" +#import "MOPUBExperimentProvider+Testing.h" NS_ASSUME_NONNULL_BEGIN @interface MoPub (Testing) +- (instancetype)initWithExperimentProvider:(MOPUBExperimentProvider *)experimentProvider; + +- (void)commonInitWithExperimentProvider:(MOPUBExperimentProvider *)experimentProvider; + // This method is called by `initializeSdkWithConfiguration:completion:` in a dispatch_once block, // and is exposed here for unit testing. - (void)setSdkWithConfiguration:(MPMoPubConfiguration *)configuration completion:(void(^_Nullable)(void))completionBlock; +/** + Retrieves the managed adapter configuration instance of the same name. + @param className Class name of the adapter configuration conforming to the @c MPAdapterConfiguration protocol. + @return The managed adapter configuration instance if successful; otherwise @c nil. + */ +- (id _Nullable)adapterConfigurationNamed:(NSString *)className; + +- (MOPUBExperimentProvider *)experimentProvider; + @end NS_ASSUME_NONNULL_END diff --git a/MoPubSDKTests/MoPub+Testing.m b/MoPubSDKTests/MoPub+Testing.m index b53ad7d57..317100500 100644 --- a/MoPubSDKTests/MoPub+Testing.m +++ b/MoPubSDKTests/MoPub+Testing.m @@ -1,7 +1,7 @@ // // MoPub+Testing.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -13,6 +13,13 @@ @implementation MoPub (Testing) +- (instancetype)initWithExperimentProvider:(MOPUBExperimentProvider *)experimentProvider { + if (self = [super init]) { + [self commonInitWithExperimentProvider:experimentProvider]; + } + return self; +} + @end #pragma clang diagnostic pop diff --git a/MoPubSDKTests/MoPubTests.m b/MoPubSDKTests/MoPubTests.m index ba2759b9e..9b2efb4e4 100644 --- a/MoPubSDKTests/MoPubTests.m +++ b/MoPubSDKTests/MoPubTests.m @@ -1,7 +1,7 @@ // // MoPubTests.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // @@ -11,8 +11,10 @@ #import "MoPub+Testing.h" #import "MPAdConfiguration.h" #import "MPMediationManager.h" -#import "MPMockAdColonyRewardedVideoCustomEvent.h" -#import "MPMockChartboostRewardedVideoCustomEvent.h" +#import "MPMediationManager+Testing.h" +#import "MPMockAdColonyAdapterConfiguration.h" +#import "MPMockChartboostAdapterConfiguration.h" +#import "MPMockTapjoyAdapterConfiguration.h" #import "MPWebView+Testing.h" #import "MRController.h" #import "MRController+Testing.h" @@ -28,29 +30,29 @@ @implementation MoPubTests - (void)setUp { [super setUp]; [MPMediationManager.sharedManager clearCache]; - - [MoPub sharedInstance].forceWKWebView = NO; - [MoPub sharedInstance].logLevel = MPLogLevelInfo; + MPLogging.consoleLogLevel = MPBLogLevelInfo; } -#pragma mark - Rewarded Video +#pragma mark - Initialization - (void)testInitializingNetworkFromCache { // Reset initialized state - [MPMockAdColonyRewardedVideoCustomEvent reset]; - [MPMockChartboostRewardedVideoCustomEvent reset]; - XCTAssertFalse([MPMockAdColonyRewardedVideoCustomEvent isSdkInitialized]); - XCTAssertFalse([MPMockChartboostRewardedVideoCustomEvent isSdkInitialized]); + MPMockAdColonyAdapterConfiguration.isSdkInitialized = NO; + MPMockChartboostAdapterConfiguration.isSdkInitialized = NO; + MPMockTapjoyAdapterConfiguration.isSdkInitialized = NO; + XCTAssertFalse(MPMockAdColonyAdapterConfiguration.isSdkInitialized); + XCTAssertFalse(MPMockChartboostAdapterConfiguration.isSdkInitialized); + XCTAssertFalse(MPMockTapjoyAdapterConfiguration.isSdkInitialized); // Put data into the cache to simulate having been cache prior. - [MPMediationManager.sharedManager setCachedInitializationParameters:@{ @"appId": @"aaaa" } forNetwork:MPMockAdColonyRewardedVideoCustomEvent.class]; - [MPMediationManager.sharedManager setCachedInitializationParameters:@{ @"appId": @"bbbb" } forNetwork:MPMockChartboostRewardedVideoCustomEvent.class]; + [MPMockAdColonyAdapterConfiguration setCachedInitializationParameters:@{ @"appId": @"aaaa" }]; + [MPMockChartboostAdapterConfiguration setCachedInitializationParameters:@{ @"appId": @"bbbb" }]; + [MPMockTapjoyAdapterConfiguration setCachedInitializationParameters:@{ @"appId": @"cccc" }]; // Initialize MPMoPubConfiguration * config = [[MPMoPubConfiguration alloc] initWithAdUnitIdForAppInitialization:@"fake_adunit_id"]; - config.advancedBidders = nil; + config.additionalNetworks = nil; config.globalMediationSettings = nil; - config.mediatedNetworks = MoPub.sharedInstance.allCachedNetworks; // Wait for SDKs to initialize XCTestExpectation * expectation = [self expectationWithDescription:@"Expect timer to fire"]; @@ -63,26 +65,34 @@ - (void)testInitializingNetworkFromCache { }]; // Verify initialized sdks - XCTAssertTrue([MPMockAdColonyRewardedVideoCustomEvent isSdkInitialized]); - XCTAssertTrue([MPMockChartboostRewardedVideoCustomEvent isSdkInitialized]); + XCTAssertTrue(MPMockAdColonyAdapterConfiguration.isSdkInitialized); + XCTAssertTrue(MPMockChartboostAdapterConfiguration.isSdkInitialized); + XCTAssertFalse(MPMockTapjoyAdapterConfiguration.isSdkInitialized); + + // Verify adapter configurations exist + XCTAssertNotNil([MoPub.sharedInstance adapterConfigurationNamed:@"MPMockAdColonyAdapterConfiguration"]); + XCTAssertNotNil([MoPub.sharedInstance adapterConfigurationNamed:@"MPMockChartboostAdapterConfiguration"]); + XCTAssertNil([MoPub.sharedInstance adapterConfigurationNamed:@"MPMockTapjoyAdapterConfiguration"]); } -- (void)testPartialInitializingNetworkFromCache { +- (void)testAdditionalInitializingNetworkFromCache { // Reset initialized state - [MPMockAdColonyRewardedVideoCustomEvent reset]; - [MPMockChartboostRewardedVideoCustomEvent reset]; - XCTAssertFalse([MPMockAdColonyRewardedVideoCustomEvent isSdkInitialized]); - XCTAssertFalse([MPMockChartboostRewardedVideoCustomEvent isSdkInitialized]); + MPMockAdColonyAdapterConfiguration.isSdkInitialized = NO; + MPMockChartboostAdapterConfiguration.isSdkInitialized = NO; + MPMockTapjoyAdapterConfiguration.isSdkInitialized = NO; + XCTAssertFalse(MPMockAdColonyAdapterConfiguration.isSdkInitialized); + XCTAssertFalse(MPMockChartboostAdapterConfiguration.isSdkInitialized); + XCTAssertFalse(MPMockTapjoyAdapterConfiguration.isSdkInitialized); // Put data into the cache to simulate having been cache prior. - [MPMediationManager.sharedManager setCachedInitializationParameters:@{ @"appId": @"aaaa" } forNetwork:MPMockAdColonyRewardedVideoCustomEvent.class]; - [MPMediationManager.sharedManager setCachedInitializationParameters:@{ @"appId": @"bbbb" } forNetwork:MPMockChartboostRewardedVideoCustomEvent.class]; + [MPMockAdColonyAdapterConfiguration setCachedInitializationParameters:@{ @"appId": @"aaaa" }]; + [MPMockChartboostAdapterConfiguration setCachedInitializationParameters:@{ @"appId": @"bbbb" }]; + [MPMockTapjoyAdapterConfiguration setCachedInitializationParameters:@{ @"appId": @"cccc" }]; // Initialize MPMoPubConfiguration * config = [[MPMoPubConfiguration alloc] initWithAdUnitIdForAppInitialization:@"fake_adunit_id"]; - config.advancedBidders = nil; + config.additionalNetworks = [NSArray arrayWithObject:MPMockTapjoyAdapterConfiguration.class]; config.globalMediationSettings = nil; - config.mediatedNetworks = @[MPMockAdColonyRewardedVideoCustomEvent.class]; // Wait for SDKs to initialize XCTestExpectation * expectation = [self expectationWithDescription:@"Expect timer to fire"]; @@ -95,26 +105,32 @@ - (void)testPartialInitializingNetworkFromCache { }]; // Verify initialized sdks - XCTAssertTrue([MPMockAdColonyRewardedVideoCustomEvent isSdkInitialized]); - XCTAssertFalse([MPMockChartboostRewardedVideoCustomEvent isSdkInitialized]); + XCTAssertTrue(MPMockAdColonyAdapterConfiguration.isSdkInitialized); + XCTAssertTrue(MPMockChartboostAdapterConfiguration.isSdkInitialized); + XCTAssertTrue(MPMockTapjoyAdapterConfiguration.isSdkInitialized); + + // Verify adapter configurations exist + XCTAssertNotNil([MoPub.sharedInstance adapterConfigurationNamed:@"MPMockAdColonyAdapterConfiguration"]); + XCTAssertNotNil([MoPub.sharedInstance adapterConfigurationNamed:@"MPMockChartboostAdapterConfiguration"]); + XCTAssertNotNil([MoPub.sharedInstance adapterConfigurationNamed:@"MPMockTapjoyAdapterConfiguration"]); } - (void)testNoInitializingNetworkFromCache { // Reset initialized state - [MPMockAdColonyRewardedVideoCustomEvent reset]; - [MPMockChartboostRewardedVideoCustomEvent reset]; - XCTAssertFalse([MPMockAdColonyRewardedVideoCustomEvent isSdkInitialized]); - XCTAssertFalse([MPMockChartboostRewardedVideoCustomEvent isSdkInitialized]); - - // Put data into the cache to simulate having been cache prior. - [MPMediationManager.sharedManager setCachedInitializationParameters:@{ @"appId": @"aaaa" } forNetwork:MPMockAdColonyRewardedVideoCustomEvent.class]; - [MPMediationManager.sharedManager setCachedInitializationParameters:@{ @"appId": @"bbbb" } forNetwork:MPMockChartboostRewardedVideoCustomEvent.class]; + MPMockAdColonyAdapterConfiguration.isSdkInitialized = NO; + MPMockChartboostAdapterConfiguration.isSdkInitialized = NO; + MPMockTapjoyAdapterConfiguration.isSdkInitialized = NO; + XCTAssertFalse(MPMockAdColonyAdapterConfiguration.isSdkInitialized); + XCTAssertFalse(MPMockChartboostAdapterConfiguration.isSdkInitialized); + XCTAssertFalse(MPMockTapjoyAdapterConfiguration.isSdkInitialized); + + // Remove data from the cache. + [MPMediationManager.sharedManager clearCache]; // Initialize MPMoPubConfiguration * config = [[MPMoPubConfiguration alloc] initWithAdUnitIdForAppInitialization:@"fake_adunit_id"]; - config.advancedBidders = nil; + config.additionalNetworks = nil; config.globalMediationSettings = nil; - config.mediatedNetworks = @[]; // Wait for SDKs to initialize XCTestExpectation * expectation = [self expectationWithDescription:@"Expect timer to fire"]; @@ -127,26 +143,20 @@ - (void)testNoInitializingNetworkFromCache { }]; // Verify initialized sdks - XCTAssertFalse([MPMockAdColonyRewardedVideoCustomEvent isSdkInitialized]); - XCTAssertFalse([MPMockChartboostRewardedVideoCustomEvent isSdkInitialized]); + XCTAssertFalse(MPMockAdColonyAdapterConfiguration.isSdkInitialized); + XCTAssertFalse(MPMockChartboostAdapterConfiguration.isSdkInitialized); + XCTAssertFalse(MPMockTapjoyAdapterConfiguration.isSdkInitialized); + + // Verify adapter configurations exist + XCTAssertNotNil([MoPub.sharedInstance adapterConfigurationNamed:@"MPMockAdColonyAdapterConfiguration"]); + XCTAssertNotNil([MoPub.sharedInstance adapterConfigurationNamed:@"MPMockChartboostAdapterConfiguration"]); + XCTAssertNil([MoPub.sharedInstance adapterConfigurationNamed:@"MPMockTapjoyAdapterConfiguration"]); } -- (void)testNoInitializingNetworkFromCacheWithNil { - // Reset initialized state - [MPMockAdColonyRewardedVideoCustomEvent reset]; - [MPMockChartboostRewardedVideoCustomEvent reset]; - XCTAssertFalse([MPMockAdColonyRewardedVideoCustomEvent isSdkInitialized]); - XCTAssertFalse([MPMockChartboostRewardedVideoCustomEvent isSdkInitialized]); - - // Put data into the cache to simulate having been cache prior. - [MPMediationManager.sharedManager setCachedInitializationParameters:@{ @"appId": @"aaaa" } forNetwork:MPMockAdColonyRewardedVideoCustomEvent.class]; - [MPMediationManager.sharedManager setCachedInitializationParameters:@{ @"appId": @"bbbb" } forNetwork:MPMockChartboostRewardedVideoCustomEvent.class]; - +- (void)testInitializingWithLegitimateInterest { // Initialize MPMoPubConfiguration * config = [[MPMoPubConfiguration alloc] initWithAdUnitIdForAppInitialization:@"fake_adunit_id"]; - config.advancedBidders = nil; - config.globalMediationSettings = nil; - config.mediatedNetworks = nil; + config.allowLegitimateInterest = YES; // Wait for SDKs to initialize XCTestExpectation * expectation = [self expectationWithDescription:@"Expect timer to fire"]; @@ -158,27 +168,13 @@ - (void)testNoInitializingNetworkFromCacheWithNil { XCTAssertNil(error); }]; - // Verify initialized sdks - XCTAssertFalse([MPMockAdColonyRewardedVideoCustomEvent isSdkInitialized]); - XCTAssertFalse([MPMockChartboostRewardedVideoCustomEvent isSdkInitialized]); + // Verify legitimate interest is set + XCTAssertTrue(MoPub.sharedInstance.allowLegitimateInterest); } -- (void)testBadInitializingNetworkFromCache { - // Reset initialized state - [MPMockAdColonyRewardedVideoCustomEvent reset]; - [MPMockChartboostRewardedVideoCustomEvent reset]; - XCTAssertFalse([MPMockAdColonyRewardedVideoCustomEvent isSdkInitialized]); - XCTAssertFalse([MPMockChartboostRewardedVideoCustomEvent isSdkInitialized]); - - // Put data into the cache to simulate having been cache prior. - [MPMediationManager.sharedManager setCachedInitializationParameters:@{ @"appId": @"aaaa" } forNetwork:MPMockAdColonyRewardedVideoCustomEvent.class]; - [MPMediationManager.sharedManager setCachedInitializationParameters:@{ @"appId": @"bbbb" } forNetwork:MPMockChartboostRewardedVideoCustomEvent.class]; - +- (void)testInitializingWithoutLegitimateInterest { // Initialize MPMoPubConfiguration * config = [[MPMoPubConfiguration alloc] initWithAdUnitIdForAppInitialization:@"fake_adunit_id"]; - config.advancedBidders = nil; - config.globalMediationSettings = nil; - config.mediatedNetworks = @[MPRewardedVideo.class]; // Wait for SDKs to initialize XCTestExpectation * expectation = [self expectationWithDescription:@"Expect timer to fire"]; @@ -190,57 +186,16 @@ - (void)testBadInitializingNetworkFromCache { XCTAssertNil(error); }]; - // Verify initialized sdks - XCTAssertFalse([MPMockAdColonyRewardedVideoCustomEvent isSdkInitialized]); - XCTAssertFalse([MPMockChartboostRewardedVideoCustomEvent isSdkInitialized]); -} - -#pragma mark - WKWebView - -- (void)testNoForceWKWebView { - // Normal WKWebView behavior - [MoPub sharedInstance].forceWKWebView = NO; - - // Verify that UIWebView was used instead of WKWebView for video ads - NSDictionary * headers = @{ kAdTypeMetadataKey: @"rewarded_video", - kIsVastVideoPlayerKey: @(1), - kRewardedCurrenciesMetadataKey: @{ @"rewards": @[ @{ @"name": @"Coins", @"amount": @(8) }, @{ @"name": @"Diamonds", @"amount": @(1) }, @{ @"name": @"Energy", @"amount": @(20) } ] } - }; - - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; - - MRController * controller = [[MRController alloc] initWithAdViewFrame:CGRectZero adPlacementType:MRAdViewPlacementTypeInterstitial delegate:nil]; - [controller loadAdWithConfiguration:config]; - - XCTAssertNil(controller.mraidWebView.wkWebView); - XCTAssertNotNil(controller.mraidWebView.uiWebView); -} - -- (void)testForceWKWebView { - // Force WKWebView - [MoPub sharedInstance].forceWKWebView = YES; - - // Verify that WKWebView was used instead of UIWebView for video ads - NSDictionary * headers = @{ kAdTypeMetadataKey: @"rewarded_video", - kIsVastVideoPlayerKey: @(1), - kRewardedCurrenciesMetadataKey: @{ @"rewards": @[ @{ @"name": @"Coins", @"amount": @(8) }, @{ @"name": @"Diamonds", @"amount": @(1) }, @{ @"name": @"Energy", @"amount": @(20) } ] } - }; - - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; - - MRController * controller = [[MRController alloc] initWithAdViewFrame:CGRectZero adPlacementType:MRAdViewPlacementTypeInterstitial delegate:nil]; - [controller loadAdWithConfiguration:config]; - - XCTAssertNotNil(controller.mraidWebView.wkWebView); - XCTAssertNil(controller.mraidWebView.uiWebView); + // Verify legitimate interest is not set by default + XCTAssertFalse(MoPub.sharedInstance.allowLegitimateInterest); } #pragma mark - Logging - (void)testSetLogLevel { - [MoPub sharedInstance].logLevel = MPLogLevelFatal; + MPLogging.consoleLogLevel = MPBLogLevelDebug; - XCTAssertTrue([MoPub sharedInstance].logLevel == MPLogLevelFatal); + XCTAssertTrue(MPLogging.consoleLogLevel == MPBLogLevelDebug); } @end diff --git a/MoPubSDKTests/NSErrorTests.m b/MoPubSDKTests/NSErrorTests.m index 12fa0b8b4..f0140ace7 100644 --- a/MoPubSDKTests/NSErrorTests.m +++ b/MoPubSDKTests/NSErrorTests.m @@ -1,7 +1,7 @@ // // NSErrorTests.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/NSURLComponents+Testing.h b/MoPubSDKTests/NSURLComponents+Testing.h index 753370d54..c9ba34b37 100644 --- a/MoPubSDKTests/NSURLComponents+Testing.h +++ b/MoPubSDKTests/NSURLComponents+Testing.h @@ -1,7 +1,7 @@ // // NSURLComponents+Testing.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/NSURLComponents+Testing.m b/MoPubSDKTests/NSURLComponents+Testing.m index 659a7fa5c..5779e3c31 100644 --- a/MoPubSDKTests/NSURLComponents+Testing.m +++ b/MoPubSDKTests/NSURLComponents+Testing.m @@ -1,7 +1,7 @@ // // NSURLComponents+Testing.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/NSURLSessionTask+Testing.h b/MoPubSDKTests/NSURLSessionTask+Testing.h index 30cdb5d98..02afad322 100644 --- a/MoPubSDKTests/NSURLSessionTask+Testing.h +++ b/MoPubSDKTests/NSURLSessionTask+Testing.h @@ -1,7 +1,7 @@ // // NSURLSessionTask+Testing.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/NSURLSessionTask+Testing.m b/MoPubSDKTests/NSURLSessionTask+Testing.m index de96630de..3e23e6f52 100644 --- a/MoPubSDKTests/NSURLSessionTask+Testing.m +++ b/MoPubSDKTests/NSURLSessionTask+Testing.m @@ -1,7 +1,7 @@ // // NSURLSessionTask+Testing.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // diff --git a/MoPubSDKTests/XCTestCase+MPAddition.h b/MoPubSDKTests/XCTestCase+MPAddition.h index 331714912..6ce2dc59f 100644 --- a/MoPubSDKTests/XCTestCase+MPAddition.h +++ b/MoPubSDKTests/XCTestCase+MPAddition.h @@ -1,15 +1,18 @@ // // XCTestCase+MPAddition.h // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // #import +#import "MPVASTResponse.h" @interface XCTestCase (MPAddition) -- (NSData *)dataFromXMLFileNamed:(NSString *)name class:(Class)aClass; +- (NSData *)dataFromXMLFileNamed:(NSString *)name; + +- (MPVASTResponse *)vastResponseFromXMLFile:(NSString *)fileName; @end diff --git a/MoPubSDKTests/XCTestCase+MPAddition.m b/MoPubSDKTests/XCTestCase+MPAddition.m index a69fb5c73..67e80265e 100644 --- a/MoPubSDKTests/XCTestCase+MPAddition.m +++ b/MoPubSDKTests/XCTestCase+MPAddition.m @@ -1,19 +1,38 @@ // // XCTestCase+MPAddition.m // -// Copyright 2018 Twitter, Inc. +// Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement // http://www.mopub.com/legal/sdk-license-agreement/ // +#import "MPVASTManager.h" #import "XCTestCase+MPAddition.h" @implementation XCTestCase (MPAddition) -- (NSData *)dataFromXMLFileNamed:(NSString *)name class:(Class)aClass +- (NSData *)dataFromXMLFileNamed:(NSString *)name { - NSString *file = [[NSBundle bundleForClass:[aClass class]] pathForResource:name ofType:@"xml"]; + NSString *file = [[NSBundle bundleForClass:[self class]] pathForResource:name ofType:@"xml"]; return [NSData dataWithContentsOfFile:file]; } +- (MPVASTResponse *)vastResponseFromXMLFile:(NSString *)fileName { + XCTestExpectation *expectation = [self expectationWithDescription:@"Wait for fetching data from xml."]; + NSData *vastData = [self dataFromXMLFileNamed:fileName]; + __block MPVASTResponse *vastResponse; + + [MPVASTManager fetchVASTWithData:vastData completion:^(MPVASTResponse *response, NSError *error) { + XCTAssertNil(error); + vastResponse = response; + [expectation fulfill]; + }]; + + [self waitForExpectationsWithTimeout:1 handler:^(NSError * _Nullable error) { + XCTAssertNil(error); + }]; + + return vastResponse; +} + @end diff --git a/MoPubSampleApp/AppDelegate.h b/MoPubSampleApp/AppDelegate.h deleted file mode 100644 index 314601261..000000000 --- a/MoPubSampleApp/AppDelegate.h +++ /dev/null @@ -1,15 +0,0 @@ -// -// AppDelegate.h -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import - -@interface AppDelegate : UIResponder - -@property (strong, nonatomic) UIWindow *window; - -@end diff --git a/MoPubSampleApp/AppDelegate.m b/MoPubSampleApp/AppDelegate.m deleted file mode 100644 index 058b7cb7a..000000000 --- a/MoPubSampleApp/AppDelegate.m +++ /dev/null @@ -1,113 +0,0 @@ -// -// AppDelegate.m -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import "AppDelegate.h" -#import "MPAdPersistenceManager.h" -#import "MPAdTableViewController.h" -#import "MPAdSection.h" -#import "MPIdentityProvider.h" -#import "MPAdConversionTracker.h" -#import "MPAdInfo.h" -#import "MPLogging.h" -#import "MoPub.h" -#import - -@interface AppDelegate() -@property (nonatomic, strong) MPAdTableViewController * adTable; -@end - -@implementation AppDelegate - -- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions -{ - NSLog(@"This device's advertisingIdentifier: %@", [MPIdentityProvider identifier]); - - self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; - self.window.backgroundColor = [UIColor whiteColor]; - self.adTable = [[MPAdTableViewController alloc] initWithAdSections:[MPAdSection adSections]]; - UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:self.adTable]; - self.window.rootViewController = navController; - [self.window makeKeyAndVisible]; - - [[MPAdConversionTracker sharedConversionTracker] reportApplicationOpenForApplicationID:@"112358"]; - - [[UITableViewHeaderFooterView appearance] setTintColor:[UIColor colorWithRed:0.4 green:0.4 blue:0.4 alpha:1]]; - navController.navigationBar.barStyle = UIBarStyleBlackOpaque; - navController.navigationBar.titleTextAttributes = @{NSForegroundColorAttributeName: [UIColor colorWithRed:0.86 green:0.86 blue:0.86 alpha:1]}; - - MPMoPubConfiguration * sdkConfig = [[MPMoPubConfiguration alloc] initWithAdUnitIdForAppInitialization: @"0ac59b0996d947309c33f59d6676399f"]; - sdkConfig.globalMediationSettings = @[]; - sdkConfig.mediatedNetworks = @[]; - sdkConfig.advancedBidders = nil; - [[MoPub sharedInstance] initializeSdkWithConfiguration:sdkConfig completion:^{ - NSLog(@"SDK initialization complete"); - }]; - - return YES; -} - -- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation -{ - if ([url.scheme isEqualToString:@"mopub"] && [url.host isEqualToString:@"load"]) { - // Convert the query parameters into a dictionary. - NSDictionary * queryParameters = ({ - NSURLComponents * urlComponents = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO]; - - NSMutableDictionary * params = [NSMutableDictionary dictionary]; - for (NSURLQueryItem * queryItem in urlComponents.queryItems) { - [params setObject:queryItem.value forKey:queryItem.name]; - } - - params; - }); - - // Extract the info needed to create the `MPAdInfo` object. - MPAdInfo * adUnit = [MPAdInfo infoWithDictionary:queryParameters]; - if (adUnit == nil) { - return NO; - } - - // Dispatch the display of the ad unit onto the main thread. - dispatch_async(dispatch_get_main_queue(), ^{ - [self.adTable loadAd:adUnit]; - [[MPAdPersistenceManager sharedManager] addSavedAd:adUnit]; - }); - - return YES; - } - return NO; -} - -- (void)applicationWillResignActive:(UIApplication *)application -{ - // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. - // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. -} - -- (void)applicationDidEnterBackground:(UIApplication *)application -{ - // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. - // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. -} - -- (void)applicationWillEnterForeground:(UIApplication *)application -{ - // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. -} - -- (void)applicationDidBecomeActive:(UIApplication *)application -{ - // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. -} - -- (void)applicationWillTerminate:(UIApplication *)application -{ - // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. -} - -@end diff --git a/MoPubSampleApp/Assets/Default-568h@2x.png b/MoPubSampleApp/Assets/Default-568h@2x.png deleted file mode 100644 index a8b29c27d..000000000 Binary files a/MoPubSampleApp/Assets/Default-568h@2x.png and /dev/null differ diff --git a/MoPubSampleApp/Assets/Default.png b/MoPubSampleApp/Assets/Default.png deleted file mode 100644 index 5010d5a55..000000000 Binary files a/MoPubSampleApp/Assets/Default.png and /dev/null differ diff --git a/MoPubSampleApp/Assets/Default@2x.png b/MoPubSampleApp/Assets/Default@2x.png deleted file mode 100644 index 6ecbfd1c3..000000000 Binary files a/MoPubSampleApp/Assets/Default@2x.png and /dev/null differ diff --git a/MoPubSampleApp/Assets/icon.png b/MoPubSampleApp/Assets/icon.png deleted file mode 100644 index 34f9c19e9..000000000 Binary files a/MoPubSampleApp/Assets/icon.png and /dev/null differ diff --git a/MoPubSampleApp/Assets/icon@2x.png b/MoPubSampleApp/Assets/icon@2x.png deleted file mode 100644 index 2560d162c..000000000 Binary files a/MoPubSampleApp/Assets/icon@2x.png and /dev/null differ diff --git a/MoPubSampleApp/Assets/white_button.png b/MoPubSampleApp/Assets/white_button.png deleted file mode 100644 index 1bbc3e0b6..000000000 Binary files a/MoPubSampleApp/Assets/white_button.png and /dev/null differ diff --git a/MoPubSampleApp/Controllers/MPAdEntryViewController.h b/MoPubSampleApp/Controllers/MPAdEntryViewController.h deleted file mode 100644 index 466638b92..000000000 --- a/MoPubSampleApp/Controllers/MPAdEntryViewController.h +++ /dev/null @@ -1,17 +0,0 @@ -// -// MPAdEntryViewController.h -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import - -@class MPAdInfo; - -@interface MPAdEntryViewController : UIViewController - -- (id)initWithAdInfo:(MPAdInfo *)adInfo; - -@end diff --git a/MoPubSampleApp/Controllers/MPAdEntryViewController.m b/MoPubSampleApp/Controllers/MPAdEntryViewController.m deleted file mode 100644 index 67fb5f650..000000000 --- a/MoPubSampleApp/Controllers/MPAdEntryViewController.m +++ /dev/null @@ -1,245 +0,0 @@ -// -// MPAdEntryViewController.m -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import "MPAdEntryViewController.h" -#import "MPAdInfo.h" -#import "MPBannerAdDetailViewController.h" -#import "MPMRectBannerAdDetailViewController.h" -#import "MPLeaderboardBannerAdDetailViewController.h" -#import "MPInterstitialAdDetailViewController.h" -#import "MPAdPersistenceManager.h" -#import "MPNativeAdDetailViewController.h" -#import "MPRewardedVideoAdDetailViewController.h" - -#import - -#define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending) - -@interface MPAdEntryViewController () - -@property (weak, nonatomic) IBOutlet UIButton *adTypeButton; -@property (weak, nonatomic) IBOutlet UIButton *pickerDoneButton; -@property (weak, nonatomic) IBOutlet UIPickerView *adTypePicker; -@property (weak, nonatomic) IBOutlet UIToolbar *pickerToolbar; -@property (weak, nonatomic) IBOutlet UITextField *adUnitTextField; -@property (weak, nonatomic) IBOutlet UIBarButtonItem *showBarButton; -@property (weak, nonatomic) IBOutlet UIBarButtonItem *showAndSaveBarButton; -@property (weak, nonatomic) IBOutlet UIToolbar *showToolbar; -@property (weak, nonatomic) IBOutlet UITextField *adNameTextField; - -@property (nonatomic, assign) MPAdInfoType selectedAdType; -@property (nonatomic, strong) MPAdInfo *initialAdInfo; -@property (nonatomic, strong) NSArray *sortedSupportedAdTypes; - -@end - -@implementation MPAdEntryViewController - -- (id)init -{ - return [self initWithAdInfo:nil]; -} - -- (id)initWithAdInfo:(MPAdInfo *)adInfo -{ - self = [super initWithNibName:@"MPAdEntryViewController" bundle:nil]; - if (self) { - _initialAdInfo = adInfo; - self.title = (adInfo.title != nil) ? adInfo.title : @"New Ad"; - - _sortedSupportedAdTypes = [[MPAdInfo supportedAddedAdTypes].allKeys sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)]; - - if ([self respondsToSelector:@selector(setEdgesForExtendedLayout:)]) { - self.edgesForExtendedLayout = UIRectEdgeNone; - } - } - return self; -} - -- (MPAdInfo *)adInfoForCurrentInput -{ - MPAdInfo *info = [[MPAdInfo alloc] init]; - info.title = (self.adNameTextField.text.length > 0) ? self.adNameTextField.text : self.adTypeButton.titleLabel.text; - info.type = self.selectedAdType; - info.ID = self.adUnitTextField.text; - - return info; -} - -#pragma mark - UI Actions - -- (void)showAd -{ - UIViewController *detailViewController = nil; - - MPAdInfo *info = [self adInfoForCurrentInput]; - - switch (info.type) { - case MPAdInfoBanner: - detailViewController = [[MPBannerAdDetailViewController alloc] initWithAdInfo:info]; - break; - case MPAdInfoMRectBanner: - detailViewController = [[MPMRectBannerAdDetailViewController alloc] initWithAdInfo:info]; - break; - case MPAdInfoLeaderboardBanner: - detailViewController = [[MPLeaderboardBannerAdDetailViewController alloc] initWithAdInfo:info]; - break; - case MPAdInfoInterstitial: - detailViewController = [[MPInterstitialAdDetailViewController alloc] initWithAdInfo:info]; - break; - case MPAdInfoNative: - detailViewController = [[MPNativeAdDetailViewController alloc] initWithAdInfo:info]; - break; - case MPAdInfoRewardedVideo: - detailViewController = [[MPRewardedVideoAdDetailViewController alloc] initWithAdInfo:info]; - break; - default: - break; - } - - if (detailViewController) { - [self.navigationController pushViewController:detailViewController animated:YES]; - } -} - -- (IBAction)showAndSaveBarButtonClicked:(id)sender -{ - [[MPAdPersistenceManager sharedManager] addSavedAd:[self adInfoForCurrentInput]]; - - [self showAd]; -} - -- (IBAction)showBarButtonClicked:(id)sender -{ - [self showAd]; -} - -- (IBAction)touchEaterClicked:(id)sender -{ - [self animateOutPicker]; -} - -- (IBAction)pickerDoneButtonClicked:(id)sender -{ - [self animateOutPicker]; -} - -- (IBAction)adTypeButtonClicked:(id)sender -{ - [self animateInPicker]; -} - -- (void)animateInPicker -{ - [self.adUnitTextField endEditing:YES]; - - self.pickerDoneButton.alpha = self.adTypePicker.alpha = self.pickerToolbar.alpha = 0; - self.pickerDoneButton.hidden = self.adTypePicker.hidden = self.pickerToolbar.hidden = NO; - - [UIView animateWithDuration:0.2 - animations:^{ - self.pickerDoneButton.alpha = 0.5; - self.adTypePicker.alpha = 1; - self.pickerToolbar.alpha = 1; - self.showToolbar.alpha = 0; - } - completion:^(BOOL finished) { - [self.adTypePicker selectRow:self.selectedAdType inComponent:0 animated:NO]; - }]; -} - -- (void)animateOutPicker -{ - [UIView animateWithDuration:0.2 - animations:^{ - self.pickerDoneButton.alpha = self.adTypePicker.alpha = self.pickerToolbar.alpha = 0; - self.showToolbar.alpha = 1; - } - completion:^(BOOL finished) { - self.pickerDoneButton.hidden = self.adTypePicker.hidden = self.pickerToolbar.hidden = YES; - }]; -} - -- (void)updateActionButtonStates -{ - if (self.adUnitTextField.text.length > 0) { - self.showAndSaveBarButton.enabled = self.showBarButton.enabled = YES; - } else { - self.showAndSaveBarButton.enabled = self.showBarButton.enabled = NO; - } -} - -#pragma mark - UITextFieldDelegate - -- (BOOL)textFieldShouldReturn:(UITextField *)textField -{ - [textField endEditing:YES]; - - return YES; -} - -- (void)textFieldDidEndEditing:(UITextField *)textField -{ - [self updateActionButtonStates]; -} - -#pragma mark - UIPickerViewDataSource - -- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView -{ - return 1; -} - -- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component -{ - return self.sortedSupportedAdTypes.count; -} - -#pragma mark - UIPickerViewDelegate - -- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component -{ - return [self.sortedSupportedAdTypes objectAtIndex:row]; -} - -- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component -{ - self.selectedAdType = [[[MPAdInfo supportedAddedAdTypes] objectForKey:[self pickerView:pickerView titleForRow:row forComponent:component]] integerValue]; - - [self.adTypeButton setTitle:[self pickerView:pickerView titleForRow:row forComponent:component] forState:UIControlStateNormal]; -} - -#pragma mark - View Lifecycle - -- (void)viewDidLoad -{ - [super viewDidLoad]; - - self.selectedAdType = (self.initialAdInfo != nil) ? self.initialAdInfo.type : MPAdInfoBanner; - self.adUnitTextField.text = self.initialAdInfo.ID; - self.adNameTextField.text = self.initialAdInfo.title; - - self.pickerDoneButton.hidden = self.adTypePicker.hidden = YES; - - // add a border around this button on iOS 7 - if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"7.0")) { - self.adTypeButton.layer.borderWidth = 1.0f; - self.adTypeButton.layer.cornerRadius = 10.0f; - self.adTypeButton.layer.borderColor = [UIColor colorWithRed:63 / 255.0f green:117 / 255.0f blue:1.0f alpha:1.0f].CGColor; - } - - [self updateActionButtonStates]; -} - -- (void)didReceiveMemoryWarning -{ - [super didReceiveMemoryWarning]; - // Dispose of any resources that can be recreated. -} - -@end diff --git a/MoPubSampleApp/Controllers/MPAdEntryViewController.xib b/MoPubSampleApp/Controllers/MPAdEntryViewController.xib deleted file mode 100644 index c62f25aa4..000000000 --- a/MoPubSampleApp/Controllers/MPAdEntryViewController.xib +++ /dev/null @@ -1,132 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/MoPubSampleApp/Controllers/MPAdTableViewController.h b/MoPubSampleApp/Controllers/MPAdTableViewController.h deleted file mode 100644 index 4b7cf1eb9..000000000 --- a/MoPubSampleApp/Controllers/MPAdTableViewController.h +++ /dev/null @@ -1,17 +0,0 @@ -// -// MPAdTableViewController.h -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import -#import "MPAdInfo.h" - -@interface MPAdTableViewController : UITableViewController - -- (id)initWithAdSections:(NSArray *)sections; -- (void)loadAd:(MPAdInfo *)info; - -@end diff --git a/MoPubSampleApp/Controllers/MPAdTableViewController.m b/MoPubSampleApp/Controllers/MPAdTableViewController.m deleted file mode 100644 index 37fb86883..000000000 --- a/MoPubSampleApp/Controllers/MPAdTableViewController.m +++ /dev/null @@ -1,255 +0,0 @@ -// -// MPAdTableViewController.m -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import "MPAdTableViewController.h" -#import "MPAdInfo.h" -#import "MPAdSection.h" -#import "MPBannerAdDetailViewController.h" -#import "MPInterstitialAdDetailViewController.h" -#import "MPMRectBannerAdDetailViewController.h" -#import "MPLeaderboardBannerAdDetailViewController.h" -#import "MPNativeAdDetailViewController.h" -#import "MPGlobal.h" -#import "MPAdPersistenceManager.h" -#import "MPAdEntryViewController.h" -#import "MPNativeAdPlacerTableViewController.h" -#import "MPNativeAdPlacerCollectionViewController.h" -#import "MPNativeAdPlacerPageViewController.h" -#import "MPSampleAppLogReader.h" -#import "MPRewardedVideoAdDetailViewController.h" -#import "MoPub.h" - -@interface MPAdTableViewController () - -@property (nonatomic, strong) NSArray *sections; -@property (nonatomic, strong) NSIndexPath *selectedSavedIndexPath; - -@end - -@implementation MPAdTableViewController - -- (id)initWithStyle:(UITableViewStyle)style -{ - [self doesNotRecognizeSelector:_cmd]; - return nil; -} - -- (id)initWithAdSections:(NSArray *)sections -{ - self = [super initWithStyle:UITableViewStylePlain]; - if (self) { - self.sections = sections; - - if ([self respondsToSelector:@selector(setEdgesForExtendedLayout:)]) { - self.edgesForExtendedLayout = UIRectEdgeNone; - } - } - return self; -} - -- (MPAdInfo *)infoAtIndexPath:(NSIndexPath *)indexPath -{ - return [self.sections[indexPath.section] adAtIndex:indexPath.row]; -} - -- (void)viewDidLoad -{ - self.view.backgroundColor = [UIColor colorWithRed:0.21 green:0.21 blue:0.21 alpha:1]; - self.tableView.separatorColor = [UIColor colorWithRed:0.31 green:0.31 blue:0.31 alpha:1]; - self.tableView.rowHeight = 50; - self.tableView.sectionHeaderHeight = 30; - - self.title = @"Ads"; - self.tableView.accessibilityLabel = @"Ad Table View"; - [self.tableView reloadData]; - - self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(didTapNewAdButton:)]; - self.navigationItem.rightBarButtonItem.accessibilityLabel = @"New Ad"; - - UIButton* myInfoButton = [UIButton buttonWithType:UIButtonTypeInfoLight]; - [myInfoButton addTarget:self action:@selector(infoButtonClicked:) forControlEvents:UIControlEventTouchUpInside]; - self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:myInfoButton]; - - [[MPSampleAppLogReader sharedLogReader] beginReadingLogMessages]; - - [super viewDidLoad]; -} - -- (void)infoButtonClicked:(id)sender -{ - UIAlertView *infoAV = [[UIAlertView alloc] initWithTitle:@"MoPub Sample App" - message:[NSString stringWithFormat:@"MoPub SDK Version: %@", MP_SDK_VERSION] - delegate:nil - cancelButtonTitle:@"OK" - otherButtonTitles:nil]; - [infoAV show]; -} - -- (void)didTapNewAdButton:(id)sender -{ - [self.navigationController pushViewController:[[MPAdEntryViewController alloc] init] animated:YES]; -} - -- (void)viewWillAppear:(BOOL)animated -{ - [super viewWillAppear:animated]; - - [self.tableView reloadData]; -} - -- (void)viewDidAppear:(BOOL)animated { - [super viewDidAppear:animated]; - - // Attempt to load/show the consent dialog if needed - if ([MoPub sharedInstance].shouldShowConsentDialog) { - __weak __typeof__(self) weakSelf = self; - [[MoPub sharedInstance] loadConsentDialogWithCompletion:^(NSError *error){ - if (error == nil) { - [[MoPub sharedInstance] showConsentDialogFromViewController:weakSelf completion:nil]; - } else { - NSLog(@"MoPubSampleApp failed to load consent dialog with error: %@", error); - } - }]; - } -} - -- (void)loadAd:(MPAdInfo *)info -{ - UIViewController *detailViewController = nil; - - switch (info.type) { - case MPAdInfoBanner: - detailViewController = [[MPBannerAdDetailViewController alloc] initWithAdInfo:info]; - break; - case MPAdInfoMRectBanner: - detailViewController = [[MPMRectBannerAdDetailViewController alloc] initWithAdInfo:info]; - break; - case MPAdInfoLeaderboardBanner: - detailViewController = [[MPLeaderboardBannerAdDetailViewController alloc] initWithAdInfo:info]; - break; - case MPAdInfoInterstitial: - detailViewController = [[MPInterstitialAdDetailViewController alloc] initWithAdInfo:info]; - break; - case MPAdInfoRewardedVideo: - detailViewController = [[MPRewardedVideoAdDetailViewController alloc] initWithAdInfo:info]; - break; - case MPAdInfoNative: - detailViewController = [[MPNativeAdDetailViewController alloc] initWithAdInfo:info]; - break; - case MPAdInfoNativeInCollectionView: - detailViewController = [[MPNativeAdPlacerCollectionViewController alloc] initWithAdInfo:info]; - break; - case MPAdInfoNativeTableViewPlacer: - detailViewController = [[MPNativeAdPlacerTableViewController alloc] initWithAdInfo:info]; - break; - case MPAdInfoNativePageViewControllerPlacer: - detailViewController = [[MPNativeAdPlacerPageViewController alloc] initWithAdInfo:info]; - break; - default: - break; - } - - if (detailViewController) { - [self.navigationController pushViewController:detailViewController animated:YES]; - } -} - -#pragma mark - Table view data source - -- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView -{ - return [self.sections count]; -} - -- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section -{ - return [self.sections[section] count]; -} - -- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath -{ - static NSString *CellIdentifier = @"Cell"; - UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; - if (!cell) { - cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier]; - } - - MPAdInfo *info = [self infoAtIndexPath:indexPath]; - - cell.textLabel.text = info.title; - cell.detailTextLabel.text = info.ID; - cell.textLabel.textColor = [UIColor colorWithRed:0.42 green:0.66 blue:0.85 alpha:1]; - cell.detailTextLabel.textColor = [UIColor colorWithRed:0.86 green:0.86 blue:0.86 alpha:1]; - - MPAdSection *section = self.sections[indexPath.section]; - cell.accessoryType = [section.title isEqualToString:@"Saved Ads"] ? UITableViewCellAccessoryDetailDisclosureButton : UITableViewCellAccessoryNone; - - return cell; -} - -- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section -{ - return [self.sections[section] title]; -} - -#pragma mark - Table view delegate - -- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath -{ - MPAdInfo *info = [self infoAtIndexPath:indexPath]; - [self loadAd:info]; -} - -- (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath -{ - self.selectedSavedIndexPath = indexPath; - - MPAdInfo *info = [self infoAtIndexPath:indexPath]; - - UIActionSheet *adActionsSheet = [[UIActionSheet alloc] initWithTitle:info.title - delegate:self - cancelButtonTitle:@"Cancel" - destructiveButtonTitle:@"Delete" - otherButtonTitles:@"Edit", nil]; - - [adActionsSheet showInView:self.view]; -} - -- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex -{ - MPAdInfo *info = [self infoAtIndexPath:self.selectedSavedIndexPath]; - - if(buttonIndex == actionSheet.destructiveButtonIndex) - { - UIAlertView *deleteConfirmAV = [[UIAlertView alloc] initWithTitle:@"Confirm Delete" - message:[NSString stringWithFormat:@"Are you sure you want to delete %@?", info.title] - delegate:self - cancelButtonTitle:@"No" - otherButtonTitles:@"Yes", nil]; - [deleteConfirmAV show]; - } - else if(buttonIndex == 1) // edit, go to pre-configured ad entry view controller - { - [self.navigationController pushViewController:[[MPAdEntryViewController alloc] initWithAdInfo:info] animated:YES]; - } -} - -- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex -{ - if(buttonIndex != alertView.cancelButtonIndex) - { - if(alertView.alertViewStyle == UIAlertViewStyleDefault) - { - [[MPAdPersistenceManager sharedManager] removeSavedAd:[self infoAtIndexPath:self.selectedSavedIndexPath]]; - } - - [self.tableView reloadData]; - } -} - -@end diff --git a/MoPubSampleApp/Controllers/MPBannerAdDetailViewController.h b/MoPubSampleApp/Controllers/MPBannerAdDetailViewController.h deleted file mode 100644 index 1a08eb7c3..000000000 --- a/MoPubSampleApp/Controllers/MPBannerAdDetailViewController.h +++ /dev/null @@ -1,25 +0,0 @@ -// -// MPBannerAdDetailViewController.h -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import -#import "MPAdView.h" -#import "MPViewController.h" - -@class MPAdInfo; - -@interface MPBannerAdDetailViewController : MPViewController - -@property (weak, nonatomic) IBOutlet UILabel *titleLabel; -@property (weak, nonatomic) IBOutlet UILabel *IDLabel; -@property (weak, nonatomic) IBOutlet UIView *adViewContainer; -@property (weak, nonatomic) IBOutlet UIActivityIndicatorView *spinner; -@property (weak, nonatomic) IBOutlet UILabel *failLabel; - -- (id)initWithAdInfo:(MPAdInfo *)info; - -@end diff --git a/MoPubSampleApp/Controllers/MPBannerAdDetailViewController.m b/MoPubSampleApp/Controllers/MPBannerAdDetailViewController.m deleted file mode 100644 index 36f6f6d72..000000000 --- a/MoPubSampleApp/Controllers/MPBannerAdDetailViewController.m +++ /dev/null @@ -1,140 +0,0 @@ -// -// MPBannerAdDetailViewController.m -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import "MPBannerAdDetailViewController.h" -#import "MPAdInfo.h" -#import "MPSampleAppInstanceProvider.h" -#import "MPGlobal.h" -#import "MPAdPersistenceManager.h" - -@interface MPBannerAdDetailViewController () - -@property (weak, nonatomic) IBOutlet UITextField *keywordsTextField; -@property (weak, nonatomic) IBOutlet UIButton *loadAdButton; - -@property (nonatomic, strong) MPAdInfo *info; -@property (nonatomic, strong) MPAdView *adView; -@property (nonatomic, assign) BOOL didLoadAd; - -@end - -@implementation MPBannerAdDetailViewController - -- (id)initWithAdInfo:(MPAdInfo *)info -{ - self = [super initWithNibName:@"MPBannerAdDetailViewController" bundle:nil]; - if (self) { - self.info = info; - - if ([self respondsToSelector:@selector(setEdgesForExtendedLayout:)]) { - self.edgesForExtendedLayout = UIRectEdgeNone; - } - } - return self; -} - -- (void)viewDidLoad -{ - [super viewDidLoad]; - - self.title = @"Banner"; - self.titleLabel.text = self.info.title; - self.IDLabel.text = self.info.ID; - self.keywordsTextField.text = self.info.keywords; - - self.loadAdButton.enabled = NO; - - [self.spinner startAnimating]; -} - -- (IBAction)loadAdClicked:(id)sender -{ - self.adView.keywords = self.keywordsTextField.text; - - self.info.keywords = self.adView.keywords; - // persist last used keywords if this is a saved ad - if ([[MPAdPersistenceManager sharedManager] savedAdForID:self.info.ID] != nil) { - [[MPAdPersistenceManager sharedManager] addSavedAd:self.info]; - } - - [self loadAd]; -} - -- (void)configureAd -{ - self.adView = [[MPSampleAppInstanceProvider sharedProvider] buildMPAdViewWithAdUnitID:self.info.ID - size:self.adViewContainer.bounds.size]; - self.adView.delegate = self; - self.adView.accessibilityLabel = @"banner"; - self.adView.autoresizingMask = UIViewAutoresizingFlexibleWidth; - [self.adViewContainer addSubview:self.adView]; - - [self.adView stopAutomaticallyRefreshingContents]; -} - -- (void)viewDidAppear:(BOOL)animated -{ - [super viewDidAppear:animated]; - - if (!self.didLoadAd) { - [self configureAd]; - [self loadAd]; - self.didLoadAd = YES; - } -} - -- (void)loadAd -{ - [self.keywordsTextField endEditing:YES]; - - self.loadAdButton.enabled = NO; - self.failLabel.hidden = YES; - [self.spinner startAnimating]; - [self startTimer]; - [self.adView loadAd]; -} - -- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration -{ - [self.adView rotateToOrientation:toInterfaceOrientation]; - [super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration]; -} - -- (BOOL)textFieldShouldReturn:(UITextField *)textField -{ - [textField endEditing:YES]; - - return YES; -} - -#pragma mark - - -- (UIViewController *)viewControllerForPresentingModalView -{ - return self; -} - -- (void)adViewDidLoadAd:(MPAdView *)view -{ - self.loadAdButton.enabled = YES; - - [self.spinner stopAnimating]; - [self endTimer]; -} - -- (void)adViewDidFailToLoadAd:(MPAdView *)view -{ - self.loadAdButton.enabled = YES; - - [self.spinner stopAnimating]; - self.failLabel.hidden = NO; - - [self endTimer]; -} - -@end diff --git a/MoPubSampleApp/Controllers/MPBannerAdDetailViewController.xib b/MoPubSampleApp/Controllers/MPBannerAdDetailViewController.xib deleted file mode 100644 index e3b785f6f..000000000 --- a/MoPubSampleApp/Controllers/MPBannerAdDetailViewController.xib +++ /dev/null @@ -1,81 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/MoPubSampleApp/Controllers/MPInterstitialAdDetailViewController.h b/MoPubSampleApp/Controllers/MPInterstitialAdDetailViewController.h deleted file mode 100644 index f31eb812a..000000000 --- a/MoPubSampleApp/Controllers/MPInterstitialAdDetailViewController.h +++ /dev/null @@ -1,32 +0,0 @@ -// -// MPInterstitialAdDetailViewController.h -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import -#import "MPInterstitialAdController.h" -#import "MPViewController.h" - -@class MPAdInfo; - -@interface MPInterstitialAdDetailViewController : MPViewController - -@property (weak, nonatomic) IBOutlet UILabel *titleLabel; -@property (weak, nonatomic) IBOutlet UILabel *IDLabel; -@property (weak, nonatomic) IBOutlet UIButton *showButton; -@property (weak, nonatomic) IBOutlet UIButton *loadButton; -@property (weak, nonatomic) IBOutlet UIActivityIndicatorView *spinner; -@property (weak, nonatomic) IBOutlet UILabel *failLabel; -@property (weak, nonatomic) IBOutlet UILabel *expireLabel; -@property (weak, nonatomic) IBOutlet UILabel *willAppearLabel; -@property (weak, nonatomic) IBOutlet UILabel *didAppearLabel; -@property (weak, nonatomic) IBOutlet UILabel *willDisappearLabel; -@property (weak, nonatomic) IBOutlet UILabel *didDisappearLabel; -@property (weak, nonatomic) IBOutlet UILabel *didReceiveTapLabel; - -- (id)initWithAdInfo:(MPAdInfo *)adInfo; - -@end diff --git a/MoPubSampleApp/Controllers/MPInterstitialAdDetailViewController.m b/MoPubSampleApp/Controllers/MPInterstitialAdDetailViewController.m deleted file mode 100644 index 60dd2cf10..000000000 --- a/MoPubSampleApp/Controllers/MPInterstitialAdDetailViewController.m +++ /dev/null @@ -1,150 +0,0 @@ -// -// MPInterstitialAdDetailViewController.m -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import "MPInterstitialAdDetailViewController.h" -#import "MPAdInfo.h" -#import "MPSampleAppInstanceProvider.h" -#import "MPGlobal.h" -#import "MPAdPersistenceManager.h" - -@interface MPInterstitialAdDetailViewController () - -@property (weak, nonatomic) IBOutlet UITextField *keywordsTextField; - -@property (nonatomic, strong) MPAdInfo *info; -@property (nonatomic, strong) MPInterstitialAdController *interstitial; - -@end - -@implementation MPInterstitialAdDetailViewController - -- (id)initWithAdInfo:(MPAdInfo *)adInfo -{ - self = [super init]; - if (self) { - self.info = adInfo; - - if ([self respondsToSelector:@selector(setEdgesForExtendedLayout:)]) { - self.edgesForExtendedLayout = UIRectEdgeNone; - } - } - return self; -} - -- (void)viewDidLoad -{ - self.title = @"Interstitial"; - self.titleLabel.text = self.info.title; - self.IDLabel.text = self.info.ID; - self.showButton.hidden = YES; - - self.interstitial = [[MPSampleAppInstanceProvider sharedProvider] buildMPInterstitialAdControllerWithAdUnitID:self.info.ID]; - self.interstitial.delegate = self; - - self.keywordsTextField.text = self.info.keywords; - - [super viewDidLoad]; -} - -- (IBAction)didTapLoadButton:(id)sender -{ - [self.keywordsTextField endEditing:YES]; - - [self.spinner startAnimating]; - self.showButton.hidden = YES; - self.loadButton.enabled = NO; - self.expireLabel.hidden = YES; - self.failLabel.hidden = YES; - self.willAppearLabel.alpha = 0.1; - self.didAppearLabel.alpha = 0.1; - self.willDisappearLabel.alpha = 0.1; - self.didDisappearLabel.alpha = 0.1; - self.didReceiveTapLabel.alpha = 0.1; - - self.interstitial.keywords = self.keywordsTextField.text; - - self.info.keywords = self.interstitial.keywords; - // persist last used keywords if this is a saved ad - if ([[MPAdPersistenceManager sharedManager] savedAdForID:self.info.ID] != nil) { - [[MPAdPersistenceManager sharedManager] addSavedAd:self.info]; - } - - [self startTimer]; - [self.interstitial loadAd]; -} - -- (void)dealloc -{ - self.interstitial.delegate = nil; -} - -- (IBAction)didTapShowButton:(id)sender -{ - [self.interstitial showFromViewController:self]; -} - -- (BOOL)textFieldShouldReturn:(UITextField *)textField -{ - [textField endEditing:YES]; - - return YES; -} - -#pragma mark - - -- (void)interstitialDidLoadAd:(MPInterstitialAdController *)interstitial -{ - [self endTimer]; - [self.spinner stopAnimating]; - self.showButton.hidden = NO; - self.loadButton.enabled = YES; -} - -- (void)interstitialDidFailToLoadAd:(MPInterstitialAdController *)interstitial -{ - [self endTimer]; - self.failLabel.hidden = NO; - self.loadButton.enabled = YES; - [self.spinner stopAnimating]; -} - -- (void)interstitialDidExpire:(MPInterstitialAdController *)interstitial -{ - self.expireLabel.hidden = NO; - self.loadButton.enabled = YES; - self.showButton.hidden = YES; - [self.spinner stopAnimating]; -} - -- (void)interstitialWillAppear:(MPInterstitialAdController *)interstitial -{ - self.willAppearLabel.alpha = 1.0; -} - -- (void)interstitialDidAppear:(MPInterstitialAdController *)interstitial -{ - self.didAppearLabel.alpha = 1.0; -} - -- (void)interstitialWillDisappear:(MPInterstitialAdController *)interstitial -{ - self.willDisappearLabel.alpha = 1.0; -} - -- (void)interstitialDidDisappear:(MPInterstitialAdController *)interstitial -{ - self.showButton.hidden = YES; - self.didDisappearLabel.alpha = 1.0; -} - -- (void)interstitialDidReceiveTapEvent:(MPInterstitialAdController *)interstitial -{ - self.didReceiveTapLabel.alpha = 1.0; -} - -@end diff --git a/MoPubSampleApp/Controllers/MPInterstitialAdDetailViewController.xib b/MoPubSampleApp/Controllers/MPInterstitialAdDetailViewController.xib deleted file mode 100644 index 427764d1a..000000000 --- a/MoPubSampleApp/Controllers/MPInterstitialAdDetailViewController.xib +++ /dev/null @@ -1,154 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/MoPubSampleApp/Controllers/MPLeaderboardBannerAdDetailViewController.h b/MoPubSampleApp/Controllers/MPLeaderboardBannerAdDetailViewController.h deleted file mode 100644 index 296c3aa2c..000000000 --- a/MoPubSampleApp/Controllers/MPLeaderboardBannerAdDetailViewController.h +++ /dev/null @@ -1,13 +0,0 @@ -// -// MPLeaderboardBannerAdDetailViewController.h -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import "MPBannerAdDetailViewController.h" - -@interface MPLeaderboardBannerAdDetailViewController : MPBannerAdDetailViewController - -@end diff --git a/MoPubSampleApp/Controllers/MPLeaderboardBannerAdDetailViewController.m b/MoPubSampleApp/Controllers/MPLeaderboardBannerAdDetailViewController.m deleted file mode 100644 index 08a00afef..000000000 --- a/MoPubSampleApp/Controllers/MPLeaderboardBannerAdDetailViewController.m +++ /dev/null @@ -1,44 +0,0 @@ -// -// MPLeaderboardBannerAdDetailViewController.m -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import "MPLeaderboardBannerAdDetailViewController.h" -#import "MPAdInfo.h" -#import "MPSampleAppInstanceProvider.h" -#import "MPConstants.h" - -@interface MPBannerAdDetailViewController () - -@property (nonatomic, strong) MPAdInfo *info; -@property (nonatomic, strong) MPAdView *adView; -@property (nonatomic, assign) BOOL didLoadAd; - -@end - -@implementation MPLeaderboardBannerAdDetailViewController - -// override -- (void)configureAd -{ - // since our xib is targeted for the iPhone, ie, 320xH, we need to use UIScreen's bounds to determine proper centering. - CGRect screenBounds = [UIScreen mainScreen].bounds; - - CGFloat sideBuffer = (screenBounds.size.width - MOPUB_LEADERBOARD_SIZE.width) / 2; - - // again, our xib is targeted for the iPhone, so we can't use MOPUB_LEADERBOARD_SIZE.width as the width here. Subtract - // left and right margins to center the container in the 320-width coordinate space - self.adViewContainer.frame = CGRectMake(sideBuffer, self.adViewContainer.frame.origin.y, self.view.bounds.size.width - sideBuffer * 2, MOPUB_LEADERBOARD_SIZE.height); - - self.adView = [[MPSampleAppInstanceProvider sharedProvider] buildMPAdViewWithAdUnitID:self.info.ID - size:MOPUB_LEADERBOARD_SIZE]; - self.adView.delegate = self; - self.adView.accessibilityLabel = @"leaderboard_banner"; - self.adView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin; - [self.adViewContainer addSubview:self.adView]; -} - -@end diff --git a/MoPubSampleApp/Controllers/MPMRectBannerAdDetailViewController.h b/MoPubSampleApp/Controllers/MPMRectBannerAdDetailViewController.h deleted file mode 100644 index 9f9cd6fe8..000000000 --- a/MoPubSampleApp/Controllers/MPMRectBannerAdDetailViewController.h +++ /dev/null @@ -1,14 +0,0 @@ -// -// MPMRectBannerAdDetailViewController.h -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import -#import "MPBannerAdDetailViewController.h" - -@interface MPMRectBannerAdDetailViewController : MPBannerAdDetailViewController - -@end diff --git a/MoPubSampleApp/Controllers/MPMRectBannerAdDetailViewController.m b/MoPubSampleApp/Controllers/MPMRectBannerAdDetailViewController.m deleted file mode 100644 index 0abdb10aa..000000000 --- a/MoPubSampleApp/Controllers/MPMRectBannerAdDetailViewController.m +++ /dev/null @@ -1,42 +0,0 @@ -// -// MPMRectBannerAdDetailViewController.m -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import "MPMRectBannerAdDetailViewController.h" -#import "MPSampleAppInstanceProvider.h" -#import "MPAdInfo.h" -#import "MPConstants.h" - -@interface MPBannerAdDetailViewController () - -@property (nonatomic, strong) MPAdInfo *info; -@property (nonatomic, strong) MPAdView *adView; -@property (nonatomic, assign) BOOL didLoadAd; - -@end - -@interface MPMRectBannerAdDetailViewController () - -@end - -@implementation MPMRectBannerAdDetailViewController - -// override -- (void)configureAd -{ - CGFloat sideBuffer = (self.view.bounds.size.width - MOPUB_MEDIUM_RECT_SIZE.width) / 2; - self.adViewContainer.frame = CGRectMake(sideBuffer, self.adViewContainer.frame.origin.y, MOPUB_MEDIUM_RECT_SIZE.width, MOPUB_MEDIUM_RECT_SIZE.height); - - self.adView = [[MPSampleAppInstanceProvider sharedProvider] buildMPAdViewWithAdUnitID:self.info.ID - size:MOPUB_MEDIUM_RECT_SIZE]; - self.adView.delegate = self; - self.adView.accessibilityLabel = @"mrect_banner"; - self.adView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin; - [self.adViewContainer addSubview:self.adView]; -} - -@end diff --git a/MoPubSampleApp/Controllers/MPNativeAdDetailViewController.h b/MoPubSampleApp/Controllers/MPNativeAdDetailViewController.h deleted file mode 100644 index f265b6e97..000000000 --- a/MoPubSampleApp/Controllers/MPNativeAdDetailViewController.h +++ /dev/null @@ -1,20 +0,0 @@ -// -// MPNativeAdDetailViewController.h -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import -#import "MPViewController.h" - -@class MPAdInfo; - -extern NSString *const kNativeAdDefaultActionViewKey; - -@interface MPNativeAdDetailViewController : MPViewController - -- (id)initWithAdInfo:(MPAdInfo *)info; - -@end diff --git a/MoPubSampleApp/Controllers/MPNativeAdDetailViewController.m b/MoPubSampleApp/Controllers/MPNativeAdDetailViewController.m deleted file mode 100644 index 2a09b0d42..000000000 --- a/MoPubSampleApp/Controllers/MPNativeAdDetailViewController.m +++ /dev/null @@ -1,180 +0,0 @@ -// -// MPNativeAdDetailViewController.m -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import "MPNativeAdDetailViewController.h" -#import "MPAdInfo.h" -#import "MPNativeAdRequest.h" -#import "MPNativeAd.h" -#import "MPAdPersistenceManager.h" -#import "MPNativeAdRequestTargeting.h" -#import "MPStaticNativeAdView.h" -#import "MPNativeAdDelegate.h" -#import "MPStaticNativeAdRenderer.h" -#import "MPStaticNativeAdRendererSettings.h" -#import "MOPUBNativeVideoAdRendererSettings.h" -#import "MOPUBNativeVideoAdRenderer.h" -#import "MPNativeVideoView.h" - -NSString *const kNativeAdDefaultActionViewKey = @"kNativeAdDefaultActionButtonKey"; - -@interface MPNativeAdDetailViewController () - -@property (nonatomic, strong) MPAdInfo *info; -@property (nonatomic, strong) MPNativeAd *nativeAd; -@property (weak, nonatomic) IBOutlet UILabel *IDLabel; -@property (weak, nonatomic) IBOutlet UIView *adViewContainer; -@property (weak, nonatomic) IBOutlet UIActivityIndicatorView *spinner; -@property (weak, nonatomic) IBOutlet UILabel *failLabel; -@property (weak, nonatomic) IBOutlet UIButton *loadAdButton; -@property (weak, nonatomic) IBOutlet UITextField *keywordsTextField; - -@end - -@implementation MPNativeAdDetailViewController - -- (id)initWithAdInfo:(MPAdInfo *)info -{ - self = [super initWithNibName:@"MPNativeAdDetailViewController" bundle:nil]; - if (self) { - self.info = info; - - if ([self respondsToSelector:@selector(setEdgesForExtendedLayout:)]) { - self.edgesForExtendedLayout = UIRectEdgeNone; - } - } - return self; -} - -- (void)viewDidLoad -{ - [super viewDidLoad]; - - self.title = @"Native"; - self.IDLabel.text = self.info.ID; - self.keywordsTextField.text = self.info.keywords; - self.adViewContainer.accessibilityLabel = kNativeAdDefaultActionViewKey; - - [self loadAd:nil]; -} - -- (void)didReceiveMemoryWarning -{ - [super didReceiveMemoryWarning]; - // Dispose of any resources that can be recreated. -} - -#pragma mark - Ad Configuration - -- (IBAction)loadAd:(id)sender -{ - [self.keywordsTextField endEditing:YES]; - - self.loadAdButton.enabled = NO; - [self.spinner startAnimating]; - [self clearAd]; - - // Create and configure a renderer configuration for native ads. - MPStaticNativeAdRendererSettings *settings = [[MPStaticNativeAdRendererSettings alloc] init]; - settings.renderingViewClass = [MPStaticNativeAdView class]; - - MPNativeAdRendererConfiguration *config = [MPStaticNativeAdRenderer rendererConfigurationWithRendererSettings:settings]; - NSMutableArray * configurations = [NSMutableArray arrayWithObject:config]; - - // Video configuration. You don't need to create nativeVideoAdSettings and nativeVideoConfig unless you are using native video ads. - MOPUBNativeVideoAdRendererSettings *nativeVideoAdSettings = [[MOPUBNativeVideoAdRendererSettings alloc] init]; - nativeVideoAdSettings.renderingViewClass = [MPNativeVideoView class]; - nativeVideoAdSettings.viewSizeHandler = ^(CGFloat maximumWidth) { - return CGSizeMake(maximumWidth, 312.0f); - }; - - MPNativeAdRendererConfiguration *nativeVideoConfig = [MOPUBNativeVideoAdRenderer rendererConfigurationWithRendererSettings:nativeVideoAdSettings]; - [configurations addObject:nativeVideoConfig]; - - MPNativeAdRequest *adRequest1 = [MPNativeAdRequest requestWithAdUnitIdentifier:self.info.ID rendererConfigurations:configurations]; - MPNativeAdRequestTargeting *targeting = [[MPNativeAdRequestTargeting alloc] init]; - - targeting.keywords = self.keywordsTextField.text; - adRequest1.targeting = targeting; - self.info.keywords = adRequest1.targeting.keywords; - // persist last used keywords if this is a saved ad - if ([[MPAdPersistenceManager sharedManager] savedAdForID:self.info.ID] != nil) { - [[MPAdPersistenceManager sharedManager] addSavedAd:self.info]; - } - - [self startTimer]; - [adRequest1 startWithCompletionHandler:^(MPNativeAdRequest *request, MPNativeAd *response, NSError *error) { - [self endTimer]; - if (error) { - NSLog(@"================> %@", error); - [self configureAdLoadFail]; - } else { - self.nativeAd = response; - self.nativeAd.delegate = self; - [self displayAd]; - NSLog(@"Received Native Ad"); - } - [self.spinner stopAnimating]; - }]; -} - -- (void)clearAd -{ - [[self.adViewContainer subviews] makeObjectsPerformSelector:@selector(removeFromSuperview)]; - self.nativeAd = nil; - self.failLabel.hidden = YES; -} - -- (void)displayAd -{ - self.loadAdButton.enabled = YES; - - [[self.adViewContainer subviews] makeObjectsPerformSelector:@selector(removeFromSuperview)]; - UIView *adView = [self.nativeAd retrieveAdViewWithError:nil]; - [self.adViewContainer addSubview:adView]; - adView.frame = self.adViewContainer.bounds; -} - -- (void)configureAdLoadFail -{ - self.loadAdButton.enabled = YES; - self.failLabel.hidden = NO; -} - -#pragma mark - UITextFieldDelegate - - -- (BOOL)textFieldShouldReturn:(UITextField *)textField -{ - [textField endEditing:YES]; - - return YES; -} - -#pragma mark - MPNativeAdDelegate - -- (void)willPresentModalForNativeAd:(MPNativeAd *)nativeAd -{ - NSLog(@"Will present modal for native ad."); -} - -- (void)didDismissModalForNativeAd:(MPNativeAd *)nativeAd -{ - NSLog(@"Did dismiss modal for native ad."); -} - -- (void)willLeaveApplicationFromNativeAd:(MPNativeAd *)nativeAd -{ - NSLog(@"Will leave application from native ad."); -} - -- (UIViewController *)viewControllerForPresentingModalView -{ - return self; -} - -@end diff --git a/MoPubSampleApp/Controllers/MPNativeAdDetailViewController.xib b/MoPubSampleApp/Controllers/MPNativeAdDetailViewController.xib deleted file mode 100644 index 0db66b551..000000000 --- a/MoPubSampleApp/Controllers/MPNativeAdDetailViewController.xib +++ /dev/null @@ -1,79 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/MoPubSampleApp/Controllers/MPNativeAdPlacerCollectionViewController.h b/MoPubSampleApp/Controllers/MPNativeAdPlacerCollectionViewController.h deleted file mode 100644 index 40f6de5e7..000000000 --- a/MoPubSampleApp/Controllers/MPNativeAdPlacerCollectionViewController.h +++ /dev/null @@ -1,16 +0,0 @@ -// -// MPNativeAdPlacerCollectionViewController.h -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import -@class MPAdInfo; - -@interface MPNativeAdPlacerCollectionViewController : UICollectionViewController - -- (id)initWithAdInfo:(MPAdInfo *)info; - -@end diff --git a/MoPubSampleApp/Controllers/MPNativeAdPlacerCollectionViewController.m b/MoPubSampleApp/Controllers/MPNativeAdPlacerCollectionViewController.m deleted file mode 100644 index 2e222ccdb..000000000 --- a/MoPubSampleApp/Controllers/MPNativeAdPlacerCollectionViewController.m +++ /dev/null @@ -1,149 +0,0 @@ -// -// MPNativeAdPlacerCollectionViewController.m -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import "MPNativeAdPlacerCollectionViewController.h" -#import "MPAdInfo.h" -#import "MPCollectionViewAdPlacer.h" -#import "MPCollectionViewAdPlacerView.h" -#import "MPClientAdPositioning.h" -#import "MPNativeAdRequestTargeting.h" -#import "MPNativeAdConstants.h" -#import "MPStaticNativeAdRenderer.h" -#import "MPNativeAdRendererConfiguration.h" -#import "MPStaticNativeAdRendererSettings.h" -#import - -@interface MPNativeAdPlacerCollectionViewController () - -@property (nonatomic) MPAdInfo *adInfo; -@property (nonatomic) NSMutableArray *contentItems; -@property (nonatomic) MPCollectionViewAdPlacer *placer; - -@end - -static NSString *const kReuseIdentifier = @"cell"; - -@implementation MPNativeAdPlacerCollectionViewController - -- (id)initWithAdInfo:(MPAdInfo *)info -{ - UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init]; - layout.scrollDirection = UICollectionViewScrollDirectionHorizontal; - layout.itemSize = CGSizeMake(70, 113); - - self = [super initWithCollectionViewLayout:layout]; - if (self) { - self.title = @"Collection View Ads"; - self.adInfo = info; - } - return self; -} - -- (void)viewDidLoad -{ - [super viewDidLoad]; - - [self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:kReuseIdentifier]; - - [self setupContent]; - [self setupAdPlacer]; -} - -- (void)dealloc -{ - self.collectionView.delegate = nil; - self.collectionView.dataSource = nil; -} - -#pragma mark - Content - -- (void)setupContent -{ - self.contentItems = [NSMutableArray array]; - - for (NSInteger i = 0; i < 200; i++) { - NSInteger r = arc4random() % 256; - NSInteger g = arc4random() % 256; - NSInteger b = arc4random() % 256; - [self.contentItems addObject:[UIColor colorWithRed:r/255.0 green:g/255.0 blue:b/255.0 alpha:1.0]]; - } -} - -#pragma mark - AdPlacer - -- (void)setupAdPlacer -{ - // Create a targeting object to serve better ads. - MPNativeAdRequestTargeting *targeting = [MPNativeAdRequestTargeting targeting]; - targeting.desiredAssets = [NSSet setWithObjects:kAdTitleKey, kAdIconImageKey, kAdCTATextKey, nil]; - targeting.location = [[CLLocation alloc] initWithLatitude:37.7793 longitude:-122.4175]; - - // Create and configure a renderer configuration for native ads. - MPStaticNativeAdRendererSettings *settings = [[MPStaticNativeAdRendererSettings alloc] init]; - settings.renderingViewClass = [MPCollectionViewAdPlacerView class]; - settings.viewSizeHandler = ^(CGFloat maximumWidth) { - return CGSizeMake(70.0f, 113.0f); - }; - - MPNativeAdRendererConfiguration *config = [MPStaticNativeAdRenderer rendererConfigurationWithRendererSettings:settings]; - - // Create a collection view ad placer that uses server-side ad positioning. - self.placer = [MPCollectionViewAdPlacer placerWithCollectionView:self.collectionView viewController:self rendererConfigurations:@[config]]; - - // If you wish to use client-side ad positioning rather than configuring your ad unit on the - // MoPub website, comment out the line above and use the code below instead. - - /* - // Create an ad positioning object and register the index paths where ads should be displayed. - MPClientAdPositioning *positioning = [MPClientAdPositioning positioning]; - [positioning addFixedIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]]; - [positioning enableRepeatingPositionsWithInterval:15]; - - self.placer = [MPCollectionViewAdPlacer placerWithCollectionView:self.collectionView viewController:self adPositioning:positioning rendererConfigurations:@[config]]; - */ - - self.placer.delegate = self; - // Load ads (using a test ad unit ID). Feel free to replace this ad unit ID with your own. - [self.placer loadAdsForAdUnitID:self.adInfo.ID targeting:targeting]; -} - -#pragma mark - - -- (void)nativeAdWillPresentModalForCollectionViewAdPlacer:(MPCollectionViewAdPlacer *)placer -{ - NSLog(@"Collection view ad placer will present modal."); -} - -- (void)nativeAdDidDismissModalForCollectionViewAdPlacer:(MPCollectionViewAdPlacer *)placer -{ - NSLog(@"Collection view ad placer did dismiss modal."); -} - -- (void)nativeAdWillLeaveApplicationFromCollectionViewAdPlacer:(MPCollectionViewAdPlacer *)placer -{ - NSLog(@"Collection view ad placer will leave application."); -} - -#pragma mark - - -- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section -{ - return [self.contentItems count]; -} - -- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath -{ - /* - * IMPORTANT: add the mp_ prefix to dequeueReusableCellWithReuseIdentifier:forIndexPath:. - */ - UICollectionViewCell *cell = [collectionView mp_dequeueReusableCellWithReuseIdentifier:kReuseIdentifier forIndexPath:indexPath]; - cell.backgroundColor = self.contentItems[indexPath.item]; - return cell; -} - -@end diff --git a/MoPubSampleApp/Controllers/MPNativeAdPlacerPageViewController.h b/MoPubSampleApp/Controllers/MPNativeAdPlacerPageViewController.h deleted file mode 100644 index e956ba48d..000000000 --- a/MoPubSampleApp/Controllers/MPNativeAdPlacerPageViewController.h +++ /dev/null @@ -1,18 +0,0 @@ -// -// MPNativeAdPlacerPageViewController.h -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import -#import "MPViewController.h" - -@class MPAdInfo; - -@interface MPNativeAdPlacerPageViewController : MPViewController - -- (instancetype)initWithAdInfo:(MPAdInfo *)info; - -@end diff --git a/MoPubSampleApp/Controllers/MPNativeAdPlacerPageViewController.m b/MoPubSampleApp/Controllers/MPNativeAdPlacerPageViewController.m deleted file mode 100644 index 5638b1c32..000000000 --- a/MoPubSampleApp/Controllers/MPNativeAdPlacerPageViewController.m +++ /dev/null @@ -1,283 +0,0 @@ -// -// MPNativeAdPlacerPageViewController.m -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import "MPNativeAdPlacerPageViewController.h" -#import "MPAdInfo.h" -#import "MPStreamAdPlacer.h" -#import "MPClientAdPositioning.h" -#import "MPNativeAdPageView.h" -#import "MPNativeAdRendererConfiguration.h" -#import "MPStaticNativeAdRenderer.h" -#import "MPStaticNativeAdRendererSettings.h" - -// This variable will extend the ad placer's visible range by kVisiblePathLookAhead view controllers. -const NSUInteger kVisiblePathLookAhead = 0; -const NSUInteger kBeginningNumberOfPages = 8; - -@interface MPNativeAdPlacerPageViewController () - -@property (nonatomic, readonly) MPStreamAdPlacer *streamAdPlacer; -@property (nonatomic, readonly) MPAdInfo *adInfo; -@property (nonatomic, readonly) NSMutableArray *contentViewControllers; -@property (nonatomic, readonly) UIPageViewController *pageViewController; -@property (nonatomic, readonly) NSMutableArray *visibleIndexPaths; -@property (nonatomic, readwrite) UIActionSheet *actionSheet; -@property (nonatomic, readwrite) UIAlertView *deleteAlertView; -@property (nonatomic, readwrite) UIAlertView *insertAlertView; -@property (nonatomic, readwrite) NSInteger adCount; - -@end - -@implementation MPNativeAdPlacerPageViewController - -- (instancetype)initWithAdInfo:(MPAdInfo *)info -{ - self = [super init]; - if (self) { - self.title = @"Page View Controller Ad Placer"; - _adInfo = info; - - _pageViewController = [[UIPageViewController alloc] initWithTransitionStyle:UIPageViewControllerTransitionStyleScroll navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal options:nil]; - _pageViewController.dataSource = self; - _pageViewController.delegate = self; - - // Set up where we want to place our ads within the page view controller. - MPClientAdPositioning *positioning = [MPClientAdPositioning positioning]; - [positioning addFixedIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]]; - [positioning enableRepeatingPositionsWithInterval:2]; - - MPStaticNativeAdRendererSettings *settings = [[MPStaticNativeAdRendererSettings alloc] init]; - settings.renderingViewClass = [MPNativeAdPageView class]; - settings.viewSizeHandler = ^(CGFloat maxWidth) { - return self.view.bounds.size; - }; - - MPNativeAdRendererConfiguration *config = [MPStaticNativeAdRenderer rendererConfigurationWithRendererSettings:settings]; - _streamAdPlacer = [MPStreamAdPlacer placerWithViewController:self adPositioning:positioning rendererConfigurations:@[config]]; - _streamAdPlacer.delegate = self; - - // Create a bunch of alternating red/green view controllers. - _contentViewControllers = [[NSMutableArray alloc] init]; - - for (NSUInteger i = 0; i < kBeginningNumberOfPages; ++i) { - UIViewController *vc = [[UIViewController alloc] init]; - - if (i % 2 == 0) { - vc.view.backgroundColor = [UIColor redColor]; - } else { - vc.view.backgroundColor = [UIColor greenColor]; - } - - [_contentViewControllers addObject:vc]; - } - - _visibleIndexPaths = [NSMutableArray array]; - - if ([self respondsToSelector:@selector(setEdgesForExtendedLayout:)]) { - self.edgesForExtendedLayout = UIRectEdgeNone; - } - - self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemEdit target:self action:@selector(editButtonPressed:)]; - - _deleteAlertView = [[UIAlertView alloc] initWithTitle:@"Choose which page(s) to delete" message:@"You can delete multiple pages by separating indices with commas. Do not delete ads." delegate:self cancelButtonTitle:@"Cancel" otherButtonTitles:@"Delete", nil]; - _deleteAlertView.alertViewStyle = UIAlertViewStylePlainTextInput; - - _insertAlertView = [[UIAlertView alloc] initWithTitle:@"Choose which page(s) to insert" message:@"You can insert multiple pages by separating indices with commas." delegate:self cancelButtonTitle:@"Cancel" otherButtonTitles:@"Insert", nil]; - _insertAlertView.alertViewStyle = UIAlertViewStylePlainTextInput; - } - - return self; -} - -- (void)viewDidLoad -{ - [super viewDidLoad]; - - self.view.backgroundColor = [UIColor blackColor]; - - [[self.pageViewController view] setFrame:[[self view] bounds]]; - [self addChildViewController:self.pageViewController]; - [[self view] addSubview:[self.pageViewController view]]; - [self.pageViewController didMoveToParentViewController:self]; - [self.pageViewController setViewControllers:@[self.contentViewControllers[0]] direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:nil]; - - // Upon load, set our visible index paths and issue a load ads request. - [self updateVisibleIndexPaths]; - [self.streamAdPlacer setItemCount:self.contentViewControllers.count forSection:0]; - [self.streamAdPlacer loadAdsForAdUnitID:self.adInfo.ID]; -} - -- (void)editButtonPressed:(UIBarButtonItem *)item -{ - self.actionSheet = [[UIActionSheet alloc] initWithTitle:nil delegate:self cancelButtonTitle:@"Cancel" destructiveButtonTitle:@"Delete Page(s)" otherButtonTitles:@"Insert Page(s)", @"Re-insert ads", nil]; - [self.actionSheet showInView:self.view]; -} - -#pragma mark - UIPageViewControllerDataSource - -- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController -{ - NSUInteger newPos = [self.contentViewControllers indexOfObject:viewController] + 1; - - if (newPos < self.contentViewControllers.count) { - return self.contentViewControllers[newPos]; - } else { - return nil; - } -} - -- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController -{ - NSInteger newPos = [self.contentViewControllers indexOfObject:viewController] - 1; - - if (newPos >= 0) { - return self.contentViewControllers[newPos]; - } else { - return nil; - } -} - -- (NSInteger)presentationCountForPageViewController:(UIPageViewController *)pageViewController -{ - return self.contentViewControllers.count; -} - -- (NSInteger)presentationIndexForPageViewController:(UIPageViewController *)pageViewController -{ - if (self.pageViewController.viewControllers.count) { - return [self.contentViewControllers indexOfObject:self.pageViewController.viewControllers[0]]; - } else { - return 0; - } -} - -#pragma mark - UIPageViewControllerDelegate - -- (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed -{ - [self updateVisibleIndexPaths]; -} - -#pragma mark - Private - -- (void)updateVisibleIndexPaths -{ - NSUInteger pos = [self.contentViewControllers indexOfObject:self.pageViewController.viewControllers[0]]; - - [self.visibleIndexPaths removeAllObjects]; - - for (NSInteger i = pos; i <= pos + kVisiblePathLookAhead; ++i) { - if (i < self.contentViewControllers.count) { - [self.visibleIndexPaths addObject:[NSIndexPath indexPathForRow:i inSection:0]]; - } - } - - self.streamAdPlacer.visibleIndexPaths = self.visibleIndexPaths; -} - -#pragma mark - MPStreamAdPlacerDelegate - -- (void)adPlacer:(MPStreamAdPlacer *)adPlacer didLoadAdAtIndexPath:(NSIndexPath *)indexPath -{ - CGRect frame = CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height); - UIView *adView = [[UIView alloc] initWithFrame:frame]; - UIViewController *vc = [[UIViewController alloc] init]; - [vc.view addSubview:adView]; - [vc.view setFrame:frame]; - - [self.contentViewControllers insertObject:vc atIndex:indexPath.row]; - [self.streamAdPlacer renderAdAtIndexPath:indexPath inView:adView]; - - [self.pageViewController setViewControllers:@[self.pageViewController.viewControllers[0]] direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:nil]; - [self updateVisibleIndexPaths]; - - self.adCount++; -} - -- (void)adPlacer:(MPStreamAdPlacer *)adPlacer didRemoveAdsAtIndexPaths:(NSArray *)indexPaths -{ - NSMutableIndexSet *deletionIndices = [NSMutableIndexSet indexSet]; - - for (NSIndexPath *indexPath in indexPaths) { - [deletionIndices addIndex:indexPath.row]; - } - - [self.contentViewControllers removeObjectsAtIndexes:deletionIndices]; - - [self.pageViewController setViewControllers:@[self.contentViewControllers[0]] direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:nil]; - [self updateVisibleIndexPaths]; - - self.adCount -= deletionIndices.count; -} - -#pragma mark - UIActionSheetDelegate - -- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex -{ - switch (buttonIndex) { - case 0: - [self.deleteAlertView show]; - break; - case 1: - [self.insertAlertView show]; - break; - case 2: - [self.streamAdPlacer loadAdsForAdUnitID:self.adInfo.ID]; - break; - case 3: - break; - default: - break; - } -} - -#pragma mark - UIAlertViewDelegate - -- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex -{ - if (buttonIndex == 1) { - if (alertView == self.deleteAlertView) { - NSString *text = [alertView textFieldAtIndex:0].text; - NSArray *deletionIndicesStr = [text componentsSeparatedByString:@","]; - NSMutableArray *deletionIndexPaths = [NSMutableArray array]; - NSMutableIndexSet *deletionIndices = [NSMutableIndexSet indexSet]; - - for (NSString *indexStr in deletionIndicesStr) { - NSInteger indexNum = [indexStr integerValue]; - [deletionIndexPaths addObject:[NSIndexPath indexPathForRow:indexNum inSection:0]]; - [deletionIndices addIndex:indexNum]; - } - - [self.contentViewControllers removeObjectsAtIndexes:deletionIndices]; - [self.streamAdPlacer deleteItemsAtIndexPaths:deletionIndexPaths]; - - // Don't worry about the logic to shift or keep the same place. Just go to the first view controller. - [self.pageViewController setViewControllers:@[self.contentViewControllers.firstObject] direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:nil]; - } else if (alertView == self.insertAlertView) { - NSString *text = [alertView textFieldAtIndex:0].text; - NSArray *insertionIndicesStr = [text componentsSeparatedByString:@","]; - NSMutableArray *insertionIndexPaths = [NSMutableArray array]; - - for (NSString *indexStr in insertionIndicesStr) { - NSInteger indexNum = [indexStr integerValue]; - [insertionIndexPaths addObject:[NSIndexPath indexPathForRow:indexNum inSection:0]]; - - UIViewController *vc = [[UIViewController alloc] init]; - vc.view.backgroundColor = [UIColor blueColor]; - [self.contentViewControllers insertObject:vc atIndex:indexNum]; - } - - [self.streamAdPlacer insertItemsAtIndexPaths:insertionIndexPaths]; - [self.pageViewController setViewControllers:@[self.pageViewController.viewControllers[0]] direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:nil]; - } - - [self.streamAdPlacer setItemCount:self.contentViewControllers.count-self.adCount forSection:0]; - } -} - -@end diff --git a/MoPubSampleApp/Controllers/MPNativeAdPlacerTableViewController.h b/MoPubSampleApp/Controllers/MPNativeAdPlacerTableViewController.h deleted file mode 100644 index e16663e38..000000000 --- a/MoPubSampleApp/Controllers/MPNativeAdPlacerTableViewController.h +++ /dev/null @@ -1,17 +0,0 @@ -// -// MPNativeAdPlacerTableViewController.h -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import -@class MPAdInfo; - -@interface MPNativeAdPlacerTableViewController : UITableViewController - -- (id)initWithAdInfo:(MPAdInfo *)info; - -@end - diff --git a/MoPubSampleApp/Controllers/MPNativeAdPlacerTableViewController.m b/MoPubSampleApp/Controllers/MPNativeAdPlacerTableViewController.m deleted file mode 100644 index 85be33ead..000000000 --- a/MoPubSampleApp/Controllers/MPNativeAdPlacerTableViewController.m +++ /dev/null @@ -1,189 +0,0 @@ -// -// MPNativeAdPlacerTableViewController.m -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import "MPNativeAdPlacerTableViewController.h" - -#import "MPNativeAdRequestTargeting.h" -#import "MPTableViewAdPlacerView.h" -#import "MPAdInfo.h" -#import "MPTableViewAdPlacer.h" -#import "MPClientAdPositioning.h" -#import "MPNativeAdConstants.h" -#import "MPStaticNativeAdRendererSettings.h" -#import "MPStaticNativeAdRenderer.h" -#import "MPNativeAdRendererConfiguration.h" -#import -#import "MPNativeVideoTableViewAdPlacerView.h" -#import "MOPUBNativeVideoAdRenderer.h" -#import "MOPUBNativeVideoAdRendererSettings.h" - -static NSString *kDefaultCellIdentifier = @"MoPubSampleAppTableViewAdPlacerCell"; - -@interface MPNativeAdPlacerTableViewController () - -@property (nonatomic, strong) NSMutableArray *contentItems; -@property (nonatomic, strong) MPAdInfo *adInfo; -@property (nonatomic, strong) MPTableViewAdPlacer *placer; - -@end - -@implementation MPNativeAdPlacerTableViewController - -#pragma mark - Object Lifecycle - -- (id)initWithAdInfo:(MPAdInfo *)info -{ - self = [super initWithStyle:UITableViewStylePlain]; - if (self) { - self.title = @"Table View Ad Placer"; - self.adInfo = info; - self.contentItems = [NSMutableArray array]; - - if ([self respondsToSelector:@selector(setEdgesForExtendedLayout:)]) { - self.edgesForExtendedLayout = UIRectEdgeNone; - } - } - return self; -} - -- (void)viewDidLoad -{ - [super viewDidLoad]; - - if ([self.tableView respondsToSelector:@selector(registerClass:forCellReuseIdentifier:)]) { - [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:kDefaultCellIdentifier]; - } - - [self setupContent]; - [self setupAdPlacer]; -} - -- (void)dealloc -{ - self.tableView.delegate = nil; - self.tableView.dataSource = nil; -} - -#pragma mark - Content - -- (void)setupContent -{ - self.contentItems = [NSMutableArray array]; - - for (NSString *fontFamilyName in [UIFont familyNames]) { - for (NSString *fontName in [UIFont fontNamesForFamilyName:fontFamilyName]) { - [self.contentItems addObject:fontName]; - } - } - - [self.contentItems sortUsingSelector:@selector(compare:)]; -} - -#pragma mark - AdPlacer - -- (void)setupAdPlacer -{ - // Create a targeting object to serve better ads. - MPNativeAdRequestTargeting *targeting = [MPNativeAdRequestTargeting targeting]; - targeting.location = [[CLLocation alloc] initWithLatitude:37.7793 longitude:-122.4175]; - targeting.desiredAssets = [NSSet setWithObjects:kAdIconImageKey, kAdMainImageKey, kAdCTATextKey, kAdTextKey, kAdTitleKey, nil]; - - // Create and configure a renderer configuration. - - // Static native ads - MPStaticNativeAdRendererSettings *nativeAdSettings = [[MPStaticNativeAdRendererSettings alloc] init]; - nativeAdSettings.renderingViewClass = [MPTableViewAdPlacerView class]; - nativeAdSettings.viewSizeHandler = ^(CGFloat maximumWidth) { - return CGSizeMake(maximumWidth, 312.0f); - }; - MPNativeAdRendererConfiguration *nativeAdConfig = [MPStaticNativeAdRenderer rendererConfigurationWithRendererSettings:nativeAdSettings]; - nativeAdConfig.supportedCustomEvents = @[@"MPMoPubNativeCustomEvent", @"FlurryNativeCustomEvent"]; - - // Native video ads. You don't need to create nativeVideoAdSettings and nativeVideoConfig unless you are using native video ads. - MOPUBNativeVideoAdRendererSettings *nativeVideoAdSettings = [[MOPUBNativeVideoAdRendererSettings alloc] init]; - nativeVideoAdSettings.renderingViewClass = [MPNativeVideoTableViewAdPlacerView class]; - nativeVideoAdSettings.viewSizeHandler = ^(CGFloat maximumWidth) { - return CGSizeMake(maximumWidth, 312.0f); - }; - MPNativeAdRendererConfiguration *nativeVideoConfig = [MOPUBNativeVideoAdRenderer rendererConfigurationWithRendererSettings:nativeVideoAdSettings]; - - // Create a table view ad placer that uses server-side ad positioning. - self.placer = [MPTableViewAdPlacer placerWithTableView:self.tableView viewController:self rendererConfigurations:@[nativeAdConfig, nativeVideoConfig]]; - - // If you wish to use client-side ad positioning rather than configuring your ad unit on the - // MoPub website, comment out the line above and use the code below instead. - - // Create an ad positioning object and register the index paths where ads should be displayed. - /* - MPClientAdPositioning *positioning = [MPClientAdPositioning positioning]; - [positioning addFixedIndexPath:[NSIndexPath indexPathForRow:2 inSection:0]]; - [positioning addFixedIndexPath:[NSIndexPath indexPathForRow:30 inSection:0]]; - [positioning addFixedIndexPath:[NSIndexPath indexPathForRow:60 inSection:0]]; - [positioning addFixedIndexPath:[NSIndexPath indexPathForRow:90 inSection:0]]; - [positioning enableRepeatingPositionsWithInterval:10]; - - - self.placer = [MPTableViewAdPlacer placerWithTableView:self.tableView viewController:self adPositioning:positioning rendererConfigurations:@[nativeAdConfig, nativeVideoConfig]]; - */ - - self.placer.delegate = self; - // Load ads (using a test ad unit ID). Feel free to replace this ad unit ID with your own. - [self.placer loadAdsForAdUnitID:self.adInfo.ID targeting:targeting]; -} - -#pragma mark - UITableViewAdPlacerDelegate - -- (void)nativeAdWillPresentModalForTableViewAdPlacer:(MPTableViewAdPlacer *)placer -{ - NSLog(@"Table view ad placer will present modal."); -} - -- (void)nativeAdDidDismissModalForTableViewAdPlacer:(MPTableViewAdPlacer *)placer -{ - NSLog(@"Table view ad placer did dismiss modal."); -} - -- (void)nativeAdWillLeaveApplicationFromTableViewAdPlacer:(MPTableViewAdPlacer *)placer -{ - NSLog(@"Table view ad placer will leave application."); -} - -#pragma mark - UITableViewDataSource - -- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section -{ - return [self.contentItems count]; -} - -- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath -{ - /* - * IMPORTANT: add the mp_ prefix to dequeueReusableCellWithReuseIdentifier:forIndexPath:. - */ - UITableViewCell *cell = [tableView mp_dequeueReusableCellWithIdentifier:kDefaultCellIdentifier forIndexPath:indexPath]; - if (!cell) { - cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:kDefaultCellIdentifier]; - } - - NSString *fontName = self.contentItems[indexPath.row]; - cell.textLabel.font = [UIFont fontWithName:fontName size:20.0]; - cell.textLabel.text = fontName; - return cell; -} - -#pragma mark - UITableViewDelegate - -- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath -{ - /* - * IMPORTANT: add the mp_ prefix to deselectRowAtIndexPath:animated:. - */ - [tableView mp_deselectRowAtIndexPath:indexPath animated:YES]; -} - -@end diff --git a/MoPubSampleApp/Controllers/MPRewardedVideoAdDetailViewController.h b/MoPubSampleApp/Controllers/MPRewardedVideoAdDetailViewController.h deleted file mode 100644 index 1f9e8e680..000000000 --- a/MoPubSampleApp/Controllers/MPRewardedVideoAdDetailViewController.h +++ /dev/null @@ -1,32 +0,0 @@ -// -// MPRewardedVideoAdDetailViewController.h -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import -#import "MPViewController.h" - -@class MPAdInfo; - -@interface MPRewardedVideoAdDetailViewController : MPViewController - -@property (weak, nonatomic) IBOutlet UILabel *titleLabel; -@property (weak, nonatomic) IBOutlet UILabel *IDLabel; -@property (weak, nonatomic) IBOutlet UIButton *showButton; -@property (weak, nonatomic) IBOutlet UIButton *loadButton; -@property (weak, nonatomic) IBOutlet UIActivityIndicatorView *spinner; -@property (weak, nonatomic) IBOutlet UILabel *failLabel; -@property (weak, nonatomic) IBOutlet UILabel *expireLabel; -@property (weak, nonatomic) IBOutlet UILabel *willAppearLabel; -@property (weak, nonatomic) IBOutlet UILabel *didAppearLabel; -@property (weak, nonatomic) IBOutlet UILabel *willDisappearLabel; -@property (weak, nonatomic) IBOutlet UILabel *didDisappearLabel; -@property (weak, nonatomic) IBOutlet UILabel *didReceiveTapLabel; -@property (weak, nonatomic) IBOutlet UILabel *shouldRewardLabel; - -- (id)initWithAdInfo:(MPAdInfo *)adInfo; - -@end diff --git a/MoPubSampleApp/Controllers/MPRewardedVideoAdDetailViewController.m b/MoPubSampleApp/Controllers/MPRewardedVideoAdDetailViewController.m deleted file mode 100644 index a06661d54..000000000 --- a/MoPubSampleApp/Controllers/MPRewardedVideoAdDetailViewController.m +++ /dev/null @@ -1,201 +0,0 @@ -// -// MPRewardedVideoAdDetailViewController.m -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import "MPRewardedVideoAdDetailViewController.h" -#import "MPAdPersistenceManager.h" -#import "MPRewardedVideo.h" -#import "MPAdInfo.h" -#import "MoPub.h" - -@interface MPRewardedVideoAdDetailViewController () - -@property (weak, nonatomic) IBOutlet UITextField *keywordsTextField; -@property (weak, nonatomic) IBOutlet UITextField *customDataTextField; -@property (weak, nonatomic) IBOutlet UIPickerView *rewardPickerView; -@property (nonatomic, assign) NSInteger selectedRewardIndex; -@property (nonatomic, strong) MPAdInfo *info; - -@end - -@implementation MPRewardedVideoAdDetailViewController - -- (id)initWithAdInfo:(MPAdInfo *)adInfo -{ - self = [super init]; - if (self) { - self.info = adInfo; - - if ([self respondsToSelector:@selector(setEdgesForExtendedLayout:)]) { - self.edgesForExtendedLayout = UIRectEdgeNone; - } - } - return self; -} - -- (void)viewDidLoad -{ - self.title = @"Rewarded Video"; - self.titleLabel.text = self.info.title; - self.IDLabel.text = self.info.ID; - self.showButton.hidden = YES; - self.rewardPickerView.dataSource = self; - self.rewardPickerView.delegate = self; - self.keywordsTextField.text = self.info.keywords; - - [MPRewardedVideo setDelegate:self forAdUnitId:self.info.ID]; - - [super viewDidLoad]; -} - -- (void)dealloc -{ - [MPRewardedVideo removeDelegateForAdUnitId:self.info.ID]; -} - -- (IBAction)didTapLoadButton:(id)sender -{ - [self.keywordsTextField endEditing:YES]; - - [self.spinner startAnimating]; - self.showButton.hidden = YES; - self.loadButton.enabled = NO; - self.expireLabel.hidden = YES; - self.failLabel.hidden = YES; - self.willAppearLabel.alpha = 0.1; - self.didAppearLabel.alpha = 0.1; - self.willDisappearLabel.alpha = 0.1; - self.didDisappearLabel.alpha = 0.1; - self.didReceiveTapLabel.alpha = 0.1; - self.shouldRewardLabel.alpha = 0.1; - self.rewardPickerView.userInteractionEnabled = false; - [self.rewardPickerView reloadAllComponents]; - - self.info.keywords = self.keywordsTextField.text; - // persist last used keywords if this is a saved ad - if ([[MPAdPersistenceManager sharedManager] savedAdForID:self.info.ID] != nil) { - [[MPAdPersistenceManager sharedManager] addSavedAd:self.info]; - } - - - // create Instance Mediation Settings as needed here - [self startTimer]; - [MPRewardedVideo loadRewardedVideoAdWithAdUnitID:self.info.ID keywords:self.info.keywords userDataKeywords:nil location:nil customerId:@"testCustomerId" mediationSettings:@[]]; -} - -- (IBAction)didTapShowButton:(id)sender -{ - if ([MPRewardedVideo hasAdAvailableForAdUnitID:self.info.ID]) { - NSArray * rewards = [MPRewardedVideo availableRewardsForAdUnitID:self.info.ID]; - MPRewardedVideoReward * reward = rewards[self.selectedRewardIndex]; - [MPRewardedVideo presentRewardedVideoAdForAdUnitID:self.info.ID fromViewController:self withReward:reward customData:self.customDataTextField.text]; - } -} - -- (BOOL)textFieldShouldReturn:(UITextField *)textField -{ - [textField endEditing:YES]; - - return YES; -} - -#pragma mark - - -- (void)rewardedVideoAdDidLoadForAdUnitID:(NSString *)adUnitID -{ - [self endTimer]; - [self.spinner stopAnimating]; - self.showButton.hidden = NO; - self.loadButton.enabled = YES; - - self.rewardPickerView.userInteractionEnabled = true; - [self.rewardPickerView reloadAllComponents]; -} - -- (void)rewardedVideoAdDidFailToLoadForAdUnitID:(NSString *)adUnitID error:(NSError *)error -{ - [self endTimer]; - self.failLabel.hidden = NO; - self.loadButton.enabled = YES; - [self.spinner stopAnimating]; -} - -- (void)rewardedVideoAdWillAppearForAdUnitID:(NSString *)adUnitID -{ - self.willAppearLabel.alpha = 1.0; -} - -- (void)rewardedVideoAdDidAppearForAdUnitID:(NSString *)adUnitID -{ - self.didAppearLabel.alpha = 1.0; -} - -- (void)rewardedVideoAdWillDisappearForAdUnitID:(NSString *)adUnitID -{ - self.willDisappearLabel.alpha = 1.0; -} - -- (void)rewardedVideoAdDidDisappearForAdUnitID:(NSString *)adUnitID -{ - self.showButton.hidden = YES; - self.didDisappearLabel.alpha = 1.0; -} - -- (void)rewardedVideoAdDidExpireForAdUnitID:(NSString *)adUnitID -{ - self.expireLabel.hidden = NO; - self.loadButton.enabled = YES; - self.showButton.hidden = YES; - [self.spinner stopAnimating]; -} - -- (void)rewardedVideoAdDidReceiveTapEventForAdUnitID:(NSString *)adUnitID -{ - self.didReceiveTapLabel.alpha = 1.0; -} - -- (void)rewardedVideoWillLeaveApplicationForAdUnitID:(NSString *)adUnitID -{ - -} - -- (void)rewardedVideoAdShouldRewardForAdUnitID:(NSString *)adUnitID reward:(MPRewardedVideoReward *)reward -{ - self.shouldRewardLabel.alpha = 1.0; -} - -#pragma mark - UIPickerViewDataSource - -- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView { - return 1; -} - -- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component { - if (![MPRewardedVideo hasAdAvailableForAdUnitID:self.info.ID]) { - return 0; - } - - NSArray * rewards = [MPRewardedVideo availableRewardsForAdUnitID:self.info.ID]; - return rewards.count; -} - -#pragma mark - UIPickerViewDelegate - -- (NSAttributedString *)pickerView:(UIPickerView *)pickerView attributedTitleForRow:(NSInteger)row forComponent:(NSInteger)component { - NSArray * rewards = [MPRewardedVideo availableRewardsForAdUnitID:self.info.ID]; - MPRewardedVideoReward * reward = rewards[row]; - NSString * title = [NSString stringWithFormat:@"%@ %@", reward.amount, reward.currencyType]; - NSAttributedString * attributedTitle = [[NSAttributedString alloc] initWithString:title attributes:@{NSForegroundColorAttributeName:[UIColor whiteColor]}]; - - return attributedTitle; -} - -- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component { - self.selectedRewardIndex = row; -} - -@end diff --git a/MoPubSampleApp/Controllers/MPRewardedVideoAdDetailViewController.xib b/MoPubSampleApp/Controllers/MPRewardedVideoAdDetailViewController.xib deleted file mode 100644 index 93f9bbba9..000000000 --- a/MoPubSampleApp/Controllers/MPRewardedVideoAdDetailViewController.xib +++ /dev/null @@ -1,175 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/MoPubSampleApp/Controllers/MPViewController.h b/MoPubSampleApp/Controllers/MPViewController.h deleted file mode 100644 index 1326c9272..000000000 --- a/MoPubSampleApp/Controllers/MPViewController.h +++ /dev/null @@ -1,16 +0,0 @@ -// -// MPViewController.h -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import - -@interface MPViewController : UIViewController -@property (nonatomic, readonly) NSTimeInterval lastTimeInterval; - -- (void)startTimer; -- (void)endTimer; -@end diff --git a/MoPubSampleApp/Controllers/MPViewController.m b/MoPubSampleApp/Controllers/MPViewController.m deleted file mode 100644 index 8764e6dcb..000000000 --- a/MoPubSampleApp/Controllers/MPViewController.m +++ /dev/null @@ -1,51 +0,0 @@ -// -// MPViewController.m -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import "MPViewController.h" - -@interface MPViewController () -@property (nonatomic, strong) NSDate * startTime; -@property (nonatomic, strong) NSDate * endTime; -@property (nonatomic, strong) UILabel * timeLabel; -@end - -@implementation MPViewController - -- (void)viewDidLoad { - [super viewDidLoad]; - - self.timeLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 300, 20)]; - self.timeLabel.font = [UIFont systemFontOfSize:10]; - self.timeLabel.textColor = [UIColor whiteColor]; - self.timeLabel.textAlignment = NSTextAlignmentRight; - [self.view addSubview:self.timeLabel]; - - [self.timeLabel.topAnchor constraintEqualToAnchor:self.view.topAnchor].active = YES; - [self.timeLabel.rightAnchor constraintEqualToAnchor:self.view.rightAnchor].active = YES; - [self.timeLabel.heightAnchor constraintEqualToConstant:20].active = YES; - [self.timeLabel.widthAnchor constraintEqualToConstant:300].active = YES; -} - -- (NSTimeInterval)lastTimeInterval { - if (self.endTime == nil || self.startTime == nil) { - return 0; - } - - return (self.endTime.timeIntervalSince1970 - self.startTime.timeIntervalSince1970); -} - -- (void)startTimer { - self.startTime = [NSDate date]; -} - -- (void)endTimer { - self.endTime = [NSDate date]; - self.timeLabel.text = [NSString stringWithFormat:@"%f seconds", self.lastTimeInterval]; -} - -@end diff --git a/MoPubSampleApp/Domain/MPAdInfo.h b/MoPubSampleApp/Domain/MPAdInfo.h deleted file mode 100644 index e0eb94b94..000000000 --- a/MoPubSampleApp/Domain/MPAdInfo.h +++ /dev/null @@ -1,44 +0,0 @@ -// -// MPAdInfo.h -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import - -// Valid dictionary keys used for `MPAdInfo` initialization. -extern NSString * const kAdInfoIdKey; // Non-nil, non-empty `NSString` -extern NSString * const kAdInfoFormatKey; // Non-nil, non-empty `NSString` -extern NSString * const kAdInfoKeywordsKey; // Optional comma-delimited `NSString` -extern NSString * const kAdInfoNameKey; // Optional name for the ad unit as a `NSString` - -typedef NS_ENUM(NSInteger, MPAdInfoType) { - MPAdInfoBanner, - MPAdInfoInterstitial, - MPAdInfoMRectBanner, - MPAdInfoLeaderboardBanner, - MPAdInfoNative, - MPAdInfoNativeTableViewPlacer, - MPAdInfoNativePageViewControllerPlacer, - MPAdInfoNativeInCollectionView, - MPAdInfoRewardedVideo -}; - -@interface MPAdInfo : NSObject - -@property (nonatomic, copy) NSString *title; -@property (nonatomic, copy) NSString *ID; -@property (nonatomic, assign) MPAdInfoType type; -@property (nonatomic, copy) NSString *keywords; - -+ (NSArray *)bannerAds; -+ (NSArray *)interstitialAds; -+ (NSArray *)rewardedVideoAds; -+ (NSArray *)nativeAds; -+ (MPAdInfo *)infoWithTitle:(NSString *)title ID:(NSString *)ID type:(MPAdInfoType)type; -+ (MPAdInfo *)infoWithDictionary:(NSDictionary *)dict; -+ (NSDictionary *)supportedAddedAdTypes; - -@end diff --git a/MoPubSampleApp/Domain/MPAdInfo.m b/MoPubSampleApp/Domain/MPAdInfo.m deleted file mode 100644 index 9dd72cc10..000000000 --- a/MoPubSampleApp/Domain/MPAdInfo.m +++ /dev/null @@ -1,127 +0,0 @@ -// -// MPAdInfo.m -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import "MPAdInfo.h" - -#import - -NSString * const kAdInfoIdKey = @"adUnitId"; -NSString * const kAdInfoFormatKey = @"format"; -NSString * const kAdInfoKeywordsKey = @"keywords"; -NSString * const kAdInfoNameKey = @"name"; - -@implementation MPAdInfo - -+ (NSDictionary *)supportedAddedAdTypes -{ - static NSDictionary *adTypes = nil; - - static dispatch_once_t once; - dispatch_once(&once, ^{ - adTypes = @{@"Banner":@(MPAdInfoBanner), @"Interstitial":@(MPAdInfoInterstitial), @"MRect":@(MPAdInfoMRectBanner), @"Leaderboard":@(MPAdInfoLeaderboardBanner), @"Native":@(MPAdInfoNative), @"Rewarded Video":@(MPAdInfoRewardedVideo), @"Rewarded":@(MPAdInfoRewardedVideo), @"NativeTablePlacer":@(MPAdInfoNativeTableViewPlacer), @"NativeCollectionPlacer":@(MPAdInfoNativeInCollectionView)}; - }); - - return adTypes; -} - -+ (NSArray *)bannerAds -{ - NSMutableArray *ads = [NSMutableArray array]; - - [ads addObjectsFromArray:@[ - [MPAdInfo infoWithTitle:@"HTML Banner Ad" ID:@"0ac59b0996d947309c33f59d6676399f" type:MPAdInfoBanner], - [MPAdInfo infoWithTitle:@"MRAID Banner Ad" ID:@"23b49916add211e281c11231392559e4" type:MPAdInfoBanner], - [MPAdInfo infoWithTitle:@"HTML MRECT Banner Ad" ID:@"2aae44d2ab91424d9850870af33e5af7" type:MPAdInfoMRectBanner], - ]]; - - if(UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) - { - [ads addObject:[MPAdInfo infoWithTitle:@"HTML Leaderboard Banner Ad" ID:@"d456ea115eec497ab33e02531a5efcbc" type:MPAdInfoLeaderboardBanner]]; - } - - return ads; -} - -+ (NSArray *)interstitialAds -{ - return @[ - [MPAdInfo infoWithTitle:@"HTML Interstitial Ad" ID:@"4f117153f5c24fa6a3a92b818a5eb630" type:MPAdInfoInterstitial], - [MPAdInfo infoWithTitle:@"MRAID Interstitial Ad" ID:@"3aba0056add211e281c11231392559e4" type:MPAdInfoInterstitial], - ]; -} - -+ (NSArray *)rewardedVideoAds -{ - return @[ - [MPAdInfo infoWithTitle:@"Rewarded Video Ad" ID:@"8f000bd5e00246de9c789eed39ff6096" type:MPAdInfoRewardedVideo], - [MPAdInfo infoWithTitle:@"Rewarded Rich Media Ad" ID:@"98c29e015e7346bd9c380b1467b33850" type:MPAdInfoRewardedVideo], - ]; -} - -+ (NSArray *)nativeAds -{ - return @[ - [MPAdInfo infoWithTitle:@"Native Ad" ID:@"76a3fefaced247959582d2d2df6f4757" type:MPAdInfoNative], - [MPAdInfo infoWithTitle:@"Native Video Ad" ID:@"b2b67c2a8c0944eda272ed8e4ddf7ed4" type:MPAdInfoNative], - [MPAdInfo infoWithTitle:@"Native Ad (CollectionView Placer)" ID:@"76a3fefaced247959582d2d2df6f4757" type:MPAdInfoNativeInCollectionView], - [MPAdInfo infoWithTitle:@"Native Ad (TableView Placer)" ID:@"76a3fefaced247959582d2d2df6f4757" type:MPAdInfoNativeTableViewPlacer], - [MPAdInfo infoWithTitle:@"Native Video Ad (TableView Placer)" ID:@"b2b67c2a8c0944eda272ed8e4ddf7ed4" type:MPAdInfoNativeTableViewPlacer], - ]; -} - -+ (MPAdInfo *)infoWithTitle:(NSString *)title ID:(NSString *)ID type:(MPAdInfoType)type { - MPAdInfo *info = [[MPAdInfo alloc] init]; - info.title = title; - info.ID = ID; - info.type = type; - return info; -} - -+ (MPAdInfo *)infoWithDictionary:(NSDictionary *)dict -{ - // Extract the required fields from the dictionary. If either of the required fields - // is invalid, object creation will not be performed. - NSString * adUnitId = [[dict objectForKey:kAdInfoIdKey] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; - NSString * formatString = [[dict objectForKey:kAdInfoFormatKey] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; - NSString * keywords = [[dict objectForKey:kAdInfoKeywordsKey] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; - NSString * name = [[dict objectForKey:kAdInfoNameKey] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; - - if (adUnitId.length == 0 || formatString.length == 0 || (formatString != nil && [[self supportedAddedAdTypes] objectForKey:formatString] == nil)) { - return nil; - } - - MPAdInfoType format = (MPAdInfoType)[[[self supportedAddedAdTypes] objectForKey:formatString] integerValue]; - NSString * title = (name != nil ? name : adUnitId); - MPAdInfo * info = [MPAdInfo infoWithTitle:title ID:adUnitId type:format]; - info.keywords = keywords; - - return info; -} - -- (id)initWithCoder:(NSCoder *)aDecoder -{ - self = [super init]; - if(self != nil) - { - self.title = [aDecoder decodeObjectForKey:@"title"]; - self.ID = [aDecoder decodeObjectForKey:@"ID"]; - self.type = [aDecoder decodeIntegerForKey:@"type"]; - self.keywords = [aDecoder decodeObjectForKey:@"keywords"]; - } - return self; -} - -- (void)encodeWithCoder:(NSCoder *)aCoder -{ - [aCoder encodeObject:self.title forKey:@"title"]; - [aCoder encodeObject:self.ID forKey:@"ID"]; - [aCoder encodeInteger:self.type forKey:@"type"]; - [aCoder encodeObject:((self.keywords != nil) ? self.keywords : @"") forKey:@"keywords"]; -} - -@end diff --git a/MoPubSampleApp/Domain/MPAdSection.h b/MoPubSampleApp/Domain/MPAdSection.h deleted file mode 100644 index f4364f5f7..000000000 --- a/MoPubSampleApp/Domain/MPAdSection.h +++ /dev/null @@ -1,23 +0,0 @@ -// -// MPAdSection.h -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import - -@class MPAdInfo; - -@interface MPAdSection : NSObject - -@property (nonatomic, strong) NSString *title; -@property (nonatomic, assign, readonly) NSUInteger count; - -+ (NSArray *)adSections; -+ (MPAdSection *)sectionWithTitle:(NSString *)title ads:(NSArray *)ads; - -- (MPAdInfo *)adAtIndex:(NSUInteger)index; - -@end diff --git a/MoPubSampleApp/Domain/MPAdSection.m b/MoPubSampleApp/Domain/MPAdSection.m deleted file mode 100644 index a44cda6bc..000000000 --- a/MoPubSampleApp/Domain/MPAdSection.m +++ /dev/null @@ -1,50 +0,0 @@ -// -// MPAdSection.m -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import "MPAdSection.h" -#import "MPAdInfo.h" -#import "MPAdPersistenceManager.h" - -@interface MPAdSection () - -@property (nonatomic, strong) NSArray *ads; - -@end - -@implementation MPAdSection - -+ (NSArray *)adSections -{ - return @[ - [MPAdSection sectionWithTitle:@"Banner Ads" ads:[MPAdInfo bannerAds]], - [MPAdSection sectionWithTitle:@"Interstitial Ads" ads:[MPAdInfo interstitialAds]], - [MPAdSection sectionWithTitle:@"Rewarded Video Ads" ads:[MPAdInfo rewardedVideoAds]], - [MPAdSection sectionWithTitle:@"Native Ads" ads:[MPAdInfo nativeAds]], - [MPAdSection sectionWithTitle:@"Saved Ads" ads:[MPAdPersistenceManager sharedManager].savedAds] - ]; -} - -+ (MPAdSection *)sectionWithTitle:(NSString *)title ads:(NSArray *)ads -{ - MPAdSection *section = [[MPAdSection alloc] init]; - section.title = title; - section.ads = ads; - return section; -} - -- (MPAdInfo *)adAtIndex:(NSUInteger)index -{ - return [self.ads objectAtIndex:index]; -} - -- (NSUInteger)count -{ - return [self.ads count]; -} - -@end diff --git a/MoPubSampleApp/LaunchScreen.storyboard b/MoPubSampleApp/LaunchScreen.storyboard deleted file mode 100644 index 982d9d876..000000000 --- a/MoPubSampleApp/LaunchScreen.storyboard +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/MoPubSampleApp/MPAdPersistenceManager.h b/MoPubSampleApp/MPAdPersistenceManager.h deleted file mode 100644 index 6a747c173..000000000 --- a/MoPubSampleApp/MPAdPersistenceManager.h +++ /dev/null @@ -1,26 +0,0 @@ -// -// MPAdPersistenceManager.h -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import - -@class MPAdInfo; - -@interface MPAdPersistenceManager : NSObject -{ - NSMutableArray *_savedAds; -} - -@property (nonatomic, readonly) NSArray *savedAds; - -+ (MPAdPersistenceManager *)sharedManager; - -- (void)addSavedAd:(MPAdInfo *)adInfo; -- (void)removeSavedAd:(MPAdInfo *)adInfo; -- (MPAdInfo *)savedAdForID:(NSString *)adID; - -@end diff --git a/MoPubSampleApp/MPAdPersistenceManager.m b/MoPubSampleApp/MPAdPersistenceManager.m deleted file mode 100644 index 1d727d53f..000000000 --- a/MoPubSampleApp/MPAdPersistenceManager.m +++ /dev/null @@ -1,92 +0,0 @@ -// -// MPAdPersistenceManager.m -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import "MPAdPersistenceManager.h" -#import "MPAdInfo.h" - -#define kSavedAdsInfoKey @"com.mopub.adunitids" - -@implementation MPAdPersistenceManager - -@synthesize savedAds = _savedAds; - -static MPAdPersistenceManager *sharedManager = nil; - -+ (MPAdPersistenceManager *)sharedManager -{ - static dispatch_once_t once; - dispatch_once(&once, ^{ - sharedManager = [[self alloc] init]; - }); - - return sharedManager; -} - -- (id)init -{ - self = [super init]; - if (self != nil) { - _savedAds = [NSMutableArray array]; - [self loadSavedAds]; - } - return self; -} - -- (void)loadSavedAds -{ - NSData *persistedData = [[NSUserDefaults standardUserDefaults] objectForKey:kSavedAdsInfoKey]; - if (persistedData != nil) { - NSArray *persistedArray = [NSKeyedUnarchiver unarchiveObjectWithData:persistedData]; - if (persistedArray != nil) { - [_savedAds addObjectsFromArray:persistedArray]; - } - } -} - -- (void)persistSavedAds -{ - NSData *persistData = [NSKeyedArchiver archivedDataWithRootObject:_savedAds]; - - [[NSUserDefaults standardUserDefaults] setObject:persistData forKey:kSavedAdsInfoKey]; - [[NSUserDefaults standardUserDefaults] synchronize]; -} - -- (MPAdInfo *)savedAdForID:(NSString *)adID -{ - MPAdInfo *target = nil; - - for (MPAdInfo *ad in self.savedAds) { - if ([ad.ID isEqualToString:adID]) { - target = ad; - break; - } - } - - return target; -} - -- (void)addSavedAd:(MPAdInfo *)adInfo -{ - // overwrite if this ad unit id already exists - [self removeSavedAd:adInfo]; - - [_savedAds addObject:adInfo]; - - [self persistSavedAds]; -} - -- (void)removeSavedAd:(MPAdInfo *)adInfo -{ - MPAdInfo *target = [self savedAdForID:adInfo.ID]; - if (target != nil) { - [_savedAds removeObject:target]; - [self persistSavedAds]; - } -} - -@end diff --git a/MoPubSampleApp/MPSampleAppInstanceProvider.h b/MoPubSampleApp/MPSampleAppInstanceProvider.h deleted file mode 100644 index 1cc2554a8..000000000 --- a/MoPubSampleApp/MPSampleAppInstanceProvider.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// MPSampleAppInstanceProvider.h -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import - -@class MPAdView, MPInterstitialAdController; - -@interface MPSampleAppInstanceProvider : NSObject - -+ (MPSampleAppInstanceProvider *)sharedProvider; -- (MPAdView *)buildMPAdViewWithAdUnitID:(NSString *)ID size:(CGSize)size; -- (MPInterstitialAdController *)buildMPInterstitialAdControllerWithAdUnitID:(NSString *)ID; - -@end diff --git a/MoPubSampleApp/MPSampleAppInstanceProvider.m b/MoPubSampleApp/MPSampleAppInstanceProvider.m deleted file mode 100644 index 77c8ba029..000000000 --- a/MoPubSampleApp/MPSampleAppInstanceProvider.m +++ /dev/null @@ -1,35 +0,0 @@ -// -// MPSampleAppInstanceProvider.m -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import "MPSampleAppInstanceProvider.h" -#import "MPAdView.h" -#import "MPInterstitialAdController.h" - -static MPSampleAppInstanceProvider *sharedProvider = nil; - -@implementation MPSampleAppInstanceProvider - -+ (MPSampleAppInstanceProvider *)sharedProvider -{ - if (!sharedProvider) { - sharedProvider = [[MPSampleAppInstanceProvider alloc] init]; - } - return sharedProvider; -} - -- (MPAdView *)buildMPAdViewWithAdUnitID:(NSString *)ID size:(CGSize)size -{ - return [[MPAdView alloc] initWithAdUnitId:ID size:size]; -} - -- (MPInterstitialAdController *)buildMPInterstitialAdControllerWithAdUnitID:(NSString *)ID -{ - return [MPInterstitialAdController interstitialAdControllerForAdUnitId:ID]; -} - -@end diff --git a/MoPubSampleApp/MPSampleAppLogReader.h b/MoPubSampleApp/MPSampleAppLogReader.h deleted file mode 100644 index 9984e1400..000000000 --- a/MoPubSampleApp/MPSampleAppLogReader.h +++ /dev/null @@ -1,17 +0,0 @@ -// -// MPSampleAppLogReader.h -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import - -@interface MPSampleAppLogReader : NSObject - -+ (MPSampleAppLogReader *)sharedLogReader; - -- (void)beginReadingLogMessages; - -@end diff --git a/MoPubSampleApp/MPSampleAppLogReader.m b/MoPubSampleApp/MPSampleAppLogReader.m deleted file mode 100644 index e05cfcbb3..000000000 --- a/MoPubSampleApp/MPSampleAppLogReader.m +++ /dev/null @@ -1,73 +0,0 @@ -// -// MPSampleAppLogReader.m -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import "MPSampleAppLogReader.h" -#import "MPLogProvider.h" - -@interface MPSampleAppLogReader () - -@property (nonatomic, strong) UIAlertView *warmingUpAlertView; - -@end - -@implementation MPSampleAppLogReader - -+ (MPSampleAppLogReader *)sharedLogReader -{ - static dispatch_once_t once; - static MPSampleAppLogReader *sharedLogReader; - dispatch_once(&once, ^{ - sharedLogReader = [[self alloc] init]; - }); - - return sharedLogReader; -} - -- (void)dealloc -{ - [[MPLogProvider sharedLogProvider] removeLogger:self]; -} - -- (void)beginReadingLogMessages -{ - [[MPLogProvider sharedLogProvider] removeLogger:self]; - [[MPLogProvider sharedLogProvider] addLogger:self]; -} - -#pragma mark - - -- (MPLogLevel)logLevel -{ - return MPLogLevelAll; -} - -- (void)logMessage:(NSString *)message -{ - NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:[kMPWarmingUpErrorLogFormatWithAdUnitID stringByReplacingOccurrencesOfString:@"%@" withString:@".*"] - options:NSRegularExpressionCaseInsensitive - error:nil]; - - if (self.warmingUpAlertView == nil && [regex numberOfMatchesInString:message options:0 range:NSMakeRange(0, message.length)] > 0) { - self.warmingUpAlertView = [[UIAlertView alloc] initWithTitle:@"Warming Up" - message:message - delegate:self - cancelButtonTitle:@"OK" - otherButtonTitles:nil]; - [self.warmingUpAlertView show]; - } -} - -#pragma mark - - -- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex -{ - self.warmingUpAlertView = nil; -} - - -@end diff --git a/MoPubSampleApp/MoPubSampleApp+Framework-Info.plist b/MoPubSampleApp/MoPubSampleApp+Framework-Info.plist deleted file mode 100644 index 4b7fa73d8..000000000 --- a/MoPubSampleApp/MoPubSampleApp+Framework-Info.plist +++ /dev/null @@ -1,76 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleDisplayName - ${PRODUCT_NAME} - CFBundleExecutable - ${EXECUTABLE_NAME} - CFBundleIcons - - CFBundlePrimaryIcon - - CFBundleIconFiles - - icon.png - icon@2x.png - - - - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - ${PRODUCT_NAME} - CFBundlePackageType - APPL - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleURLTypes - - - CFBundleTypeRole - Editor - CFBundleURLName - com.mopub.samples.objc.MoPubSampleApp - CFBundleURLSchemes - - mopub - - - - CFBundleVersion - 1.0 - LSRequiresIPhoneOS - - NSAppTransportSecurity - - NSAllowsArbitraryLoads - - - UILaunchStoryboardName - LaunchScreen - UIRequiredDeviceCapabilities - - armv7 - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - - diff --git a/MoPubSampleApp/MoPubSampleApp-Info.plist b/MoPubSampleApp/MoPubSampleApp-Info.plist deleted file mode 100644 index 4b7fa73d8..000000000 --- a/MoPubSampleApp/MoPubSampleApp-Info.plist +++ /dev/null @@ -1,76 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleDisplayName - ${PRODUCT_NAME} - CFBundleExecutable - ${EXECUTABLE_NAME} - CFBundleIcons - - CFBundlePrimaryIcon - - CFBundleIconFiles - - icon.png - icon@2x.png - - - - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - ${PRODUCT_NAME} - CFBundlePackageType - APPL - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleURLTypes - - - CFBundleTypeRole - Editor - CFBundleURLName - com.mopub.samples.objc.MoPubSampleApp - CFBundleURLSchemes - - mopub - - - - CFBundleVersion - 1.0 - LSRequiresIPhoneOS - - NSAppTransportSecurity - - NSAllowsArbitraryLoads - - - UILaunchStoryboardName - LaunchScreen - UIRequiredDeviceCapabilities - - armv7 - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - - diff --git a/MoPubSampleApp/MoPubSampleApp-Prefix.pch b/MoPubSampleApp/MoPubSampleApp-Prefix.pch deleted file mode 100644 index 384d97548..000000000 --- a/MoPubSampleApp/MoPubSampleApp-Prefix.pch +++ /dev/null @@ -1,14 +0,0 @@ -// -// Prefix header for all source files of the 'MoPubSampleApp' target in the 'MoPubSampleApp' project -// - -#import - -#ifndef __IPHONE_3_0 -#warning "This project uses features only available in iOS SDK 3.0 and later." -#endif - -#ifdef __OBJC__ - #import - #import -#endif diff --git a/MoPubSampleApp/MoPubSampleApp.xcodeproj/project.pbxproj b/MoPubSampleApp/MoPubSampleApp.xcodeproj/project.pbxproj deleted file mode 100644 index 6b3ea15a7..000000000 --- a/MoPubSampleApp/MoPubSampleApp.xcodeproj/project.pbxproj +++ /dev/null @@ -1,825 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 46; - objects = { - -/* Begin PBXBuildFile section */ - 2A440B7A2023BD390003CC2A /* MediaPlayer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 46B3345719F083350078078A /* MediaPlayer.framework */; }; - 2A440B7B2023BD390003CC2A /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 46B3345B19F083F70078078A /* SystemConfiguration.framework */; }; - 2A440B7C2023BD390003CC2A /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 46B3345919F083EC0078078A /* QuartzCore.framework */; }; - 2A440B7D2023BD390003CC2A /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 46B9E8AF17E0D7F600B78D11 /* AVFoundation.framework */; }; - 2A440B7E2023BD390003CC2A /* CoreTelephony.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AE10920A170120E700812E6E /* CoreTelephony.framework */; }; - 2A440B7F2023BD390003CC2A /* SafariServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2AC79F431DF7814700195AC5 /* SafariServices.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; - 2A440B802023BD390003CC2A /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 57EF00CD1BD96EB5002634DE /* WebKit.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; - 2A440B812023BD390003CC2A /* CoreMedia.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 46E5043F18035A26006A2FC3 /* CoreMedia.framework */; }; - 2A440B832023BD390003CC2A /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AE23239616F78A17002C2082 /* CoreGraphics.framework */; }; - 2A440B842023BD390003CC2A /* MessageUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 46B9E8B117E0D80400B78D11 /* MessageUI.framework */; }; - 2A440B852023BD390003CC2A /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = AE10920C170120E700812E6E /* libz.dylib */; }; - 2A440B862023BD390003CC2A /* libsqlite3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = AE10920D170120E700812E6E /* libsqlite3.dylib */; }; - 2A440B872023BD390003CC2A /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AE23239416F78A17002C2082 /* Foundation.framework */; }; - 2A440B882023BD390003CC2A /* CoreLocation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AE0F1FB1171F1D2800FA3BE6 /* CoreLocation.framework */; }; - 2A440B892023BD390003CC2A /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AED9E47016F78DA900EA71A7 /* StoreKit.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; - 2A440B8B2023BD390003CC2A /* AdSupport.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AED9E47216F78DAD00EA71A7 /* AdSupport.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; - 2A440B8C2023BD390003CC2A /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AE23239216F78A17002C2082 /* UIKit.framework */; }; - 2A440B8E2023BD390003CC2A /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = AE23239B16F78A17002C2082 /* InfoPlist.strings */; }; - 2A440B902023BD390003CC2A /* MPBannerAdDetailViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = AED9E4CE16F798AF00EA71A7 /* MPBannerAdDetailViewController.xib */; }; - 2A440B912023BD390003CC2A /* MPInterstitialAdDetailViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = AE43414616F9068000B73710 /* MPInterstitialAdDetailViewController.xib */; }; - 2A440B922023BD390003CC2A /* mopub_logo.png in Resources */ = {isa = PBXBuildFile; fileRef = 2A5874FD1F90341F00E8CDFA /* mopub_logo.png */; }; - 2A440B992023BD390003CC2A /* MPNativeAdDetailViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4A6FC1F618C561B3007A1197 /* MPNativeAdDetailViewController.xib */; }; - 2A440B9D2023BD390003CC2A /* icon.png in Resources */ = {isa = PBXBuildFile; fileRef = AE8E1D441729A76C0051FA6C /* icon.png */; }; - 2A440BA12023BD390003CC2A /* MPAdEntryViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 46DA87B0185010DD00F34858 /* MPAdEntryViewController.xib */; }; - 2A440BA22023BD390003CC2A /* icon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AE8E1D461729A76F0051FA6C /* icon@2x.png */; }; - 2A440BA42023BD390003CC2A /* Default.png in Resources */ = {isa = PBXBuildFile; fileRef = AE8E1D481729A7730051FA6C /* Default.png */; }; - 2A440BA52023BD390003CC2A /* Default@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AE8E1D4A1729A7760051FA6C /* Default@2x.png */; }; - 2A440BA72023BD390003CC2A /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AE8E1D4C1729A7790051FA6C /* Default-568h@2x.png */; }; - 2A440BA92023BD390003CC2A /* white_button.png in Resources */ = {isa = PBXBuildFile; fileRef = AE8E1D4F1729AA460051FA6C /* white_button.png */; }; - 2A440BAA2023BD390003CC2A /* MPRewardedVideoAdDetailViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4ADE5FCB1B4FE836004C3673 /* MPRewardedVideoAdDetailViewController.xib */; }; - 2A440BAE2023BD390003CC2A /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2A5874FA1F9033E800E8CDFA /* LaunchScreen.storyboard */; }; - 2A440BB22023BD390003CC2A /* MPNativeAdTableHeaderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4A8DD10518C90636005E9389 /* MPNativeAdTableHeaderView.xib */; }; - 2A440BBE2023BD710003CC2A /* MPAdInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = AED9E4C416F7934900EA71A7 /* MPAdInfo.m */; }; - 2A440BBF2023BD710003CC2A /* MPAdSection.m in Sources */ = {isa = PBXBuildFile; fileRef = AE43413C16F8EEC500B73710 /* MPAdSection.m */; }; - 2A440BC02023BD710003CC2A /* MPAdTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = AED9E4BA16F7912900EA71A7 /* MPAdTableViewController.m */; }; - 2A440BC12023BD710003CC2A /* MPBannerAdDetailViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = AED9E4CB16F7987F00EA71A7 /* MPBannerAdDetailViewController.m */; }; - 2A440BC22023BD710003CC2A /* MPMRectBannerAdDetailViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 462E156E17F0F5D100408E17 /* MPMRectBannerAdDetailViewController.m */; }; - 2A440BC32023BD710003CC2A /* MPLeaderboardBannerAdDetailViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 46F883C817FB7AC5004CE563 /* MPLeaderboardBannerAdDetailViewController.m */; }; - 2A440BC42023BD710003CC2A /* MPInterstitialAdDetailViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = AE43414516F9068000B73710 /* MPInterstitialAdDetailViewController.m */; }; - 2A440BC52023BD710003CC2A /* MPRewardedVideoAdDetailViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4ADE5FCD1B4FE836004C3673 /* MPRewardedVideoAdDetailViewController.m */; }; - 2A440BC72023BD710003CC2A /* MPAdEntryViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 46DA87AF185010DD00F34858 /* MPAdEntryViewController.m */; }; - 2A440BC82023BD710003CC2A /* MPNativeAdDetailViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A6FC1F518C561B3007A1197 /* MPNativeAdDetailViewController.m */; }; - 2A440BC92023BD710003CC2A /* MPNativeAdPlacerTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A1C1DB71980722600B6DB33 /* MPNativeAdPlacerTableViewController.m */; }; - 2A440BCA2023BD710003CC2A /* MPNativeAdPlacerCollectionViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = E060FE6B1994000C00F4F33C /* MPNativeAdPlacerCollectionViewController.m */; }; - 2A440BCB2023BD710003CC2A /* MPNativeAdPlacerPageViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 577DF03B19A28786001514D3 /* MPNativeAdPlacerPageViewController.m */; }; - 2A440BCC2023BD710003CC2A /* MPNativeVideoTableViewAdPlacerView.m in Sources */ = {isa = PBXBuildFile; fileRef = 57C784D51BCC4C6C00E4BB7D /* MPNativeVideoTableViewAdPlacerView.m */; }; - 2A440BCD2023BD710003CC2A /* MPTableViewAdPlacerView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A8DD10018C90542005E9389 /* MPTableViewAdPlacerView.m */; }; - 2A440BCE2023BD710003CC2A /* MPIndexPathPickerView.m in Sources */ = {isa = PBXBuildFile; fileRef = A7736CEB199D73D500AD4887 /* MPIndexPathPickerView.m */; }; - 2A440BCF2023BD710003CC2A /* MPNativeAdTableHeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A8DD10318C90620005E9389 /* MPNativeAdTableHeaderView.m */; }; - 2A440BD02023BD710003CC2A /* MPCollectionViewAdPlacerView.m in Sources */ = {isa = PBXBuildFile; fileRef = 57FFAB85199C110D00F655CF /* MPCollectionViewAdPlacerView.m */; }; - 2A440BD12023BD710003CC2A /* MPNativeAdPageView.m in Sources */ = {isa = PBXBuildFile; fileRef = 577A1FA119AE7EA6006DA28B /* MPNativeAdPageView.m */; }; - 2A440BD22023BD710003CC2A /* MPStaticNativeAdView.m in Sources */ = {isa = PBXBuildFile; fileRef = 571EF4271B7194D60035C9BB /* MPStaticNativeAdView.m */; }; - 2A440BD32023BD710003CC2A /* MPNativeVideoView.m in Sources */ = {isa = PBXBuildFile; fileRef = 57C784E81BCC4F4600E4BB7D /* MPNativeVideoView.m */; }; - 2A440BD42023BD710003CC2A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = AE2323A216F78A17002C2082 /* AppDelegate.m */; }; - 2A440BD52023BD710003CC2A /* MPSampleAppInstanceProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = AED9E4DD16F7B00100EA71A7 /* MPSampleAppInstanceProvider.m */; }; - 2A440BD62023BD710003CC2A /* MPAdPersistenceManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 467343EF17FE3D6900D68BB6 /* MPAdPersistenceManager.m */; }; - 2A440BD72023BD710003CC2A /* MPSampleAppLogReader.m in Sources */ = {isa = PBXBuildFile; fileRef = 469A15EF1AC1F0BA00D6F0EF /* MPSampleAppLogReader.m */; }; - 2A440BD82023BD710003CC2A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = AE23239E16F78A17002C2082 /* main.m */; }; - BC343E4D212C92710001C1C1 /* MPViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = BCEF09E41F72D6A2004CEBD6 /* MPViewController.m */; }; - BC6FFA7C20E2F83900FC7FAD /* MoPubSDKFramework.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BC6FFA7720E2EF5100FC7FAD /* MoPubSDKFramework.framework */; }; -/* End PBXBuildFile section */ - -/* Begin PBXContainerItemProxy section */ - BC6FFA6E20E2EF5100FC7FAD /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = BC6FFA6620E2EF5000FC7FAD /* MoPubSDK.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = AE515F9C171F1B110086B464; - remoteInfo = MoPubSDK; - }; - BC6FFA7020E2EF5100FC7FAD /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = BC6FFA6620E2EF5000FC7FAD /* MoPubSDK.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = BCA00B971EF47A91006FF762; - remoteInfo = "MoPubSDK-ExcludeNative"; - }; - BC6FFA7220E2EF5100FC7FAD /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = BC6FFA6620E2EF5000FC7FAD /* MoPubSDK.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 570A005B19D6206800065388; - remoteInfo = MoPubResources; - }; - BC6FFA7420E2EF5100FC7FAD /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = BC6FFA6620E2EF5000FC7FAD /* MoPubSDK.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 2AAA8DF91D95C77B006962E8; - remoteInfo = MoPubSDKTests; - }; - BC6FFA7620E2EF5100FC7FAD /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = BC6FFA6620E2EF5000FC7FAD /* MoPubSDK.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 2AF030C12016723700909F29; - remoteInfo = MoPubSDKFramework; - }; - BC6FFA7820E2EF5D00FC7FAD /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = BC6FFA6620E2EF5000FC7FAD /* MoPubSDK.xcodeproj */; - proxyType = 1; - remoteGlobalIDString = 2AF030C02016723700909F29; - remoteInfo = MoPubSDKFramework; - }; -/* End PBXContainerItemProxy section */ - -/* Begin PBXCopyFilesBuildPhase section */ - 2AA733B72023BE37006EDA5C /* Embed Frameworks */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - ); - name = "Embed Frameworks"; - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - -/* Begin PBXFileReference section */ - 2A440BB72023BD390003CC2A /* MoPubSampleApp+Framework.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "MoPubSampleApp+Framework.app"; sourceTree = BUILT_PRODUCTS_DIR; }; - 2A5874FA1F9033E800E8CDFA /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; - 2A5874FD1F90341F00E8CDFA /* mopub_logo.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = mopub_logo.png; sourceTree = ""; }; - 2AC79F431DF7814700195AC5 /* SafariServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SafariServices.framework; path = System/Library/Frameworks/SafariServices.framework; sourceTree = SDKROOT; }; - 447AE7061E6B2A0700823DF1 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; }; - 44F0E2DF1E6AB43400A059DA /* EventKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = EventKit.framework; path = System/Library/Frameworks/EventKit.framework; sourceTree = SDKROOT; }; - 44F0E2E01E6AB43400A059DA /* EventKitUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = EventKitUI.framework; path = System/Library/Frameworks/EventKitUI.framework; sourceTree = SDKROOT; }; - 4614B3F417E8D01D00812D2C /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = System/Library/Frameworks/CFNetwork.framework; sourceTree = SDKROOT; }; - 462E156D17F0F5D100408E17 /* MPMRectBannerAdDetailViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPMRectBannerAdDetailViewController.h; sourceTree = ""; }; - 462E156E17F0F5D100408E17 /* MPMRectBannerAdDetailViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPMRectBannerAdDetailViewController.m; sourceTree = ""; }; - 467343EE17FE3D6900D68BB6 /* MPAdPersistenceManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAdPersistenceManager.h; sourceTree = ""; }; - 467343EF17FE3D6900D68BB6 /* MPAdPersistenceManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAdPersistenceManager.m; sourceTree = ""; }; - 469A15EE1AC1F0BA00D6F0EF /* MPSampleAppLogReader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPSampleAppLogReader.h; sourceTree = ""; }; - 469A15EF1AC1F0BA00D6F0EF /* MPSampleAppLogReader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPSampleAppLogReader.m; sourceTree = ""; }; - 46B3345419F083050078078A /* libz.1.2.5.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.1.2.5.dylib; path = usr/lib/libz.1.2.5.dylib; sourceTree = SDKROOT; }; - 46B3345719F083350078078A /* MediaPlayer.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MediaPlayer.framework; path = System/Library/Frameworks/MediaPlayer.framework; sourceTree = SDKROOT; }; - 46B3345919F083EC0078078A /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; - 46B3345B19F083F70078078A /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; }; - 46B3348519F0874E0078078A /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = System/Library/Frameworks/MobileCoreServices.framework; sourceTree = SDKROOT; }; - 46B9E8AD17E0D7EE00B78D11 /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; }; - 46B9E8AF17E0D7F600B78D11 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; }; - 46B9E8B117E0D80400B78D11 /* MessageUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MessageUI.framework; path = System/Library/Frameworks/MessageUI.framework; sourceTree = SDKROOT; }; - 46DA87AE185010DD00F34858 /* MPAdEntryViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAdEntryViewController.h; sourceTree = ""; }; - 46DA87AF185010DD00F34858 /* MPAdEntryViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAdEntryViewController.m; sourceTree = ""; }; - 46DA87B0185010DD00F34858 /* MPAdEntryViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MPAdEntryViewController.xib; sourceTree = ""; }; - 46E5043F18035A26006A2FC3 /* CoreMedia.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMedia.framework; path = System/Library/Frameworks/CoreMedia.framework; sourceTree = SDKROOT; }; - 46F883C717FB7AC5004CE563 /* MPLeaderboardBannerAdDetailViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPLeaderboardBannerAdDetailViewController.h; sourceTree = ""; }; - 46F883C817FB7AC5004CE563 /* MPLeaderboardBannerAdDetailViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPLeaderboardBannerAdDetailViewController.m; sourceTree = ""; }; - 4A1C1DB61980722600B6DB33 /* MPNativeAdPlacerTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPNativeAdPlacerTableViewController.h; sourceTree = ""; }; - 4A1C1DB71980722600B6DB33 /* MPNativeAdPlacerTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPNativeAdPlacerTableViewController.m; sourceTree = ""; }; - 4A6FC1F418C561B3007A1197 /* MPNativeAdDetailViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPNativeAdDetailViewController.h; sourceTree = ""; }; - 4A6FC1F518C561B3007A1197 /* MPNativeAdDetailViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPNativeAdDetailViewController.m; sourceTree = ""; }; - 4A6FC1F618C561B3007A1197 /* MPNativeAdDetailViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MPNativeAdDetailViewController.xib; sourceTree = ""; }; - 4A8DD0FF18C90542005E9389 /* MPTableViewAdPlacerView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPTableViewAdPlacerView.h; sourceTree = ""; }; - 4A8DD10018C90542005E9389 /* MPTableViewAdPlacerView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPTableViewAdPlacerView.m; sourceTree = ""; }; - 4A8DD10218C90620005E9389 /* MPNativeAdTableHeaderView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPNativeAdTableHeaderView.h; sourceTree = ""; }; - 4A8DD10318C90620005E9389 /* MPNativeAdTableHeaderView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPNativeAdTableHeaderView.m; sourceTree = ""; }; - 4A8DD10518C90636005E9389 /* MPNativeAdTableHeaderView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MPNativeAdTableHeaderView.xib; sourceTree = ""; }; - 4A93504F1B66D78900ABF4A3 /* CoreBluetooth.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreBluetooth.framework; path = System/Library/Frameworks/CoreBluetooth.framework; sourceTree = SDKROOT; }; - 4A9350511B66D7B500ABF4A3 /* libxml2.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libxml2.dylib; path = usr/lib/libxml2.dylib; sourceTree = SDKROOT; }; - 4ADE5FCB1B4FE836004C3673 /* MPRewardedVideoAdDetailViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MPRewardedVideoAdDetailViewController.xib; sourceTree = ""; }; - 4ADE5FCC1B4FE836004C3673 /* MPRewardedVideoAdDetailViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPRewardedVideoAdDetailViewController.h; sourceTree = ""; }; - 4ADE5FCD1B4FE836004C3673 /* MPRewardedVideoAdDetailViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPRewardedVideoAdDetailViewController.m; sourceTree = ""; }; - 571EF4261B7194D60035C9BB /* MPStaticNativeAdView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPStaticNativeAdView.h; sourceTree = ""; }; - 571EF4271B7194D60035C9BB /* MPStaticNativeAdView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPStaticNativeAdView.m; sourceTree = ""; }; - 577A1FA019AE7EA6006DA28B /* MPNativeAdPageView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPNativeAdPageView.h; sourceTree = ""; }; - 577A1FA119AE7EA6006DA28B /* MPNativeAdPageView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPNativeAdPageView.m; sourceTree = ""; }; - 577DF03A19A28786001514D3 /* MPNativeAdPlacerPageViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPNativeAdPlacerPageViewController.h; sourceTree = ""; }; - 577DF03B19A28786001514D3 /* MPNativeAdPlacerPageViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPNativeAdPlacerPageViewController.m; sourceTree = ""; }; - 57881F201BDED96A0046122E /* CoreMotion.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMotion.framework; path = System/Library/Frameworks/CoreMotion.framework; sourceTree = SDKROOT; }; - 57C784D41BCC4C6C00E4BB7D /* MPNativeVideoTableViewAdPlacerView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPNativeVideoTableViewAdPlacerView.h; sourceTree = ""; }; - 57C784D51BCC4C6C00E4BB7D /* MPNativeVideoTableViewAdPlacerView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPNativeVideoTableViewAdPlacerView.m; sourceTree = ""; }; - 57C784E71BCC4F4600E4BB7D /* MPNativeVideoView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPNativeVideoView.h; sourceTree = ""; }; - 57C784E81BCC4F4600E4BB7D /* MPNativeVideoView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPNativeVideoView.m; sourceTree = ""; }; - 57D3223F1BD9944500ACF21E /* libc++.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = "libc++.dylib"; path = "usr/lib/libc++.dylib"; sourceTree = SDKROOT; }; - 57EF00CD1BD96EB5002634DE /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = System/Library/Frameworks/WebKit.framework; sourceTree = SDKROOT; }; - 57FFAB84199C110D00F655CF /* MPCollectionViewAdPlacerView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPCollectionViewAdPlacerView.h; sourceTree = ""; }; - 57FFAB85199C110D00F655CF /* MPCollectionViewAdPlacerView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPCollectionViewAdPlacerView.m; sourceTree = ""; }; - A7736CEA199D73D500AD4887 /* MPIndexPathPickerView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPIndexPathPickerView.h; sourceTree = ""; }; - A7736CEB199D73D500AD4887 /* MPIndexPathPickerView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPIndexPathPickerView.m; sourceTree = ""; }; - AE0F1FB1171F1D2800FA3BE6 /* CoreLocation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreLocation.framework; path = System/Library/Frameworks/CoreLocation.framework; sourceTree = SDKROOT; }; - AE10920A170120E700812E6E /* CoreTelephony.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreTelephony.framework; path = System/Library/Frameworks/CoreTelephony.framework; sourceTree = SDKROOT; }; - AE10920B170120E700812E6E /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; - AE10920C170120E700812E6E /* libz.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.dylib; path = usr/lib/libz.dylib; sourceTree = SDKROOT; }; - AE10920D170120E700812E6E /* libsqlite3.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libsqlite3.dylib; path = usr/lib/libsqlite3.dylib; sourceTree = SDKROOT; }; - AE23239216F78A17002C2082 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; - AE23239416F78A17002C2082 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; - AE23239616F78A17002C2082 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; - AE23239A16F78A17002C2082 /* MoPubSampleApp-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "MoPubSampleApp-Info.plist"; sourceTree = ""; }; - AE23239C16F78A17002C2082 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; - AE23239E16F78A17002C2082 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; - AE2323A016F78A17002C2082 /* MoPubSampleApp-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MoPubSampleApp-Prefix.pch"; sourceTree = ""; }; - AE2323A116F78A17002C2082 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; - AE2323A216F78A17002C2082 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = AppDelegate.m; sourceTree = ""; }; - AE43413B16F8EEC400B73710 /* MPAdSection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = MPAdSection.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; - AE43413C16F8EEC500B73710 /* MPAdSection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = MPAdSection.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; - AE43414416F9068000B73710 /* MPInterstitialAdDetailViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPInterstitialAdDetailViewController.h; sourceTree = ""; }; - AE43414516F9068000B73710 /* MPInterstitialAdDetailViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPInterstitialAdDetailViewController.m; sourceTree = ""; }; - AE43414616F9068000B73710 /* MPInterstitialAdDetailViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MPInterstitialAdDetailViewController.xib; sourceTree = ""; }; - AE8E1D441729A76C0051FA6C /* icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = icon.png; sourceTree = ""; }; - AE8E1D461729A76F0051FA6C /* icon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon@2x.png"; sourceTree = ""; }; - AE8E1D481729A7730051FA6C /* Default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Default.png; sourceTree = ""; }; - AE8E1D4A1729A7760051FA6C /* Default@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default@2x.png"; sourceTree = ""; }; - AE8E1D4C1729A7790051FA6C /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-568h@2x.png"; sourceTree = ""; }; - AE8E1D4F1729AA460051FA6C /* white_button.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = white_button.png; sourceTree = ""; }; - AE9029E5171F549100991DF9 /* libxml2.2.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libxml2.2.dylib; path = usr/lib/libxml2.2.dylib; sourceTree = SDKROOT; }; - AE902A31171F559400991DF9 /* AddressBook.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AddressBook.framework; path = System/Library/Frameworks/AddressBook.framework; sourceTree = SDKROOT; }; - AE902A32171F559400991DF9 /* AddressBookUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AddressBookUI.framework; path = System/Library/Frameworks/AddressBookUI.framework; sourceTree = SDKROOT; }; - AE902A39171F559F00991DF9 /* PassKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PassKit.framework; path = System/Library/Frameworks/PassKit.framework; sourceTree = SDKROOT; }; - AE902A3B171F55A900991DF9 /* Twitter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Twitter.framework; path = System/Library/Frameworks/Twitter.framework; sourceTree = SDKROOT; }; - AE902A3D171F55AE00991DF9 /* Social.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Social.framework; path = System/Library/Frameworks/Social.framework; sourceTree = SDKROOT; }; - AED9E47016F78DA900EA71A7 /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; }; - AED9E47216F78DAD00EA71A7 /* AdSupport.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AdSupport.framework; path = System/Library/Frameworks/AdSupport.framework; sourceTree = SDKROOT; }; - AED9E4B916F7912900EA71A7 /* MPAdTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAdTableViewController.h; sourceTree = ""; }; - AED9E4BA16F7912900EA71A7 /* MPAdTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = MPAdTableViewController.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; - AED9E4C316F7934900EA71A7 /* MPAdInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = MPAdInfo.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; - AED9E4C416F7934900EA71A7 /* MPAdInfo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = MPAdInfo.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; - AED9E4CA16F7987F00EA71A7 /* MPBannerAdDetailViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = MPBannerAdDetailViewController.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; - AED9E4CB16F7987F00EA71A7 /* MPBannerAdDetailViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = MPBannerAdDetailViewController.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; - AED9E4CE16F798AF00EA71A7 /* MPBannerAdDetailViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MPBannerAdDetailViewController.xib; sourceTree = ""; }; - AED9E4DC16F7B00100EA71A7 /* MPSampleAppInstanceProvider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPSampleAppInstanceProvider.h; sourceTree = ""; }; - AED9E4DD16F7B00100EA71A7 /* MPSampleAppInstanceProvider.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPSampleAppInstanceProvider.m; sourceTree = ""; }; - BC6FFA6520E2EF0300FC7FAD /* MoPubSampleApp+Framework-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "MoPubSampleApp+Framework-Info.plist"; sourceTree = ""; }; - BC6FFA6620E2EF5000FC7FAD /* MoPubSDK.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = MoPubSDK.xcodeproj; path = ../MoPubSDK.xcodeproj; sourceTree = ""; }; - BCEF09E31F72D6A2004CEBD6 /* MPViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPViewController.h; sourceTree = ""; }; - BCEF09E41F72D6A2004CEBD6 /* MPViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPViewController.m; sourceTree = ""; }; - E060FE6A1994000C00F4F33C /* MPNativeAdPlacerCollectionViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPNativeAdPlacerCollectionViewController.h; sourceTree = ""; }; - E060FE6B1994000C00F4F33C /* MPNativeAdPlacerCollectionViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPNativeAdPlacerCollectionViewController.m; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 2A440B792023BD390003CC2A /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - BC6FFA7C20E2F83900FC7FAD /* MoPubSDKFramework.framework in Frameworks */, - 2A440B7A2023BD390003CC2A /* MediaPlayer.framework in Frameworks */, - 2A440B7B2023BD390003CC2A /* SystemConfiguration.framework in Frameworks */, - 2A440B7C2023BD390003CC2A /* QuartzCore.framework in Frameworks */, - 2A440B7D2023BD390003CC2A /* AVFoundation.framework in Frameworks */, - 2A440B7E2023BD390003CC2A /* CoreTelephony.framework in Frameworks */, - 2A440B7F2023BD390003CC2A /* SafariServices.framework in Frameworks */, - 2A440B802023BD390003CC2A /* WebKit.framework in Frameworks */, - 2A440B812023BD390003CC2A /* CoreMedia.framework in Frameworks */, - 2A440B832023BD390003CC2A /* CoreGraphics.framework in Frameworks */, - 2A440B842023BD390003CC2A /* MessageUI.framework in Frameworks */, - 2A440B852023BD390003CC2A /* libz.dylib in Frameworks */, - 2A440B862023BD390003CC2A /* libsqlite3.dylib in Frameworks */, - 2A440B872023BD390003CC2A /* Foundation.framework in Frameworks */, - 2A440B882023BD390003CC2A /* CoreLocation.framework in Frameworks */, - 2A440B892023BD390003CC2A /* StoreKit.framework in Frameworks */, - 2A440B8B2023BD390003CC2A /* AdSupport.framework in Frameworks */, - 2A440B8C2023BD390003CC2A /* UIKit.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 4A8DD0FE18C90542005E9389 /* Views */ = { - isa = PBXGroup; - children = ( - 57C784D41BCC4C6C00E4BB7D /* MPNativeVideoTableViewAdPlacerView.h */, - 57C784D51BCC4C6C00E4BB7D /* MPNativeVideoTableViewAdPlacerView.m */, - 4A8DD0FF18C90542005E9389 /* MPTableViewAdPlacerView.h */, - 4A8DD10018C90542005E9389 /* MPTableViewAdPlacerView.m */, - A7736CEA199D73D500AD4887 /* MPIndexPathPickerView.h */, - A7736CEB199D73D500AD4887 /* MPIndexPathPickerView.m */, - 4A8DD10218C90620005E9389 /* MPNativeAdTableHeaderView.h */, - 4A8DD10318C90620005E9389 /* MPNativeAdTableHeaderView.m */, - 4A8DD10518C90636005E9389 /* MPNativeAdTableHeaderView.xib */, - 57FFAB84199C110D00F655CF /* MPCollectionViewAdPlacerView.h */, - 57FFAB85199C110D00F655CF /* MPCollectionViewAdPlacerView.m */, - 577A1FA019AE7EA6006DA28B /* MPNativeAdPageView.h */, - 577A1FA119AE7EA6006DA28B /* MPNativeAdPageView.m */, - 571EF4261B7194D60035C9BB /* MPStaticNativeAdView.h */, - 571EF4271B7194D60035C9BB /* MPStaticNativeAdView.m */, - 57C784E71BCC4F4600E4BB7D /* MPNativeVideoView.h */, - 57C784E81BCC4F4600E4BB7D /* MPNativeVideoView.m */, - ); - path = Views; - sourceTree = ""; - }; - AE23238616F78A17002C2082 = { - isa = PBXGroup; - children = ( - AE23239816F78A17002C2082 /* MoPubSampleApp */, - AE8E1D4E1729AA450051FA6C /* Assets */, - AE23239116F78A17002C2082 /* Frameworks */, - AE23239016F78A17002C2082 /* Products */, - BC6FFA6620E2EF5000FC7FAD /* MoPubSDK.xcodeproj */, - ); - sourceTree = ""; - }; - AE23239016F78A17002C2082 /* Products */ = { - isa = PBXGroup; - children = ( - 2A440BB72023BD390003CC2A /* MoPubSampleApp+Framework.app */, - ); - name = Products; - sourceTree = ""; - }; - AE23239116F78A17002C2082 /* Frameworks */ = { - isa = PBXGroup; - children = ( - AE902A31171F559400991DF9 /* AddressBook.framework */, - AE902A32171F559400991DF9 /* AddressBookUI.framework */, - AED9E47216F78DAD00EA71A7 /* AdSupport.framework */, - 46B9E8AD17E0D7EE00B78D11 /* AudioToolbox.framework */, - 46B9E8AF17E0D7F600B78D11 /* AVFoundation.framework */, - 4614B3F417E8D01D00812D2C /* CFNetwork.framework */, - 4A93504F1B66D78900ABF4A3 /* CoreBluetooth.framework */, - 447AE7061E6B2A0700823DF1 /* CoreData.framework */, - AE23239616F78A17002C2082 /* CoreGraphics.framework */, - AE0F1FB1171F1D2800FA3BE6 /* CoreLocation.framework */, - 46E5043F18035A26006A2FC3 /* CoreMedia.framework */, - 57881F201BDED96A0046122E /* CoreMotion.framework */, - AE10920A170120E700812E6E /* CoreTelephony.framework */, - 44F0E2DF1E6AB43400A059DA /* EventKit.framework */, - 44F0E2E01E6AB43400A059DA /* EventKitUI.framework */, - AE23239416F78A17002C2082 /* Foundation.framework */, - 57D3223F1BD9944500ACF21E /* libc++.dylib */, - AE10920D170120E700812E6E /* libsqlite3.dylib */, - AE9029E5171F549100991DF9 /* libxml2.2.dylib */, - 4A9350511B66D7B500ABF4A3 /* libxml2.dylib */, - 46B3345419F083050078078A /* libz.1.2.5.dylib */, - AE10920C170120E700812E6E /* libz.dylib */, - 46B3345719F083350078078A /* MediaPlayer.framework */, - 46B9E8B117E0D80400B78D11 /* MessageUI.framework */, - 46B3348519F0874E0078078A /* MobileCoreServices.framework */, - AE902A39171F559F00991DF9 /* PassKit.framework */, - 46B3345919F083EC0078078A /* QuartzCore.framework */, - 2AC79F431DF7814700195AC5 /* SafariServices.framework */, - AE10920B170120E700812E6E /* Security.framework */, - AE902A3D171F55AE00991DF9 /* Social.framework */, - AED9E47016F78DA900EA71A7 /* StoreKit.framework */, - 46B3345B19F083F70078078A /* SystemConfiguration.framework */, - AE902A3B171F55A900991DF9 /* Twitter.framework */, - AE23239216F78A17002C2082 /* UIKit.framework */, - 57EF00CD1BD96EB5002634DE /* WebKit.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; - AE23239816F78A17002C2082 /* MoPubSampleApp */ = { - isa = PBXGroup; - children = ( - AED9E4C216F7932400EA71A7 /* Domain */, - AED9E47416F78EBB00EA71A7 /* Controllers */, - 4A8DD0FE18C90542005E9389 /* Views */, - AE2323A116F78A17002C2082 /* AppDelegate.h */, - AE2323A216F78A17002C2082 /* AppDelegate.m */, - AED9E4DC16F7B00100EA71A7 /* MPSampleAppInstanceProvider.h */, - AED9E4DD16F7B00100EA71A7 /* MPSampleAppInstanceProvider.m */, - 467343EE17FE3D6900D68BB6 /* MPAdPersistenceManager.h */, - 467343EF17FE3D6900D68BB6 /* MPAdPersistenceManager.m */, - 469A15EE1AC1F0BA00D6F0EF /* MPSampleAppLogReader.h */, - 469A15EF1AC1F0BA00D6F0EF /* MPSampleAppLogReader.m */, - AE23239916F78A17002C2082 /* Supporting Files */, - ); - name = MoPubSampleApp; - sourceTree = ""; - }; - AE23239916F78A17002C2082 /* Supporting Files */ = { - isa = PBXGroup; - children = ( - AE23239A16F78A17002C2082 /* MoPubSampleApp-Info.plist */, - BC6FFA6520E2EF0300FC7FAD /* MoPubSampleApp+Framework-Info.plist */, - AE23239B16F78A17002C2082 /* InfoPlist.strings */, - AE23239E16F78A17002C2082 /* main.m */, - AE2323A016F78A17002C2082 /* MoPubSampleApp-Prefix.pch */, - 2A5874FA1F9033E800E8CDFA /* LaunchScreen.storyboard */, - 2A5874FD1F90341F00E8CDFA /* mopub_logo.png */, - ); - name = "Supporting Files"; - sourceTree = ""; - }; - AE8E1D4E1729AA450051FA6C /* Assets */ = { - isa = PBXGroup; - children = ( - AE8E1D4C1729A7790051FA6C /* Default-568h@2x.png */, - AE8E1D4A1729A7760051FA6C /* Default@2x.png */, - AE8E1D481729A7730051FA6C /* Default.png */, - AE8E1D461729A76F0051FA6C /* icon@2x.png */, - AE8E1D441729A76C0051FA6C /* icon.png */, - AE8E1D4F1729AA460051FA6C /* white_button.png */, - ); - path = Assets; - sourceTree = ""; - }; - AED9E47416F78EBB00EA71A7 /* Controllers */ = { - isa = PBXGroup; - children = ( - AED9E4B916F7912900EA71A7 /* MPAdTableViewController.h */, - AED9E4BA16F7912900EA71A7 /* MPAdTableViewController.m */, - AED9E4CA16F7987F00EA71A7 /* MPBannerAdDetailViewController.h */, - AED9E4CB16F7987F00EA71A7 /* MPBannerAdDetailViewController.m */, - AED9E4CE16F798AF00EA71A7 /* MPBannerAdDetailViewController.xib */, - 462E156D17F0F5D100408E17 /* MPMRectBannerAdDetailViewController.h */, - 462E156E17F0F5D100408E17 /* MPMRectBannerAdDetailViewController.m */, - 46F883C717FB7AC5004CE563 /* MPLeaderboardBannerAdDetailViewController.h */, - 46F883C817FB7AC5004CE563 /* MPLeaderboardBannerAdDetailViewController.m */, - AE43414416F9068000B73710 /* MPInterstitialAdDetailViewController.h */, - AE43414516F9068000B73710 /* MPInterstitialAdDetailViewController.m */, - AE43414616F9068000B73710 /* MPInterstitialAdDetailViewController.xib */, - 4ADE5FCC1B4FE836004C3673 /* MPRewardedVideoAdDetailViewController.h */, - 4ADE5FCD1B4FE836004C3673 /* MPRewardedVideoAdDetailViewController.m */, - 4ADE5FCB1B4FE836004C3673 /* MPRewardedVideoAdDetailViewController.xib */, - 46DA87AE185010DD00F34858 /* MPAdEntryViewController.h */, - 46DA87AF185010DD00F34858 /* MPAdEntryViewController.m */, - 46DA87B0185010DD00F34858 /* MPAdEntryViewController.xib */, - 4A6FC1F418C561B3007A1197 /* MPNativeAdDetailViewController.h */, - 4A6FC1F518C561B3007A1197 /* MPNativeAdDetailViewController.m */, - 4A6FC1F618C561B3007A1197 /* MPNativeAdDetailViewController.xib */, - 4A1C1DB61980722600B6DB33 /* MPNativeAdPlacerTableViewController.h */, - 4A1C1DB71980722600B6DB33 /* MPNativeAdPlacerTableViewController.m */, - E060FE6A1994000C00F4F33C /* MPNativeAdPlacerCollectionViewController.h */, - E060FE6B1994000C00F4F33C /* MPNativeAdPlacerCollectionViewController.m */, - 577DF03A19A28786001514D3 /* MPNativeAdPlacerPageViewController.h */, - 577DF03B19A28786001514D3 /* MPNativeAdPlacerPageViewController.m */, - BCEF09E31F72D6A2004CEBD6 /* MPViewController.h */, - BCEF09E41F72D6A2004CEBD6 /* MPViewController.m */, - ); - path = Controllers; - sourceTree = ""; - }; - AED9E4C216F7932400EA71A7 /* Domain */ = { - isa = PBXGroup; - children = ( - AED9E4C316F7934900EA71A7 /* MPAdInfo.h */, - AED9E4C416F7934900EA71A7 /* MPAdInfo.m */, - AE43413B16F8EEC400B73710 /* MPAdSection.h */, - AE43413C16F8EEC500B73710 /* MPAdSection.m */, - ); - path = Domain; - sourceTree = ""; - }; - BC6FFA6720E2EF5000FC7FAD /* Products */ = { - isa = PBXGroup; - children = ( - BC6FFA6F20E2EF5100FC7FAD /* libMoPubSDK.a */, - BC6FFA7120E2EF5100FC7FAD /* libMoPubSDK-ExcludeNative.a */, - BC6FFA7320E2EF5100FC7FAD /* MoPub.bundle */, - BC6FFA7520E2EF5100FC7FAD /* MoPubSDKTests.xctest */, - BC6FFA7720E2EF5100FC7FAD /* MoPubSDKFramework.framework */, - ); - name = Products; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 2A440AB22023BD390003CC2A /* MoPubSampleApp+Framework */ = { - isa = PBXNativeTarget; - buildConfigurationList = 2A440BB42023BD390003CC2A /* Build configuration list for PBXNativeTarget "MoPubSampleApp+Framework" */; - buildPhases = ( - 2A440AB42023BD390003CC2A /* Sources */, - 2A440B792023BD390003CC2A /* Frameworks */, - 2A440B8D2023BD390003CC2A /* Resources */, - 2AA733B72023BE37006EDA5C /* Embed Frameworks */, - ); - buildRules = ( - ); - dependencies = ( - BC6FFA7920E2EF5D00FC7FAD /* PBXTargetDependency */, - ); - name = "MoPubSampleApp+Framework"; - productName = MoPubSampleApp; - productReference = 2A440BB72023BD390003CC2A /* MoPubSampleApp+Framework.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - AE23238716F78A17002C2082 /* Project object */ = { - isa = PBXProject; - attributes = { - LastUpgradeCheck = 0900; - ORGANIZATIONNAME = MoPub; - TargetAttributes = { - 2A440AB22023BD390003CC2A = { - DevelopmentTeam = 4S7XS533V3; - }; - }; - }; - buildConfigurationList = AE23238A16F78A17002C2082 /* Build configuration list for PBXProject "MoPubSampleApp" */; - compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; - hasScannedForEncodings = 0; - knownRegions = ( - en, - ); - mainGroup = AE23238616F78A17002C2082; - productRefGroup = AE23239016F78A17002C2082 /* Products */; - projectDirPath = ""; - projectReferences = ( - { - ProductGroup = BC6FFA6720E2EF5000FC7FAD /* Products */; - ProjectRef = BC6FFA6620E2EF5000FC7FAD /* MoPubSDK.xcodeproj */; - }, - ); - projectRoot = ""; - targets = ( - 2A440AB22023BD390003CC2A /* MoPubSampleApp+Framework */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXReferenceProxy section */ - BC6FFA6F20E2EF5100FC7FAD /* libMoPubSDK.a */ = { - isa = PBXReferenceProxy; - fileType = archive.ar; - path = libMoPubSDK.a; - remoteRef = BC6FFA6E20E2EF5100FC7FAD /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; - BC6FFA7120E2EF5100FC7FAD /* libMoPubSDK-ExcludeNative.a */ = { - isa = PBXReferenceProxy; - fileType = archive.ar; - path = "libMoPubSDK-ExcludeNative.a"; - remoteRef = BC6FFA7020E2EF5100FC7FAD /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; - BC6FFA7320E2EF5100FC7FAD /* MoPub.bundle */ = { - isa = PBXReferenceProxy; - fileType = wrapper.cfbundle; - path = MoPub.bundle; - remoteRef = BC6FFA7220E2EF5100FC7FAD /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; - BC6FFA7520E2EF5100FC7FAD /* MoPubSDKTests.xctest */ = { - isa = PBXReferenceProxy; - fileType = wrapper.cfbundle; - path = MoPubSDKTests.xctest; - remoteRef = BC6FFA7420E2EF5100FC7FAD /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; - BC6FFA7720E2EF5100FC7FAD /* MoPubSDKFramework.framework */ = { - isa = PBXReferenceProxy; - fileType = wrapper.framework; - path = MoPubSDKFramework.framework; - remoteRef = BC6FFA7620E2EF5100FC7FAD /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; -/* End PBXReferenceProxy section */ - -/* Begin PBXResourcesBuildPhase section */ - 2A440B8D2023BD390003CC2A /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 2A440B8E2023BD390003CC2A /* InfoPlist.strings in Resources */, - 2A440B902023BD390003CC2A /* MPBannerAdDetailViewController.xib in Resources */, - 2A440B912023BD390003CC2A /* MPInterstitialAdDetailViewController.xib in Resources */, - 2A440B922023BD390003CC2A /* mopub_logo.png in Resources */, - 2A440B992023BD390003CC2A /* MPNativeAdDetailViewController.xib in Resources */, - 2A440B9D2023BD390003CC2A /* icon.png in Resources */, - 2A440BA12023BD390003CC2A /* MPAdEntryViewController.xib in Resources */, - 2A440BA22023BD390003CC2A /* icon@2x.png in Resources */, - 2A440BA42023BD390003CC2A /* Default.png in Resources */, - 2A440BA52023BD390003CC2A /* Default@2x.png in Resources */, - 2A440BA72023BD390003CC2A /* Default-568h@2x.png in Resources */, - 2A440BA92023BD390003CC2A /* white_button.png in Resources */, - 2A440BAA2023BD390003CC2A /* MPRewardedVideoAdDetailViewController.xib in Resources */, - 2A440BAE2023BD390003CC2A /* LaunchScreen.storyboard in Resources */, - 2A440BB22023BD390003CC2A /* MPNativeAdTableHeaderView.xib in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 2A440AB42023BD390003CC2A /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 2A440BBE2023BD710003CC2A /* MPAdInfo.m in Sources */, - 2A440BBF2023BD710003CC2A /* MPAdSection.m in Sources */, - 2A440BC02023BD710003CC2A /* MPAdTableViewController.m in Sources */, - 2A440BC12023BD710003CC2A /* MPBannerAdDetailViewController.m in Sources */, - 2A440BC22023BD710003CC2A /* MPMRectBannerAdDetailViewController.m in Sources */, - 2A440BC32023BD710003CC2A /* MPLeaderboardBannerAdDetailViewController.m in Sources */, - 2A440BC42023BD710003CC2A /* MPInterstitialAdDetailViewController.m in Sources */, - BC343E4D212C92710001C1C1 /* MPViewController.m in Sources */, - 2A440BC52023BD710003CC2A /* MPRewardedVideoAdDetailViewController.m in Sources */, - 2A440BC72023BD710003CC2A /* MPAdEntryViewController.m in Sources */, - 2A440BC82023BD710003CC2A /* MPNativeAdDetailViewController.m in Sources */, - 2A440BC92023BD710003CC2A /* MPNativeAdPlacerTableViewController.m in Sources */, - 2A440BCA2023BD710003CC2A /* MPNativeAdPlacerCollectionViewController.m in Sources */, - 2A440BCB2023BD710003CC2A /* MPNativeAdPlacerPageViewController.m in Sources */, - 2A440BCC2023BD710003CC2A /* MPNativeVideoTableViewAdPlacerView.m in Sources */, - 2A440BCD2023BD710003CC2A /* MPTableViewAdPlacerView.m in Sources */, - 2A440BCE2023BD710003CC2A /* MPIndexPathPickerView.m in Sources */, - 2A440BCF2023BD710003CC2A /* MPNativeAdTableHeaderView.m in Sources */, - 2A440BD02023BD710003CC2A /* MPCollectionViewAdPlacerView.m in Sources */, - 2A440BD12023BD710003CC2A /* MPNativeAdPageView.m in Sources */, - 2A440BD22023BD710003CC2A /* MPStaticNativeAdView.m in Sources */, - 2A440BD32023BD710003CC2A /* MPNativeVideoView.m in Sources */, - 2A440BD42023BD710003CC2A /* AppDelegate.m in Sources */, - 2A440BD52023BD710003CC2A /* MPSampleAppInstanceProvider.m in Sources */, - 2A440BD62023BD710003CC2A /* MPAdPersistenceManager.m in Sources */, - 2A440BD72023BD710003CC2A /* MPSampleAppLogReader.m in Sources */, - 2A440BD82023BD710003CC2A /* main.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXTargetDependency section */ - BC6FFA7920E2EF5D00FC7FAD /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - name = MoPubSDKFramework; - targetProxy = BC6FFA7820E2EF5D00FC7FAD /* PBXContainerItemProxy */; - }; -/* End PBXTargetDependency section */ - -/* Begin PBXVariantGroup section */ - AE23239B16F78A17002C2082 /* InfoPlist.strings */ = { - isa = PBXVariantGroup; - children = ( - AE23239C16F78A17002C2082 /* en */, - ); - name = InfoPlist.strings; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - -/* Begin XCBuildConfiguration section */ - 2A440BB52023BD390003CC2A /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_ENABLE_MODULES = NO; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - DEVELOPMENT_TEAM = 4S7XS533V3; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/../MoPubSDK/Viewability/MOAT", - "$(PROJECT_DIR)/../MoPubSDK", - ); - GCC_PRECOMPILE_PREFIX_HEADER = YES; - GCC_PREFIX_HEADER = "MoPubSampleApp-Prefix.pch"; - HEADER_SEARCH_PATHS = ( - "$(inherited)", - "$(SRCROOT)/../MoPubSDK/**", - ); - INFOPLIST_FILE = "MoPubSampleApp+Framework-Info.plist"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/..", - "$(PROJECT_DIR)/../MoPubSDK/Viewability/Avid", - ); - OTHER_LDFLAGS = ( - "-ObjC", - "$(inherited)", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.mopub.samples.objc.MoPubSampleAppFramework; - PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE = ""; - PROVISIONING_PROFILE_SPECIFIER = ""; - WRAPPER_EXTENSION = app; - }; - name = Debug; - }; - 2A440BB62023BD390003CC2A /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_ENABLE_MODULES = NO; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - DEVELOPMENT_TEAM = 4S7XS533V3; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/../MoPubSDK/Viewability/MOAT", - "$(PROJECT_DIR)/../MoPubSDK", - ); - GCC_PRECOMPILE_PREFIX_HEADER = YES; - GCC_PREFIX_HEADER = "MoPubSampleApp-Prefix.pch"; - HEADER_SEARCH_PATHS = ( - "$(inherited)", - "$(SRCROOT)/../MoPubSDK/**", - ); - INFOPLIST_FILE = "MoPubSampleApp+Framework-Info.plist"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/..", - "$(PROJECT_DIR)/../MoPubSDK/Viewability/Avid", - ); - OTHER_LDFLAGS = ( - "-ObjC", - "$(inherited)", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.mopub.samples.objc.MoPubSampleAppFramework; - PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE = ""; - PROVISIONING_PROFILE_SPECIFIER = ""; - WRAPPER_EXTENSION = app; - }; - name = Release; - }; - AE2323AA16F78A17002C2082 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_SYMBOLS_PRIVATE_EXTERN = NO; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - AE2323AB16F78A17002C2082 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = YES; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)"; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1"; - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 2A440BB42023BD390003CC2A /* Build configuration list for PBXNativeTarget "MoPubSampleApp+Framework" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 2A440BB52023BD390003CC2A /* Debug */, - 2A440BB62023BD390003CC2A /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - AE23238A16F78A17002C2082 /* Build configuration list for PBXProject "MoPubSampleApp" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - AE2323AA16F78A17002C2082 /* Debug */, - AE2323AB16F78A17002C2082 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = AE23238716F78A17002C2082 /* Project object */; -} diff --git a/MoPubSampleApp/Views/MPCollectionViewAdPlacerView.h b/MoPubSampleApp/Views/MPCollectionViewAdPlacerView.h deleted file mode 100644 index e4de7879c..000000000 --- a/MoPubSampleApp/Views/MPCollectionViewAdPlacerView.h +++ /dev/null @@ -1,20 +0,0 @@ -// -// MPCollectionViewAdPlacerView.h -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import -#import "MPNativeAdRendering.h" - - -@interface MPCollectionViewAdPlacerView : UIView - -@property (strong, nonatomic) UILabel *titleLabel; -@property (strong, nonatomic) UIImageView *iconImageView; -@property (strong, nonatomic) UILabel *ctaLabel; -@property (strong, nonatomic) UIImageView *privacyInformationIconImageView; - -@end diff --git a/MoPubSampleApp/Views/MPCollectionViewAdPlacerView.m b/MoPubSampleApp/Views/MPCollectionViewAdPlacerView.m deleted file mode 100644 index d89bc4575..000000000 --- a/MoPubSampleApp/Views/MPCollectionViewAdPlacerView.m +++ /dev/null @@ -1,65 +0,0 @@ -// -// MPCollectionViewAdPlacerView.m -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import "MPCollectionViewAdPlacerView.h" - -@implementation MPCollectionViewAdPlacerView - -- (id)initWithFrame:(CGRect)frame -{ - self = [super initWithFrame:frame]; - if (self) { - self.titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(2, 0, 61, 24)]; - [self.titleLabel setFont:[UIFont boldSystemFontOfSize:16.0f]]; - [self.titleLabel setText:@"Title"]; - [self addSubview:self.titleLabel]; - - self.iconImageView = [[UIImageView alloc] initWithFrame:CGRectMake(5, 30, 60, 60)]; - [self.iconImageView setClipsToBounds:YES]; - [self.iconImageView setContentMode:UIViewContentModeScaleAspectFill]; - [self addSubview:self.iconImageView]; - - self.privacyInformationIconImageView = [[UIImageView alloc] initWithFrame:CGRectMake(60, 5, 8, 8)]; - [self addSubview:self.privacyInformationIconImageView]; - - self.ctaLabel = [[UILabel alloc] initWithFrame:CGRectMake(2, 99, 66, 10)]; - [self.ctaLabel setFont:[UIFont systemFontOfSize:10.0f]]; - [self.ctaLabel setText:@"CTA Text"]; - [self.ctaLabel setTextColor:[UIColor greenColor]]; - [self.ctaLabel setTextAlignment:NSTextAlignmentRight]; - [self addSubview:self.ctaLabel]; - - self.backgroundColor = [UIColor colorWithWhite:0.21 alpha:1.0f]; - self.titleLabel.textColor = [UIColor colorWithWhite:0.86 alpha:1.0f]; - } - return self; -} - -#pragma mark - - -- (UILabel *)nativeTitleTextLabel -{ - return self.titleLabel; -} - -- (UILabel *)nativeCallToActionTextLabel -{ - return self.ctaLabel; -} - -- (UIImageView *)nativeIconImageView -{ - return self.iconImageView; -} - -- (UIImageView *)nativePrivacyInformationIconImageView -{ - return self.privacyInformationIconImageView; -} - -@end diff --git a/MoPubSampleApp/Views/MPIndexPathPickerView.h b/MoPubSampleApp/Views/MPIndexPathPickerView.h deleted file mode 100644 index 7e1bcf90e..000000000 --- a/MoPubSampleApp/Views/MPIndexPathPickerView.h +++ /dev/null @@ -1,37 +0,0 @@ -// -// MPIndexPathPickerView.h -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import - -@protocol MPIndexPathPickerViewDelegate; - -//////////////////////////////////////////////////////////////////////////////////////////////////// - -@interface MPIndexPathPickerView : UIView - -@end - -//////////////////////////////////////////////////////////////////////////////////////////////////// - -@interface MPIndexPathPickerView () - -@property (nonatomic, weak) id delegate; -@property (nonatomic) UIPickerView *pickerView; -@property (nonatomic) UIToolbar *toolbar; - -@end - -//////////////////////////////////////////////////////////////////////////////////////////////////// - -@protocol MPIndexPathPickerViewDelegate - -- (NSInteger)numberOfSectionsForIndexPathPickerView:(MPIndexPathPickerView *)pickerView; -- (NSInteger)indexPathPickerView:(MPIndexPathPickerView *)pickerView numberOfItemsInSection:(NSInteger)section; -- (void)indexPathPickerView:(MPIndexPathPickerView *)pickerView didSelectIndexPath:(NSIndexPath *)indexPath; - -@end diff --git a/MoPubSampleApp/Views/MPIndexPathPickerView.m b/MoPubSampleApp/Views/MPIndexPathPickerView.m deleted file mode 100644 index 2d48d4889..000000000 --- a/MoPubSampleApp/Views/MPIndexPathPickerView.m +++ /dev/null @@ -1,90 +0,0 @@ -// -// MPIndexPathPickerView.m -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import "MPIndexPathPickerView.h" - -@implementation MPIndexPathPickerView - -- (id)initWithFrame:(CGRect)frame -{ - self = [super initWithFrame:frame]; - if (self) { - self.backgroundColor = [UIColor whiteColor]; - - self.toolbar = [[UIToolbar alloc] init]; - [self.toolbar setItems:@[[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil], - [[UIBarButtonItem alloc] initWithTitle:@"Move to Selected Index Path" style:UIBarButtonItemStyleDone target:self action:@selector(doneButtonPressed:)]]]; - [self.toolbar.items[0] setEnabled:NO]; - [self.toolbar sizeToFit]; - [self addSubview:self.toolbar]; - - self.pickerView = [[UIPickerView alloc] init]; - self.pickerView.delegate = self; - - CGRect pickerViewFrame = self.pickerView.frame; - pickerViewFrame.origin = CGPointMake(0, self.toolbar.frame.size.height); - self.pickerView.frame = pickerViewFrame; - - [self addSubview:self.pickerView]; - - CGRect selfFrame = frame; - selfFrame.size.width = [UIScreen mainScreen].applicationFrame.size.width; - selfFrame.size.height = self.pickerView.frame.size.height + self.toolbar.frame.size.height; - self.frame = selfFrame; - } - return self; -} - -- (void)doneButtonPressed:(UIBarButtonItem *)item -{ - NSIndexPath *indexPath = [NSIndexPath indexPathForItem:[self.pickerView selectedRowInComponent:1] inSection:[self.pickerView selectedRowInComponent:0]]; - [self.delegate indexPathPickerView:self didSelectIndexPath:indexPath]; -} - -#pragma mark - - -- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView -{ - // Index path section and item. - return 2; -} - -- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component -{ - if (component == 0) { - return [self.delegate numberOfSectionsForIndexPathPickerView:self]; - } else if (component == 1) { - NSInteger selectedSectionIndex = [pickerView selectedRowInComponent:0]; - return [self.delegate indexPathPickerView:self numberOfItemsInSection:selectedSectionIndex]; - } else { - return 0; - } -} - -#pragma mark - - -- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component -{ - if (component == 0) { - return [NSString stringWithFormat:@"%ld", (long)row]; - } else if (component == 1) { - return [NSString stringWithFormat:@"%ld", (long)row]; - } else { - return @"Invalid"; - } -} - -- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component -{ - // If the user changes sections, refresh the component that displays the number of items. - if (component == 0) { - [pickerView reloadComponent:1]; - } -} - -@end diff --git a/MoPubSampleApp/Views/MPNativeAdCell.h b/MoPubSampleApp/Views/MPNativeAdCell.h deleted file mode 100644 index 5be4361cc..000000000 --- a/MoPubSampleApp/Views/MPNativeAdCell.h +++ /dev/null @@ -1,21 +0,0 @@ -// -// MPNativeAdCell.h -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import -#import "MPNativeAdRendering.h" - -@interface MPNativeAdCell : UITableViewCell - -@property (strong, nonatomic) UILabel *titleLabel; -@property (strong, nonatomic) UILabel *mainTextLabel; -@property (strong, nonatomic) UIImageView *iconImageView; -@property (strong, nonatomic) UIImageView *mainImageView; -@property (strong, nonatomic) UIImageView *DAAIconImageView; -@property (strong, nonatomic) UILabel *ctaLabel; - -@end diff --git a/MoPubSampleApp/Views/MPNativeAdCell.m b/MoPubSampleApp/Views/MPNativeAdCell.m deleted file mode 100644 index a7ab6b625..000000000 --- a/MoPubSampleApp/Views/MPNativeAdCell.m +++ /dev/null @@ -1,83 +0,0 @@ -// -// MPNativeAdCell.m -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import "MPNativeAdCell.h" - - -@implementation MPNativeAdCell - -- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier -{ - self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]; - if (self) { - self.titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(75, 10, 212, 60)]; - [self.titleLabel setFont:[UIFont boldSystemFontOfSize:17.0f]]; - [self.titleLabel setText:@"Title"]; - [self addSubview:self.titleLabel]; - - self.mainTextLabel = [[UILabel alloc] initWithFrame:CGRectMake(10, 68, 300, 50)]; - [self.mainTextLabel setFont:[UIFont systemFontOfSize:14.0f]]; - [self.mainTextLabel setText:@"Text"]; - [self.mainTextLabel setNumberOfLines:2]; - [self addSubview:self.mainTextLabel]; - - self.iconImageView = [[UIImageView alloc] initWithFrame:CGRectMake(10, 10, 60, 60)]; - [self addSubview:self.iconImageView]; - - self.DAAIconImageView = [[UIImageView alloc] initWithFrame:CGRectMake(290, 10, 20, 20)]; - [self addSubview:self.DAAIconImageView]; - - self.mainImageView = [[UIImageView alloc] initWithFrame:CGRectMake(10, 119, 300, 156)]; - [self.mainImageView setClipsToBounds:YES]; - [self.mainImageView setContentMode:UIViewContentModeScaleAspectFill]; - [self addSubview:self.mainImageView]; - - self.ctaLabel = [[UILabel alloc] initWithFrame:CGRectMake(10, 270, 300, 48)]; - [self.ctaLabel setFont:[UIFont systemFontOfSize:14.0f]]; - [self.ctaLabel setText:@"CTA Text"]; - [self.ctaLabel setTextColor:[UIColor greenColor]]; - [self.ctaLabel setTextAlignment:NSTextAlignmentRight]; - [self addSubview:self.ctaLabel]; - - self.backgroundColor = [UIColor colorWithWhite:0.21 alpha:1.0f]; - self.titleLabel.textColor = [UIColor colorWithWhite:0.86 alpha:1.0f]; - self.mainTextLabel.textColor = [UIColor colorWithWhite:0.86 alpha:1.0f]; - - self.clipsToBounds = YES; - } - return self; -} - -#pragma mark - - -- (UILabel *)nativeMainTextLabel -{ - return self.mainTextLabel; -} - -- (UILabel *)nativeTitleTextLabel -{ - return self.titleLabel; -} - -- (UILabel *)nativeCtaTextLabel -{ - return self.ctaLabel; -} - -- (UIImageView *)nativeIconImageView -{ - return self.iconImageView; -} - -- (UIImageView *)nativeMainImageView -{ - return self.mainImageView; -} - -@end diff --git a/MoPubSampleApp/Views/MPNativeAdPageView.h b/MoPubSampleApp/Views/MPNativeAdPageView.h deleted file mode 100644 index 2a3008e16..000000000 --- a/MoPubSampleApp/Views/MPNativeAdPageView.h +++ /dev/null @@ -1,21 +0,0 @@ -// -// MPNativeAdPageView.h -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import -#import "MPNativeAdRendering.h" - -@interface MPNativeAdPageView : UIView - -@property (strong, nonatomic) UILabel *titleLabel; -@property (strong, nonatomic) UILabel *mainTextLabel; -@property (strong, nonatomic) UIImageView *iconImageView; -@property (strong, nonatomic) UIImageView *mainImageView; -@property (strong, nonatomic) UILabel *ctaLabel; -@property (strong, nonatomic) UIImageView *privacyInformationIconImageView; - -@end diff --git a/MoPubSampleApp/Views/MPNativeAdPageView.m b/MoPubSampleApp/Views/MPNativeAdPageView.m deleted file mode 100644 index 2f674096e..000000000 --- a/MoPubSampleApp/Views/MPNativeAdPageView.m +++ /dev/null @@ -1,86 +0,0 @@ -// -// MPNativeAdPageView.m -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import "MPNativeAdPageView.h" - -@implementation MPNativeAdPageView - -- (id)initWithFrame:(CGRect)frame -{ - self = [super initWithFrame:frame]; - if (self) { - self.titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(75, 10, 212, 60)]; - [self.titleLabel setFont:[UIFont boldSystemFontOfSize:17.0f]]; - [self.titleLabel setText:@"Title"]; - [self addSubview:self.titleLabel]; - - self.mainTextLabel = [[UILabel alloc] initWithFrame:CGRectMake(10, 75, 300, 26)]; - [self.mainTextLabel setFont:[UIFont systemFontOfSize:14.0f]]; - [self.mainTextLabel setText:@"Text"]; - [self addSubview:self.mainTextLabel]; - - self.iconImageView = [[UIImageView alloc] initWithFrame:CGRectMake(10, 10, 60, 60)]; - [self addSubview:self.iconImageView]; - - self.mainImageView = [[UIImageView alloc] initWithFrame:CGRectMake(10, 109, 300, 156)]; - [self.mainImageView setClipsToBounds:YES]; - [self.mainImageView setContentMode:UIViewContentModeScaleAspectFill]; - [self addSubview:self.mainImageView]; - - self.ctaLabel = [[UILabel alloc] initWithFrame:CGRectMake(10, 265, 300, 48)]; - [self.ctaLabel setFont:[UIFont systemFontOfSize:14.0f]]; - [self.ctaLabel setText:@"CTA Text"]; - [self.ctaLabel setTextColor:[UIColor greenColor]]; - [self.ctaLabel setTextAlignment:NSTextAlignmentRight]; - [self addSubview:self.ctaLabel]; - - self.privacyInformationIconImageView = [[UIImageView alloc] initWithFrame:CGRectMake(290, 10, 20, 20)]; - [self addSubview:self.privacyInformationIconImageView]; - - self.backgroundColor = [UIColor colorWithWhite:0.21 alpha:1.0f]; - self.titleLabel.textColor = [UIColor colorWithWhite:0.86 alpha:1.0f]; - self.mainTextLabel.textColor = [UIColor colorWithWhite:0.86 alpha:1.0f]; - - self.titleLabel.backgroundColor = [UIColor clearColor]; - self.mainTextLabel.backgroundColor = [UIColor clearColor]; - self.ctaLabel.backgroundColor = [UIColor clearColor]; - } - return self; -} - -- (UILabel *)nativeMainTextLabel -{ - return self.mainTextLabel; -} - -- (UILabel *)nativeTitleTextLabel -{ - return self.titleLabel; -} - -- (UILabel *)nativeCallToActionTextLabel -{ - return self.ctaLabel; -} - -- (UIImageView *)nativeIconImageView -{ - return self.iconImageView; -} - -- (UIImageView *)nativeMainImageView -{ - return self.mainImageView; -} - -- (UIImageView *)nativePrivacyInformationIconImageView -{ - return self.privacyInformationIconImageView; -} - -@end diff --git a/MoPubSampleApp/Views/MPNativeAdTableHeaderView.h b/MoPubSampleApp/Views/MPNativeAdTableHeaderView.h deleted file mode 100644 index d2f6003b9..000000000 --- a/MoPubSampleApp/Views/MPNativeAdTableHeaderView.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// MPNativeAdTableHeaderView.h -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import - -@interface MPNativeAdTableHeaderView : UIView - -@property (weak, nonatomic) IBOutlet UILabel *IDLabel; -@property (weak, nonatomic) IBOutlet UILabel *failLabel; -@property (weak, nonatomic) IBOutlet UIButton *loadAdButton; -@property (weak, nonatomic) IBOutlet UITextField *keywordsTextField; -@property (weak, nonatomic) IBOutlet UIActivityIndicatorView *spinner; - -@end diff --git a/MoPubSampleApp/Views/MPNativeAdTableHeaderView.m b/MoPubSampleApp/Views/MPNativeAdTableHeaderView.m deleted file mode 100644 index 9b1779b62..000000000 --- a/MoPubSampleApp/Views/MPNativeAdTableHeaderView.m +++ /dev/null @@ -1,13 +0,0 @@ -// -// MPNativeAdTableHeaderView.m -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import "MPNativeAdTableHeaderView.h" - -@implementation MPNativeAdTableHeaderView - -@end diff --git a/MoPubSampleApp/Views/MPNativeAdTableHeaderView.xib b/MoPubSampleApp/Views/MPNativeAdTableHeaderView.xib deleted file mode 100644 index 5562b50cc..000000000 --- a/MoPubSampleApp/Views/MPNativeAdTableHeaderView.xib +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/MoPubSampleApp/Views/MPNativeCollectionViewAdCollectionViewCell.h b/MoPubSampleApp/Views/MPNativeCollectionViewAdCollectionViewCell.h deleted file mode 100644 index aa443a233..000000000 --- a/MoPubSampleApp/Views/MPNativeCollectionViewAdCollectionViewCell.h +++ /dev/null @@ -1,20 +0,0 @@ -// -// MPNativeCollectionViewAdCollectionViewCell.h -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import -#import "MPNativeAdRendering.h" - - -@interface MPNativeCollectionViewAdCollectionViewCell : UICollectionViewCell - -@property (strong, nonatomic) UILabel *titleLabel; -@property (strong, nonatomic) UIImageView *iconImageView; -@property (strong, nonatomic) UIImageView *DAAIconImageView; -@property (strong, nonatomic) UILabel *ctaLabel; - -@end diff --git a/MoPubSampleApp/Views/MPNativeCollectionViewAdCollectionViewCell.m b/MoPubSampleApp/Views/MPNativeCollectionViewAdCollectionViewCell.m deleted file mode 100644 index b3f5218be..000000000 --- a/MoPubSampleApp/Views/MPNativeCollectionViewAdCollectionViewCell.m +++ /dev/null @@ -1,60 +0,0 @@ -// -// MPNativeCollectionViewAdCollectionViewCell.m -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import "MPNativeCollectionViewAdCollectionViewCell.h" - -@implementation MPNativeCollectionViewAdCollectionViewCell - -- (id)initWithFrame:(CGRect)frame -{ - self = [super initWithFrame:frame]; - if (self) { - self.titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(2, 0, 61, 24)]; - [self.titleLabel setFont:[UIFont boldSystemFontOfSize:16.0f]]; - [self.titleLabel setText:@"Title"]; - [self addSubview:self.titleLabel]; - - self.iconImageView = [[UIImageView alloc] initWithFrame:CGRectMake(5, 30, 60, 60)]; - [self.iconImageView setClipsToBounds:YES]; - [self.iconImageView setContentMode:UIViewContentModeScaleAspectFill]; - [self addSubview:self.iconImageView]; - - self.DAAIconImageView = [[UIImageView alloc] initWithFrame:CGRectMake(60, 5, 8, 8)]; - [self addSubview:self.DAAIconImageView]; - - self.ctaLabel = [[UILabel alloc] initWithFrame:CGRectMake(2, 99, 66, 10)]; - [self.ctaLabel setFont:[UIFont systemFontOfSize:10.0f]]; - [self.ctaLabel setText:@"CTA Text"]; - [self.ctaLabel setTextColor:[UIColor greenColor]]; - [self.ctaLabel setTextAlignment:NSTextAlignmentRight]; - [self addSubview:self.ctaLabel]; - - self.backgroundColor = [UIColor colorWithWhite:0.21 alpha:1.0f]; - self.titleLabel.textColor = [UIColor colorWithWhite:0.86 alpha:1.0f]; - } - return self; -} - -#pragma mark - - -- (UILabel *)nativeTitleTextLabel -{ - return self.titleLabel; -} - -- (UILabel *)nativeCtaTextLabel -{ - return self.ctaLabel; -} - -- (UIImageView *)nativeIconImageView -{ - return self.iconImageView; -} - -@end diff --git a/MoPubSampleApp/Views/MPNativeVideoTableViewAdPlacerView.h b/MoPubSampleApp/Views/MPNativeVideoTableViewAdPlacerView.h deleted file mode 100644 index edc224d87..000000000 --- a/MoPubSampleApp/Views/MPNativeVideoTableViewAdPlacerView.h +++ /dev/null @@ -1,21 +0,0 @@ -// -// MPNativeVideoTableViewAdPlacerView.h -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import "MPNativeAdRendering.h" - -@interface MPNativeVideoTableViewAdPlacerView : UIView - -@property (strong, nonatomic) UILabel *titleLabel; -@property (strong, nonatomic) UILabel *mainTextLabel; -@property (strong, nonatomic) UIImageView *iconImageView; -@property (strong, nonatomic) UIImageView *mainImageView; -@property (strong, nonatomic) UIView *videoView; -@property (strong, nonatomic) UIImageView *DAAIconImageView; -@property (strong, nonatomic) UILabel *ctaLabel; - -@end diff --git a/MoPubSampleApp/Views/MPNativeVideoTableViewAdPlacerView.m b/MoPubSampleApp/Views/MPNativeVideoTableViewAdPlacerView.m deleted file mode 100644 index 6a3d728b0..000000000 --- a/MoPubSampleApp/Views/MPNativeVideoTableViewAdPlacerView.m +++ /dev/null @@ -1,109 +0,0 @@ -// -// MPNativeVideoTableViewAdPlacerView.m -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import "MPNativeVideoTableViewAdPlacerView.h" -#import "MPGlobal.h" - -@implementation MPNativeVideoTableViewAdPlacerView - -- (id)initWithFrame:(CGRect)frame -{ - self = [super initWithFrame:frame]; - if (self) { - self.titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(75, 10, 212, 60)]; - [self.titleLabel setFont:[UIFont boldSystemFontOfSize:17.0f]]; - [self.titleLabel setText:@"Title"]; - [self addSubview:self.titleLabel]; - - self.mainTextLabel = [[UILabel alloc] initWithFrame:CGRectMake(10, 68, 300, 50)]; - [self.mainTextLabel setFont:[UIFont systemFontOfSize:14.0f]]; - [self.mainTextLabel setText:@"Text"]; - [self.mainTextLabel setNumberOfLines:2]; - [self addSubview:self.mainTextLabel]; - - self.iconImageView = [[UIImageView alloc] initWithFrame:CGRectMake(10, 10, 60, 60)]; - [self addSubview:self.iconImageView]; - - self.mainImageView = [[UIImageView alloc] initWithFrame:CGRectMake(10, 119, 300, 156)]; - [self.mainImageView setClipsToBounds:YES]; - [self.mainImageView setContentMode:UIViewContentModeScaleAspectFill]; - [self addSubview:self.mainImageView]; - - self.videoView = [[UIView alloc] initWithFrame:CGRectMake(10, 119, 300, 156)]; - self.videoView.clipsToBounds = YES; - [self.videoView setContentMode:UIViewContentModeScaleAspectFill]; - [self addSubview:self.videoView]; - - self.ctaLabel = [[UILabel alloc] initWithFrame:CGRectMake(10, 270, 300, 48)]; - [self.ctaLabel setFont:[UIFont systemFontOfSize:14.0f]]; - [self.ctaLabel setText:@"CTA Text"]; - [self.ctaLabel setTextColor:[UIColor greenColor]]; - [self.ctaLabel setTextAlignment:NSTextAlignmentRight]; - [self addSubview:self.ctaLabel]; - - self.DAAIconImageView = [[UIImageView alloc] initWithFrame:CGRectMake(290, 10, 20, 20)]; - [self addSubview:self.DAAIconImageView]; - - self.backgroundColor = [UIColor colorWithWhite:0.21 alpha:1.0f]; - self.titleLabel.textColor = [UIColor colorWithWhite:0.86 alpha:1.0f]; - self.mainTextLabel.textColor = [UIColor colorWithWhite:0.86 alpha:1.0f]; - - self.clipsToBounds = YES; - } - return self; -} - -- (void)layoutSubviews -{ - if (UIInterfaceOrientationIsLandscape(MPInterfaceOrientation())) { - self.titleLabel.frame = CGRectMake(200, 10, 212, 60); - self.videoView.frame = CGRectMake(0, 119, 320, 156); - } else { - self.titleLabel.frame = CGRectMake(75, 10, 212, 60); - self.videoView.frame = CGRectMake(10, 119, 300, 156); - } -} - -#pragma mark - - -- (UILabel *)nativeMainTextLabel -{ - return self.mainTextLabel; -} - -- (UILabel *)nativeTitleTextLabel -{ - return self.titleLabel; -} - -- (UILabel *)nativeCallToActionTextLabel -{ - return self.ctaLabel; -} - -- (UIImageView *)nativeIconImageView -{ - return self.iconImageView; -} - -- (UIImageView *)nativeMainImageView -{ - return self.mainImageView; -} - -- (UIView *)nativeVideoView -{ - return self.videoView; -} - -- (UIImageView *)nativePrivacyInformationIconImageView -{ - return self.DAAIconImageView; -} - -@end diff --git a/MoPubSampleApp/Views/MPNativeVideoView.h b/MoPubSampleApp/Views/MPNativeVideoView.h deleted file mode 100644 index bea690014..000000000 --- a/MoPubSampleApp/Views/MPNativeVideoView.h +++ /dev/null @@ -1,23 +0,0 @@ -// -// MPNativeVideoView.h -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import -#import "MPNativeAdRendering.h" - - -@interface MPNativeVideoView : UIView - -@property (strong, nonatomic) UILabel *titleLabel; -@property (strong, nonatomic) UILabel *mainTextLabel; -@property (strong, nonatomic) UIImageView *iconImageView; -@property (strong, nonatomic) UIImageView *mainImageView; -@property (strong, nonatomic) UIView *videoView; -@property (strong, nonatomic) UIImageView *privacyInformationIconImageView; -@property (strong, nonatomic) UILabel *ctaLabel; - -@end diff --git a/MoPubSampleApp/Views/MPNativeVideoView.m b/MoPubSampleApp/Views/MPNativeVideoView.m deleted file mode 100644 index 34e7ec538..000000000 --- a/MoPubSampleApp/Views/MPNativeVideoView.m +++ /dev/null @@ -1,111 +0,0 @@ -// -// MPNativeVideoView.m -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import "MPNativeVideoView.h" -#import "MPGlobal.h" - -@implementation MPNativeVideoView - -- (id)initWithFrame:(CGRect)frame -{ - self = [super initWithFrame:frame]; - if (self) { - self.titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(75, 10, 212, 60)]; - [self.titleLabel setFont:[UIFont boldSystemFontOfSize:17.0f]]; - [self.titleLabel setText:@"Title"]; - [self addSubview:self.titleLabel]; - - self.mainTextLabel = [[UILabel alloc] initWithFrame:CGRectMake(10, 68, 300, 50)]; - [self.mainTextLabel setFont:[UIFont systemFontOfSize:14.0f]]; - [self.mainTextLabel setText:@"Text"]; - [self.mainTextLabel setNumberOfLines:2]; - [self addSubview:self.mainTextLabel]; - - self.iconImageView = [[UIImageView alloc] initWithFrame:CGRectMake(10, 10, 60, 60)]; - [self addSubview:self.iconImageView]; - - self.mainImageView = [[UIImageView alloc] initWithFrame:CGRectMake(10, 119, 300, 156)]; - [self.mainImageView setClipsToBounds:YES]; - [self.mainImageView setContentMode:UIViewContentModeScaleAspectFill]; - [self addSubview:self.mainImageView]; - - self.videoView = [[UIView alloc] initWithFrame:CGRectMake(10, 119, 300, 156)]; - self.videoView.clipsToBounds = YES; - [self.videoView setContentMode:UIViewContentModeScaleAspectFill]; - [self addSubview:self.videoView]; - - self.ctaLabel = [[UILabel alloc] initWithFrame:CGRectMake(10, 270, 300, 48)]; - [self.ctaLabel setFont:[UIFont systemFontOfSize:14.0f]]; - [self.ctaLabel setText:@"CTA Text"]; - [self.ctaLabel setTextColor:[UIColor greenColor]]; - [self.ctaLabel setTextAlignment:NSTextAlignmentRight]; - [self addSubview:self.ctaLabel]; - - self.privacyInformationIconImageView = [[UIImageView alloc] initWithFrame:CGRectMake(290, 10, 20, 20)]; - [self addSubview:self.privacyInformationIconImageView]; - - self.backgroundColor = [UIColor colorWithWhite:0.21 alpha:1.0f]; - self.titleLabel.textColor = [UIColor colorWithWhite:0.86 alpha:1.0f]; - self.mainTextLabel.textColor = [UIColor colorWithWhite:0.86 alpha:1.0f]; - - self.clipsToBounds = YES; - } - return self; -} - -- (void)layoutSubviews -{ - CGFloat width = self.bounds.size.width; - - self.titleLabel.frame = CGRectMake(75, 10, 212, 60); - self.iconImageView.frame = CGRectMake(10, 10, 60, 60); - self.privacyInformationIconImageView.frame = CGRectMake(width - 30, 10, 20, 20); - self.ctaLabel.frame = CGRectMake(width - 100, 270, 90, 48); - self.mainTextLabel.frame = CGRectMake(width / 2 - 150, 68, 300, 50); - self.mainImageView.frame = CGRectMake(width / 2 - 150, 119, 300, 156); - self.videoView.frame = self.mainImageView.frame; -} - -#pragma mark - - -- (UILabel *)nativeMainTextLabel -{ - return self.mainTextLabel; -} - -- (UILabel *)nativeTitleTextLabel -{ - return self.titleLabel; -} - -- (UILabel *)nativeCallToActionTextLabel -{ - return self.ctaLabel; -} - -- (UIImageView *)nativeIconImageView -{ - return self.iconImageView; -} - -- (UIImageView *)nativeMainImageView -{ - return self.mainImageView; -} - -- (UIView *)nativeVideoView -{ - return self.videoView; -} - -- (UIImageView *)nativePrivacyInformationIconImageView -{ - return self.privacyInformationIconImageView; -} - -@end diff --git a/MoPubSampleApp/Views/MPStaticNativeAdView.h b/MoPubSampleApp/Views/MPStaticNativeAdView.h deleted file mode 100644 index 0375b747a..000000000 --- a/MoPubSampleApp/Views/MPStaticNativeAdView.h +++ /dev/null @@ -1,21 +0,0 @@ -// -// MPStaticNativeAdView.h -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import -#import "MPNativeAdRendering.h" - -@interface MPStaticNativeAdView : UIView - -@property (strong, nonatomic) UILabel *titleLabel; -@property (strong, nonatomic) UILabel *mainTextLabel; -@property (strong, nonatomic) UIImageView *iconImageView; -@property (strong, nonatomic) UIImageView *mainImageView; -@property (strong, nonatomic) UIImageView *privacyInformationIconImageView; -@property (strong, nonatomic) UILabel *ctaLabel; - -@end diff --git a/MoPubSampleApp/Views/MPStaticNativeAdView.m b/MoPubSampleApp/Views/MPStaticNativeAdView.m deleted file mode 100644 index ae74df585..000000000 --- a/MoPubSampleApp/Views/MPStaticNativeAdView.m +++ /dev/null @@ -1,100 +0,0 @@ -// -// MPStaticNativeAdView.m -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import "MPStaticNativeAdView.h" -#import "MPNativeAdRenderingImageLoader.h" - -@implementation MPStaticNativeAdView - -- (id)initWithFrame:(CGRect)frame -{ - self = [super initWithFrame:frame]; - if (self) { - self.titleLabel = [[UILabel alloc] init]; - [self.titleLabel setFont:[UIFont boldSystemFontOfSize:17.0f]]; - [self.titleLabel setText:@"Title"]; - [self addSubview:self.titleLabel]; - - self.mainTextLabel = [[UILabel alloc] init]; - [self.mainTextLabel setFont:[UIFont systemFontOfSize:14.0f]]; - [self.mainTextLabel setText:@"Text"]; - [self.mainTextLabel setNumberOfLines:2]; - [self addSubview:self.mainTextLabel]; - - self.iconImageView = [[UIImageView alloc] init]; - [self addSubview:self.iconImageView]; - - self.mainImageView = [[UIImageView alloc] init]; - [self.mainImageView setClipsToBounds:YES]; - [self.mainImageView setContentMode:UIViewContentModeScaleAspectFill]; - [self addSubview:self.mainImageView]; - - self.ctaLabel = [[UILabel alloc] init]; - [self.ctaLabel setFont:[UIFont systemFontOfSize:14.0f]]; - [self.ctaLabel setText:@"CTA Text"]; - [self.ctaLabel setTextColor:[UIColor greenColor]]; - [self.ctaLabel setTextAlignment:NSTextAlignmentRight]; - [self addSubview:self.ctaLabel]; - - self.privacyInformationIconImageView = [[UIImageView alloc] init]; - [self addSubview:self.privacyInformationIconImageView]; - - self.backgroundColor = [UIColor colorWithWhite:0.21 alpha:1.0f]; - self.titleLabel.textColor = [UIColor colorWithWhite:0.86 alpha:1.0f]; - self.mainTextLabel.textColor = [UIColor colorWithWhite:0.86 alpha:1.0f]; - - self.clipsToBounds = YES; - } - return self; -} - -- (void)layoutSubviews -{ - CGFloat width = self.bounds.size.width; - - self.titleLabel.frame = CGRectMake(75, 10, 212, 60); - self.iconImageView.frame = CGRectMake(10, 10, 60, 60); - self.privacyInformationIconImageView.frame = CGRectMake(width - 30, 10, 20, 20); - self.ctaLabel.frame = CGRectMake(width - 100, 270, 90, 48); - self.mainTextLabel.frame = CGRectMake(width / 2 - 150, 68, 300, 50); - self.mainImageView.frame = CGRectMake(width / 2 - 150, 119, 300, 156); -} - -#pragma mark - - -- (UILabel *)nativeMainTextLabel -{ - return self.mainTextLabel; -} - -- (UILabel *)nativeTitleTextLabel -{ - return self.titleLabel; -} - -- (UILabel *)nativeCallToActionTextLabel -{ - return self.ctaLabel; -} - -- (UIImageView *)nativeIconImageView -{ - return self.iconImageView; -} - -- (UIImageView *)nativeMainImageView -{ - return self.mainImageView; -} - -- (UIImageView *)nativePrivacyInformationIconImageView -{ - return self.privacyInformationIconImageView; -} - -@end diff --git a/MoPubSampleApp/Views/MPTableViewAdPlacerView.h b/MoPubSampleApp/Views/MPTableViewAdPlacerView.h deleted file mode 100644 index c0dfa73fc..000000000 --- a/MoPubSampleApp/Views/MPTableViewAdPlacerView.h +++ /dev/null @@ -1,21 +0,0 @@ -// -// MPTableViewAdPlacerView.h -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import -#import "MPNativeAdRendering.h" - -@interface MPTableViewAdPlacerView : UIView - -@property (strong, nonatomic) UILabel *titleLabel; -@property (strong, nonatomic) UILabel *mainTextLabel; -@property (strong, nonatomic) UIImageView *iconImageView; -@property (strong, nonatomic) UIImageView *mainImageView; -@property (strong, nonatomic) UIImageView *privacyInformationIconImageView; -@property (strong, nonatomic) UILabel *ctaLabel; - -@end diff --git a/MoPubSampleApp/Views/MPTableViewAdPlacerView.m b/MoPubSampleApp/Views/MPTableViewAdPlacerView.m deleted file mode 100644 index 95cda40b3..000000000 --- a/MoPubSampleApp/Views/MPTableViewAdPlacerView.m +++ /dev/null @@ -1,117 +0,0 @@ -// -// MPTableViewAdPlacerView.m -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import "MPTableViewAdPlacerView.h" -#import "MPNativeAdRenderingImageLoader.h" - -@implementation MPTableViewAdPlacerView - -- (id)initWithFrame:(CGRect)frame -{ - self = [super initWithFrame:frame]; - if (self) { - self.titleLabel = [[UILabel alloc] init]; - [self.titleLabel setFont:[UIFont boldSystemFontOfSize:17.0f]]; - [self.titleLabel setText:@"Title"]; - [self addSubview:self.titleLabel]; - - self.mainTextLabel = [[UILabel alloc] init]; - [self.mainTextLabel setFont:[UIFont systemFontOfSize:14.0f]]; - [self.mainTextLabel setText:@"Text"]; - [self.mainTextLabel setNumberOfLines:2]; - [self addSubview:self.mainTextLabel]; - - self.iconImageView = [[UIImageView alloc] init]; - [self addSubview:self.iconImageView]; - - self.mainImageView = [[UIImageView alloc] init]; - [self.mainImageView setClipsToBounds:YES]; - [self.mainImageView setContentMode:UIViewContentModeScaleAspectFill]; - [self addSubview:self.mainImageView]; - - self.ctaLabel = [[UILabel alloc] init]; - [self.ctaLabel setFont:[UIFont systemFontOfSize:14.0f]]; - [self.ctaLabel setText:@"CTA Text"]; - [self.ctaLabel setTextColor:[UIColor greenColor]]; - [self.ctaLabel setTextAlignment:NSTextAlignmentRight]; - [self addSubview:self.ctaLabel]; - - self.privacyInformationIconImageView = [[UIImageView alloc] init]; - [self addSubview:self.privacyInformationIconImageView]; - - self.backgroundColor = [UIColor colorWithWhite:0.21 alpha:1.0f]; - self.titleLabel.textColor = [UIColor colorWithWhite:0.86 alpha:1.0f]; - self.mainTextLabel.textColor = [UIColor colorWithWhite:0.86 alpha:1.0f]; - - self.clipsToBounds = YES; - } - return self; -} - -- (void)layoutSubviews -{ - CGFloat width = self.bounds.size.width; - - self.titleLabel.frame = CGRectMake(75, 10, 212, 60); - self.iconImageView.frame = CGRectMake(10, 10, 60, 60); - self.privacyInformationIconImageView.frame = CGRectMake(width - 30, 10, 20, 20); - self.ctaLabel.frame = CGRectMake(width - 100, 270, 90, 48); - self.mainTextLabel.frame = CGRectMake(width / 2 - 150, 68, 300, 50); - self.mainImageView.frame = CGRectMake(width / 2 - 150, 119, 300, 156); -} - -#pragma mark - - -- (UILabel *)nativeMainTextLabel -{ - return self.mainTextLabel; -} - -- (UILabel *)nativeTitleTextLabel -{ - return self.titleLabel; -} - -- (UILabel *)nativeCallToActionTextLabel -{ - return self.ctaLabel; -} - -- (UIImageView *)nativeIconImageView -{ - return self.iconImageView; -} - -- (UIImageView *)nativeMainImageView -{ - return self.mainImageView; -} - -- (UIImageView *)nativePrivacyInformationIconImageView -{ - return self.privacyInformationIconImageView; -} - -// This is where you can construct and layout your view that represents your star rating. -/* -- (void)layoutStarRating:(NSNumber *)starRating -{ - -} -*/ - -// This is where you can place custom assets from the properties dictionary in your view. -// The code below shows how a custom image can be loaded. -/* -- (void)layoutCustomAssetsWithProperties:(NSDictionary *)customProperties imageLoader:(MPNativeAdRenderingImageLoader *)imageLoader -{ - [imageLoader loadImageForURL:[NSURL URLWithString:customProperties[@"wutImage"]] intoImageView:self.customImageView]; -} -*/ - -@end diff --git a/MoPubSampleApp/en.lproj/InfoPlist.strings b/MoPubSampleApp/en.lproj/InfoPlist.strings deleted file mode 100644 index 477b28ff8..000000000 --- a/MoPubSampleApp/en.lproj/InfoPlist.strings +++ /dev/null @@ -1,2 +0,0 @@ -/* Localized versions of Info.plist keys */ - diff --git a/MoPubSampleApp/main.m b/MoPubSampleApp/main.m deleted file mode 100644 index a1cef282a..000000000 --- a/MoPubSampleApp/main.m +++ /dev/null @@ -1,18 +0,0 @@ -// -// main.m -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import - -#import "AppDelegate.h" - -int main(int argc, char *argv[]) -{ - @autoreleasepool { - return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); - } -} diff --git a/MoPubSampleApp/mopub_logo.png b/MoPubSampleApp/mopub_logo.png deleted file mode 100644 index be8a1c94c..000000000 Binary files a/MoPubSampleApp/mopub_logo.png and /dev/null differ diff --git a/README.md b/README.md index 461f22173..933363887 100644 --- a/README.md +++ b/README.md @@ -6,12 +6,10 @@ Sign up for an account at [http://app.mopub.com/](http://app.mopub.com/). ## Need Help? -You can find integration documentation on our [wiki](https://github.com/mopub/mopub-ios-sdk/wiki/Getting-Started) and additional help documentation on our [developer help site](https://www.mopub.com/resources/docs). +You can find integration documentation on our [developer help site](https://developers.mopub.com/publishers/ios/get-started/). Additional documentation can be found [here](https://www.mopub.com/resources/docs). To file an issue with our team, email [support@mopub.com](mailto:support@mopub.com). -**Please Note: We no longer accept GitHub Issues** - ## New Pull Requests? Thank you for submitting pull requests to the MoPub iOS GitHub repository. Our team regularly monitors and investigates all submissions for inclusion in our official SDK releases. Please note that MoPub does not directly merge these pull requests at this time. Please reach out to your account team or [support@mopub.com](mailto:support@mopub.com) if you have further questions. @@ -25,7 +23,7 @@ If you do not remove or disable IAS's and/or Moat’s technology in accordance w The MoPub SDK supports multiple methods for installing the library in a project. -The current version of the SDK is 5.4.0 +The current version of the SDK is 5.9.0 ### Installation with CocoaPods @@ -40,11 +38,11 @@ To integrate MoPub SDK into your Xcode project using CocoaPods, specify it in yo ``` source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '9.0' use_frameworks! target 'MyApp' do - pod 'mopub-ios-sdk', '~> 5.0' + pod 'mopub-ios-sdk', '~> 5.9' end ``` @@ -58,7 +56,7 @@ $ pod install MoPub provides a prepackaged archive of the dynamic framework: -- **[MoPub SDK Framework.zip](http://bit.ly/2OV5fiw)** +- **[MoPub SDK Framework.zip](https://github.com/mopub/mopub-ios-sdk/releases/download/5.9.0/mopub-framework-5.9.0.zip)** Includes everything you need to serve HTML, MRAID, and Native MoPub advertisements. Third party ad networks are not included. @@ -68,11 +66,11 @@ Add the dynamic framework to the target's Embedded Binaries section of the Gener MoPub provides two prepackaged archives of source code: -- **[MoPub Base SDK.zip](http://bit.ly/2bH8ObO)** +- **[MoPub Base SDK.zip](https://github.com/mopub/mopub-ios-sdk/releases/download/5.9.0/mopub-base-5.9.0.zip)** Includes everything you need to serve HTML, MRAID, and Native MoPub advertisements. Third party ad networks are not included. -- **[MoPub Base SDK Excluding Native.zip](http://bit.ly/2bCCgRw)** +- **[MoPub Base SDK Excluding Native.zip](https://github.com/mopub/mopub-ios-sdk/releases/download/5.9.0/mopub-nonnative-5.9.0.zip)** Includes everything you need to serve HTML and MRAID advertisements. Third party ad networks and Native MoPub advertisements are not included. @@ -85,14 +83,23 @@ Integration instructions are available on the [wiki](https://github.com/mopub/mo Please view the [changelog](https://github.com/mopub/mopub-ios-sdk/blob/master/CHANGELOG.md) for details. - **Features** - - SDK distribution as a dynamic framework is now available. - - Local extras are now supported for all ad formats. + - Add iOS 13 support to both SDK and MoPub Sample app. + - Totally remove `UIWebView` implementation and comments in MoPub SDK and MoPub Sample app. + - Add multi-window support for MoPub Sample app in iPadOS 13. New window can be opened by Drag & Dropping an ad cell in the ad list. + - Remove support for `tel` and `sms` functions for MRAID ads. + - Add Dark Mode support for MoPub Sample app in iOS 13. + - Remove the Objective C sample app project. + - Adopt `XCFramework` and the new Xcode build system with fastlane script updates, and thus require Xcode 11 to build instead of Xcode 9. + - Remove deprecated VAST extension `MoPubViewabilityTracker`. + - Replace deprecated `MPMoviePlayerViewController` with `AVPlayerViewController`. This affects MRAID videos. + - Replace deprecated `UIAlertView` with `UIAlertViewController`. - **Bug Fixes** - - HTTP error codes now include the localized error description. - - Added missing mraid.js file protections when showing MRAID ads. - - Fixed native video crash. - - Fixed native ad timeout timer invalidation. + - Update `MPRealTimeTimer` so that it can properly handle foreground notifications that aren't balanced with backgrounding notifications. + - Fix an assertion crash in GDPR Sync that only happens in debug builds. + - Present `SKStoreProductViewController` only in portrait mode, so that we can prevent a `SKStoreProductViewController` crash in landscape mode (as designed by Apple). + - Fix an infinite load ad bug that happens when the ad URL to retry is the same as the failed ad URL. + - Fix a bug where location information is not sent to Ad Server when location permission has been allowed, the app can collect PII, and no app-specified location is set. See the [Getting Started Guide](https://github.com/mopub/mopub-ios-sdk/wiki/Getting-Started#app-transport-security-settings) for instructions on setting up ATS in your app. @@ -118,8 +125,8 @@ If you would like to opt out of viewability measurement but do not want to modif ## Requirements -- iOS 8.0 and up -- Xcode 9.0 and up +- iOS 9.0 and up +- Xcode 11.0 and up ## License diff --git a/Scripts/mraid_build.sh b/Scripts/mraid_build.sh index 29d09f97d..ae2e10563 100644 --- a/Scripts/mraid_build.sh +++ b/Scripts/mraid_build.sh @@ -19,7 +19,7 @@ copyMRAIDToResources() { MRAID_JS_FILE_CONTENT=`cat "${MRAID_FILE_LOCATION}"` # Insert the comment before the mraid.js content and output it to the mraid.js in our resources. - echo -n "${MRAID_JS_COMMENT}${MRAID_JS_FILE_CONTENT}" > "$1/MoPubSDK/Resources/MRAID.bundle/mraid.js" + echo "${MRAID_JS_COMMENT}${MRAID_JS_FILE_CONTENT}" > "$1/MoPubSDK/Resources/MRAID.bundle/mraid.js" else echo "Could not find mraid.js at location ${MRAID_FILE_LOCATION}. Will not copy mraid.js from mopub-sdk-common to MoPub resources." fi diff --git a/mopub-ios-sdk.podspec b/mopub-ios-sdk.podspec index 14f333069..a69e1ac34 100644 --- a/mopub-ios-sdk.podspec +++ b/mopub-ios-sdk.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |spec| spec.name = 'mopub-ios-sdk' spec.module_name = 'MoPub' - spec.version = '5.4.0' + spec.version = '5.9.0' spec.license = { :type => 'New BSD', :file => 'LICENSE' } spec.homepage = 'https://github.com/mopub/mopub-ios-sdk' spec.authors = { 'MoPub' => 'support@mopub.com' } @@ -14,11 +14,12 @@ Pod::Spec.new do |spec| To learn more or sign up for an account, go to http://www.mopub.com. \n DESC spec.social_media_url = 'http://twitter.com/mopub' - spec.source = { :git => 'https://github.com/mopub/mopub-ios-sdk.git', :tag => '5.4.0' } + spec.source = { :git => 'https://github.com/mopub/mopub-ios-sdk.git', :tag => '5.9.0' } spec.requires_arc = true - spec.ios.deployment_target = '8.0' + spec.ios.deployment_target = '9.0' spec.frameworks = [ 'AVFoundation', + 'AVKit', 'CoreGraphics', 'CoreLocation', 'CoreMedia', @@ -45,7 +46,7 @@ Pod::Spec.new do |spec| spec.subspec 'Core' do |core| core.source_files = 'MoPubSDK/**/*.{h,m}' - core.resources = 'MoPubSDK/**/*.{png,bundle,xib,nib,html}' + core.resources = ['MoPubSDK/**/*.{png,bundle,xib,nib}', 'MoPubSDK/**/MPAdapters.plist'] core.exclude_files = ['MoPubSDK/Viewability/Moat', 'MoPubSDK/Viewability/Avid'] end