From c1deac071a5f17fbe5eb1d2cf5cd30b89eeeaab8 Mon Sep 17 00:00:00 2001 From: Kelly Dun Date: Wed, 28 Nov 2018 09:46:25 -0800 Subject: [PATCH 01/12] 5.4.1 --- CHANGELOG.md | 6 + Canary/Canary.xcodeproj/project.pbxproj | 16 +- .../xcschemes/Canary (Internal).xcscheme | 91 ++++ .../xcshareddata/xcschemes/Canary.xcscheme | 91 ++++ Canary/Canary/Info.plist | 4 +- MoPubResources/Info.plist | 4 +- MoPubSDK.xcodeproj/project.pbxproj | 30 +- .../xcschemes/MoPubSDKFramework.xcscheme | 2 +- MoPubSDK/MPConstants.h | 2 +- MoPubSDK/MoPub-Bridging-Header.h | 52 -- MoPubSDK/MoPub.h | 1 + .../Internal/MPNativePositionSource.m | 6 + .../Internal/MPRewardedVideoAdManager.m | 6 + MoPubSDKFramework/Info.plist | 4 +- MoPubSDKFramework/MoPubSDKFramework.modulemap | 3 + MoPubSDKTests/Info.plist | 4 +- .../MoPubSampleApp.xcodeproj/project.pbxproj | 452 +++++++++--------- .../xcschemes/MoPubSampleApp.xcscheme | 2 - README.md | 13 +- mopub-ios-sdk.podspec | 4 +- 20 files changed, 469 insertions(+), 324 deletions(-) create mode 100644 Canary/Canary.xcodeproj/xcshareddata/xcschemes/Canary (Internal).xcscheme create mode 100644 Canary/Canary.xcodeproj/xcshareddata/xcschemes/Canary.xcscheme delete mode 100644 MoPubSDK/MoPub-Bridging-Header.h diff --git a/CHANGELOG.md b/CHANGELOG.md index b4e9cc432..ba2a4879e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 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. diff --git a/Canary/Canary.xcodeproj/project.pbxproj b/Canary/Canary.xcodeproj/project.pbxproj index 921cc7316..755cc17b8 100644 --- a/Canary/Canary.xcodeproj/project.pbxproj +++ b/Canary/Canary.xcodeproj/project.pbxproj @@ -667,8 +667,8 @@ ); 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}/TapjoySDK/TapjoySDK_iOS_v12.1.0/Libraries/Tapjoy.embeddedframework/Resources/TapjoyResources.bundle", + "${PODS_ROOT}/TapjoySDK/TapjoySDK_iOS_v12.1.0/Libraries/Tapjoy.embeddedframework/Tapjoy.framework/Versions/A/Resources/TapjoyResources.bundle", ); name = "[CP] Copy Pods Resources"; outputPaths = ( @@ -686,8 +686,8 @@ ); 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}/TapjoySDK/TapjoySDK_iOS_v12.1.0/Libraries/Tapjoy.embeddedframework/Resources/TapjoyResources.bundle", + "${PODS_ROOT}/TapjoySDK/TapjoySDK_iOS_v12.1.0/Libraries/Tapjoy.embeddedframework/Tapjoy.framework/Versions/A/Resources/TapjoyResources.bundle", ); name = "[CP] Copy Pods Resources"; outputPaths = ( @@ -1028,7 +1028,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 5.4.0; + CURRENT_PROJECT_VERSION = 5.4.1; DEVELOPMENT_TEAM = 4S7XS533V3; INFOPLIST_FILE = Canary/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; @@ -1046,7 +1046,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 5.4.0; + CURRENT_PROJECT_VERSION = 5.4.1; DEVELOPMENT_TEAM = 4S7XS533V3; INFOPLIST_FILE = Canary/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; @@ -1064,7 +1064,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 5.4.0; + CURRENT_PROJECT_VERSION = 5.4.1; DEVELOPMENT_TEAM = 4S7XS533V3; INFOPLIST_FILE = "Canary/Internal/Internal-Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; @@ -1083,7 +1083,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 5.4.0; + CURRENT_PROJECT_VERSION = 5.4.1; DEVELOPMENT_TEAM = 4S7XS533V3; INFOPLIST_FILE = "Canary/Internal/Internal-Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; diff --git a/Canary/Canary.xcodeproj/xcshareddata/xcschemes/Canary (Internal).xcscheme b/Canary/Canary.xcodeproj/xcshareddata/xcschemes/Canary (Internal).xcscheme new file mode 100644 index 000000000..3dd34bb8c --- /dev/null +++ b/Canary/Canary.xcodeproj/xcshareddata/xcschemes/Canary (Internal).xcscheme @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Canary/Canary.xcodeproj/xcshareddata/xcschemes/Canary.xcscheme b/Canary/Canary.xcodeproj/xcshareddata/xcschemes/Canary.xcscheme new file mode 100644 index 000000000..f244b59a6 --- /dev/null +++ b/Canary/Canary.xcodeproj/xcshareddata/xcschemes/Canary.xcscheme @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Canary/Canary/Info.plist b/Canary/Canary/Info.plist index ef008b71a..9028023f0 100644 --- a/Canary/Canary/Info.plist +++ b/Canary/Canary/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 5.4.0 + 5.4.1 CFBundleURLTypes @@ -28,7 +28,7 @@ CFBundleVersion - 5.4.0 + 5.4.1 LSRequiresIPhoneOS NSAppTransportSecurity diff --git a/MoPubResources/Info.plist b/MoPubResources/Info.plist index 5c3264f9f..310ac5acc 100644 --- a/MoPubResources/Info.plist +++ b/MoPubResources/Info.plist @@ -13,7 +13,7 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 5.4.0 + 5.4.1 CFBundleSignature ???? CFBundleSupportedPlatforms @@ -21,7 +21,7 @@ iPhoneOS CFBundleVersion - 5.4.0 + 5.4.1 NSHumanReadableCopyright Copyright 2018 Twitter Inc. All rights reserved. NSPrincipalClass diff --git a/MoPubSDK.xcodeproj/project.pbxproj b/MoPubSDK.xcodeproj/project.pbxproj index fbea29a16..07bf58dae 100644 --- a/MoPubSDK.xcodeproj/project.pbxproj +++ b/MoPubSDK.xcodeproj/project.pbxproj @@ -280,7 +280,6 @@ 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, ); }; }; 2AF031F52016B78800909F29 /* MOPUBExperimentProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = B2D54B541ED20521004E3C7B /* MOPUBExperimentProvider.h */; }; 2AF031F62016B78800909F29 /* MOPUBActivityIndicatorView.h in Headers */ = {isa = PBXBuildFile; fileRef = 57AC691F1BB21FA20053C556 /* MOPUBActivityIndicatorView.h */; }; @@ -1015,7 +1014,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 = ""; }; @@ -1946,7 +1944,6 @@ isa = PBXGroup; children = ( AEF9D4C7171CA895005AAA5A /* Internal */, - 5755ADCD19C79CAD0012B01F /* MoPub-Bridging-Header.h */, A71DB8111A2FE68300D3B229 /* MoPub.h */, A71DB8121A2FE68300D3B229 /* MoPub.m */, 2A2701EE2020FCDC004A72E6 /* MOPUBDisplayAgentType.h */, @@ -2473,7 +2470,6 @@ 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 */, 2AF032892016B78800909F29 /* MPViewabilityAdapter.h in Headers */, @@ -3507,7 +3503,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.4.1; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 4S7XS533V3; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -3548,7 +3544,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.4.1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 4S7XS533V3; ENABLE_NS_ASSERTIONS = NO; @@ -3589,12 +3585,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.4.1; DEBUG_INFORMATION_FORMAT = dwarf; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 4S7XS533V3; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 5.4.0; + DYLIB_CURRENT_VERSION = 5.4.1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_PREPROCESSOR_DEFINITIONS = ( @@ -3634,12 +3630,12 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 5.4.0; + CURRENT_PROJECT_VERSION = 5.4.1; 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.4.1; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_NS_ASSERTIONS = NO; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -3672,7 +3668,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.4.1; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_GENERATE_DEBUGGING_SYMBOLS = NO; GCC_PREPROCESSOR_DEFINITIONS = ( @@ -3706,7 +3702,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.4.1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -3733,7 +3729,7 @@ AE515F9A171F1B110086B464 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - CURRENT_PROJECT_VERSION = 5.4.0; + CURRENT_PROJECT_VERSION = 5.4.1; DSTROOT = /tmp/MoPubSDK.dst; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -3753,7 +3749,7 @@ AE515F9B171F1B110086B464 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - CURRENT_PROJECT_VERSION = 5.4.0; + CURRENT_PROJECT_VERSION = 5.4.1; DSTROOT = /tmp/MoPubSDK.dst; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -3813,7 +3809,7 @@ GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 8.0; ONLY_ACTIVE_ARCH = YES; - OTHER_CFLAGS = "-fembed-bitcode-marker"; + OTHER_CFLAGS = "-fembed-bitcode"; OTHER_LDFLAGS = ( "-ObjC", "$(inherited)", @@ -3870,7 +3866,7 @@ BCA00B951EF47A91006FF762 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - CURRENT_PROJECT_VERSION = 5.4.0; + CURRENT_PROJECT_VERSION = 5.4.1; DSTROOT = /tmp/MoPubSDK.dst; GCC_PRECOMPILE_PREFIX_HEADER = NO; GCC_TREAT_WARNINGS_AS_ERRORS = YES; @@ -3887,7 +3883,7 @@ BCA00B961EF47A91006FF762 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - CURRENT_PROJECT_VERSION = 5.4.0; + CURRENT_PROJECT_VERSION = 5.4.1; DSTROOT = /tmp/MoPubSDK.dst; GCC_PRECOMPILE_PREFIX_HEADER = NO; GCC_TREAT_WARNINGS_AS_ERRORS = YES; diff --git a/MoPubSDK.xcodeproj/xcshareddata/xcschemes/MoPubSDKFramework.xcscheme b/MoPubSDK.xcodeproj/xcshareddata/xcschemes/MoPubSDKFramework.xcscheme index 5d2291f6b..8b9805eb1 100644 --- a/MoPubSDK.xcodeproj/xcshareddata/xcschemes/MoPubSDKFramework.xcscheme +++ b/MoPubSDK.xcodeproj/xcshareddata/xcschemes/MoPubSDKFramework.xcscheme @@ -81,7 +81,7 @@ ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction"> + scriptText = " "> = strongSelf.maximumRetryInterval) { strongSelf.completionHandler(nil, error); strongSelf.completionHandler = nil; diff --git a/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdManager.m b/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdManager.m index 1d2b9cfe5..68c86c933 100644 --- a/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdManager.m +++ b/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdManager.m @@ -303,6 +303,11 @@ - (void)rewardedVideoDidExpireForAdapter:(MPRewardedVideoAdapter *)adapter - (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 = YES; + [self.delegate rewardedVideoDidFailToPlayForAdManager:self error:error]; } @@ -323,6 +328,7 @@ - (void)rewardedVideoWillDisappearForAdapter:(MPRewardedVideoAdapter *)adapter - (void)rewardedVideoDidDisappearForAdapter:(MPRewardedVideoAdapter *)adapter { + // Successful playback of the rewarded video; reset the internal played state. self.ready = NO; self.playedAd = YES; [self.delegate rewardedVideoDidDisappearForAdManager:self]; diff --git a/MoPubSDKFramework/Info.plist b/MoPubSDKFramework/Info.plist index 771b88ffd..317468cfe 100644 --- a/MoPubSDKFramework/Info.plist +++ b/MoPubSDKFramework/Info.plist @@ -15,9 +15,9 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 5.4.0 + 5.4.1 CFBundleVersion - 5.4.0 + 5.4.1 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/Info.plist b/MoPubSDKTests/Info.plist index 952a37794..6716c5758 100644 --- a/MoPubSDKTests/Info.plist +++ b/MoPubSDKTests/Info.plist @@ -15,8 +15,8 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 5.4.0 + 5.4.1 CFBundleVersion - 5.4.0 + 5.4.1 diff --git a/MoPubSampleApp/MoPubSampleApp.xcodeproj/project.pbxproj b/MoPubSampleApp/MoPubSampleApp.xcodeproj/project.pbxproj index 6b3ea15a7..2f47b03e3 100644 --- a/MoPubSampleApp/MoPubSampleApp.xcodeproj/project.pbxproj +++ b/MoPubSampleApp/MoPubSampleApp.xcodeproj/project.pbxproj @@ -7,69 +7,76 @@ 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 */; }; + 2A5874FB1F9033E800E8CDFA /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2A5874FA1F9033E800E8CDFA /* LaunchScreen.storyboard */; }; + 2A5874FE1F90341F00E8CDFA /* mopub_logo.png in Resources */ = {isa = PBXBuildFile; fileRef = 2A5874FD1F90341F00E8CDFA /* mopub_logo.png */; }; + 2A86E7F21D710CE60087594C /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 57EF00CD1BD96EB5002634DE /* WebKit.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; + 2AC79F441DF7814700195AC5 /* SafariServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2AC79F431DF7814700195AC5 /* SafariServices.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; + 462E156F17F0F5D100408E17 /* MPMRectBannerAdDetailViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 462E156E17F0F5D100408E17 /* MPMRectBannerAdDetailViewController.m */; }; + 467343F017FE3D6900D68BB6 /* MPAdPersistenceManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 467343EF17FE3D6900D68BB6 /* MPAdPersistenceManager.m */; }; + 469A15F01AC1F0BA00D6F0EF /* MPSampleAppLogReader.m in Sources */ = {isa = PBXBuildFile; fileRef = 469A15EF1AC1F0BA00D6F0EF /* MPSampleAppLogReader.m */; }; + 46B9E8AB17E0D7D100B78D11 /* libsqlite3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = AE10920D170120E700812E6E /* libsqlite3.dylib */; }; + 46B9E8AC17E0D7D800B78D11 /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = AE10920C170120E700812E6E /* libz.dylib */; }; + 46B9E8B217E0D80400B78D11 /* MessageUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 46B9E8B117E0D80400B78D11 /* MessageUI.framework */; }; + 46DA87B1185010DD00F34858 /* MPAdEntryViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 46DA87AF185010DD00F34858 /* MPAdEntryViewController.m */; }; + 46DA87B4185010DD00F34858 /* MPAdEntryViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 46DA87B0185010DD00F34858 /* MPAdEntryViewController.xib */; }; + 46F883C917FB7AC5004CE563 /* MPLeaderboardBannerAdDetailViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 46F883C817FB7AC5004CE563 /* MPLeaderboardBannerAdDetailViewController.m */; }; + 4A62566A19A7B45900ED18C8 /* MPNativeAdPlacerTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A1C1DB71980722600B6DB33 /* MPNativeAdPlacerTableViewController.m */; }; + 4A6FC1F718C561B3007A1197 /* MPNativeAdDetailViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A6FC1F518C561B3007A1197 /* MPNativeAdDetailViewController.m */; }; + 4A6FC1F918C561B3007A1197 /* MPNativeAdDetailViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4A6FC1F618C561B3007A1197 /* MPNativeAdDetailViewController.xib */; }; + 4A8DD10118C90542005E9389 /* MPTableViewAdPlacerView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A8DD10018C90542005E9389 /* MPTableViewAdPlacerView.m */; }; + 4A8DD10418C90620005E9389 /* MPNativeAdTableHeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A8DD10318C90620005E9389 /* MPNativeAdTableHeaderView.m */; }; + 4A8DD10618C90636005E9389 /* MPNativeAdTableHeaderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4A8DD10518C90636005E9389 /* MPNativeAdTableHeaderView.xib */; }; + 4ADE5FCE1B4FE836004C3673 /* MPRewardedVideoAdDetailViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4ADE5FCB1B4FE836004C3673 /* MPRewardedVideoAdDetailViewController.xib */; }; + 4ADE5FD01B4FE836004C3673 /* MPRewardedVideoAdDetailViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4ADE5FCD1B4FE836004C3673 /* MPRewardedVideoAdDetailViewController.m */; }; + 4AF797D519A7F9E8007CE34D /* MPNativeAdPlacerCollectionViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = E060FE6B1994000C00F4F33C /* MPNativeAdPlacerCollectionViewController.m */; }; + 571EF4281B7194D60035C9BB /* MPStaticNativeAdView.m in Sources */ = {isa = PBXBuildFile; fileRef = 571EF4271B7194D60035C9BB /* MPStaticNativeAdView.m */; }; + 577A1FA219AE7EA6006DA28B /* MPNativeAdPageView.m in Sources */ = {isa = PBXBuildFile; fileRef = 577A1FA119AE7EA6006DA28B /* MPNativeAdPageView.m */; }; + 577DF03C19A28786001514D3 /* MPNativeAdPlacerPageViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 577DF03B19A28786001514D3 /* MPNativeAdPlacerPageViewController.m */; }; + 57C784D61BCC4C6C00E4BB7D /* MPNativeVideoTableViewAdPlacerView.m in Sources */ = {isa = PBXBuildFile; fileRef = 57C784D51BCC4C6C00E4BB7D /* MPNativeVideoTableViewAdPlacerView.m */; }; + 57C784E91BCC4F4600E4BB7D /* MPNativeVideoView.m in Sources */ = {isa = PBXBuildFile; fileRef = 57C784E81BCC4F4600E4BB7D /* MPNativeVideoView.m */; }; + 57FFAB86199C110D00F655CF /* MPCollectionViewAdPlacerView.m in Sources */ = {isa = PBXBuildFile; fileRef = 57FFAB85199C110D00F655CF /* MPCollectionViewAdPlacerView.m */; }; + AE0F1FAD171F1D0A00FA3BE6 /* AdSupport.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AED9E47216F78DAD00EA71A7 /* AdSupport.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; + AE0F1FAE171F1D1000FA3BE6 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AED9E47016F78DA900EA71A7 /* StoreKit.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; + AE0F1FB2171F1D2800FA3BE6 /* CoreLocation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AE0F1FB1171F1D2800FA3BE6 /* CoreLocation.framework */; }; + AE0F1FB3171F1D3100FA3BE6 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AE23239416F78A17002C2082 /* Foundation.framework */; }; + AE23239D16F78A17002C2082 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = AE23239B16F78A17002C2082 /* InfoPlist.strings */; }; + AE23239F16F78A17002C2082 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = AE23239E16F78A17002C2082 /* main.m */; }; + AE2323A316F78A17002C2082 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = AE2323A216F78A17002C2082 /* AppDelegate.m */; }; + AE43413D16F8EEC500B73710 /* MPAdSection.m in Sources */ = {isa = PBXBuildFile; fileRef = AE43413C16F8EEC500B73710 /* MPAdSection.m */; }; + AE43414716F9068000B73710 /* MPInterstitialAdDetailViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = AE43414516F9068000B73710 /* MPInterstitialAdDetailViewController.m */; }; + AE43414A16F9068000B73710 /* MPInterstitialAdDetailViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = AE43414616F9068000B73710 /* MPInterstitialAdDetailViewController.xib */; }; + AE515FAE171F1BFF0086B464 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AE23239216F78A17002C2082 /* UIKit.framework */; }; + AE8E1D451729A76C0051FA6C /* icon.png in Resources */ = {isa = PBXBuildFile; fileRef = AE8E1D441729A76C0051FA6C /* icon.png */; }; + AE8E1D471729A76F0051FA6C /* icon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AE8E1D461729A76F0051FA6C /* icon@2x.png */; }; + AE8E1D491729A7730051FA6C /* Default.png in Resources */ = {isa = PBXBuildFile; fileRef = AE8E1D481729A7730051FA6C /* Default.png */; }; + AE8E1D4B1729A7760051FA6C /* Default@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AE8E1D4A1729A7760051FA6C /* Default@2x.png */; }; + AE8E1D4D1729A7790051FA6C /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AE8E1D4C1729A7790051FA6C /* Default-568h@2x.png */; }; + AE8E1D501729AA460051FA6C /* white_button.png in Resources */ = {isa = PBXBuildFile; fileRef = AE8E1D4F1729AA460051FA6C /* white_button.png */; }; + AED9E4BB16F7912900EA71A7 /* MPAdTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = AED9E4BA16F7912900EA71A7 /* MPAdTableViewController.m */; }; + AED9E4C516F7934900EA71A7 /* MPAdInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = AED9E4C416F7934900EA71A7 /* MPAdInfo.m */; }; + AED9E4CC16F7987F00EA71A7 /* MPBannerAdDetailViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = AED9E4CB16F7987F00EA71A7 /* MPBannerAdDetailViewController.m */; }; + AED9E4CF16F798AF00EA71A7 /* MPBannerAdDetailViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = AED9E4CE16F798AF00EA71A7 /* MPBannerAdDetailViewController.xib */; }; + AED9E4DE16F7B00100EA71A7 /* MPSampleAppInstanceProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = AED9E4DD16F7B00100EA71A7 /* MPSampleAppInstanceProvider.m */; }; + B26284151B83CACE00960F74 /* CoreMedia.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 46E5043F18035A26006A2FC3 /* CoreMedia.framework */; }; + BC3B98671E70C7E400C77554 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 46B3345919F083EC0078078A /* QuartzCore.framework */; }; + BC3B98681E70C7EC00C77554 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 46B3345B19F083F70078078A /* SystemConfiguration.framework */; }; + BC3B98691E70C7F500C77554 /* MediaPlayer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 46B3345719F083350078078A /* MediaPlayer.framework */; }; + BC3B986A1E70C85400C77554 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AE23239616F78A17002C2082 /* CoreGraphics.framework */; }; + BC6A9187216568A000C35DF5 /* MoPubSDKFramework.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BC6FFA7720E2EF5100FC7FAD /* MoPubSDKFramework.framework */; }; + BC6A9188216568A000C35DF5 /* MoPubSDKFramework.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BC6FFA7720E2EF5100FC7FAD /* MoPubSDKFramework.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + BC789C1C1F7AD333001CE308 /* CoreTelephony.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AE10920A170120E700812E6E /* CoreTelephony.framework */; }; + BCD33AD7201002E2003F4FFB /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 46B9E8AF17E0D7F600B78D11 /* AVFoundation.framework */; }; + BCEF09E51F72D6A2004CEBD6 /* MPViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = BCEF09E41F72D6A2004CEBD6 /* MPViewController.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + BC6A9189216568A000C35DF5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BC6FFA6620E2EF5000FC7FAD /* MoPubSDK.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 2AF030C02016723700909F29; + remoteInfo = MoPubSDKFramework; + }; BC6FFA6E20E2EF5100FC7FAD /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BC6FFA6620E2EF5000FC7FAD /* MoPubSDK.xcodeproj */; @@ -105,22 +112,16 @@ 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 */ = { + BC6A918B216568A000C35DF5 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( + BC6A9188216568A000C35DF5 /* MoPubSDKFramework.framework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; @@ -128,7 +129,6 @@ /* 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; }; @@ -193,6 +193,7 @@ 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; }; + AE23238F16F78A17002C2082 /* MoPubSampleApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MoPubSampleApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 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; }; @@ -230,6 +231,8 @@ 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 = ""; }; + AEEA5E4B16F940EC003D48F4 /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS6.1.sdk/System/Library/Frameworks/MobileCoreServices.framework; sourceTree = ""; }; + AEEA5E4F16F940EC003D48F4 /* MessageUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MessageUI.framework; path = /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS6.1.sdk/System/Library/Frameworks/MessageUI.framework; 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 = ""; }; @@ -239,28 +242,28 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ - 2A440B792023BD390003CC2A /* Frameworks */ = { + AE23238C16F78A17002C2082 /* 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 */, + BC3B98691E70C7F500C77554 /* MediaPlayer.framework in Frameworks */, + BC3B98681E70C7EC00C77554 /* SystemConfiguration.framework in Frameworks */, + BC3B98671E70C7E400C77554 /* QuartzCore.framework in Frameworks */, + BCD33AD7201002E2003F4FFB /* AVFoundation.framework in Frameworks */, + BC789C1C1F7AD333001CE308 /* CoreTelephony.framework in Frameworks */, + 2AC79F441DF7814700195AC5 /* SafariServices.framework in Frameworks */, + 2A86E7F21D710CE60087594C /* WebKit.framework in Frameworks */, + B26284151B83CACE00960F74 /* CoreMedia.framework in Frameworks */, + BC3B986A1E70C85400C77554 /* CoreGraphics.framework in Frameworks */, + 46B9E8B217E0D80400B78D11 /* MessageUI.framework in Frameworks */, + 46B9E8AC17E0D7D800B78D11 /* libz.dylib in Frameworks */, + 46B9E8AB17E0D7D100B78D11 /* libsqlite3.dylib in Frameworks */, + AE0F1FB3171F1D3100FA3BE6 /* Foundation.framework in Frameworks */, + AE0F1FB2171F1D2800FA3BE6 /* CoreLocation.framework in Frameworks */, + BC6A9187216568A000C35DF5 /* MoPubSDKFramework.framework in Frameworks */, + AE0F1FAE171F1D1000FA3BE6 /* StoreKit.framework in Frameworks */, + AE0F1FAD171F1D0A00FA3BE6 /* AdSupport.framework in Frameworks */, + AE515FAE171F1BFF0086B464 /* UIKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -305,7 +308,7 @@ AE23239016F78A17002C2082 /* Products */ = { isa = PBXGroup; children = ( - 2A440BB72023BD390003CC2A /* MoPubSampleApp+Framework.app */, + AE23238F16F78A17002C2082 /* MoPubSampleApp.app */, ); name = Products; sourceTree = ""; @@ -337,7 +340,9 @@ AE10920C170120E700812E6E /* libz.dylib */, 46B3345719F083350078078A /* MediaPlayer.framework */, 46B9E8B117E0D80400B78D11 /* MessageUI.framework */, + AEEA5E4F16F940EC003D48F4 /* MessageUI.framework */, 46B3348519F0874E0078078A /* MobileCoreServices.framework */, + AEEA5E4B16F940EC003D48F4 /* MobileCoreServices.framework */, AE902A39171F559F00991DF9 /* PassKit.framework */, 46B3345919F083EC0078078A /* QuartzCore.framework */, 2AC79F431DF7814700195AC5 /* SafariServices.framework */, @@ -460,23 +465,23 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ - 2A440AB22023BD390003CC2A /* MoPubSampleApp+Framework */ = { + AE23238E16F78A17002C2082 /* MoPubSampleApp */ = { isa = PBXNativeTarget; - buildConfigurationList = 2A440BB42023BD390003CC2A /* Build configuration list for PBXNativeTarget "MoPubSampleApp+Framework" */; + buildConfigurationList = AE2323AC16F78A17002C2082 /* Build configuration list for PBXNativeTarget "MoPubSampleApp" */; buildPhases = ( - 2A440AB42023BD390003CC2A /* Sources */, - 2A440B792023BD390003CC2A /* Frameworks */, - 2A440B8D2023BD390003CC2A /* Resources */, - 2AA733B72023BE37006EDA5C /* Embed Frameworks */, + AE23238B16F78A17002C2082 /* Sources */, + AE23238C16F78A17002C2082 /* Frameworks */, + AE23238D16F78A17002C2082 /* Resources */, + BC6A918B216568A000C35DF5 /* Embed Frameworks */, ); buildRules = ( ); dependencies = ( - BC6FFA7920E2EF5D00FC7FAD /* PBXTargetDependency */, + BC6A918A216568A000C35DF5 /* PBXTargetDependency */, ); - name = "MoPubSampleApp+Framework"; + name = MoPubSampleApp; productName = MoPubSampleApp; - productReference = 2A440BB72023BD390003CC2A /* MoPubSampleApp+Framework.app */; + productReference = AE23238F16F78A17002C2082 /* MoPubSampleApp.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ @@ -488,8 +493,9 @@ LastUpgradeCheck = 0900; ORGANIZATIONNAME = MoPub; TargetAttributes = { - 2A440AB22023BD390003CC2A = { + AE23238E16F78A17002C2082 = { DevelopmentTeam = 4S7XS533V3; + ProvisioningStyle = Automatic; }; }; }; @@ -511,7 +517,7 @@ ); projectRoot = ""; targets = ( - 2A440AB22023BD390003CC2A /* MoPubSampleApp+Framework */, + AE23238E16F78A17002C2082 /* MoPubSampleApp */, ); }; /* End PBXProject section */ @@ -555,72 +561,71 @@ /* End PBXReferenceProxy section */ /* Begin PBXResourcesBuildPhase section */ - 2A440B8D2023BD390003CC2A /* Resources */ = { + AE23238D16F78A17002C2082 /* 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 */, + AE23239D16F78A17002C2082 /* InfoPlist.strings in Resources */, + AED9E4CF16F798AF00EA71A7 /* MPBannerAdDetailViewController.xib in Resources */, + AE43414A16F9068000B73710 /* MPInterstitialAdDetailViewController.xib in Resources */, + 2A5874FE1F90341F00E8CDFA /* mopub_logo.png in Resources */, + 4A6FC1F918C561B3007A1197 /* MPNativeAdDetailViewController.xib in Resources */, + AE8E1D451729A76C0051FA6C /* icon.png in Resources */, + 46DA87B4185010DD00F34858 /* MPAdEntryViewController.xib in Resources */, + AE8E1D471729A76F0051FA6C /* icon@2x.png in Resources */, + AE8E1D491729A7730051FA6C /* Default.png in Resources */, + AE8E1D4B1729A7760051FA6C /* Default@2x.png in Resources */, + AE8E1D4D1729A7790051FA6C /* Default-568h@2x.png in Resources */, + AE8E1D501729AA460051FA6C /* white_button.png in Resources */, + 4ADE5FCE1B4FE836004C3673 /* MPRewardedVideoAdDetailViewController.xib in Resources */, + 2A5874FB1F9033E800E8CDFA /* LaunchScreen.storyboard in Resources */, + 4A8DD10618C90636005E9389 /* MPNativeAdTableHeaderView.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ - 2A440AB42023BD390003CC2A /* Sources */ = { + AE23238B16F78A17002C2082 /* 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 */, + AE23239F16F78A17002C2082 /* main.m in Sources */, + AE2323A316F78A17002C2082 /* AppDelegate.m in Sources */, + AED9E4BB16F7912900EA71A7 /* MPAdTableViewController.m in Sources */, + AED9E4C516F7934900EA71A7 /* MPAdInfo.m in Sources */, + AED9E4CC16F7987F00EA71A7 /* MPBannerAdDetailViewController.m in Sources */, + AED9E4DE16F7B00100EA71A7 /* MPSampleAppInstanceProvider.m in Sources */, + AE43413D16F8EEC500B73710 /* MPAdSection.m in Sources */, + AE43414716F9068000B73710 /* MPInterstitialAdDetailViewController.m in Sources */, + BCEF09E51F72D6A2004CEBD6 /* MPViewController.m in Sources */, + 467343F017FE3D6900D68BB6 /* MPAdPersistenceManager.m in Sources */, + 46F883C917FB7AC5004CE563 /* MPLeaderboardBannerAdDetailViewController.m in Sources */, + 4ADE5FD01B4FE836004C3673 /* MPRewardedVideoAdDetailViewController.m in Sources */, + 4A8DD10118C90542005E9389 /* MPTableViewAdPlacerView.m in Sources */, + 4A62566A19A7B45900ED18C8 /* MPNativeAdPlacerTableViewController.m in Sources */, + 57C784D61BCC4C6C00E4BB7D /* MPNativeVideoTableViewAdPlacerView.m in Sources */, + 57FFAB86199C110D00F655CF /* MPCollectionViewAdPlacerView.m in Sources */, + 4AF797D519A7F9E8007CE34D /* MPNativeAdPlacerCollectionViewController.m in Sources */, + 4A6FC1F718C561B3007A1197 /* MPNativeAdDetailViewController.m in Sources */, + 469A15F01AC1F0BA00D6F0EF /* MPSampleAppLogReader.m in Sources */, + 462E156F17F0F5D100408E17 /* MPMRectBannerAdDetailViewController.m in Sources */, + 46DA87B1185010DD00F34858 /* MPAdEntryViewController.m in Sources */, + 571EF4281B7194D60035C9BB /* MPStaticNativeAdView.m in Sources */, + 577A1FA219AE7EA6006DA28B /* MPNativeAdPageView.m in Sources */, + 577DF03C19A28786001514D3 /* MPNativeAdPlacerPageViewController.m in Sources */, + 4A8DD10418C90620005E9389 /* MPNativeAdTableHeaderView.m in Sources */, + 57C784E91BCC4F4600E4BB7D /* MPNativeVideoView.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ - BC6FFA7920E2EF5D00FC7FAD /* PBXTargetDependency */ = { + BC6A918A216568A000C35DF5 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = MoPubSDKFramework; - targetProxy = BC6FFA7820E2EF5D00FC7FAD /* PBXContainerItemProxy */; + targetProxy = BC6A9189216568A000C35DF5 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ @@ -636,78 +641,6 @@ /* 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 = { @@ -798,23 +731,94 @@ }; name = Release; }; + AE2323AD16F78A17002C2082 /* 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", + ); + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "MoPubSampleApp-Prefix.pch"; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "$(SRCROOT)/../MoPubSDK/**", + ); + INFOPLIST_FILE = "MoPubSampleApp-Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/..", + "$(PROJECT_DIR)/../MoPubSDK/Viewability/Avid", + ); + ONLY_ACTIVE_ARCH = NO; + OTHER_LDFLAGS = ( + "-ObjC", + "$(inherited)", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.mopub.samples.objc.MoPubSampleApp; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE = ""; + PROVISIONING_PROFILE_SPECIFIER = ""; + WRAPPER_EXTENSION = app; + }; + name = Debug; + }; + AE2323AE16F78A17002C2082 /* 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", + ); + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "MoPubSampleApp-Prefix.pch"; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "$(SRCROOT)/../MoPubSDK/**", + ); + INFOPLIST_FILE = "MoPubSampleApp-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.MoPubSampleApp; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE = ""; + PROVISIONING_PROFILE_SPECIFIER = ""; + WRAPPER_EXTENSION = app; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - 2A440BB42023BD390003CC2A /* Build configuration list for PBXNativeTarget "MoPubSampleApp+Framework" */ = { + AE23238A16F78A17002C2082 /* Build configuration list for PBXProject "MoPubSampleApp" */ = { isa = XCConfigurationList; buildConfigurations = ( - 2A440BB52023BD390003CC2A /* Debug */, - 2A440BB62023BD390003CC2A /* Release */, + AE2323AA16F78A17002C2082 /* Debug */, + AE2323AB16F78A17002C2082 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - AE23238A16F78A17002C2082 /* Build configuration list for PBXProject "MoPubSampleApp" */ = { + AE2323AC16F78A17002C2082 /* Build configuration list for PBXNativeTarget "MoPubSampleApp" */ = { isa = XCConfigurationList; buildConfigurations = ( - AE2323AA16F78A17002C2082 /* Debug */, - AE2323AB16F78A17002C2082 /* Release */, + AE2323AD16F78A17002C2082 /* Debug */, + AE2323AE16F78A17002C2082 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; diff --git a/MoPubSampleApp/MoPubSampleApp.xcodeproj/xcshareddata/xcschemes/MoPubSampleApp.xcscheme b/MoPubSampleApp/MoPubSampleApp.xcodeproj/xcshareddata/xcschemes/MoPubSampleApp.xcscheme index d78321188..e9950f8e3 100644 --- a/MoPubSampleApp/MoPubSampleApp.xcodeproj/xcshareddata/xcschemes/MoPubSampleApp.xcscheme +++ b/MoPubSampleApp/MoPubSampleApp.xcodeproj/xcshareddata/xcschemes/MoPubSampleApp.xcscheme @@ -26,7 +26,6 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - language = "" shouldUseLaunchSchemeArgsEnv = "YES"> @@ -46,7 +45,6 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - language = "" launchStyle = "0" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" diff --git a/README.md b/README.md index 461f22173..1e3943ddb 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,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.4.1 ### Installation with CocoaPods @@ -84,15 +84,10 @@ 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. - - **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. + - 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. 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. diff --git a/mopub-ios-sdk.podspec b/mopub-ios-sdk.podspec index 14f333069..e4d41074c 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.4.1' spec.license = { :type => 'New BSD', :file => 'LICENSE' } spec.homepage = 'https://github.com/mopub/mopub-ios-sdk' spec.authors = { 'MoPub' => 'support@mopub.com' } @@ -14,7 +14,7 @@ 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.4.1' } spec.requires_arc = true spec.ios.deployment_target = '8.0' spec.frameworks = [ From 87c4bb983da7c85175e8b75574979264bb4979d3 Mon Sep 17 00:00:00 2001 From: kdun Date: Wed, 28 Nov 2018 13:44:08 -0800 Subject: [PATCH 02/12] Removed unnecessary Gemfiles (#250) --- Gemfile | 4 - Gemfile.lock | 205 --------------------------------------------------- 2 files changed, 209 deletions(-) delete mode 100644 Gemfile delete mode 100644 Gemfile.lock 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 From 68442be214276320d1a8b183c563d877d5dfa76d Mon Sep 17 00:00:00 2001 From: Caleb Lee Date: Thu, 29 Nov 2018 10:21:21 -0800 Subject: [PATCH 03/12] Adds issue template --- .github/ISSUE_TEMPLATE.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE.md diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 000000000..1f9f188cf --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,24 @@ +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: + +#### 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 From 96781401ed13dc249776e7d0ee24dde27eaef390 Mon Sep 17 00:00:00 2001 From: Caleb Lee Date: Thu, 29 Nov 2018 15:39:44 -0800 Subject: [PATCH 04/12] Updates README.md --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 1e3943ddb..9b3ecd40c 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,6 @@ You can find integration documentation on our [wiki](https://github.com/mopub/mo 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. From 1f4b83e7fefedc9fea517df5448856c63e4f16d9 Mon Sep 17 00:00:00 2001 From: Kelly Dun Date: Fri, 25 Jan 2019 12:04:13 -0800 Subject: [PATCH 05/12] 5.5.0 --- .github/ISSUE_TEMPLATE.md | 2 + CHANGELOG.md | 13 +- Canary/Canary.xcodeproj/project.pbxproj | 315 ++++++++++++++++-- .../xcschemes/CanaryUITests.xcscheme | 61 ++++ .../Canary/AppDelegate+AdvancedBidders.swift | 42 --- Canary/Canary/AppDelegate+Consent.swift | 2 +- Canary/Canary/AppDelegate.swift | 41 +-- .../AppIcon.Internal.appiconset/Contents.json | 116 +++++++ ...Pub_logo_square_white on blue (1)-1024.png | Bin 0 -> 48615 bytes ...MoPub_logo_square_white on blue (1)-20.png | Bin 0 -> 1516 bytes ...ub_logo_square_white on blue (1)-20@2x.png | Bin 0 -> 2044 bytes ...ub_logo_square_white on blue (1)-20@3x.png | Bin 0 -> 2663 bytes ...MoPub_logo_square_white on blue (1)-29.png | Bin 0 -> 1741 bytes ...ub_logo_square_white on blue (1)-29@2x.png | Bin 0 -> 2607 bytes ...ub_logo_square_white on blue (1)-29@3x.png | Bin 0 -> 3477 bytes ...MoPub_logo_square_white on blue (1)-40.png | Bin 0 -> 2044 bytes ...ub_logo_square_white on blue (1)-40@2x.png | Bin 0 -> 3291 bytes ...ub_logo_square_white on blue (1)-40@3x.png | Bin 0 -> 4498 bytes ...ub_logo_square_white on blue (1)-60@2x.png | Bin 0 -> 4498 bytes ...ub_logo_square_white on blue (1)-60@3x.png | Bin 0 -> 6568 bytes ...MoPub_logo_square_white on blue (1)-76.png | Bin 0 -> 3144 bytes ...ub_logo_square_white on blue (1)-76@2x.png | Bin 0 -> 5668 bytes ..._logo_square_white on blue (1)-83.5@2x.png | Bin 0 -> 6172 bytes .../AppIcon.appiconset/app_icon-1024.png | Bin 45173 -> 28193 bytes .../CameraButton.imageset/Contents.json | 23 ++ .../CameraButton.imageset/camera_stroke.png | Bin 0 -> 471 bytes .../camera_stroke@2x.png | Bin 0 -> 1000 bytes .../camera_stroke@3x.png | Bin 0 -> 1501 bytes .../CloseButton.imageset/Contents.json | 23 ++ .../CloseButton.imageset/MPCloseButtonX.png | Bin 0 -> 502 bytes .../MPCloseButtonX@2x.png | Bin 0 -> 1158 bytes .../MPCloseButtonX@3x.png | Bin 0 -> 2205 bytes .../Mraid.imageset/Contents.json | 23 ++ .../Assets.xcassets/Mraid.imageset/Mraid.png | Bin 0 -> 452 bytes .../Mraid.imageset/Mraid@2x.png | Bin 0 -> 880 bytes .../Mraid.imageset/Mraid@3x.png | Bin 0 -> 1394 bytes Canary/Canary/Base.lproj/Main.storyboard | 146 ++++---- Canary/Canary/Base/AdUnit.swift | 47 ++- Canary/Canary/Base/AdUnitDataSource.swift | 2 +- .../Base/AdUnitTableViewController.swift | 37 +- .../Canary/Base/BaseSplitViewController.swift | 2 +- .../Canary/Cells/AdActionsTableViewCell.swift | 2 +- Canary/Canary/Cells/AdUnitTableViewCell.swift | 2 +- .../Canary/Cells/AdUnitTableViewHeader.swift | 2 +- .../Canary/Cells/BasicMenuTableViewCell.swift | 2 +- Canary/Canary/Cells/StatusTableViewCell.swift | 5 +- .../Canary/Cells/TextEntryTableViewCell.swift | 2 +- .../Cells/TweetCollectionViewCell.swift | 15 +- .../Canary/Cells/TweetCollectionViewCell.xib | 21 +- Canary/Canary/ContainerViewController.swift | 187 +++++++---- Canary/Canary/Extensions/CGFloat+Random.swift | 2 +- .../Extensions/MPBool+Description.swift | 2 +- .../MPConsentStatus+Description.swift | 2 +- Canary/Canary/Extensions/UIColor+Random.swift | 2 +- Canary/Canary/Extensions/UIView+Nib.swift | 2 +- Canary/Canary/Formats/AdDataSource.swift | 2 +- Canary/Canary/Formats/AdFormat.swift | 2 +- .../Formats/AdTableViewController.swift | 2 +- Canary/Canary/Formats/AdViewController.swift | 2 +- .../Canary/Formats/BannerAdDataSource.swift | 2 +- .../Formats/BannerAdViewController.swift | 2 +- .../Formats/BaseNativeAdDataSource.swift | 2 +- .../Formats/InterstitialAdDataSource.swift | 2 +- .../InterstitialAdViewController.swift | 2 +- .../Formats/LeaderboardAdViewController.swift | 2 +- .../MediumRectangleAdViewController.swift | 2 +- .../NativeAdCollectionDataSource.swift | 2 +- .../NativeAdCollectionViewController.swift | 12 +- .../Canary/Formats/NativeAdDataSource.swift | 2 +- .../Formats/NativeAdTableDataSource.swift | 2 +- .../Formats/NativeAdTableViewController.swift | 2 +- Canary/Canary/Formats/NativeAdView.swift | 2 +- Canary/Canary/Formats/NativeAdView.xib | 14 +- .../Formats/NativeAdViewController.swift | 2 +- .../Canary/Formats/RewardedAdDataSource.swift | 2 +- .../Formats/RewardedAdViewController.swift | 2 +- Canary/Canary/Info.plist | 6 +- .../LogingLevelMenuDataSource.swift | 26 +- Canary/Canary/MainTabBarController.swift | 41 +-- Canary/Canary/Menu/MenuDataSource.swift | 9 +- Canary/Canary/Menu/MenuDisplayable.swift | 2 +- Canary/Canary/Menu/MenuViewController.swift | 4 +- Canary/Canary/PreferredWidthLabel.swift | 2 +- .../Privacy/PrivacyInfoDataSource.swift | 126 ------- .../Privacy/PrivacyInfoViewController.swift | 72 ---- .../Privacy/PrivacyMenuDataSource.swift | 90 ----- .../QRCodeCameraInterfaceViewController.swift | 204 ++++++++++++ Canary/Canary/RoundedButton.swift | 2 +- Canary/Canary/Samples/SampleAds.plist | 4 +- .../Samples/SampleAdsViewController.swift | 2 +- .../Canary/SavedAds/SavedAdsDataSource.swift | 2 +- Canary/Canary/SavedAds/SavedAdsManager.swift | 2 +- .../SavedAds/SavedAdsViewController.swift | 2 +- Canary/Canary/StoryboardInstantiable.swift | 2 +- Canary/Canary/TableViewCellRegisterable.swift | 2 +- MoPubResources/Info.plist | 6 +- MoPubSDK.xcodeproj/project.pbxproj | 248 ++++++++------ .../xcschemes/MoPubSDKFramework.xcscheme | 20 +- MoPubSDK/Internal/Banners/MPBannerAdManager.h | 2 +- MoPubSDK/Internal/Banners/MPBannerAdManager.m | 33 +- .../Banners/MPBannerAdManagerDelegate.h | 2 +- .../Banners/MPBannerCustomEvent+Internal.h | 2 +- .../Banners/MPBannerCustomEvent+Internal.m | 2 +- .../Banners/MPBannerCustomEventAdapter.h | 2 +- .../Banners/MPBannerCustomEventAdapter.m | 9 +- .../Internal/Banners/MPBaseBannerAdapter.h | 2 +- .../Internal/Banners/MPBaseBannerAdapter.m | 6 +- .../MPPrivateBannerCustomEventDelegate.h | 2 +- .../AdAlerts/MPAdAlertGestureRecognizer.h | 2 +- .../AdAlerts/MPAdAlertGestureRecognizer.m | 2 +- .../Common/AdAlerts/MPAdAlertManager.h | 2 +- .../Common/AdAlerts/MPAdAlertManager.m | 7 +- MoPubSDK/Internal/Common/MPAPIEndpoints.h | 2 +- MoPubSDK/Internal/Common/MPAPIEndpoints.m | 2 +- ...PActivityViewControllerHelper+TweetShare.h | 2 +- ...PActivityViewControllerHelper+TweetShare.m | 2 +- .../Common/MPActivityViewControllerHelper.h | 2 +- .../Common/MPActivityViewControllerHelper.m | 2 +- .../Internal/Common/MPAdBrowserController.h | 2 +- .../Internal/Common/MPAdBrowserController.m | 4 +- MoPubSDK/Internal/Common/MPAdConfiguration.h | 2 +- MoPubSDK/Internal/Common/MPAdConfiguration.m | 8 +- .../Common/MPAdDestinationDisplayAgent.h | 2 +- .../Common/MPAdDestinationDisplayAgent.m | 4 +- .../Internal/Common/MPAdImpressionTimer.h | 2 +- .../Internal/Common/MPAdImpressionTimer.m | 8 +- .../Internal/Common/MPAdServerCommunicator.h | 2 +- .../Internal/Common/MPAdServerCommunicator.m | 73 ++-- .../Internal/Common/MPAdServerURLBuilder.h | 2 +- .../Internal/Common/MPAdServerURLBuilder.m | 32 +- MoPubSDK/Internal/Common/MPClosableView.h | 2 +- MoPubSDK/Internal/Common/MPClosableView.m | 2 +- .../Internal/Common/MPCountdownTimerView.h | 2 +- .../Internal/Common/MPCountdownTimerView.m | 4 +- .../Common/MPEnhancedDeeplinkRequest.h | 2 +- .../Common/MPEnhancedDeeplinkRequest.m | 2 +- .../Internal/Common/MPLastResortDelegate.h | 2 +- .../Internal/Common/MPLastResortDelegate.m | 2 +- .../Internal/Common/MPProgressOverlayView.h | 2 +- .../Internal/Common/MPProgressOverlayView.m | 2 +- MoPubSDK/Internal/Common/MPRealTimeTimer.h | 2 +- MoPubSDK/Internal/Common/MPRealTimeTimer.m | 2 +- MoPubSDK/Internal/Common/MPURLActionInfo.h | 2 +- MoPubSDK/Internal/Common/MPURLActionInfo.m | 2 +- MoPubSDK/Internal/Common/MPURLResolver.h | 2 +- MoPubSDK/Internal/Common/MPURLResolver.m | 4 +- MoPubSDK/Internal/Common/MPVideoConfig.h | 2 +- MoPubSDK/Internal/Common/MPVideoConfig.m | 4 +- MoPubSDK/Internal/Common/MPXMLParser.h | 2 +- MoPubSDK/Internal/Common/MPXMLParser.m | 2 +- MoPubSDK/Internal/HTML/MPAdWebViewAgent.h | 2 +- MoPubSDK/Internal/HTML/MPAdWebViewAgent.m | 4 +- MoPubSDK/Internal/HTML/MPContentBlocker.h | 20 ++ MoPubSDK/Internal/HTML/MPContentBlocker.m | 66 ++++ .../Internal/HTML/MPHTMLBannerCustomEvent.h | 2 +- .../Internal/HTML/MPHTMLBannerCustomEvent.m | 22 +- .../HTML/MPHTMLInterstitialCustomEvent.h | 2 +- .../HTML/MPHTMLInterstitialCustomEvent.m | 35 +- .../HTML/MPHTMLInterstitialViewController.h | 2 +- .../HTML/MPHTMLInterstitialViewController.m | 2 +- MoPubSDK/Internal/HTML/MPWebView.h | 2 +- MoPubSDK/Internal/HTML/MPWebView.m | 12 +- .../Interstitials/MPBaseInterstitialAdapter.h | 2 +- .../Interstitials/MPBaseInterstitialAdapter.m | 4 +- .../Interstitials/MPInterstitialAdManager.h | 2 +- .../Interstitials/MPInterstitialAdManager.m | 40 ++- .../MPInterstitialAdManagerDelegate.h | 2 +- .../MPInterstitialCustomEventAdapter.h | 2 +- .../MPInterstitialCustomEventAdapter.m | 17 +- .../MPInterstitialViewController.h | 4 +- .../MPInterstitialViewController.m | 14 +- ...MPPrivateInterstitialCustomEventDelegate.h | 2 +- MoPubSDK/Internal/MPAdServerKeys.h | 6 +- MoPubSDK/Internal/MPAdServerKeys.m | 6 +- MoPubSDK/Internal/MPAdvancedBiddingManager.h | 41 --- MoPubSDK/Internal/MPAdvancedBiddingManager.m | 115 ------- .../Internal/MPConsentDialogViewController.h | 2 +- .../Internal/MPConsentDialogViewController.m | 2 +- MoPubSDK/Internal/MPConsentManager.h | 7 +- MoPubSDK/Internal/MPConsentManager.m | 179 ++++++---- .../Internal/MPCoreInstanceProvider+MRAID.h | 2 +- .../Internal/MPCoreInstanceProvider+MRAID.m | 2 +- MoPubSDK/Internal/MPCoreInstanceProvider.h | 2 +- MoPubSDK/Internal/MPCoreInstanceProvider.m | 2 +- MoPubSDK/Internal/MPHTTPNetworkSession.h | 2 +- MoPubSDK/Internal/MPHTTPNetworkSession.m | 14 +- MoPubSDK/Internal/MPHTTPNetworkTaskData.h | 2 +- MoPubSDK/Internal/MPHTTPNetworkTaskData.m | 2 +- MoPubSDK/Internal/MPMediationManager.h | 58 +++- MoPubSDK/Internal/MPMediationManager.m | 276 ++++++++++++--- MoPubSDK/Internal/MPMemoryCache.h | 2 +- MoPubSDK/Internal/MPMemoryCache.m | 12 +- MoPubSDK/Internal/MPReachabilityManager.h | 2 +- MoPubSDK/Internal/MPReachabilityManager.m | 2 +- MoPubSDK/Internal/MPURL.h | 2 +- MoPubSDK/Internal/MPURL.m | 2 +- MoPubSDK/Internal/MPURLRequest.h | 2 +- MoPubSDK/Internal/MPURLRequest.m | 8 +- MoPubSDK/Internal/MPVASTTracking.h | 2 +- MoPubSDK/Internal/MPVASTTracking.m | 2 +- .../MRAID/MPForceableOrientationProtocol.h | 2 +- .../Internal/MRAID/MPMRAIDBannerCustomEvent.h | 2 +- .../Internal/MRAID/MPMRAIDBannerCustomEvent.m | 20 +- .../MRAID/MPMRAIDInterstitialCustomEvent.h | 2 +- .../MRAID/MPMRAIDInterstitialCustomEvent.m | 35 +- .../MRAID/MPMRAIDInterstitialViewController.h | 2 +- .../MRAID/MPMRAIDInterstitialViewController.m | 7 +- MoPubSDK/Internal/MRAID/MRBridge.h | 2 +- MoPubSDK/Internal/MRAID/MRBridge.m | 4 +- MoPubSDK/Internal/MRAID/MRBundleManager.h | 2 +- MoPubSDK/Internal/MRAID/MRBundleManager.m | 2 +- MoPubSDK/Internal/MRAID/MRCommand.h | 2 +- MoPubSDK/Internal/MRAID/MRCommand.m | 2 +- MoPubSDK/Internal/MRAID/MRConstants.h | 2 +- MoPubSDK/Internal/MRAID/MRConstants.m | 2 +- MoPubSDK/Internal/MRAID/MRController.h | 4 +- MoPubSDK/Internal/MRAID/MRController.m | 55 ++- MoPubSDK/Internal/MRAID/MRError.h | 2 +- MoPubSDK/Internal/MRAID/MRError.m | 2 +- .../MRAID/MRExpandModalViewController.h | 2 +- .../MRAID/MRExpandModalViewController.m | 2 +- .../Internal/MRAID/MRNativeCommandHandler.h | 2 +- .../Internal/MRAID/MRNativeCommandHandler.m | 2 +- MoPubSDK/Internal/MRAID/MRProperty.h | 2 +- MoPubSDK/Internal/MRAID/MRProperty.m | 2 +- .../Internal/MRAID/MRVideoPlayerManager.h | 2 +- .../Internal/MRAID/MRVideoPlayerManager.m | 2 +- .../Utility/Categories/NSBundle+MPAdditions.h | 2 +- .../Utility/Categories/NSBundle+MPAdditions.m | 2 +- .../Utility/Categories/NSDate+MPAdditions.h | 2 +- .../Utility/Categories/NSDate+MPAdditions.m | 2 +- .../Categories/NSDictionary+MPAdditions.h | 2 +- .../Categories/NSDictionary+MPAdditions.m | 2 +- .../Utility/Categories/NSError+MPAdditions.h | 2 +- .../Utility/Categories/NSError+MPAdditions.m | 6 +- .../NSHTTPURLResponse+MPAdditions.h | 2 +- .../NSHTTPURLResponse+MPAdditions.m | 4 +- .../NSJSONSerialization+MPAdditions.h | 2 +- .../NSJSONSerialization+MPAdditions.m | 2 +- .../Categories/NSMutableArray+MPAdditions.h | 2 +- .../Categories/NSMutableArray+MPAdditions.m | 2 +- .../Utility/Categories/NSString+MPAdditions.h | 2 +- .../Utility/Categories/NSString+MPAdditions.m | 2 +- .../Categories/NSString+MPConsentStatus.h | 2 +- .../Categories/NSString+MPConsentStatus.m | 2 +- .../Utility/Categories/NSURL+MPAdditions.h | 2 +- .../Utility/Categories/NSURL+MPAdditions.m | 2 +- .../Utility/Categories/UIButton+MPAdditions.h | 2 +- .../Utility/Categories/UIButton+MPAdditions.m | 2 +- .../Utility/Categories/UIColor+MPAdditions.h | 2 +- .../Utility/Categories/UIColor+MPAdditions.m | 2 +- .../Utility/Categories/UIView+MPAdditions.h | 2 +- .../Utility/Categories/UIView+MPAdditions.m | 2 +- .../Categories/UIWebView+MPAdditions.h | 2 +- .../Categories/UIWebView+MPAdditions.m | 2 +- .../Utility/MOPUBExperimentProvider.h | 2 +- .../Utility/MOPUBExperimentProvider.m | 2 +- .../Internal/Utility/MPAnalyticsTracker.h | 2 +- .../Internal/Utility/MPAnalyticsTracker.m | 2 +- MoPubSDK/Internal/Utility/MPError.h | 38 ++- MoPubSDK/Internal/Utility/MPError.m | 73 +++- .../Internal/Utility/MPGeolocationProvider.h | 2 +- .../Internal/Utility/MPGeolocationProvider.m | 2 +- MoPubSDK/Internal/Utility/MPGlobal.h | 4 +- MoPubSDK/Internal/Utility/MPGlobal.m | 15 +- .../Internal/Utility/MPIdentityProvider.h | 2 +- .../Internal/Utility/MPIdentityProvider.m | 2 +- MoPubSDK/Internal/Utility/MPInternalUtils.h | 2 +- MoPubSDK/Internal/Utility/MPInternalUtils.m | 2 +- MoPubSDK/Internal/Utility/MPLogProvider.h | 28 -- MoPubSDK/Internal/Utility/MPLogProvider.m | 84 ----- MoPubSDK/Internal/Utility/MPLogging.h | 43 --- MoPubSDK/Internal/Utility/MPLogging.m | 102 ------ MoPubSDK/Internal/Utility/MPSessionTracker.h | 2 +- MoPubSDK/Internal/Utility/MPSessionTracker.m | 2 +- .../Internal/Utility/MPStoreKitProvider.h | 2 +- .../Internal/Utility/MPStoreKitProvider.m | 2 +- MoPubSDK/Internal/Utility/MPTimer.h | 2 +- MoPubSDK/Internal/Utility/MPTimer.m | 6 +- .../MPUserInteractionGestureRecognizer.h | 2 +- .../MPUserInteractionGestureRecognizer.m | 2 +- MoPubSDK/Internal/VAST/MPVASTAd.h | 2 +- MoPubSDK/Internal/VAST/MPVASTAd.m | 4 +- MoPubSDK/Internal/VAST/MPVASTCompanionAd.h | 2 +- MoPubSDK/Internal/VAST/MPVASTCompanionAd.m | 2 +- MoPubSDK/Internal/VAST/MPVASTCreative.h | 2 +- MoPubSDK/Internal/VAST/MPVASTCreative.m | 2 +- MoPubSDK/Internal/VAST/MPVASTDurationOffset.h | 2 +- MoPubSDK/Internal/VAST/MPVASTDurationOffset.m | 2 +- MoPubSDK/Internal/VAST/MPVASTIndustryIcon.h | 2 +- MoPubSDK/Internal/VAST/MPVASTIndustryIcon.m | 2 +- MoPubSDK/Internal/VAST/MPVASTInline.h | 2 +- MoPubSDK/Internal/VAST/MPVASTInline.m | 2 +- MoPubSDK/Internal/VAST/MPVASTLinearAd.h | 2 +- MoPubSDK/Internal/VAST/MPVASTLinearAd.m | 2 +- MoPubSDK/Internal/VAST/MPVASTMacroProcessor.h | 2 +- MoPubSDK/Internal/VAST/MPVASTMacroProcessor.m | 2 +- MoPubSDK/Internal/VAST/MPVASTManager.h | 2 +- MoPubSDK/Internal/VAST/MPVASTManager.m | 2 +- MoPubSDK/Internal/VAST/MPVASTMediaFile.h | 2 +- MoPubSDK/Internal/VAST/MPVASTMediaFile.m | 2 +- MoPubSDK/Internal/VAST/MPVASTModel.h | 2 +- MoPubSDK/Internal/VAST/MPVASTModel.m | 4 +- MoPubSDK/Internal/VAST/MPVASTResource.h | 2 +- MoPubSDK/Internal/VAST/MPVASTResource.m | 2 +- MoPubSDK/Internal/VAST/MPVASTResponse.h | 2 +- MoPubSDK/Internal/VAST/MPVASTResponse.m | 2 +- .../Internal/VAST/MPVASTStringUtilities.h | 2 +- .../Internal/VAST/MPVASTStringUtilities.m | 2 +- MoPubSDK/Internal/VAST/MPVASTTrackingEvent.h | 2 +- MoPubSDK/Internal/VAST/MPVASTTrackingEvent.m | 2 +- MoPubSDK/Internal/VAST/MPVASTWrapper.h | 2 +- MoPubSDK/Internal/VAST/MPVASTWrapper.m | 2 +- MoPubSDK/Logging/Internal/MPConsoleLogger.h | 22 ++ MoPubSDK/Logging/Internal/MPConsoleLogger.m | 28 ++ MoPubSDK/Logging/Internal/MPLogManager.h | 67 ++++ MoPubSDK/Logging/Internal/MPLogManager.m | 117 +++++++ MoPubSDK/Logging/MPLogEvent.h | 134 ++++++++ MoPubSDK/Logging/MPLogEvent.m | 306 +++++++++++++++++ MoPubSDK/Logging/MPLogLevel.h | 19 ++ MoPubSDK/Logging/MPLogger.h | 28 ++ MoPubSDK/Logging/MPLogging.h | 70 ++++ MoPubSDK/Logging/MPLogging.m | 41 +++ MoPubSDK/MOPUBDisplayAgentType.h | 2 +- MoPubSDK/MPAdConversionTracker.h | 2 +- MoPubSDK/MPAdConversionTracker.m | 2 +- MoPubSDK/MPAdTargeting.h | 2 +- MoPubSDK/MPAdTargeting.m | 2 +- MoPubSDK/MPAdView.h | 2 +- MoPubSDK/MPAdView.m | 2 +- MoPubSDK/MPAdapterConfiguration.h | 78 +++++ MoPubSDK/MPAdvancedBidder.h | 23 -- MoPubSDK/MPBannerCustomEvent.h | 2 +- MoPubSDK/MPBannerCustomEvent.m | 2 +- MoPubSDK/MPBannerCustomEventDelegate.h | 2 +- MoPubSDK/MPBaseAdapterConfiguration.h | 81 +++++ MoPubSDK/MPBaseAdapterConfiguration.m | 62 ++++ MoPubSDK/MPBool.h | 2 +- MoPubSDK/MPConsentChangedNotification.h | 4 +- MoPubSDK/MPConsentChangedNotification.m | 2 +- MoPubSDK/MPConsentChangedReason.h | 2 +- MoPubSDK/MPConsentChangedReason.m | 2 +- MoPubSDK/MPConsentError.h | 2 +- MoPubSDK/MPConsentStatus.h | 2 +- MoPubSDK/MPConstants.h | 6 +- MoPubSDK/MPConstants.m | 2 +- MoPubSDK/MPInterstitialAdController.h | 2 +- MoPubSDK/MPInterstitialAdController.m | 6 +- MoPubSDK/MPInterstitialCustomEvent.h | 2 +- MoPubSDK/MPInterstitialCustomEvent.m | 2 +- MoPubSDK/MPInterstitialCustomEventDelegate.h | 2 +- MoPubSDK/MPLogLevel.h | 24 -- MoPubSDK/MPMediationSdkInitializable.h | 26 -- MoPubSDK/MPMediationSettingsProtocol.h | 2 +- MoPubSDK/MPMoPubConfiguration.h | 52 ++- MoPubSDK/MPMoPubConfiguration.m | 69 +++- MoPubSDK/MoPub.h | 28 +- MoPubSDK/MoPub.m | 62 ++-- .../MPNativeAdRequest+MPNativeAdSource.h | 2 +- .../NativeAds/Internal/MPAdPlacerInvocation.h | 2 +- .../NativeAds/Internal/MPAdPlacerInvocation.m | 2 +- .../Internal/MPCollectionViewAdPlacerCell.h | 2 +- .../Internal/MPCollectionViewAdPlacerCell.m | 2 +- MoPubSDK/NativeAds/Internal/MPDiskLRUCache.h | 2 +- MoPubSDK/NativeAds/Internal/MPDiskLRUCache.m | 2 +- .../NativeAds/Internal/MPImageDownloadQueue.h | 2 +- .../NativeAds/Internal/MPImageDownloadQueue.m | 2 +- .../Internal/MPMoPubNativeAdAdapter.h | 2 +- .../Internal/MPMoPubNativeAdAdapter.m | 2 +- .../Internal/MPMoPubNativeCustomEvent.h | 2 +- .../Internal/MPMoPubNativeCustomEvent.m | 20 +- .../NativeAds/Internal/MPNativeAd+Internal.h | 2 +- .../NativeAds/Internal/MPNativeAd+Internal.m | 2 +- .../MPNativeAdConfigValues+Internal.h | 2 +- .../MPNativeAdConfigValues+Internal.m | 2 +- .../Internal/MPNativeAdConfigValues.h | 2 +- .../Internal/MPNativeAdConfigValues.m | 2 +- .../Internal/MPNativeAdRendererConstants.h | 2 +- .../Internal/MPNativeAdRendererImageHandler.h | 2 +- .../Internal/MPNativeAdRendererImageHandler.m | 6 +- .../Internal/MPNativeAdSourceQueue.h | 2 +- .../Internal/MPNativeAdSourceQueue.m | 2 +- MoPubSDK/NativeAds/Internal/MPNativeAdUtils.h | 2 +- MoPubSDK/NativeAds/Internal/MPNativeAdUtils.m | 2 +- MoPubSDK/NativeAds/Internal/MPNativeCache.h | 2 +- MoPubSDK/NativeAds/Internal/MPNativeCache.m | 3 +- .../MPNativePositionResponseDeserializer.h | 2 +- .../MPNativePositionResponseDeserializer.m | 2 +- .../Internal/MPNativePositionSource.h | 2 +- .../Internal/MPNativePositionSource.m | 2 +- MoPubSDK/NativeAds/Internal/MPNativeView.h | 2 +- MoPubSDK/NativeAds/Internal/MPNativeView.m | 2 +- .../Internal/MPTableViewAdPlacerCell.h | 2 +- .../Internal/MPTableViewAdPlacerCell.m | 2 +- .../MPTableViewCellImpressionTracker.h | 2 +- .../MPTableViewCellImpressionTracker.m | 2 +- MoPubSDK/NativeAds/MPAdPositioning.h | 2 +- MoPubSDK/NativeAds/MPAdPositioning.m | 2 +- MoPubSDK/NativeAds/MPClientAdPositioning.h | 2 +- MoPubSDK/NativeAds/MPClientAdPositioning.m | 4 +- MoPubSDK/NativeAds/MPCollectionViewAdPlacer.h | 2 +- MoPubSDK/NativeAds/MPCollectionViewAdPlacer.m | 2 +- MoPubSDK/NativeAds/MPNativeAd.h | 2 +- MoPubSDK/NativeAds/MPNativeAd.m | 8 +- MoPubSDK/NativeAds/MPNativeAdAdapter.h | 2 +- MoPubSDK/NativeAds/MPNativeAdConstants.h | 6 +- MoPubSDK/NativeAds/MPNativeAdConstants.m | 6 +- MoPubSDK/NativeAds/MPNativeAdData.h | 2 +- MoPubSDK/NativeAds/MPNativeAdData.m | 2 +- MoPubSDK/NativeAds/MPNativeAdDelegate.h | 2 +- MoPubSDK/NativeAds/MPNativeAdError.h | 2 +- MoPubSDK/NativeAds/MPNativeAdError.m | 2 +- MoPubSDK/NativeAds/MPNativeAdRenderer.h | 2 +- .../MPNativeAdRendererConfiguration.h | 2 +- .../MPNativeAdRendererConfiguration.m | 2 +- .../NativeAds/MPNativeAdRendererSettings.h | 2 +- MoPubSDK/NativeAds/MPNativeAdRendering.h | 2 +- .../MPNativeAdRenderingImageLoader.h | 2 +- .../MPNativeAdRenderingImageLoader.m | 2 +- MoPubSDK/NativeAds/MPNativeAdRequest.h | 2 +- MoPubSDK/NativeAds/MPNativeAdRequest.m | 32 +- .../NativeAds/MPNativeAdRequestTargeting.h | 2 +- .../NativeAds/MPNativeAdRequestTargeting.m | 2 +- MoPubSDK/NativeAds/MPNativeAdSource.h | 2 +- MoPubSDK/NativeAds/MPNativeAdSource.m | 2 +- MoPubSDK/NativeAds/MPNativeAdSourceDelegate.h | 2 +- MoPubSDK/NativeAds/MPNativeCustomEvent.h | 2 +- MoPubSDK/NativeAds/MPNativeCustomEvent.m | 2 +- .../NativeAds/MPNativeCustomEventDelegate.h | 2 +- MoPubSDK/NativeAds/MPServerAdPositioning.h | 2 +- MoPubSDK/NativeAds/MPServerAdPositioning.m | 2 +- MoPubSDK/NativeAds/MPStaticNativeAdRenderer.h | 2 +- MoPubSDK/NativeAds/MPStaticNativeAdRenderer.m | 2 +- .../MPStaticNativeAdRendererSettings.h | 2 +- .../MPStaticNativeAdRendererSettings.m | 2 +- MoPubSDK/NativeAds/MPStreamAdPlacementData.h | 2 +- MoPubSDK/NativeAds/MPStreamAdPlacementData.m | 4 +- MoPubSDK/NativeAds/MPStreamAdPlacer.h | 2 +- MoPubSDK/NativeAds/MPStreamAdPlacer.m | 12 +- MoPubSDK/NativeAds/MPTableViewAdPlacer.h | 2 +- MoPubSDK/NativeAds/MPTableViewAdPlacer.m | 2 +- MoPubSDK/NativeVideo/Internal/MOPUBAVPlayer.h | 2 +- MoPubSDK/NativeVideo/Internal/MOPUBAVPlayer.m | 2 +- .../NativeVideo/Internal/MOPUBAVPlayerView.h | 2 +- .../NativeVideo/Internal/MOPUBAVPlayerView.m | 2 +- .../Internal/MOPUBActivityIndicatorView.h | 2 +- .../Internal/MOPUBActivityIndicatorView.m | 2 +- .../MOPUBFullscreenPlayerViewController.h | 2 +- .../MOPUBFullscreenPlayerViewController.m | 2 +- .../Internal/MOPUBNativeVideoAdAdapter.h | 2 +- .../Internal/MOPUBNativeVideoAdAdapter.m | 2 +- .../Internal/MOPUBNativeVideoAdConfigValues.h | 2 +- .../Internal/MOPUBNativeVideoAdConfigValues.m | 2 +- .../Internal/MOPUBNativeVideoCustomEvent.h | 2 +- .../Internal/MOPUBNativeVideoCustomEvent.m | 29 +- .../MOPUBNativeVideoImpressionAgent.h | 2 +- .../MOPUBNativeVideoImpressionAgent.m | 2 +- .../NativeVideo/Internal/MOPUBPlayerManager.h | 2 +- .../NativeVideo/Internal/MOPUBPlayerManager.m | 2 +- .../NativeVideo/Internal/MOPUBPlayerView.h | 2 +- .../NativeVideo/Internal/MOPUBPlayerView.m | 10 +- .../Internal/MOPUBPlayerViewController.h | 2 +- .../Internal/MOPUBPlayerViewController.m | 12 +- .../NativeVideo/Internal/MOPUBReplayView.h | 2 +- .../NativeVideo/Internal/MOPUBReplayView.m | 2 +- .../NativeVideo/MOPUBNativeVideoAdRenderer.h | 2 +- .../NativeVideo/MOPUBNativeVideoAdRenderer.m | 2 +- .../MOPUBNativeVideoAdRendererSettings.h | 2 +- .../MOPUBNativeVideoAdRendererSettings.m | 2 +- MoPubSDK/Resources/MPAdapters.plist | 17 + .../MPMoPubRewardedPlayableCustomEvent.h | 2 +- .../MPMoPubRewardedPlayableCustomEvent.m | 75 +++-- .../MPMoPubRewardedVideoCustomEvent.h | 2 +- .../MPMoPubRewardedVideoCustomEvent.m | 52 ++- ...PPrivateRewardedVideoCustomEventDelegate.h | 2 +- .../Internal/MPRewardedVideo+Internal.h | 2 +- .../Internal/MPRewardedVideoAdManager.h | 2 +- .../Internal/MPRewardedVideoAdManager.m | 27 +- .../Internal/MPRewardedVideoAdapter.h | 2 +- .../Internal/MPRewardedVideoAdapter.m | 11 +- .../Internal/MPRewardedVideoConnection.h | 2 +- .../Internal/MPRewardedVideoConnection.m | 2 +- MoPubSDK/RewardedVideo/MPRewardedVideo.h | 2 +- MoPubSDK/RewardedVideo/MPRewardedVideo.m | 9 +- .../MPRewardedVideoCustomEvent+Caching.h | 28 -- .../MPRewardedVideoCustomEvent+Caching.m | 23 -- .../MPRewardedVideoCustomEvent.h | 25 +- .../MPRewardedVideoCustomEvent.m | 8 +- MoPubSDK/RewardedVideo/MPRewardedVideoError.h | 2 +- MoPubSDK/RewardedVideo/MPRewardedVideoError.m | 2 +- .../RewardedVideo/MPRewardedVideoReward.h | 2 +- .../RewardedVideo/MPRewardedVideoReward.m | 2 +- .../Avid/MPViewabilityAdapterAvid.h | 2 +- .../Avid/MPViewabilityAdapterAvid.m | 8 +- .../MOAT/MPViewabilityAdapterMoat.h | 2 +- .../MOAT/MPViewabilityAdapterMoat.m | 8 +- MoPubSDK/Viewability/MPViewabilityAdapter.h | 2 +- MoPubSDK/Viewability/MPViewabilityOption.h | 2 +- MoPubSDK/Viewability/MPViewabilityTracker.h | 2 +- MoPubSDK/Viewability/MPViewabilityTracker.m | 6 +- MoPubSDK/Viewability/MPWebView+Viewability.h | 2 +- MoPubSDK/Viewability/MPWebView+Viewability.m | 2 +- MoPubSDKFramework/Info.plist | 4 +- MoPubSDKTests/Info.plist | 4 +- .../MOPUBExperimentProvider+Testing.h | 2 +- MoPubSDKTests/MOPUBExperimentProviderTests.m | 2 +- .../MOPUBNativeVideoAdAdapter+Testing.h | 2 +- .../MOPUBNativeVideoAdAdapter+Testing.m | 2 +- .../MOPUBNativeVideoAdAdapterTests.m | 2 +- .../MOPUBNativeVideoAdConfigValuesTests.m | 2 +- MoPubSDKTests/MPAdConfiguration+Testing.h | 2 +- MoPubSDKTests/MPAdConfiguration+Testing.m | 5 +- MoPubSDKTests/MPAdConfigurationFactory.h | 2 +- MoPubSDKTests/MPAdConfigurationFactory.m | 2 +- MoPubSDKTests/MPAdConfigurationTests.m | 2 +- MoPubSDKTests/MPAdImpressionTimer+Testing.h | 2 +- MoPubSDKTests/MPAdImpressionTimer+Testing.m | 2 +- MoPubSDKTests/MPAdImpressionTimerTests.m | 2 +- .../MPAdServerCommunicator+Testing.h | 3 +- .../MPAdServerCommunicator+Testing.m | 2 +- MoPubSDKTests/MPAdServerCommunicatorTests.m | 51 ++- MoPubSDKTests/MPAdServerURLBuilder+Testing.h | 3 +- MoPubSDKTests/MPAdServerURLBuilder+Testing.m | 2 +- MoPubSDKTests/MPAdServerURLBuilderTests.m | 45 +-- MoPubSDKTests/MPAdView+Testing.h | 2 +- MoPubSDKTests/MPAdView+Testing.m | 2 +- MoPubSDKTests/MPAdViewTests.m | 2 +- .../MPAdserverCommunicatorDelegateHandler.h | 2 +- .../MPAdserverCommunicatorDelegateHandler.m | 2 +- .../MPAdvancedBiddingManager+Testing.h | 13 - .../MPAdvancedBiddingManager+Testing.m | 13 - MoPubSDKTests/MPAdvancedBiddingManagerTests.m | 102 ------ MoPubSDKTests/MPBannerAdManager+Testing.h | 2 +- MoPubSDKTests/MPBannerAdManager+Testing.m | 2 +- .../MPBannerAdManagerDelegateHandler.h | 2 +- .../MPBannerAdManagerDelegateHandler.m | 2 +- MoPubSDKTests/MPBannerAdManagerTests.m | 2 +- .../MPBannerAdapterDelegateHandler.h | 2 +- .../MPBannerAdapterDelegateHandler.m | 2 +- .../MPBannerCustomEventAdapter+Testing.h | 2 +- .../MPBannerCustomEventAdapter+Testing.m | 2 +- .../MPBannerCustomEventAdapterTests.m | 2 +- ...nsentDialogViewControllerDelegateHandler.h | 2 +- ...nsentDialogViewControllerDelegateHandler.m | 2 +- .../MPConsentDialogViewControllerTests.m | 2 +- MoPubSDKTests/MPConsentManager+Testing.h | 2 +- MoPubSDKTests/MPConsentManager+Testing.m | 2 +- MoPubSDKTests/MPConsentManagerTests.m | 2 +- MoPubSDKTests/MPConstants+Testing.h | 2 +- MoPubSDKTests/MPConstants+Testing.m | 2 +- MoPubSDKTests/MPCountdownTimerViewTests.m | 2 +- MoPubSDKTests/MPDictionaryAdditionTests.m | 2 +- MoPubSDKTests/MPGeolocationProviderTest.m | 2 +- MoPubSDKTests/MPHTTPNetworkSession+Testing.h | 2 +- MoPubSDKTests/MPHTTPNetworkSession+Testing.m | 2 +- MoPubSDKTests/MPHTTPNetworkSessionTests.m | 2 +- .../MPInterstitialAdController+Testing.h | 2 +- .../MPInterstitialAdController+Testing.m | 2 +- .../MPInterstitialAdControllerTests.m | 2 +- .../MPInterstitialAdManager+Testing.h | 2 +- .../MPInterstitialAdManager+Testing.m | 2 +- .../MPInterstitialAdManagerDelegateHandler.h | 2 +- .../MPInterstitialAdManagerDelegateHandler.m | 2 +- MoPubSDKTests/MPInterstitialAdManagerTests.m | 2 +- .../MPInterstitialAdapterDelegateHandler.h | 2 +- .../MPInterstitialAdapterDelegateHandler.m | 2 +- ...MPInterstitialCustomEventAdapter+Testing.h | 2 +- ...MPInterstitialCustomEventAdapter+Testing.m | 2 +- .../MPInterstitialCustomEventAdapterTests.m | 2 +- MoPubSDKTests/MPMediationManager+Testing.h | 19 ++ MoPubSDKTests/MPMediationManager+Testing.m | 32 ++ MoPubSDKTests/MPMediationManagerTests.m | 172 ++++++++-- MoPubSDKTests/MPMemoryCacheTests.m | 2 +- MoPubSDKTests/MPMoPubConfigurationTests.m | 44 +++ .../MPMoPubNativeAdAdapter+Testing.h | 2 +- .../MPMoPubNativeAdAdapter+Testing.m | 2 +- MoPubSDKTests/MPMoPubNativeAdAdapterTests.m | 2 +- ...MoPubRewardedPlayableCustomEvent+Testing.h | 2 +- ...MoPubRewardedPlayableCustomEvent+Testing.m | 2 +- .../MPMoPubRewardedPlayableCustomEventTests.m | 2 +- .../MPMockAdColonyAdapterConfiguration.h | 21 ++ .../MPMockAdColonyAdapterConfiguration.m | 45 +++ .../MPMockAdColonyRewardedVideoCustomEvent.h | 14 - .../MPMockAdColonyRewardedVideoCustomEvent.m | 33 -- .../MPMockAdDestinationDisplayAgent.h | 2 +- .../MPMockAdDestinationDisplayAgent.m | 2 +- MoPubSDKTests/MPMockAdServerCommunicator.h | 2 +- MoPubSDKTests/MPMockAdServerCommunicator.m | 2 +- MoPubSDKTests/MPMockAdapters.plist | 9 + MoPubSDKTests/MPMockBannerCustomEvent.h | 2 +- MoPubSDKTests/MPMockBannerCustomEvent.m | 2 +- .../MPMockChartboostAdapterConfiguration.h | 21 ++ .../MPMockChartboostAdapterConfiguration.m | 45 +++ ...MPMockChartboostRewardedVideoCustomEvent.h | 9 +- ...MPMockChartboostRewardedVideoCustomEvent.m | 20 +- MoPubSDKTests/MPMockInterstitialCustomEvent.h | 2 +- MoPubSDKTests/MPMockInterstitialCustomEvent.m | 2 +- .../MPMockLongLoadNativeCustomEvent.h | 2 +- .../MPMockLongLoadNativeCustomEvent.m | 2 +- .../MPMockMRAIDInterstitialViewController.h | 2 +- .../MPMockMRAIDInterstitialViewController.m | 2 +- MoPubSDKTests/MPMockNativeCustomEvent.h | 2 +- MoPubSDKTests/MPMockNativeCustomEvent.m | 2 +- MoPubSDKTests/MPMockRewardedVideoAdapter.h | 2 +- MoPubSDKTests/MPMockRewardedVideoAdapter.m | 2 +- .../MPMockRewardedVideoCustomEvent.h | 2 +- .../MPMockRewardedVideoCustomEvent.m | 2 +- .../MPMockTapjoyAdapterConfiguration.h | 22 ++ .../MPMockTapjoyAdapterConfiguration.m | 45 +++ MoPubSDKTests/MPMockViewabilityAdapterAvid.h | 2 +- MoPubSDKTests/MPMockViewabilityAdapterAvid.m | 2 +- MoPubSDKTests/MPNativeAdConfigValuesTests.m | 2 +- MoPubSDKTests/MPNativeAdRequest+Testing.h | 2 +- MoPubSDKTests/MPNativeAdRequest+Testing.m | 3 +- MoPubSDKTests/MPNativeAdRequestTests.m | 2 +- ...eRewardedVideoCustomEventDelegateHandler.h | 2 +- ...eRewardedVideoCustomEventDelegateHandler.m | 6 +- MoPubSDKTests/MPReachabilityTests.m | 2 +- MoPubSDKTests/MPRealTimeTimerTests.m | 2 +- MoPubSDKTests/MPRewardedVideo+Testing.h | 2 +- MoPubSDKTests/MPRewardedVideo+Testing.m | 2 +- .../MPRewardedVideoAdManager+Testing.h | 2 +- .../MPRewardedVideoAdManager+Testing.m | 2 +- MoPubSDKTests/MPRewardedVideoAdManagerTests.m | 2 +- .../MPRewardedVideoAdapter+Testing.h | 2 +- .../MPRewardedVideoAdapter+Testing.m | 2 +- .../MPRewardedVideoAdapterDelegateHandler.h | 2 +- .../MPRewardedVideoAdapterDelegateHandler.m | 2 +- MoPubSDKTests/MPRewardedVideoAdapterTests.m | 2 +- .../MPRewardedVideoDelegateHandler.h | 2 +- .../MPRewardedVideoDelegateHandler.m | 2 +- MoPubSDKTests/MPRewardedVideoRewardTests.m | 2 +- MoPubSDKTests/MPRewardedVideoTests.m | 5 +- MoPubSDKTests/MPStubAdvancedBidder.h | 15 - MoPubSDKTests/MPStubAdvancedBidder.m | 21 -- MoPubSDKTests/MPStubCustomEvent.h | 2 +- MoPubSDKTests/MPStubCustomEvent.m | 2 +- MoPubSDKTests/MPStubMediatedNetwork.h | 23 -- MoPubSDKTests/MPStubMediatedNetwork.m | 25 -- MoPubSDKTests/MPURLRequest+Testing.h | 2 +- MoPubSDKTests/MPURLRequest+Testing.m | 7 +- MoPubSDKTests/MPURLRequestTests.m | 2 +- MoPubSDKTests/MPURLResolverTests.m | 2 +- MoPubSDKTests/MPVASTLinearAdTests.m | 2 +- MoPubSDKTests/MPVASTModelTests.m | 2 +- MoPubSDKTests/MPVideoConfigTests.m | 2 +- MoPubSDKTests/MPViewabilityTracker+Testing.h | 2 +- MoPubSDKTests/MPViewabilityTracker+Testing.m | 2 +- MoPubSDKTests/MPViewabilityTrackerTests.m | 2 +- MoPubSDKTests/MPWebView+Testing.h | 2 +- MoPubSDKTests/MPWebView+Testing.m | 2 +- MoPubSDKTests/MPWebViewTests.m | 2 +- MoPubSDKTests/MRController+Testing.h | 2 +- MoPubSDKTests/MRController+Testing.m | 2 +- MoPubSDKTests/MoPub+Testing.h | 9 +- MoPubSDKTests/MoPub+Testing.m | 2 +- MoPubSDKTests/MoPubTests.m | 150 ++++----- MoPubSDKTests/NSErrorTests.m | 2 +- MoPubSDKTests/NSURLComponents+Testing.h | 2 +- MoPubSDKTests/NSURLComponents+Testing.m | 2 +- MoPubSDKTests/NSURLSessionTask+Testing.h | 2 +- MoPubSDKTests/NSURLSessionTask+Testing.m | 2 +- MoPubSDKTests/XCTestCase+MPAddition.h | 2 +- MoPubSDKTests/XCTestCase+MPAddition.m | 2 +- MoPubSampleApp/AppDelegate.h | 2 +- MoPubSampleApp/AppDelegate.m | 5 +- .../Controllers/MPAdEntryViewController.h | 2 +- .../Controllers/MPAdEntryViewController.m | 2 +- .../Controllers/MPAdTableViewController.h | 2 +- .../Controllers/MPAdTableViewController.m | 2 +- .../MPBannerAdDetailViewController.h | 2 +- .../MPBannerAdDetailViewController.m | 2 +- .../MPInterstitialAdDetailViewController.h | 2 +- .../MPInterstitialAdDetailViewController.m | 2 +- ...PLeaderboardBannerAdDetailViewController.h | 2 +- ...PLeaderboardBannerAdDetailViewController.m | 2 +- .../MPMRectBannerAdDetailViewController.h | 2 +- .../MPMRectBannerAdDetailViewController.m | 2 +- .../MPNativeAdDetailViewController.h | 2 +- .../MPNativeAdDetailViewController.m | 2 +- ...MPNativeAdPlacerCollectionViewController.h | 2 +- ...MPNativeAdPlacerCollectionViewController.m | 2 +- .../MPNativeAdPlacerPageViewController.h | 2 +- .../MPNativeAdPlacerPageViewController.m | 2 +- .../MPNativeAdPlacerTableViewController.h | 2 +- .../MPNativeAdPlacerTableViewController.m | 2 +- .../MPRewardedVideoAdDetailViewController.h | 2 +- .../MPRewardedVideoAdDetailViewController.m | 2 +- MoPubSampleApp/Controllers/MPViewController.h | 2 +- MoPubSampleApp/Controllers/MPViewController.m | 2 +- MoPubSampleApp/Domain/MPAdInfo.h | 2 +- MoPubSampleApp/Domain/MPAdInfo.m | 2 +- MoPubSampleApp/Domain/MPAdSection.h | 2 +- MoPubSampleApp/Domain/MPAdSection.m | 2 +- MoPubSampleApp/MPAdPersistenceManager.h | 2 +- MoPubSampleApp/MPAdPersistenceManager.m | 2 +- MoPubSampleApp/MPSampleAppInstanceProvider.h | 2 +- MoPubSampleApp/MPSampleAppInstanceProvider.m | 2 +- MoPubSampleApp/MPSampleAppLogReader.h | 2 +- MoPubSampleApp/MPSampleAppLogReader.m | 12 +- .../Views/MPCollectionViewAdPlacerView.h | 2 +- .../Views/MPCollectionViewAdPlacerView.m | 2 +- MoPubSampleApp/Views/MPIndexPathPickerView.h | 2 +- MoPubSampleApp/Views/MPIndexPathPickerView.m | 2 +- MoPubSampleApp/Views/MPNativeAdCell.h | 2 +- MoPubSampleApp/Views/MPNativeAdCell.m | 2 +- MoPubSampleApp/Views/MPNativeAdPageView.h | 2 +- MoPubSampleApp/Views/MPNativeAdPageView.m | 2 +- .../Views/MPNativeAdTableHeaderView.h | 2 +- .../Views/MPNativeAdTableHeaderView.m | 2 +- ...NativeCollectionViewAdCollectionViewCell.h | 2 +- ...NativeCollectionViewAdCollectionViewCell.m | 2 +- .../MPNativeVideoTableViewAdPlacerView.h | 2 +- .../MPNativeVideoTableViewAdPlacerView.m | 2 +- MoPubSampleApp/Views/MPNativeVideoView.h | 2 +- MoPubSampleApp/Views/MPNativeVideoView.m | 2 +- MoPubSampleApp/Views/MPStaticNativeAdView.h | 2 +- MoPubSampleApp/Views/MPStaticNativeAdView.m | 2 +- .../Views/MPTableViewAdPlacerView.h | 2 +- .../Views/MPTableViewAdPlacerView.m | 2 +- MoPubSampleApp/main.m | 2 +- README.md | 13 +- mopub-ios-sdk.podspec | 6 +- 723 files changed, 4832 insertions(+), 2803 deletions(-) create mode 100644 Canary/Canary.xcodeproj/xcshareddata/xcschemes/CanaryUITests.xcscheme delete mode 100644 Canary/Canary/AppDelegate+AdvancedBidders.swift create mode 100644 Canary/Canary/Assets.xcassets/AppIcon.Internal.appiconset/Contents.json create mode 100644 Canary/Canary/Assets.xcassets/AppIcon.Internal.appiconset/MoPub_logo_square_white on blue (1)-1024.png create mode 100644 Canary/Canary/Assets.xcassets/AppIcon.Internal.appiconset/MoPub_logo_square_white on blue (1)-20.png create mode 100644 Canary/Canary/Assets.xcassets/AppIcon.Internal.appiconset/MoPub_logo_square_white on blue (1)-20@2x.png create mode 100644 Canary/Canary/Assets.xcassets/AppIcon.Internal.appiconset/MoPub_logo_square_white on blue (1)-20@3x.png create mode 100644 Canary/Canary/Assets.xcassets/AppIcon.Internal.appiconset/MoPub_logo_square_white on blue (1)-29.png create mode 100644 Canary/Canary/Assets.xcassets/AppIcon.Internal.appiconset/MoPub_logo_square_white on blue (1)-29@2x.png create mode 100644 Canary/Canary/Assets.xcassets/AppIcon.Internal.appiconset/MoPub_logo_square_white on blue (1)-29@3x.png create mode 100644 Canary/Canary/Assets.xcassets/AppIcon.Internal.appiconset/MoPub_logo_square_white on blue (1)-40.png create mode 100644 Canary/Canary/Assets.xcassets/AppIcon.Internal.appiconset/MoPub_logo_square_white on blue (1)-40@2x.png create mode 100644 Canary/Canary/Assets.xcassets/AppIcon.Internal.appiconset/MoPub_logo_square_white on blue (1)-40@3x.png create mode 100644 Canary/Canary/Assets.xcassets/AppIcon.Internal.appiconset/MoPub_logo_square_white on blue (1)-60@2x.png create mode 100644 Canary/Canary/Assets.xcassets/AppIcon.Internal.appiconset/MoPub_logo_square_white on blue (1)-60@3x.png create mode 100644 Canary/Canary/Assets.xcassets/AppIcon.Internal.appiconset/MoPub_logo_square_white on blue (1)-76.png create mode 100644 Canary/Canary/Assets.xcassets/AppIcon.Internal.appiconset/MoPub_logo_square_white on blue (1)-76@2x.png create mode 100644 Canary/Canary/Assets.xcassets/AppIcon.Internal.appiconset/MoPub_logo_square_white on blue (1)-83.5@2x.png create mode 100644 Canary/Canary/Assets.xcassets/CameraButton.imageset/Contents.json create mode 100644 Canary/Canary/Assets.xcassets/CameraButton.imageset/camera_stroke.png create mode 100644 Canary/Canary/Assets.xcassets/CameraButton.imageset/camera_stroke@2x.png create mode 100644 Canary/Canary/Assets.xcassets/CameraButton.imageset/camera_stroke@3x.png create mode 100644 Canary/Canary/Assets.xcassets/CloseButton.imageset/Contents.json create mode 100644 Canary/Canary/Assets.xcassets/CloseButton.imageset/MPCloseButtonX.png create mode 100644 Canary/Canary/Assets.xcassets/CloseButton.imageset/MPCloseButtonX@2x.png create mode 100644 Canary/Canary/Assets.xcassets/CloseButton.imageset/MPCloseButtonX@3x.png create mode 100644 Canary/Canary/Assets.xcassets/Mraid.imageset/Contents.json create mode 100644 Canary/Canary/Assets.xcassets/Mraid.imageset/Mraid.png create mode 100644 Canary/Canary/Assets.xcassets/Mraid.imageset/Mraid@2x.png create mode 100644 Canary/Canary/Assets.xcassets/Mraid.imageset/Mraid@3x.png delete mode 100644 Canary/Canary/Privacy/PrivacyInfoDataSource.swift delete mode 100644 Canary/Canary/Privacy/PrivacyInfoViewController.swift delete mode 100644 Canary/Canary/Privacy/PrivacyMenuDataSource.swift create mode 100644 Canary/Canary/QRCodeCameraInterfaceViewController.swift create mode 100644 MoPubSDK/Internal/HTML/MPContentBlocker.h create mode 100644 MoPubSDK/Internal/HTML/MPContentBlocker.m delete mode 100644 MoPubSDK/Internal/MPAdvancedBiddingManager.h delete mode 100644 MoPubSDK/Internal/MPAdvancedBiddingManager.m delete mode 100644 MoPubSDK/Internal/Utility/MPLogProvider.h delete mode 100644 MoPubSDK/Internal/Utility/MPLogProvider.m delete mode 100644 MoPubSDK/Internal/Utility/MPLogging.h delete mode 100644 MoPubSDK/Internal/Utility/MPLogging.m create mode 100644 MoPubSDK/Logging/Internal/MPConsoleLogger.h create mode 100644 MoPubSDK/Logging/Internal/MPConsoleLogger.m create mode 100644 MoPubSDK/Logging/Internal/MPLogManager.h create mode 100644 MoPubSDK/Logging/Internal/MPLogManager.m create mode 100644 MoPubSDK/Logging/MPLogEvent.h create mode 100644 MoPubSDK/Logging/MPLogEvent.m create mode 100644 MoPubSDK/Logging/MPLogLevel.h create mode 100644 MoPubSDK/Logging/MPLogger.h create mode 100644 MoPubSDK/Logging/MPLogging.h create mode 100644 MoPubSDK/Logging/MPLogging.m create mode 100644 MoPubSDK/MPAdapterConfiguration.h delete mode 100644 MoPubSDK/MPAdvancedBidder.h create mode 100644 MoPubSDK/MPBaseAdapterConfiguration.h create mode 100644 MoPubSDK/MPBaseAdapterConfiguration.m delete mode 100644 MoPubSDK/MPLogLevel.h delete mode 100644 MoPubSDK/MPMediationSdkInitializable.h create mode 100644 MoPubSDK/Resources/MPAdapters.plist delete mode 100644 MoPubSDK/RewardedVideo/MPRewardedVideoCustomEvent+Caching.h delete mode 100644 MoPubSDK/RewardedVideo/MPRewardedVideoCustomEvent+Caching.m delete mode 100644 MoPubSDKTests/MPAdvancedBiddingManager+Testing.h delete mode 100644 MoPubSDKTests/MPAdvancedBiddingManager+Testing.m delete mode 100644 MoPubSDKTests/MPAdvancedBiddingManagerTests.m create mode 100644 MoPubSDKTests/MPMediationManager+Testing.h create mode 100644 MoPubSDKTests/MPMediationManager+Testing.m create mode 100644 MoPubSDKTests/MPMoPubConfigurationTests.m create mode 100644 MoPubSDKTests/MPMockAdColonyAdapterConfiguration.h create mode 100644 MoPubSDKTests/MPMockAdColonyAdapterConfiguration.m delete mode 100644 MoPubSDKTests/MPMockAdColonyRewardedVideoCustomEvent.h delete mode 100644 MoPubSDKTests/MPMockAdColonyRewardedVideoCustomEvent.m create mode 100644 MoPubSDKTests/MPMockAdapters.plist create mode 100644 MoPubSDKTests/MPMockChartboostAdapterConfiguration.h create mode 100644 MoPubSDKTests/MPMockChartboostAdapterConfiguration.m create mode 100644 MoPubSDKTests/MPMockTapjoyAdapterConfiguration.h create mode 100644 MoPubSDKTests/MPMockTapjoyAdapterConfiguration.m delete mode 100644 MoPubSDKTests/MPStubAdvancedBidder.h delete mode 100644 MoPubSDKTests/MPStubAdvancedBidder.m delete mode 100644 MoPubSDKTests/MPStubMediatedNetwork.h delete mode 100644 MoPubSDKTests/MPStubMediatedNetwork.m diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 1f9f188cf..f4b958fdc 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -9,6 +9,8 @@ One line summary of the issue here #### Device model and OS Version: +#### Xcode Version: + #### Ad Unit IDs used in reproducing the issue: #### Steps to reproduce the behavior: diff --git a/CHANGELOG.md b/CHANGELOG.md index ba2a4879e..bdf3500fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +## 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. @@ -13,7 +24,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 755cc17b8..21829b418 100644 --- a/Canary/Canary.xcodeproj/project.pbxproj +++ b/Canary/Canary.xcodeproj/project.pbxproj @@ -7,6 +7,9 @@ objects = { /* Begin PBXBuildFile section */ + 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 */; }; 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 */; }; B2564EE320AB5039000B9F7A /* SampleAds.plist in Resources */ = {isa = PBXBuildFile; fileRef = BCF7095E1FBCCF50009A3981 /* SampleAds.plist */; }; @@ -47,13 +50,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 +70,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 */; }; @@ -103,9 +102,10 @@ 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 /* MraidSplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC713BAD21700BBC003655B2 /* MraidSplitViewController.swift */; }; + BC713BB121700C9B003655B2 /* MraidTestsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC713BB021700C9B003655B2 /* MraidTestsViewController.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 +135,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 +149,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 /* MraidComplianceAds.plist in Resources */ = {isa = PBXBuildFile; fileRef = BCF6F7E321752DDF00FDE594 /* MraidComplianceAds.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 +157,37 @@ 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 */; }; + 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 */; }; D1EA998EABF442DAD42BE461 /* Pods_Canary.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E125D60FEE1A91995D3FDF82 /* Pods_Canary.framework */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + CE237F23216BF2C800A8134A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BC33E02B1FBB627B0060ECBE /* Project object */; + proxyType = 1; + remoteGlobalIDString = BC33E0321FBB627B0060ECBE; + remoteInfo = Canary; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXCopyFilesBuildPhase section */ BC4A99D5200EBCDD00E3EB07 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; @@ -173,6 +202,8 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 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 = ""; }; 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 = ""; }; @@ -213,7 +244,6 @@ 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 = ""; }; @@ -230,6 +260,8 @@ BC59F25C20F524E30051DA00 /* AdUnitDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AdUnitDataSource.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 /* MraidSplitViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MraidSplitViewController.swift; sourceTree = ""; }; + BC713BB021700C9B003655B2 /* MraidTestsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MraidTestsViewController.swift; 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,11 +286,32 @@ 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 /* MraidComplianceAds.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = MraidComplianceAds.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 = ""; }; + 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 = ""; }; 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; }; /* End PBXFileReference section */ @@ -268,6 +321,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 2A35FAA921B5DA1C00DC8805 /* AVFoundation.framework in Frameworks */, D1EA998EABF442DAD42BE461 /* Pods_Canary.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -280,6 +334,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + CE237F1B216BF2C800A8134A /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -356,6 +417,7 @@ children = ( BC33E0331FBB627B0060ECBE /* Canary.app */, BC33E0571FBB63260060ECBE /* Canary (Internal).app */, + CE237F1E216BF2C800A8134A /* CanaryUITests.xctest */, ); name = Products; sourceTree = ""; @@ -364,7 +426,6 @@ isa = PBXGroup; children = ( BC33E0361FBB627B0060ECBE /* AppDelegate.swift */, - BC467DB0206465CB0083ECC4 /* AppDelegate+AdvancedBidders.swift */, BC00C5B7208FCF46008E0AEB /* AppDelegate+Consent.swift */, BCB4DF33211CD970005BD171 /* TableViewCellRegisterable.swift */, BCA042EE211E049D001B1AF5 /* RoundedButton.swift */, @@ -382,7 +443,7 @@ BC33E03C1FBB627B0060ECBE /* Main.storyboard */, BC95D23B2097B9180030C230 /* MainTabBarController.swift */, BCB63FE420AB3D8100C22C7F /* Menu */, - BCE7078B20B373A500DA4BCB /* Privacy */, + 2A35FAAA21B5DA6200DC8805 /* QRCodeCameraInterfaceViewController.swift */, BCFE5B3D1FE44F6D00D760E9 /* Samples */, B275DB2B200FD5F500F053F8 /* SavedAds */, BC04945920F91C9E00CFD9C2 /* StoryboardInstantiable.swift */, @@ -394,14 +455,27 @@ isa = PBXGroup; children = ( 2A4D35E0211D06ED00BE9377 /* APIEndpoint */, + CE237F1F216BF2C800A8134A /* CanaryUITests */, B2564EE020AB4F2B000B9F7A /* Internal-Info.plist */, - BC04945C20F9266A00CFD9C2 /* Internal.swift */, BC04944620F91BF400CFD9C2 /* Internal.storyboard */, + BC04945C20F9266A00CFD9C2 /* Internal.swift */, + BC713BAF21700BC6003655B2 /* MRAID */, BC04944920F91BFC00CFD9C2 /* NetworkAds */, + BCE7078B20B373A500DA4BCB /* Privacy */, ); path = Internal; sourceTree = ""; }; + BC713BAF21700BC6003655B2 /* MRAID */ = { + isa = PBXGroup; + children = ( + BC713BAD21700BBC003655B2 /* MraidSplitViewController.swift */, + BC713BB021700C9B003655B2 /* MraidTestsViewController.swift */, + BCF6F7E321752DDF00FDE594 /* MraidComplianceAds.plist */, + ); + path = MRAID; + sourceTree = ""; + }; BCB63FE420AB3D8100C22C7F /* Menu */ = { isa = PBXGroup; children = ( @@ -498,6 +572,7 @@ BD565F79D171D27FE1BAD7EB /* Frameworks */ = { isa = PBXGroup; children = ( + 2A35FAA821B5DA1C00DC8805 /* AVFoundation.framework */, BCE738601FBB707500093CCD /* CoalMine.framework */, E125D60FEE1A91995D3FDF82 /* Pods_Canary.framework */, DD29E9B2187F9B97C60CAB75 /* Pods_Canary__Internal_.framework */, @@ -505,6 +580,80 @@ 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 = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -549,13 +698,31 @@ productReference = BC33E0571FBB63260060ECBE /* Canary (Internal).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 = ( + CE237F24216BF2C800A8134A /* PBXTargetDependency */, + ); + name = CanaryUITests; + productName = CanaryUITests; + productReference = CE237F1E216BF2C800A8134A /* CanaryUITests.xctest */; + productType = "com.apple.product-type.bundle.ui-testing"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ BC33E02B1FBB627B0060ECBE /* Project object */ = { isa = PBXProject; attributes = { - LastSwiftUpdateCheck = 0910; + LastSwiftUpdateCheck = 1000; LastUpgradeCheck = 0940; ORGANIZATIONNAME = MoPub; TargetAttributes = { @@ -567,6 +734,11 @@ LastSwiftMigration = 1000; ProvisioningStyle = Automatic; }; + CE237F1D216BF2C800A8134A = { + CreatedOnToolsVersion = 10.0; + ProvisioningStyle = Automatic; + TestTargetID = BC33E0321FBB627B0060ECBE; + }; }; }; buildConfigurationList = BC33E02E1FBB627B0060ECBE /* Build configuration list for PBXProject "Canary" */; @@ -584,6 +756,7 @@ targets = ( BC33E0321FBB627B0060ECBE /* Canary */, BC33E04A1FBB63260060ECBE /* Canary (Internal) */, + CE237F1D216BF2C800A8134A /* CanaryUITests */, ); }; /* End PBXProject section */ @@ -597,8 +770,8 @@ BC33E0431FBB627B0060ECBE /* LaunchScreen.storyboard in Resources */, BCA042EC211E0084001B1AF5 /* AdActionsTableViewCell.xib in Resources */, BC2B97EA20F41D7800D58F79 /* AdUnitTableViewHeader.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 */, @@ -623,18 +796,26 @@ BC2CEFBA211CF14600EAA99D /* TextEntryTableViewCell.xib in Resources */, BC33E0511FBB63260060ECBE /* LaunchScreen.storyboard 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 /* MraidComplianceAds.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; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ @@ -667,8 +848,8 @@ ); inputPaths = ( "${SRCROOT}/Pods/Target Support Files/Pods-Canary/Pods-Canary-resources.sh", - "${PODS_ROOT}/TapjoySDK/TapjoySDK_iOS_v12.1.0/Libraries/Tapjoy.embeddedframework/Resources/TapjoyResources.bundle", - "${PODS_ROOT}/TapjoySDK/TapjoySDK_iOS_v12.1.0/Libraries/Tapjoy.embeddedframework/Tapjoy.framework/Versions/A/Resources/TapjoyResources.bundle", + "${PODS_ROOT}/TapjoySDK/TapjoySDK_iOS_v12.2.0/Libraries/Tapjoy.embeddedframework/Resources/TapjoyResources.bundle", + "${PODS_ROOT}/TapjoySDK/TapjoySDK_iOS_v12.2.0/Libraries/Tapjoy.embeddedframework/Tapjoy.framework/Versions/A/Resources/TapjoyResources.bundle", ); name = "[CP] Copy Pods Resources"; outputPaths = ( @@ -686,8 +867,8 @@ ); inputPaths = ( "${SRCROOT}/Pods/Target Support Files/Pods-Canary (Internal)/Pods-Canary (Internal)-resources.sh", - "${PODS_ROOT}/TapjoySDK/TapjoySDK_iOS_v12.1.0/Libraries/Tapjoy.embeddedframework/Resources/TapjoyResources.bundle", - "${PODS_ROOT}/TapjoySDK/TapjoySDK_iOS_v12.1.0/Libraries/Tapjoy.embeddedframework/Tapjoy.framework/Versions/A/Resources/TapjoyResources.bundle", + "${PODS_ROOT}/TapjoySDK/TapjoySDK_iOS_v12.2.0/Libraries/Tapjoy.embeddedframework/Resources/TapjoyResources.bundle", + "${PODS_ROOT}/TapjoySDK/TapjoySDK_iOS_v12.2.0/Libraries/Tapjoy.embeddedframework/Tapjoy.framework/Versions/A/Resources/TapjoyResources.bundle", ); name = "[CP] Copy Pods Resources"; outputPaths = ( @@ -791,11 +972,9 @@ BC525EE92135BA4E007B1761 /* PreferredWidthLabel.swift in Sources */, BC4CE29C2136054500CA0220 /* NativeAdTableDataSource.swift in Sources */, BC04945A20F91C9E00CFD9C2 /* StoryboardInstantiable.swift in Sources */, - BCE7078320B3424E00DA4BCB /* PrivacyInfoDataSource.swift in Sources */, BC525ED4212F2D71007B1761 /* InterstitialAdDataSource.swift in Sources */, BC33E0371FBB627B0060ECBE /* AppDelegate.swift in Sources */, BCA042EF211E049D001B1AF5 /* RoundedButton.swift in Sources */, - BC96A33A20AE306F00AC3266 /* PrivacyMenuDataSource.swift in Sources */, BC3B0C982007DBAA002D28B1 /* TweetCollectionViewCell.swift in Sources */, BC59F25D20F524E40051DA00 /* AdUnitDataSource.swift in Sources */, BCB4DF28211CB62F005BD171 /* AdTableViewController.swift in Sources */, @@ -807,7 +986,6 @@ 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 +993,10 @@ BC63B9221FBD14A00033ACD6 /* AdUnit.swift in Sources */, BC4CE29F2136FB6100CA0220 /* AdViewController.swift in Sources */, BC3B0C8C20058D7C002D28B1 /* NativeAdView.swift in Sources */, + 2A35FAAB21B5DA6200DC8805 /* QRCodeCameraInterfaceViewController.swift in Sources */, BCB4DF25211B913F005BD171 /* AdDataSource.swift in Sources */, BCB4DF2E211CC265005BD171 /* StatusTableViewCell.swift in Sources */, BC96A33D20AE32DB00AC3266 /* BasicMenuTableViewCell.swift in Sources */, - BCE7078020B3392100DA4BCB /* PrivacyInfoViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -847,12 +1025,15 @@ BCA042EA211E004D001B1AF5 /* AdActionsTableViewCell.swift in Sources */, BC00C5B9208FCF46008E0AEB /* AppDelegate+Consent.swift in Sources */, BC3B0C962007DAA5002D28B1 /* NativeAdCollectionViewController.swift in Sources */, + BC713BB121700C9B003655B2 /* MraidTestsViewController.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 /* MraidSplitViewController.swift in Sources */, BC003AFE20DC42FE00CD1FEB /* LogingLevelMenuDataSource.swift in Sources */, BC525EEA2135BA4E007B1761 /* PreferredWidthLabel.swift in Sources */, + 2A35FAAC21B5DA6200DC8805 /* QRCodeCameraInterfaceViewController.swift in Sources */, BC4CE29D2136054500CA0220 /* NativeAdTableDataSource.swift in Sources */, BC04945B20F91C9E00CFD9C2 /* StoryboardInstantiable.swift in Sources */, 2A4D35E3211D074800BE9377 /* APIEndpointMenuDataSource.swift in Sources */, @@ -874,7 +1055,6 @@ BC525EE12130A362007B1761 /* NativeAdDataSource.swift in Sources */, BC04944420F904F200CFD9C2 /* AdUnitTableViewController.swift in Sources */, BC525EC9212F15A5007B1761 /* MediumRectangleAdViewController.swift in Sources */, - BC467DB2206465CB0083ECC4 /* AppDelegate+AdvancedBidders.swift in Sources */, BC95D23D2097B9180030C230 /* MainTabBarController.swift in Sources */, BC16904E201F8F8E000EA77F /* AdFormat.swift in Sources */, BCD0506D2003F20400FFC36D /* UIView+Nib.swift in Sources */, @@ -889,8 +1069,41 @@ ); 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; + }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + CE237F24216BF2C800A8134A /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = BC33E0321FBB627B0060ECBE /* Canary */; + targetProxy = CE237F23216BF2C800A8134A /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin PBXVariantGroup section */ BC33E03C1FBB627B0060ECBE /* Main.storyboard */ = { isa = PBXVariantGroup; @@ -1028,7 +1241,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 5.4.1; + CURRENT_PROJECT_VERSION = 5.5.0; DEVELOPMENT_TEAM = 4S7XS533V3; INFOPLIST_FILE = Canary/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; @@ -1046,7 +1259,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 5.4.1; + CURRENT_PROJECT_VERSION = 5.5.0; DEVELOPMENT_TEAM = 4S7XS533V3; INFOPLIST_FILE = Canary/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; @@ -1062,9 +1275,9 @@ isa = XCBuildConfiguration; baseConfigurationReference = 3FA62C94B44B098C968278EE /* Pods-Canary (Internal).debug.xcconfig */; buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon.Internal; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 5.4.1; + CURRENT_PROJECT_VERSION = 5.5.0; DEVELOPMENT_TEAM = 4S7XS533V3; INFOPLIST_FILE = "Canary/Internal/Internal-Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; @@ -1081,9 +1294,9 @@ isa = XCBuildConfiguration; baseConfigurationReference = BBA3359CC86A7CE1AF32934B /* Pods-Canary (Internal).release.xcconfig */; buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon.Internal; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 5.4.1; + CURRENT_PROJECT_VERSION = 5.5.0; DEVELOPMENT_TEAM = 4S7XS533V3; INFOPLIST_FILE = "Canary/Internal/Internal-Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; @@ -1096,6 +1309,41 @@ }; name = Release; }; + CE237F25216BF2C800A8134A /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_OBJC_WEAK = YES; + CODE_SIGN_STYLE = Automatic; + 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 = 4.2; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = Canary; + }; + name = Debug; + }; + CE237F26216BF2C800A8134A /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_OBJC_WEAK = YES; + CODE_SIGN_STYLE = Automatic; + 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 = 4.2; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = Canary; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -1126,6 +1374,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + CE237F27216BF2C800A8134A /* Build configuration list for PBXNativeTarget "CanaryUITests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + CE237F25216BF2C800A8134A /* Debug */, + CE237F26216BF2C800A8134A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ }; rootObject = BC33E02B1FBB627B0060ECBE /* Project object */; diff --git a/Canary/Canary.xcodeproj/xcshareddata/xcschemes/CanaryUITests.xcscheme b/Canary/Canary.xcodeproj/xcshareddata/xcschemes/CanaryUITests.xcscheme new file mode 100644 index 000000000..6acda9080 --- /dev/null +++ b/Canary/Canary.xcodeproj/xcshareddata/xcschemes/CanaryUITests.xcscheme @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 index bfed6402a..334d94d7e 100644 --- a/Canary/Canary/AppDelegate+Consent.swift +++ b/Canary/Canary/AppDelegate+Consent.swift @@ -1,7 +1,7 @@ // // AppDelegate+Consent.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/AppDelegate.swift b/Canary/Canary/AppDelegate.swift index d394ba7d4..5620dcceb 100644 --- a/Canary/Canary/AppDelegate.swift +++ b/Canary/Canary/AppDelegate.swift @@ -1,7 +1,7 @@ // // 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/ // @@ -46,9 +46,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // MoPub SDK initialization let sdkConfig = MPMoPubConfiguration(adUnitIdForAppInitialization: kAdUnitId) - sdkConfig.advancedBidders = supportedAdvancedBidders() sdkConfig.globalMediationSettings = [] - sdkConfig.mediatedNetworks = MoPub.sharedInstance().allCachedNetworks() + sdkConfig.loggingLevel = .info MoPub.sharedInstance().initializeSdk(with: sdkConfig) { // Request user consent to collect personally identifiable information @@ -56,8 +55,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { if let tabBarController = self.containerViewController.mainTabBarController { self.displayConsentDialog(from: tabBarController) } - - print("SDK completed initialization") } // Conversion tracking @@ -80,33 +77,31 @@ class AppDelegate: UIResponder, UIApplicationDelegate { - 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 + - Returns: true if successfully shown, false if not */ 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 { + // Get adUnit object from the URL. If an adUnit is not obtainable from the URL, return false. + guard let adUnit = AdUnit(url: url) else { return false } + return openMoPubAdUnit(adUnit: adUnit, onto: splitViewController, shouldSave: shouldSave) + } + + + /** + 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 + - Returns: true if successfully shown, false if not + */ + func openMoPubAdUnit(adUnit: AdUnit, onto splitViewController: UISplitViewController?, shouldSave: Bool) -> 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 + return false } DispatchQueue.main.async { 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 0000000000000000000000000000000000000000..76c635e4a9e9af6554622ba69f40b3dd2d3e4e1e GIT binary patch literal 48615 zcmeFZXIPV4*EJfDrl5dGld7nQGywsr!3HQzMS2NRRhsk`5)c)XCQ6kOge{0P=>!Nx zN`L?Xkq!ZY5PIk#qLF)>7m|D z0N{FjyqVn-VY4&Zjec$t7Y0l{uD$aA0z7%~pQ;<6Gczh)sdrT-&o4V4Y!0ON z3|v=gqL3cC~!UiHnmz#V>1DA4c6@jxAWr&``>F+E9UU z*#z%q<8#6`;cj^5W%yUi)TA>Ed>_kX`4#8=q7!4Zq~E=a&gEzMWbr^m+OEiuQN@Hk zsK#dY*7q>b#K;U4R$8Yk!Yl40=2*4#g*_&bVXM##703{Jlq3dRwH7^aT&`2l>5vbt z|K&8+?xzijf9kp@Gd~=|Y-ai_CXFkxuk%osgQW0n6qIZ9Z0+kZHqS9LU?#WXIsP#f z=Gpe-)vjlIAvy!5{SyD!mL#ow5%=pshr!qPFkLd+yt`+3t?=AbYGx8QkmcPp=3{vQ zH~zM5$rS6?X4qe)HE+*Ba4%JMW#(mL(5kpz)3)*AuOB+I+5$W3l&yDmG?-f7G}wIH zM*5OGO>=f|*j43^sIZXRG=7A&I;IQUh6iW%^&9YsFOrQNQI^^GXEVkv$Q7jH5t z3twF$KIk0a2gAUxREi^^%_pmlGha@>8jUz%WO2SDd4@U+`6)B`1{d4ZCa?zp{1W0YYR{8G)As z#ZON}q3sp#9whlFGMo>2t7&xbLAa8~8Gkf>&*P50rs^oADNgBOLFSzqwd%%P2b$6q zLtE3a5}8xAuO(1YBxXAVxeHWbyCS3dpmts`yTM|B2qLbL6%VAz&;FW&E?R8Y`68|R z8Y>Ok*F7x#g3Qi(vMlgz4P|t`=@i-Z*WZ=s8>@=;smO)5{U#4^+`onv}`@;vq+w2&VG*HHN54?O3tQLm87%eg$ozfnb;b+xSVHstwgVAb8Wxj#C{^*xpSL~ znU{|;A7b>m z;eT5@%gY6LQvKL0_P>|+lRiYvYp4IVHuxn1pp$>H*!usmXg)5$7iD3d|F)(}_s>~R z%aQZ{z33YH5T(D;|J&NNE8>8`C+%PP{(I4XT>RTjxqn>zdsqI6;@_M74^aGumOnu8 z2Ppm=cYiX9|72Z%GK$}6-k+rHcT)K$Fa9TQ|5JMWE>`{k#UG&fQ$W*q!yk^~A9Vb~ zQT#^7KQz#9bo|4#{gX!hAv1s18GnG{4^aG}F1rdN&$7i83|0FdR zxw|2Q3*JEc1G}ocvAfpqKTI#)*v(4w#+Yk|jE-i4k)Y!;h7LA+Tax)W;At(=v9WR) zJ$z%Oa0NkM&0*uMeeP4oXEyNQg<9Xu5hd(;v1>2ZC+l%!1>4PyXEF}Y3X+GCHX>iW zvMZf^_JFQZ=`=T!{9o%jw-_gwdy@u%_4tpzecF`lw$Lu#Dy*rS_lAq;`ZLy&PnYhe z+~FEOEhr<@Ws$6h@oLiiRbSzo7^R1Ta1S(x3XBI^cHGgUG6TBC$-lBl*Ay7~pxNEylb-&=F_8lxI>?(9W9_mp4U z-J>59l8?+N@jM$`PcD^7QbD3&>%k#Zgn63A?3LG8+=EjS-zqhw7@3Iyz0AW{@0M%a zkr6A3d@YdSA_g^O*R_)w%t$fKhdDvtKUM~sNwpkA#y+=&Adwruhs}OHhE~cgkc0QnoX=8o6nbfeoYtl}fyixU*cvrDK9wh@=@WV3sZHJ;JRIysV)$r@45t>@EaYC+LYrR6D)Vv3?kyn$mVR@acE`3&S!y0YC${vf z@jv7jQ)-5etNS!<)Cn7qXlnskYZpTG0CTRmtFMz?B`LXD=5*in9r@mj>&1KZ%yrS? zIbP%+*jrI&3ssP8w72#uZO8K4X}O0D{q{hEu)~MW9N?pMDR#?ExzMN3vd6UNBU~hYh(0R74IDIIP@_xlwx7F|LckPOAvn^U);qa-*##v(d)Q$o& z?hX=KT_R>PWOJs9YA@>j_7xLte(_EE>HDD>098tJi@bLv?A4ppx~R#236ie<#i)j+ ze$&$HrEtz_H)lw%w-C42P$K#HS+V&2s>KQ41S$yVF_RZW#jX0RX<1-AaKIMYTY2N; zVTj_$8W~r1EaS}}C=y=0hZCpkwm2M%>o5J@AoSHN$-eXZg%2HkGT|Pu#NZ(xt#?GR zfpJM}v6f$fBkWsxQ?HLHZK~Ug2KbkJAH(e&m3v8ft9w?~_0KKUCv2x*dQM9h{U%54 zbBMU918DX8O*_$Z$jDE8-DX?fUI7#i#wN;}HPo#HYqQE*O+Ulu`fgvprpOJEUs@$p zIZx7H;KzQ%VYH{*np1IGBa$x)y2tL`i-c8t%Ho=0uy>Pgg{UBPV5qZzV>6m^3BPfN zZX)}*b1&uG&_oN|xAmmoHf?>E2fHzwBa^@FBmp!yQx6AOZM9&{!6xuwOSQ`d;(Num zEde$m$e_AZS<}x=+ZAglJE#80`~%Syqg#izGA|F0cI)zq{_D*W695O!XOFE|8EA157v{u_EcXWc7;n?Elfh2S+ba{myU8IC< zvTS84V8xFK1rVvcN>~~MY}&(o-1`V|gcFvRkId9GO0~d_r2cD7zxcZuC`r5;Vxv%V z|KwSUv{3JMbX-?*#6#cJR>zd0>BDk4?hmM)2DOaS}Z=U`p;1jVD zb7`6~w^2mct&h7%N@CC3rM1w)oYZ!AG8CI1`H7^iuWD^Qq-*zNYQ7;Qf7c1(Iu3W4 zUGsLsxA%APUFC_@ME;o|BVsvd=Duk7dayi?gKsFI7KI96^Qr^YDV3mOs%dUbP$%MO zr;a^iq*&ox@^{wIQ30c_3(G=hArC|NF5V;%SqLmm&n0&}XH$=|v2 z5jr~Z5)Ragm#`d#v2)jhDFcP)$b0;J#5pCLaY`XVUYA;)K1u{RPo;_)+kW{x`iTEO zagVz1VCLW4642wH5#vu)&TqsV2X4ohVI%A8&6J22}C?7F)<@$p|H-Z_(eu`743iVu7M2KikKV(6}6Yq)ID zS;oSC65{WJ7?{Xz{cbNZV^^;9FAF+zIHrO;0wn=&D0dxjA2_#hAR<-Qh}I|0@R+CJ zJAS@E!!+!g!EKNJ8bc=+U#d;fv|wR0@0yMh-a|{VnO2-?GfPq)ywl@e|GD)x|JXuS zl(abDYQ5(|?{Ain9{1^mI%UByiG$gC=YXCRDt~?)vVD3XcL-x4RgAh*QEKnX3dUDv z>_oA#*go&~fcp=7z#)nnw4_(h161j|=SwltQuOabDL;GU{Xa3TGt8)Vq9tUlcIWK} z)wCeZkXwQ_FR%lg_AQEhM>bZ*ar=x*y ztTyymF+^zB5Wc|*GToNH6~iI_8iPewT#u$2SAH4(3N(PCk>ow4@Q0g=t+uldLESYK za*<3CpUu4%wv8B`me9wFXda%8K4NF5Om)l*R zvH6Dg!x^PS&$yZm7cYW>XADr2}wK-6CK4=V)SJoMAUaG6MK-OV%e3 zRjQ9N)Q6Q!!$4NPo)M{a-6&>&e3O@r;8XZ}MCu-_lj|ExtK;QRj(@qKfhWe88sy~O zoGD#Oh?S2oJuV+x%I3s>`&FwkT^$>EZ;|z1F(Tj`Ikb$0@k!BaoUXL`O1mK&SjUXL zq~Tn7HyzyQH(!HiC;VTdvsgP^<0ES9zRlR_kChOEWTbf8D%useg&LfbtNK{Hd6S6= zz?Zia^luXak;3GhI9UK~cdzuApAn`0-TDD6@Rwm94Ue42%4cRUN#V18Ao8ED$oNHW zr!&!#l0lN#;cqo^bdyz9#a=JqG+>zx^YmXDWJ}G+tYa-wdj^hv(VM7 zB9{jyDYpM#hlk9&8xdoWkUTPJ<%0;Q=SL3py#SF0+b!%(r_fUQ8Y=Yo0Z&S9m_`35 zuzdNtnHhH15`k54^6uDFKhvRq92Mk?*rQ>|wjli(=Kq(gEF-gcT4YnZp)0scy}Q7Q zqD99L*_4$p17R(7%1czYFSpKJh0>#sXhXdFuepGKKH?I^y- z7kk?)OO=fM$zF&WlYcux%ZHqZwBzOijPuF@4gQ7HgKW-R4%eu=`SjSwL&2sYb2eqN z(5<3=?BZ@A>Sy2JqXk|5kLTi4p-u+2v|7xgXrb_75Zn4~2Y<;~g6F^H!N!rT6S!q^ z-J_a3aJ(3GQGr=}c*zgZgcvKiOe(c|V18=l{_>dPB27tw2ISXx#J2vGe$p!&|k1uEqc}R zpWIbkC--#s9t}k|0&2z#1V+Fp+mCuiXaUsX*Map0^oVZkz()ToZUp!u4L{J(mP}hD z3yzhSv=gDjKWlf!3JsGdYE`a%dA9sbjQ%Cc{lHWIn&Xk&J1fALabGHsIK5;)37(aA z;5Iqz>UKVK?W2iR?adTp6;!n~y?P?((=;sxpvAKPw^N zG(M`0ipkW);xB;K_d}r$|1!jK!(sm`FIKi90E0Tuc2&dqK}5VwT?#+c)ejCqdGy4w zylUNKV*{)wgbN|}M5iC6CNzcr@DNCvE1vyScc5-n6 zEIIBZ-uPD--DfL#Bi?FdF_7#=BXsR75^O;j5~UPd*&kI`O?C5|d4cwplSm?zlYnPT zNblQd^FzPMa(v~Nj+Hz zs-xL!>r(AjbaM%0e9guI&ZpMB7HI;bg@A`~P%60A9gl`-(QpuX7<)*GRpZlPVwUn0 zd<=pBLJu%$5;vznV^iLzET$J(d^VM{@yu1*;zWUofk}w=D&#f2W;y?EjnNtII9$6^ zA?773rdE|)IP21+d6aIJ&7B3AFeH|8ddgUMVO&uy1J<8rc-R5eQ?<81RZUjen*sOg z_Ps2}x~2;$qh&Izx-Sm~wRF!vq!-Jlkzu7)RtNSX(1h}3#N7nUmaaDIyX*;BZY@Nh zuMJ$04-8j-*fh}MRcm_&#NRP?aSWa6Ugsfg>XVkGI!q*~hQz?5&yEA#i`UP1u*KSx z&TjfI=SZV|g_X#VN^Kq-z}ub$H0|u-Rux6|XVt4_R#vy_RZ8SA*KFiY!PwhZ=Hsgn zoOwnSr#C#Zp1b&~=q@O{?*Cf0n=9tV>ZM*%6}}q+*Q;J*P`Kjryc&0In+&{0od!-R z+w&92Afb(hD?iSzy-!q76WI_%(>%tC=ZVnA)hlc`<3R!&(Jt5zr(p&UZ1Oggx(CCw+@9Z`3L4%TxEG=0#)v@o1`i{`l(A%_ zu+r#64S4_i%8rfLgaCd-otCY9b3u|1@T4g+x-^u?qRETM&RVGsn%)vcp!MOXNTRZ} zSKX|>+I4lUi3w1Nv0GrUe7x~yVp)#VY=d(FSnEe=v(SUFrW*sNw!dB<$}O;SDSYmJ zz?HYopB?Gcj@x8SOkLnSQLJx;(&?GZ^k?Be0(V#0@@L1a%j5)%cn}9Z4{Mbxm8VOk zb+)){UC{XQ^fUh3ee8yy?C^G0-RadjoZ6rSvvLHqJ(7XEoLf zTs-DsVeG;AGTVn}NL=bv!cL_brip=+gf>KMPIlhiQO>s z0?}iB&4d}L9}f~SZZ1rF=Bew;^5#9YEms(C^4qJL4#b6gn@u3J3_B&R_?3NJVLst1 z(eg~@thUna*9hn5P9zN%sJo&^xsY)zf!vL=36%?k4{7L-vAR&u`Ulw$NEG3FQ=0k( zOc2Z`Z3GJyYiS6AO_=QiLk1p&v`MP$W1^8>?}x@ll-0b$7^P2z^_v-N@r{TkpOwvS zwbfG@?&FLdS`8CXw>EC-|LU8#+$Smu#S>UzEejbw{%hou&dj7n0Pm3~4cF=64JH zsVaxT9Dv^7T8Z2oy%CS_$}=>BNzWHxbRTaEQbS;~+oOo%8K*%HZx?CiSNFlhnKmqV zbE9N2u|v;*xHU|OX|;rg>CV2L8(+Zq%$O7otuI^es{46`G{fXwdm?T^sBS$Dk1u&* zXO&Psa^vrA6!vWzNa9DPO1(B)wri(IeRkaXdb$z&caiZag_x&JX;wjgz}a$W*s$s) z6v||B8r3EIv#k(fbsc^Bc!zBpZ&j7s34lyedGrjt)wa-WRsaFf@edRYYc#_`e0Hbs zr_c#ydq?$=8W;Iqhs+Wr^OdfNJl(EBBuzjP*B7Hn&R|EZ(cw0?K*)~`Wbds++YwaE z_|QIKH*CU(9O%pfKI|*SesD8M5z~`Yj8dmGe_D6T4#L?E{pI35-G)8cJwz&#;cVQn z40+V2+Q%HzbtHy@cYa;GmhoXAQ%>xt%~?(M1;ZG!zX5=j_1)ML@+-$0)&b?z6*yZq zzV3X>?+VSP-e~-VDuI>jn*Y>uGG6B&$li2e=a_O=FH~*1`XdAug4=F(V<1mLJ+I8h zJM=z*lD=w$pTbOYE?)bTMwxHesSoDtB@{qX-W=MJK=`RMkp~&su2a~6leYlkvQPzW z7-)AZAl5bG)62MvE(P~f`+XAPeW35lhqAsatJX+X)j@nW44qZF2k-f#!cC>2-* zSSbpvxkwr%2W4wdgo#v7o7<^t1hGwX0hYxtB|zcqi(f37xy5)_C?*Z)Z<-;{FSghcqnVT5S+mQaxVbZF*ZyMR489fka?kas6e^cOtE_ zKK$=JkKQdGYV8|4A%x!T$7)(vU*;F$e4lX<9pm0tj_=q@C+J>XZ+A1^_fp&~@Euey zjWxq}Lb93|`p0&1Do!EW7YcTzf`k)!3oKGbj!moeDN8~WDl2xRHO{~^-P}X)gxXZ^ z+xNwl%y+b&4}yk?p{Pt!(=U8bkB)tFH0Q0JwZ4@AO5n#heqHuq!&MbyRG<1v9d=sT zBSEGKjG$gEIB-^37?b}fjCJ6ivP3Iheoqjx+NqKn z>#*WqJH)E1>2-uJiJWhRBu3Sll=cRgv3w$<>FlkEQ)bJsgk}2veYQP%iI^+aJ21r4 zt%{A)ot67x16}YcM)4$wtoi4Rl&PS66eJ0}oc5+U;pE_ug|WJ1LTCI|jg4!V*k0q2 z?U`b^LY2 z_&2C=0&kkbo66(Mc~kKyhK&B2pT`6n z=WE}=`q87dm-p2OmLkLF^m_eez#k!p@BO?+52UxEFAAyMPYOpIl zO5~67M#&~w$|FL|HYG`e@K;=TUI|JI6gF~kc=&aV^KELI55+1Pougr9AGDh4yUJ|J zodL3!K*CRxR-bf%Hktj*3E|XLMsWf7<;F4a&s2Jh%)XgdK`F#)e zZkvYG%Pk7?Y};pK_|pWkX@2JoB&zPDuC;dGB$Ef`=~aYGLcSiHInpr1jz8obIw67P zjT~c8c>D7n+{sz73G1_lf_7#REW&;AR8KcF7FW6DUZ@KRqlS;Bf*MhlJ+D}8E?dPK zN(?X$U?_;5ok@9bDI8L>lRW31|5|nSUdH}=hHtF8Ga#bi0van+@?;#EMas-<`o|51BJ~k1v_HG{`0~-r>F;KfW z`nJ2Sr1Qk<+9w6ZObvY9QVJP@??AwWQU_pg*XT~pp{+KqcS2~Cefj7_5QVH3e}Vrt zInmpDrPONY@X#>S)p-INKAD>wub{k;Pv`W!6S!&KUDa`5*tJ#Q<CO_@qd~<3Wl&$yxBbJY}vR=D-+wUOoR=rX60)CcGw_?*z4C%`h zj@heHOjr)=cOsM#Pqm=Cz^N)n-$w;CfrzIF?;`@7fCxj9+Gvfm)g5;3E$dSkgG%Z_ z&N2vj_0v#(s|UchPtPIdFgppyWTOi#kmvZjOa9`Y4ht|u3FBRunl74uLd!nhMTCuG zT6Q-su|8Hossp^=MC_5AK2E;TOVAyCT2kjb{=|DGTkhl9bh_8}FH3!8d=P}ImN-#w zjW3QZcs_8i)%4r<=k?ba1z+{UE#w=}A2G{gn+b2_2h3zQ@)ybDV?akcZ}#@I@W**| zN1I^N2HyuAhS}jml(8;b+6W?qW)N0013Wq>WPZ>jdgf^dl=`Y4`(v#RUp7cd*`V_F z$?ELI34|ZETZWTcyO%BHF4mQYp%%snx*rr<{T|23$)%S-8(K z@GxVq&!mdwL!5uCw3GBqmh0n>KOcS#TmMb2G@3>lJ_b7ClXkvFm&vN>`NLD(Zufc@ zkecTfGJCC5`!*(%ZWt$BcSa}etb(kkGNoJcVV>JzaKdJ`m-0jV!=8RD3TJXMT-{ci zR;8sBE1`5N)v0IdYhg!>LBb2S=Qs(4I|Z*^6?hfNhWSzQXYl;_bMnxw$?-0q5JIru ziQ!_1k0)N2^tqxkpqW!HT6{}Z3rN<8e0;h)J28j1Z4KFpvyclUr+S~lSC{E`k)Mhm zl#F+-kSdI}flyC_b(X6SH-|EtuUT&j5X9ia{hJ=g(qW`DY#DnJac_V89JaVZ<(}&U zTkdQsS+g&pz22O z>O|o|Pg&B_&^3Ko${3PPaGh37Do^l;cvEzs8>KWQp_ggTHLU5|Hh@)GZOO+Cw>$o| zAy+q<-*oMTj#$jku|6h!=~sZrHvVzd$_#s9L65|Eoz`MU>oG~EiUr$A7t1=2Y3Dje$UzbO`5R~9QkwS1&rxQDeF8|~it z7&AaksTeRSn}hl|%h_{Tc>&)HQ4Qt~Cwsa-q*6K4`V-*OI=0{JOM<*Z22E!tLhjSFoY z76?xh4mfNCS&vPr5(7Xv%i$5H4BXDu3H8Ef(T!stcjj4dOMb)lP#X_#8@@T7H~q zLEf#|k!MOx)G=NQb(aYwCs@v22DPi%d8f~|S4jZ*Il9fW-aNghX0}0|=2NKE>vMo$ z!b)Nu#E_MM?MsXRKiK}%eDsbIP^_4sv<)LG)p-uc`tUNgi|67LI7+Xzj-`9Y*~}Nn z)e%Uf=<;D=`Q#M~eOjSjxilxxwS-?ehh&~W2Fmb}NMp#7VIu6-8{n>=lAf^rPHqiC z3%q+QaL^zDfV1W-4;`P}!6hA(??QKFQ`xT~NERx|q>j8`B3bOxLk(HECLeY3}-z3*8!dOPcD zwaO?gi6<>}4_ zVgya|6XLl$!SavTxCG=P^9fc~RLXIoUiQtp0@W+pJPgn25&!E97eHVjP$;H&U|`)F z6CMy^6*vACg7N*}%r$p1(N-+BKQ1+_OMA;Zc(I{UkDTd?ViaTwemw1LsC%|mJdn3m z7Y7_}nH3h9%vvkyGZBxnr*WZapq8msdoBmAsX#sD9zv63Cu212fZpH(m<9#j4yc+% zMcgeT_}rA?%1AxIz}SBAox~gsZ7W1B=)gb{BiVjkA4i0!tw`%sF<0KYS5tCo( z&KPnm!~K;e{w3SiZOP}BUShji_@X2hQiEQJ-Hz;EfkyuIkfXtI#=ZUuvqc1_yOVnN z^OZ(x##o7WjZDs0@hTND<#9Qk3j3Ftj-==h1u@ys9Z2y$AjHd-Q`MKWa9NjKAdGcy zQhss1$pq0aGbFZuq@UOtw3L5*Uzc5JgBBq`gqrrbH5eW1Q^*{fFZ6U*`R&OgVNvQcMNdAn0_@>r)i^v5`B{in0=YFiK7NK19C zFmFD`1aY%+@dY$&`YMiArcP^|IU_hntO8luJ7;Ol>MqoS*QR+sA@4m$LaQplM+#nh}&a7Tojp*hPspG*8In6b{fu9Cm65$gQ< z)V*5FmQ;fyBZYS`cCDOnA13Sl8Y=lPPz?O?4*GS&x#+EVG5@up9h-_2Oa7JbYAR|X z+!4U|PB}3j8#JX8aukOIM~vNO)!=`RVI2D;MSmi)blqVe3Y`n zf`K&qV%A^2w(W{%4iRDHazR?b)%fC5xbrX3Z)fBnyI!?NHeU;8Fhj&JyY*Agmva+(kRcOB}Fy( zG;aBSzz&zIt8+$;Sf13mm&5Vb6(%;0zXe(jwJ04sv%nV+3BJ_^o07w? zT+0vVA{CHZ$Lqw_4BkG*G)-U8h0-pWLqPm(tS&3e#+r`RWNNrmSnDZ5>Hwk94e9>@ ziBz$;2W7Soo*ltk5x*Wj z3MJkeJwWiJOPIm?7eHO`vEIYwpeouPSZ_n?p}nwactPXj&O)KZJYDrN!+n^|wxq47 zShUXZP2(*jt@X#>l~=qwPvS>X*{swLDoj{vjBfLEH#6}dwlq5RJmPAV{i#Ibm=M}b z&0$i7A~|+bEW7pdlR29&=9_n5Z3B(dy-hS5@zp6H5PNC?n?(0A^N+fF>fDiC%)TdD zLE6`A<1y|Xp@8_hn$?rAh`TfGW3J(i^AiG*A%P{3=F#uaaHXI;;_>NFbP9Gzp>k`~ z+skV2m6Y3j2rW&s@4~ytc1-fc0>K$1wcvGrmRW2g{j&WFVr+z#681l z_ea07@`48jmsA6_nZle`E;rV>8}|9r+t18|Wu{d9xpya}o!DoTv31(jMXII7ax15Agjq5+UZ!hJ zVjWOeq~9K`e*74z0?C1E#=&1(4*as8hqMm@MV3abQ9_%KrP1}M;Qt|ZGQQj{n`{=i z$RDAx-K|d%+bT(`D*Gq}qtR*F6*|MqO7GO8RQnJ3bo&@~MeopiK3S-|O$47K;pH@% zI>^Q}tyOCZe$qG8MEI2NQoeoXyGzc{}BrApVLv(Qav z>IEH#9wt$q2~AiNB0<5R@FKOHt*rWw#=;4ey_lg-H;GPQ{~``R_JUBwpD=F z?$kxXf|jao44SR5sx0edRfccp($y1%>oU%`5Tom*37kuav9in29o+=(iY+_!nLt=l zrJny;){1F0^Rok|)*a)y+}zY~kd|dgF-n}}c0t|;!itztN>G>4&oue;D`Q>y(1TcC zn-1Y~{X^936hlASV@=NHpcQ{2+S}w{QcX!JN7Vg_b6{(3iXjrMg9bueQ121^E`zu< zi7D}*bb(agp{|LY4FlKSI=KuvH{CG%GZ|uI9r__w>!VqA<&R;C3Sv;Z0v;5V!1`3k?~132>AKN)=;Se4*X^=5WVcK87$5Q6 z2i0cjJUxw|u*@4TJc%vW(}hIrcoB`^`T1c;oA^{v1DzL+IqW^NGTN(}=l4$Pr`kQR z*I&`zS<~i<2!z|26pC8mp?&Cn7UOdx|I3_jwZics-A_XNZ*Q4sI*#akPH+>#-3*7t zp4slaJi$!{1=-ncLI&p$vJjaZJZ-N=7V8eoNH~}nW%rPEzHl*0OHi^q_eScj#FGO8 z=;Jc;F~~*rHgx=%!yB1LTTSEd@$JC$D`|HMgDSj*cj3)J{_kjb1S@oe#xdyvSe5`z zTQ>)6`@orn%RCd?>g zyHK@f8LklD!o7UkVQbT0Z1LtsC&b z>n~16-~M(*2^e^IX1wR3v)*%9jWnxQBg$}}hN7!6O>&e+*d4Kc;YiQ=4{tGxQ^clj zht_>=;m~%AOTBsn)7JJ}Hghhzt-00Pkx5BAB@0LtDpj0UQ|UZJR+N<9?JxM#ml{xo z-AF3kj#R2mWk3Hxa_VAKx(0N%xX_4PqAZ_)3?l?dqDW58l=SW$;s_g=>}wb@A^##T z%2G7kzhvKM`RmRI8^jYz<(xNbxWowL>DIwAZ>yuEo;8~8C|y?qb)A2nHe7cA;;{?#&9Aq@Ht<5gN$y4 z{Vw(m#>hSJiBc&vsBWcqs1p9F)7N?1Y^bMqXeTYFzCiOy%)`jFC2bA$*m6x51LC38 z6s{9z)V_J$B}m-cd1+~ODf+R0w60yhpj8#v(c;=Um^8oM6`~kjv!JF{sY^X??k$?! zm(HQ@EO{uYfR&8mi+!NDTm@y~&+#KW&=s^oH!@Viohs`FU1|-Q@%?Tekpmv9WB$gh z#1Ck%)$9ee3YW2A{Po()xbX?)^IL=ZGQG;H?A!jADAF+G_Z;MGQU=lX@XO1Tc!@giEArBR*q<9e0G@l#6gA7T6f4$GpMk3vQjLG-PL_7|46TNU}?1xDt z_@kWj$I*l`g+e#igL&Rex89)h5Eck&!AyzlU4Tme*kLgX>@%R4KLA=E5*%@nytokC(R(YzC zVL<=zVe0zafG%JWJCp>u`7dQyzb{rX>>H}54-fmZ?>1zrx(;*);uXbh#&_D0^=ui_lz1My!CF!edS z`R(Xn+CH{Q;j=SWuc;#<`j%6}fMJtvP~D=R3p$+yvl5GXq=GyTfgZeigK?MUZ$>3R zfk%x*O%x%ayfcB_Bpknw^){J#=sVPNLyCwYuLap4=Ee?lf3^p3jf+H*Jx@*F2`FS{ZM?9R5=#;rO0itHC$? zVpRBdVY`s-q?z*9l-glQ6gv=JWY~28@{>SQ-k~_hgndY9biH?Z%u|LaTSc91H|~;P zBbj-=w5BITL%T(O0|tGi6Vy4L%+T2P{kWjiRX5*+LlH+*ZxQotz|PWB&$wJoCG(H? zhD8;x2_kKdAX&%Sq~4qhp0VS}H$oE!yD)i~A|T@l;L;c1xuvfrayOmj#?(UeN08i$-ZtH!aI(TvRvQUv^MW7_G2e1wlG-kN5aYPV{1gKW}kScMcsg3 z8oQC}9tkdAX5Kn4@J=jJEqrZ)hA7h9wi_exo!mG&^4cUziYi!d`gDJNKjB4Z6=vvp zv;TtjJGcI%np=+cL2?J9E<}gF(rG?_QE;p_1voMsw$gJ)ZTGv0r(7WtjFdwiulO3`>b?G~C&8gW0&_9&<#OfXleO4+HQJEn zOYfo|2>ypHO%+)&Oc6L_1E8=Y@4a0bC|+$ftg}0u#VfFI@q09U3zwUmB~LC^*n!-e zZgL|9ZHMMmb&Ef7JqEspiH3;^isQO&=0@H3<*t?#RprZmJ>x;X@g>e({2LQv2wqXN z&ReiaOSUpZF=_`cHuFfa{<{9fLJ^lit)Oc9lVX>3bE7uRRCxnNv)LEfR#GKvuS1T=9?yNG==VZ5NX;~aKqbI z!Y#0ytnqv1y0h3_y<0$K^%Ohpv7L`JmB}5gig&-n1E@Hes`&E6QWdDG*9AX|7jgz= zqqGRiJ)a=;NVV_bg>1eURr6G_m%i2>D}~4a3@3p^92&eiQ+Z!LH@|Tr9N>A$tgSwiToLn*frl8^+;4yP^&JUqr^7o-Z`CI z+TU_JRk3j;*Fp^f82@#VJjSo5r#PI@C_rp^il|!fdvJRI#J~HPwa8gghMz3-2GA+) z&+psYL9ZdMcmolQLUB&>x{fe??%9-t8<=3>iZT6=`!ZMQ(Q!Coiroica(afa0qe`t z83_=&kPFKUo3{1Mej$hY;3Z~rX0wY2s-ANp5k3qj1U$UuNZW$S zXPlu8V(j^C(C1TRa*xm{>k>!h%4`wqyO$@N_BTydK9)zpBy{Hf))51%EJyW1 zoU&{@MJ9%{m(#pH%6e94^_Hlry$7-$SFC}+REX-rz2-hz_4 zQg=Fc8*Mlp5(U&k9h^O7oEI+_s?Eq2#4gra*`>HC@AG2>$ss)_ z+rG+`6Q~ug$Vj1`8VuI#x@zRF)7=0+7N6O(_*v*-hS4!@rfw;_jDQ9W(}vf@ytcBD zZFD)UKvwPY-n}J0z%+2lBJcr*f2zlhOX6|VwXa&ZchXK!#U*rra)VZo$V6TEFumW9 z9Cj5C_InH?=mNwGef=MJ390hze(o7skXXRsM}Ot?NM@_rVfgK%uHnPnHd0lSCbkD; zlI6z0-NgL-?S|=*_vpmvnOYL};#e1BK1@4oO~<279n?<$p^nCAQE=G$;18Fc8S%|< z%k`skjDs#p*9lxF7ByU(6KGm2*X#oy5*mfE`R!EH8|DMf{pYt<}zLogFrc#hZU-vAr_i2>wTP? zZ&_&BRmx^UrB-DsQtVm-%2Zo1q854B``(nTK?Zcx<4;76yI#{}wpZ*(NGjux z0zdco^@U6P+f}+mwQ0Nh%}xPBw+DEx)FOoLgexCon+V2YZxRd( zl7h^%ck_#UpZPPfK1IaZRdjpq{hefC8xodY;*2jM#f0-)ca4ZKy)L6j1yZSuo^Ebc z@%sq{C^vhs*BcQfmTMUJt&be9?YW0*s5^^qLfod-w;1>j`+3fL1}oG?+0Ly{&i?%y ztFRc3C5p?sum9?+8L&N?fi6yjlak|d)VQIimlJhVwVJ6v`>g_Rh_f1VPE=(Uw)0Ey zs(Z(y>|D}uBw z&nw;$o1Hj2!NYyHa(?7l(v|ECc351tv^MXJlCs`k+g1Tn;pTEr&UKfSO@tihc&~%G zXV_tIJ;;95*9YB{AEM*Tuzph}=$(Kad*mnHnG!fEwI0ue ztc_&t8ep(XF7Mi zc1JYc%(@ilo02!v3Qpq&EED!&tGDH!Pu;P5e^p6G@e7@PriT`p<7nXGFt!G?66lsj z>i8&%n4^6E`m2tvL5|Q-4Qgetzu!{n<jX$DbRnTRabW+l!it~=LyQT zXsg%o2UhbRBvzV=G;%KnaQIpY;xAj6^rcSEm(04@Iq#Lnep<=iKkm!j*?0%%lY?TG z301~WW|psU26v&rSF+u;oz45whJ7a%Nb2scPRzTk_zoo$=;#ogjt?S`72v z>!-+Q5iQo8-FYGI?o~y0@nbr6h}S{R3t>#J@8;GiwW`)7<84PDggQB3y#Uec^D52l z&g<64Ir9EKbZYOlluLOf?7o)MyHB!{o2o9gRaQ6@ney*Zdxq;YX0}WAbBaYgPM1hS z=Nd{k)ko>vEj!Voq67$3yj-uCcRA>dTC`m>A*I z^!aNl^5URHBxX*x35YoOnVvd2rbJtJ7CQf7t)8Omyho1f;Eue=PgkD%&i}f8BT#>| z>tsL+N?E~LvqD)r@CdqUSN1I7{L-}ux!yZNpDFkxq^$bojB!yM+sCXQu2Xp)^Jg5m3+Ti)rQ{{l&Zw5`dHs5&QM{l;ozkY3_m*e)g&Vyb9e8N zOghiEgYZvSVg3yU&U<}01lZEhPBQWa|MGI+lR>N0v3-|lDGd|&_y5!0oBu=INB_f2 zgc7+FvPb1AdoIfuhElXBl|tDgOP0yL&m^VAUP4)>C{tMn*%`ZGm`Tdc*aw4Q24l>a zx!+yi?|psm&;1wN_v67&^YEC*d*1K!e!b4?d7kH-=Lx{zexpAJx|#@^06i`V>Fov+ zYTZF1ALY_sXD{Ov3-X93nZF?9$+<%v>^@IyV5?d;Dpzsn)}l{J*mx!N`z1a~t}6;k z+M*P?){zLoH>qP34%;9tQPeJD<#g=hOW*E3B)wMX)UguJ@2h#CI;AdAY}k^Du0u*eCHiE zzPJpEulLW7)EbHMmW_U;CKy7>V!T^_7J$0q3mdxF6(sLgce#5hw-$dHph5lJUlX#l z?x-y|?KoI1VH?cCkvPcqwnJHMgcAq{v}-$oD|dQ-fhpIdfW`9e{SGRPH+V}L&&yTb zKg3x@cxQ^sG-sXvc`?8DR%IBB+!^PlKm1yiv_Yu}?>uPaHuw)pcMjffpZdlnv!ARO zchk_@z@B5lyv68&<-! zTmZD{w8&dqqj+y89J~)`Eb_JbP>?veh`!HG>zS<{E|8o+^z?1Ybt=5Ax$^*ni?!kL zC4PUm>4$U2i+pC)>>JqP%&4g=mn<4l$Ez=K{j6eiA}TdT|Hd6iWPnkIa7@=;s62 zV~B^^!0IP?k54fdZ~Z=v+Hf@`Fete>I%=S;B1tG13^;qlBZhg8&q|d!2cN#e zDM}O*hWtH1!(NX&|F~)OpsS8G{CW`j1#G`)K=?%BlbSk5lO*HEO<9kV@L&PNU-Ccd zn=bVPjGM`LlabvZ??P8Y`Q2V$4;@P6g zJ~xV=8|pZ6G&4YZ5SngiUGjH9z@ZmxWGz8ENFQ`nJ(*%zqZ;5=5Cj-vvYyLWoQkiJ z2HMxsTnY-bq96R79QAZ;@vK=q(_Y7PiSs!{f#`3x)+yu4YH&N$sS4AvaoWA6Lrn@;t5^;w!6JJh6A=jq?0SuhqKQB0Vef z*|l*a`byEhz;o4Zh6ICnc1|8PY(`6{N++dG!PCjPG~zVqaX?}XcKl8z08w0(9+~MT z41>os271lx_g}mx*LQ_kq{%zGbV+74q{~gl@jJ~&o*8A2H*>P#HJv_0ZW%i8=vzz` z&Ct5DB^MH|C1-Ve@R}1Bux6Ddjrr3rJT@K$4I92>BUB^@IFNYhU1Z|9w^lIUTN^t? zqxvzjU0%+$JL1dU6mJ|XjMpvnQpl(R zmxb@xceo@v6p&{(Mt?H+m(88PW0}i6O?1G5`$1t4EwROxnf2e4GO&K9Okv7E)k8Nk z74hiIRe7QzDVp--A}zv8i`q|mO{gO0j$2nqZhkc=FFwY#6w+n2BA+uzd8jWJ-6$~Y zb%uzm3pFGK=CDFnIgrc5(CimYO#mSvXl_}37{rryPabkmW`pAbo7sXr^gmJ^CskGN zJbrgES2#y(cop|K@aCYjYR}^wZLTz2xw&EbJ$a%9O2O6#w{Z*EH6tqj<1;WbBz*^e z?cWY3$-@$4r_A;qy~UL>4dmZ9_H}&wk=^t@!}M1sv{LX%xR%Jx%q98Eg1UhqHxSMO za8HcyHIkw>l_2n~g+{kw9n35B6yq%n{l!-a`_u#eaZ$YI*&J8I;x{G)@wt9ci1FvnU7IhF)ous1hca5uDWANLd2#Y(xLzp<-4 zj2`fD?B2b?So7Jcx@o}9+Q$s|bEJjPFP5@80I4OnJFP*REszJ$D;Uk*m%u z4Y%I|7a2PKe%>SxBt8WF$<;a_93C%j0nsLXMqq&YBe=4YTmy7RG ziy*Oc?b%b;^QUMrJZ0BfW^pIHTa%MrPwj7Mq_`d1*P&B+PG@K`iuk~`tf=9)YA@5F z@0`x-Z*{+;@I|A9%THau1`@uaRvYv;jlLHyv4a(!b0{z5q6*hFq?8e)NnUE5n{7qd zU}rZ!*Fe1Ly~B!Fs}8Fg2Fz;E44y+HF02y9g05vJ8FN76oVamBwmmmD=-_R6y0RQ) zYJUAkwO<^NTVO3!HRS^lsM0$sMLK3eY9kGAmwXS4Po71NXZ$pY3cB z!E#L|u8i|>c`0z|DV#4h;QTFj7h__jqE-+-q#qO!KmT4jX!0hU_Qlo`1bUqR9@qYt zyoOiw0&;e#1MFH)cJT>J9*A%6bs1DzU5LC}qByWYBXrqAOz(Wp>wsYU&G&Qm4XCL- zFIjHft!f*H0nS?CG9h+21XN=@-XRNmET}6N=&_$`oWtyUKz8M0VYk%?ssVM-)iIG{ z3=dlHwWUa&E#)7(m?37v5yT%A2Zkti^j(%wICEf?qTvtU5GgPX+4h*IkP+4GjFPVM z9UaGEW1vg=@uZ%&+j3#Zlj!r|bn7bT(PD`{QIps@NXYJ+xK?iBYRaCAHo!Q!vT=_b|WQv2xl z49Z%iXwH3TLtd^b*zg0q2NF5y!Bmoyc@rC<=Q~ybIp46luR-?PQmWArY$2%wsFK+U zTi#9Ept{!MItUsoZOkoNSfgqqE`e*I?0dG?abWF=`I8GE(a%mDj(SFps155undOBB zycsWfi+1|8{+y)1H!eHVCtR(XvtMnCzuItqV?Ii4HqR z2EVX)Z?X=X_+`Ph_JygUN>7+t&wCG^3t_w;qOA3a1aA1bei-c3KO6PyH&|c;&>~%^ zkc-3M&=+1L$WR1+| zWiz$Y!mA%wL}$N2P6bKCQnNJ?;@|bcU+7-o`qAmD22LPr_JLPjq*@rFyM4-}BQ-8A z+Z?~=L2_KK+mBo-3N+|uD^u1y^Oj9M{gF%IKi zZmV=_^7iJgxzjm;YS_ISy+vg_VwJ`dc4LQIV|Ck?rtIZvZiTC*rCHoHUBr4GBIxBK z!FhvY=mPI?PmyZqghfP-nA&Lq#5YV1rC!xJoiy0#9;To7a3}O$mduD?`@OpZ-Z#zj z78ToY^6saIFTtbkE)`Ki;*^=~#pf!@bg4d1F6A_}GX#W(QGb|kqK2;c!qNAe>&3zN zXF#2R&5NR9JdfCyKX)XlD+&HpN(R{RESL0zU%3zdEhE!5(8`hA^t1CHoyDpZ4m~HC z$IMy0+&JQQ&o}VdOW^wFm02&2-JKED z1UbEj<*B6E8aaeV7+Kbw#YSC^2Mvi(I5+h>VrYGI$6ME5eSH`uZQbFKGd2)VONoIN z_kjiCUq9w@O1)=k&wp6&18|Stvg&g`5ABY}!rURPDNCSYy`(?u&fq~2`O+V}r(Aj#Ltu%$tr2>%V#;$yj4z*Kz$J!R zaJ)qg#5bBRps9|cn_ATORGg?U!X(fiQNiinWZsYt_XTZ>dpB1b%8ZzlF+I&9JR%kz z;!QynY$cCLmK)`IV!rxIup6vZ)#yndSM23NI>sn6V35;(CH!~0vet}q#&VsaNp|;g zj#_e4Yt5#x#+2&qs>Nr*6nA|KNw_*lhQ=|Q8vi6z#2?7%>TzA5|EvyZ zxZmnpb29(&hTrUW_PnTdTR*i=f;Tyi(=h!B?QbJ&X!cbz zC_%TK=_DEEfcsUP-E>2MpDXqo*iMieL{rrWqGebdX6jj~+un9yY@?46H@5tWm=O11 z9ytkGrXxFpZK@SX8(BQWO>~rDv3q#RGPOipiiz8WZ%~yTW8-tXB9e`n{&aK>D?2ZU8nj=ZMcDUnJEk>2%#=XGQ}Evwg@3D4 z)-O9-{e!eZ2LHx~?ie*x5;C*u?i04hG(V6POV+@e)9$B6$AY2~z<(|JX`*(Y@}Jq! zD}M2th#F1}sjJWG4jKr8M95qY8gq`rkrjBbe|QW3jTUvohhbsO>$|))mqWr?wg%aK z2C9;EZlG3RJe|Zqnx8!ne@|vpnS@tphxYmrG>Y#M_`oDRr}FI5;6%cV6V}7rd0SZ1 zy`p3WqT7CW4T7rTi*y{+yF%&()74*rIk3q2ZxaP?M=|z?<3iOk=f|&J=N=Nf>)7bH zqn8uHD)SFjDb+2EYO~kmnf#3Y7Ii&gX$R{x`z@hndvv2EtDywuIeRp{YeCxI8om-=`WWX;gs> zBj*S{y!Dbmfb4cAS+_2|Q{fhAvqH+)fkH6roFusjc{)a<5`L-R-0f26OR&)SZvPVW zJ7NW4K8v{nK{-GjPEl``x=iqv(KyQJfmYj`s@lOYj^)wa>7x)_z69Rr4b&xZyNeJZ z*NH|88T)d;@yZ?UN^9u#4UV|%2w@FSH@V%hSuRMWjw;a;I7@GL6K1>VmTX(Om{nGKCO?XGEi#`XXDP8`;QtSy!>3$|_a`X`q6?G;?(EK16_a)Z4trQR z8CYp?SPy0z>=8l{QT-N0XN2pm@gl1$XWas)1(o3|e(P-osJ9&2{eW$br(ZuZPfwbr zlx$I0)ye;k_~NT9Y`5k&n!;dZd)1a?6|^JRuh)+msrHmk=Gr`{FPY&ad=)KF)F69} z)<*q~IMWkhAAjNAQN`CgdS_FICCFMmZU>AVU{G!L{io4U>R)!6zQ^H4k(hjG$$|x8 zW(ecgrBqD(k7-YIvNO`m0Y_4oL2d=+Kudnt2*ke!j-6lkZbtzUL21Up@|Z{lzFF)- z|IR_$jfIQ|RW$@V>rM>nUF$FKkoBDaW|dO=p&7Z3ehxIL-(|_vmwTu)c@9b*pR1u= zcH3!O99G65IKy{L`WS>AWYq2j279NAAK{jtLgodI%c!6u7PZ9J&40i0F{yw>#)c_% zh6_c-hLPIO7anWFZ(%*T7;rsIX9_SLmEFdtavB!Vyfn0>DTb5&X{-wwd$TUwD>V}k z<=?YQVPeM({B_gc!E>06z9c1$0|3sx>2Wa#5V7h24L8^5>%ZnG#maAmu`-{lDwjkp zy$T)CXG%>m8rfNN#O?Uop=6qMx=HwuawKIZYL*F!UAnw9F*vYOQXZ3R!u|t*=Nd5e zl`N~}pb0|xRMgc*&J&rwn)eKntLJTnL9XiKm6Bv~sC{Hd(#wl8vCdP3ti(sR1a}cG z1m_8`$bxi_aZV&`{(P_;^Bh94W(#g)sGc8Qf3i{0y5q$w=Y!n-LkPG(dwaM5SAZb>!c1`YPnqM|m{z#}~y-FkTavGJHl)LM&7gZhunT2!%$dc^Xpb0n`J z$&<+U6UwobY=WbWy=?Hdpj-pq?}1t(@1%eG!W>_}SH_H&NnKIf=Uj%PuXIy@ll1UIhMH~2iZ#sqYSQl0bT&0RWh%{ zE?p3;Kdj(9^m7m07K&zl&meOa(~DJ0I9+dJbdXhV@YH8SdV@^Nb(S|N&K<>!&!Hy} zrnX?=1K*nar=aH_M^ZVDO~ld>J0V`(imT(WjC2zWY(g3mChfXE1;GJM8=Sk^V#tZX z2^g8wF_0t|+BZU77+U&HpwL($CkTkhicKaw3@B|A*cE0X{ddk6Vgl|Iayv6Ir$vlJ z7p#oAeiT)ei^ywS0CM~9ET9|)OQyl`_p3|X{^;-47tB;(znBa1%L&PreM3ytCrP}^&V!&AVadP6Y8~1@NAhU%NwK!xIxZ!*{FqSDnxTYdE2(9 z4`=0g3i3p$Z7Z9y`&k>PcszBE*qQEfoFJVB74Q6ciHCC%l^RcOk7NM5flro%%F#1L7#q_Cy$Ec3@r%RD=XPg-^4 zEg*3suK}E*U$H2WbOXTzJ zKDmi;VYt>uvn+~Ko??bYf!St8!4IWT4M+wfKyBx(5$VFH-OBE{z8nkvhj$t8GD=t# zdFeuP{p^TJ*#1C9#kP}G0YNFjx=plGZcP8?^;!g`3d+%pbtWQubE(fA$M5;R3@*8I&AB(v9$=v_Vse^`X!!^!)zH zj%zcng)NO91GW!(XP?j(J$&g~BcW%q;?jy_U*orSGSpd(lvRmEMlM4`1!db4{DL{L zl9hMjtoc2uk`T-giMfzL+x=oQq%KZ}u-Dp6`imnRX>54ZZ<)GozUf6@_$iEHDpZ)+vK>=R#?3BL05uC-6Rz4`P$WSvCM z#Z1@6bPJZ&0$2@7EYb5UtXByRwHc*7DPor4v>&jiAft*Ovf|!T$b#@BuINR8&&$vI z1b|IN)nu7`3kL%Sw#k@Y_F>(*)<>+{2GN|^uIGSYL0v<=lCLBz?qc!0GC#J4T|;fM zOmwhf%Zo)vgc}@_&7Vo7Ri%^fcgEg~+Cf^dh2!u$d@k6v29lF=Wu%dN@*A1LZBBkt zNLG1NY162k!AVE4#qO_Yr!g;Nz5n6ljYDtZ9yTiF4@FX6uTxiHFm+;r&-$rc@pOxq z<>PK=&xaE{k_;^n>1~dY3nFp2tJ7aOC8lPN)FvzgJU7?FhPo76-vq#h?n0&VYT%0^ zty%}1x>Z9g!mcfU^P|3WiP>T=Je!HRc@V7a;#sdAaJ$L81;fH)la)f+u_>BKh^C*^ z*P`j^;-;mk#FMSzk*8aV%ZtZ7&kUC8B$MxHfLepntDJ`Jq;mb~@RTa4tE2#RFcA@q zI-3=^O=5jYM5~rnhwL;CXH+zL6DlS1Y_sN(mEBrdwR6^GzNws`Zq!A{41)Uo`YXdi zN5=Ni7}RSK8!5a}IAePHu zB1$bdC^g;ZuN(u(u3%jUy3+F#vw#qc5c!~NZGBeQI6fn>qC88~>!_U^h1sil-}$V5 z==x2W$boX(bR#O3h`SDe4&>Aww0Wl{x`}9E@aS%sgNn{}Pw2FPiee}=9F;iMJ0%5Z zI^sNX+CS3p{PAV%RvY?1SdIC&Nm2j>l7aST_lm}doOU;TzWwS^SBeKX5>N@kjG{&& zehjRKpx{O^g(fpWiAh|Go@iUM>!dJqL55rkzruCH8Dg*iVzQ zGR{z9{jqMnE&kcHO29Ft#)}61BwmI20V@ z;@uUhK?a*Q3>YHtk4X`IU)kwHswc#4rRQD3A{~uR=Zuh&?CY%4r1BC<#;ObKWXZS3 zDo&3WwAO-sOxr!!k0 z(v{hfC*;?f(P~nz{w}Ed z8v94c#n!y;uLJlilc|)1bgklHFv$n$+>#Cy69kviX#d2UH4?QpaB_Kpg-O{4(482@ zc$Z7qCq+v9k2R9Z#O}?$bG`tTpj4ub0GObKr0JBn(8d+pQNDf|Q#5?(!Uyf>K32YG z(NQ~fZjj0G5_- zH>8&g2F)60eQfVLHxJ#J#`K>Q_isLDpb* z{SQsVEbS3;wR9 z9#aodAqfaM<#1%cE6Tg6SL&b6RL~Yua060%JW9&U-yhRnACaDk;Tow=$+&bjbEY!urh! zN33x+Qw?&uuTspM>UP-|qc z7*z|tlKo~bb;#jIUqVxz;t$-jL;di^B-X&tQN<7J6O}g`w1X}DHk3+_i|Mdt`lpFM z`L~4J`bO9fCwq8IEkDGRO!qytR0i>wTFXP^9BULR=kHZT1uBC-X#t(4uG{57PoETg&Ob`kIQj--gZfb^f(deIvJ;o<`OUA}Pcw!eu=^?Zh7bewOd zwD*IN2OrW8f*iz1yR;0`*#!&Wb)t*xD&Pv0mCF>#tan_|ascPRs(wj44kSvQzAPb< z!mz|w7q_Ix1z2HY8pQ``V}qmUM?7jZQPzwYb)RF)xBA-OwDd^$zIw(L&=&2&2J+u= zx`+T7vn1u%&7on4Ap+^WuvX*Wh-j|pXrQh` zDjHCqWFE$%2(~VFc8NwoS8KreJbLqvlaBXu79#L*HMs|!a2Up*H%YLnBh9@8kT3M7 z&5y^}E7#Uir59-*LkR+mc?*LoJA=)#^|0)pejf@5_BYteUZdSruveN3iFh708>3_W z3zcHo@pvqdb&KbT)Nyb7tN|ofF3Tg&JO0&r3BkE)dsXfFe1=i<7*!o65sFuTuG=jzZ1rcGxa)c@1D)$$yfwjw_nAQMxqKhMH2HhdmH;9AuJlFvwQ}#Tce78=6qVloHN1v1s~5dj(x~k{8cnpke(!5S-3oN zt7H05i0`t_`c{BBZ^l*5`hpk9S4g*0@?ca5NDJHW&yz7Co0aqZ;(S?-19=s6?8~l0 z{QEi$kYRuIs_zVjU+@hOS7+R7{SF}BR1o;Yt{kSkq25Hv89z#z4?RQYLY_U zi9fo{jfeR6t6LHZJKqT2r-`^JqF>CajRXih21Wh_*AVBIdJ;?!S(-r)kmN3q)^=

vs!RvX|#I-YEB8U+i?b_QdGb3U_(=Qig3bCVo+tVu+~2bH>WTwLvrm zAfOYsQor)J>5G7?(sA+qJJAD9pan&|-H$aKtb_Vs<*B zbt6@y>U3TBj4NZD|CGNysB@VI-{6_cO6R{7%aX45w0ErJ6`X;dhO z2;EUyb6T3E4DkBjtajwI%~Kf?3iS{9L&CAn(~e2b(VZ)ncwsc2=coO?PoL47msXp(#J=2ojw;DK~K&->5bc#UHy% zDX9$ek$dq~fTNwyl;A5h`YgxUwZqchBFAnOV_fJzO6F3se+hE4t)FsXXcrZs@Oo_& z9RhKw?2+rZ^s4Fs$%ySN-60~^U-0r4j~E~y?lko${Ve||8Dx39DRUa|y}KHzf#DxW z;|6yfcKWV87*7y285hWM@JW52XdIl&nW~qW8#HSCY8TO2{PFIQRpY^&?ADN+h6dKf z9?#%kylSHwcOBMWUYnyzaBEanIKXYLxN}4R=-!SF{fASsoiP;s=;vfGwk_TBKId_k}c5d^=E(3BMc2E{Ruq zrYyO1Vi{w~{&eb*3dg|Ag2@j%SAW{>)l8Be|@mw|(=NV-~xP zn_pE=LSj`*XvxQo6RpA=y?wS`Wf2>@#G)Ci*uQ7(i4zVEn~S{Qk72u7z^n-7+FU?*qB8=mOvz9I;OEL*zj#u*p~Vfu(~?^{kPPyuq{V(CzrIig z6Gr)4d|Y4C=CF5$H3R8mPkTwy)}%9zq7$ZIP|u5um2J&en+C}iLL7E~iH~afg#Pdb zHgVr?O-#|*wJv=(TMP^BG8cj@%N>JlzW8*P?;FdItzy!EiA#~?9IiX*JtC`?vqLn& zBL4EQ1}Gul(HegsZ+pXOY5&>hMwlHf+isNlG4+y&k#7GMkwX@*vtw!V8H!Qe3`jSz z{o4=S&S{vh7%F<;QOdG(q>flHi@08~@nmS+*>5Asw4paZr1hoEydtV13%+?FtX0c8 z4JE6+Q+2cEJe%0@SndS+iouPv9%Uri%2Kb3 zvhH&3Kln8uxehVQWM)a7+J6wiUdrq2_R6Wwl@7vsL=^)vF0@9YM*zBUH(#mV!grM^ zNY=g0mY$zheBGaRcDZ^AZ$B4=)@gd~65T|6l_F;pF;>>RkYA(MS=!K{3uJyOLRRCG zTX&<4Vk$bteCfx}74tmHgU4-s`>_Q#anIhG7@L`ULG_PHjUtd`X(M!)kwMTBdo zku^6WL{Qie;wxFMBNqFx%1}6Pu10?3F3^j-&N1zY#uyG%*@$H~KdaAnDGoO}md8yM zSQl3ty$hf5i+~Q&?SF5P^oe|sTnA**4T`-qq4ls%KADZ{6cNFSFO`zX0h1rcOsxHzm*GNp@kG`iLekG+FGcdwVW)U*{=x`Q?GtNu$=swId0Wb+hAYb&+H`Jy#rSkdq}2Y-AU@KrhMwk zg55Q`=Taoxce+U1ra3BPW3BuRlGI)5efgh<#gj;k?_ymwt}<(-=4Yt;Q}P>4U>d|w z?l~+Spr+rV+B}eEaY5fBU{s6GHqr;BaR8*^sd?){<$TiN}HX0?U3 z8{|1w9mPp|NhP4$M?NZxz2)nC^T|vWU6FM-J9dWBP%SMJ-6T>VCTzNFzM@lFGB`F! znPaW4Xbx&Ym|<^1?=1VUcZX;@e10*ZVS$JJ!_TwB)L3(;*BW_7Sq*~8X+ow-Ra-@) z`5_KLSA<)T3r_Bb*TyqX;X-mvwu}gOZwn>0gXI1qBV0Q*5Yke}b(S3w0v+^nIWZAv zlGhM)cD5^Qj4|vWhg^wVis%zI8nte7hg6ZqWTS0@x9BR<3k{`J#AdeszMokp$j$+Y z@gS@CQoY@!?3fggSzB&Nr&~m4$hIQiiVXLUk&AQCzKH^bjqF8wXpW2fi9_I5%asd5 z#KU>3#UrJtex#yu$t!1eRXes>@I+)fDh&WY4ZtP^BC$2cx}zBrQjbMZivm5xw;M6} zEsrCg)i*O80;LDqf4wp1^BUiF}NxY;G7l-F>Y* zB%&md-jWm2o#Pc>val_i5!W#DYg5BaVoI~zwj3~} zfOW%|YqIE1q2X~ctW;O~u-_0lSOc=6+sRsy(tTUzWnFk#WYDO3GmbMHaBw^Cz_sxU zZhR+_q^a2${9rG#mo_mojSvVtU0Om2^2Jm_=RxCTVCdlbIlR3%;FQCB_;$QWoe^k4 zaQZd9ynt)GpmknW2}0e>WlTbbPNsZAB#|5?S-smx79f}s%2T;cj`YdiJ>n09;6S-& zQ{d-RMSz1z2C|Hf6J|oDfuwOH@M7nA_an)L?HKqc&ZcW*c#GW<& z|6~n)F`Ke4Gol@fRJp;6hFMk2_xg$exAUjN=5^E0G_`^6|Bg(Fe#fukSol{BI}hnPSfrd${6XtQEZnvHr!zf3WSIDfUdU7lZuS z347t#e>-8%6nm!FqZR&TVD@6^|6uc;DfUdUM=Sg}8TMl7|8~NjDfUdUM=R{n3VTVc zy#VsRl;xf&_Dr#tm;PV5`n{;>KY+AnivO>fqUmSZrTsVDZ$0^+btV6tIlRjre?Xu9 z92dM&`>F3twfFx`i2ZNJ_CcXE;^s@vXwV#o*Q-~Pd;8XaB^U5_`Qr5p#pj)#{9kyB BvcdoW literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..f3a099d6b91d51a61793774189d0e03d56ab7a4c GIT binary patch literal 1516 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..656a41e3d51e2119a253269927ff4d788aea0ee0 GIT binary patch literal 2607 zcmV+~3efe5P)Hg1+lHrgWSWcKdPn90sKGrRqvPeo9CG3uKX#J{(IASm?@+di}}l?o-=)F3E6 zwD^Ni=!>T7nL9I?X}YoAW$t|Qo$sD|?zw001?ah|SeB6#0T!CBEf+H4bBB+JJu8re zhoBb*p;u8ID_yBf0ya+zcePvJL&AGs+11_tpRKn>9TgyPA7ZoSs0)aX0r00)%XR^J z`jH<$>RKN5V(7OqK*TS4xZz{h!*f1C3ECFkK$#7nA@pGN!$;%jYv zwjAKwmYb0gKL(K8-kPtb5${A?tlI~wzMrJ6wTdBr=Y%%%EaEMQ&o}4FQ^DA)s*}Z> z!FI&AHCpoWI|RUqx?7s@$8!5^Q=anY%X@i5{QA6kNcMelpE>R6eCYFpmMsVT zrI(b06~u#xf1yS}_UGdMvD``!0~u->P=lA4?YN`hilQ z|3tHka)7T{2CGqwjZfMwx$5irQN_*|e4l)UHmiYuz74Yp1t^#>hrJ3-SOXDcC_o0^ z7T9R1gAN8V6s;5)ieI5-7aQlmJn}lUna#nz!j%5V$X|o`xX!dHWQRV27P1=rj;t2b zW$~+pTw@bIek?ZvKPDL<64`^#UNTAck#RBsB6*5DP4<%UA_FqU$I>2EH_cM;u)Q~SI+rg`Rn{L z_AC5qq~L$#SMj%U$6Cz0vP{G5Y*=%5RT^yu;}-DInZ=349rJPVM6C3K^oO)8y(fJr{l>k`ead~!ea?NsT>_Ci%bnxC;Vy6= zb6>{xYV#Ue-+LB$7`JEXmTRm^AtP)R9u{)KHsMiWGV&)32xCG~*nyU<>-!d;FP=Re z4r3qYr~6#KE>;1F`>_J_P5xC?ROxV(DIHdCO*p$HRQI@7^PwV@Pvuf+ z5K}u-6REM(K@W$srgorh0{i?O)v0c>QtHxU-hBdD(>iYJ4b2sIOVX2K8m~4gmYVA5 zh^QEb$V`rCQ-|7ZS{nuL-t>?3n=-o(6I(7vocj#GzCZEo`!3>+v;dYIfPu#&ZWzzX z2i^rZ^Mu;6+rb@?NPG+6)c5T6zxpzGe*M(x+{AON=PiJ>H#?ob-|uwRK0yDg0B4PV z0id6JRRdfL?*IS=(Md!>RA>e5TU%^YRTN$Gp6LS+c`7I(sEG0q5HN;@y96snSg{u9ugEmVvr!gC}1h2AkY#h*vHIt?##Web=q5| z+B?ONVHmh4na(-)oU`{{d+oi?$(=MXa&5DN8@UC7fMcKF-GI>qh(5*r`bNEo~|j=37^q2CC=0A>y}Z6 zuk5lm-)r^p>dxl8K7F<85~#pcMgtgoYY^43K(Am0t|RMUKsA(oj^)Hi=(0Cuj+Ij=IS6&Fb>-&INluh<)ZFltTz8zI42nfO96*RvNT$Z>i#a)B zSXB^j&8aYZcGg>HPCJMcpsi1!If9xpJcRoOh0xUQ;Kv3FqXq^s?Y1!5G7f$^nZ-pa zq3wv#*qS>d7*-v?(epMAoXH|c+=@0CvCj=a2)f-M6hOe%82R5T>WxkwaLTdbkOSWlGsIO|J7pSxlL`TyJ+J~ zt8F69)!W;*Cr~jtdO#4b&Wz(Xf?2aWiMeBkmD95q&n1yA`H90~RQ3Xe-Fl|H#4-5%o;kYQOn0uR#S|>)&%%1XK>pZ~0>ZRfSx)FO7*q z!zO2yw_@&NKHGLIgHb^Si-2~-OOR!59NP@;V1C_e`oa?Fn z+Kuwk{@N)mg(e?pDZE}xoj#+*^lm9+`;3;t>&4XRGg?gVmO{4AXeqp2Or1WX#q@3| zWc!Sk!t2G<=`&hP@0LRLO~WW(nBL%6iO+HDU|9eBQi@QzRyrNW(OcV7{{a08ANXcz RTf_hW002ovPDHLkV1g&Z5-|V( literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..058084c35f368c1d2379f7b6f034f747f3a41194 GIT binary patch literal 3477 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..9ae752954b0a8f3fba2dafd84c87df5f491a722a GIT binary patch literal 4498 zcmdUz)n5}1pvBpcZh^t*le?Z+r#I(HkYoXIVci9}hO-E)bs?rrJ}tJS0xK_0NTT-<`& zXf{@|@={4;F;iBNpX)0~P>N>o6|9=xqAzu=ag4CNNYQidEuNSevAqf1fyLUI^pX4l z-{E!HqDuLk;i7X~eU zR&0*<4y`xFtkp?K)LQ20Jp6mBGlHN9Dhm1}S{oM9OVEsi!Jfk(-Gi%Pgid0|E{C}e zT6|X>L5L|xR!0Vovn+9-px*13K9g+8ogU~N%y=3bvy8ayK(uh$MazVPny4O<;GjAC z)7npFwxqH!ZZV+jD=@>$rHB~|L6xgiFF^oPK%BJ3RXR-*sU7NWQPuUWg|yhO$L<6n zXhq)JHHnt4VykB%PIWWg-*9fiwkYOFj(ZIAo`>(2jb;bjVmfVG;DT?4h+E6szYl^} zTo2A)HbS-yPusmmO{Th<)Q65-jePv{soXICfKFy}M`A~4&%I8!4T&JItnu8v`U`Vu zV-H?J2s|6(&iU2=teL0?zlgYqZsG1>3ub@(-ZLFM5j-7?2p%v8rbCbpSQP9MGDP*RI}FcDm@>6?piacP zQo5wojq`7aqMIvxi`{_t53~!_?oH6cpSH+gpz)lf`(J`-azC}7={8jLqMTWu5BFoL zD5J*Iliuk@FT0sgW1x^1$rpKoT|}g&yh{1^`2D@Ir$_g^TS+ZwSu~+xqs;D z6n;`xu2Ij`(_zdcZl-+89n!(lXp^AA63ZbHn^oGuSX}%@j@ZVCCS?Rn=9yD|jkxY9 zlzRO3&$<}JFYSRSL3S73A9r5opBMQ2hYtcJI)1c>xU@hJ_NTW^xtI@n-b+B-hRJ<- zF$^Ps?(I!yk~R z+3Q3rj72Xz@vnuD2H;T#292Q>^Z!Lm5;~2SS)cA#3(kn=`BqyHL~TSRN}cKfR;VZ` zX7nmA)03S=pWK&|y&rU?hQ35^0cB2)+SXot&(Guc)h&ArC)|wWB^b%ajiMX(m#g4M zc6()>U2^Cw!Pt4I3BPP#K5{r|*kKrDzs2=5c9;}k9NP{jAs2M9EB{T5i47)2tR<)+ zm04J>L#j2O+C&&b)~K>SGV~tBnSh}bS(5-^fW?V_d^Y36up!xod+7eLR}*u2Q_-`J zUr@r>m7M{VX_NP8w@D|=SR+J%w6;2+fLA?YY`c!T0&^B>$YKJ0UyR#+8tox&4z~;qvo) z$lta<RPO+ZJ02(Z3I6KySq@DV613rrMs+%urB5dn?%TaQ>;jZDg zqqf@^x)kED6mKhMyB6}9DmL6s`1`Rg`AfuabJYaq;8z3deUfZ*W4+d~OpadCNq^Z? z`$c^+y3hGf=^L+N|69hWQc}114)XNmYxdM4aSYydNiQ38S?$NIcXo|{uE;7+ zGJA~dRQsh_l`TR~Wt(uf`Jsin_FpZc zTD}C7Dk;t{w9;15!X4OZZg8aD55~J>S|3jn=@(uujd$7J&0Y9ZE`7w05mg}AV!#^- zyx6MOj024+m1CCKbiY?Ge}LJU!OmSBC5^?mwiQl7|H%M;Y?W#&=JAn$T+96dEQBJ(pUJIe!U#4(@9AJ+3J=p z(2Zwz=2dQuYR{J{2t6lIaO?!dxvf%D@9Z*X1{R*d9s znO9@Y7_H}vB$pTxb57R0J+{C~clD9(oHy=)-S8#Ct43qYp(6)hb_ zPv?osX7x=!FI}*^bBVsxHTTNfyrhl^&iVXE_(e8T zP~^f8MsPnGfQ~iFe?21S17J)xp$z^97I8gKvPo4vRjyG zA|AlwOLr!87Qiyr`kPoQF|O8z7t4XxUKX~SABD{wui6JE)lTQX=T8Th!e0bm?LRaP znQXsHs7KeR*YzW!=}#DJtr{dCkQez#TC+t73J8}gYqN403O>pxxs^{i=$Ib|(MGLE zgwG7c;dsVLHMZ~4t^VMuYUq4*-kQ%y_pTv@h5owScJp0x1A=;)h;7^732REz!uKJw2%vNn6ciTb#9mfg+W`?uR(!d)+_Q#i{W*X zQ>hZ^#AFq-3xQB%R&*u`8-5yfQxWa-^p-4k<>dHRH@lU{j7ULK8`|njQtX)FE5+A8 z#?^BDn?{E+k;VN0^`3)UwFVv&m;Q`j8AgX4F%j_vaHX;I7S4}D*?spwCv^W z&D+a@`b&p?75~gqOWUO1SFC{-{AKgf6+Ec4iXE0?n1T>brnjok;jr~%(X{Zkz`a46 zmpVjXZn3@4mApb;_I7WZW1fjdOeT(E_vvzWy{~pAVHqR2)oLYcCEbU*&BhT5i{&9J zgL8ePp1oVEH0`kuZ#tik<$!JK)_D^X&XIdf-Fs>_kgeZTopH^QD-gA2`!4?K=lNJ886N<4+&XS;uH2_MdFuNN)7}wH?IX`R!0) zlZcAOA8fo9soVjJqs{%@Y;z53{wYzolzIXcAu_qK8O|qZ$k`GT^8YrnQ(P-24oJ^l zV6{7EL+0{Iwd3$h|7KamjrlIHzz(?d&{`x>@b@UhC)F(jJ_)JHbb-*!oaL@d>Xraw4&J;BxVg#M4qK-2Z%FPv^Tv56kBu%Jc`^21+ zj&eI7hTVG8bQZykJL&5~;p&?&a(x7ZE~!2|#9lWs=Y&INJ;mfH&`sbB?HL!Z}LxX>_QpDr#r?5ygGL2k{_Dr&c-JUV=Cj}XvZ2QiK&4; zh_6hoQee$!xcT_7mA3aHP!ljEeE1BC?347P9Qspp6{|8kx&S1?;kA6ImlK)9&38&0 zjd?~fnCj22fqUAE^5XLSUgC9o|DIjNF4`!1t@9R3BN*alJwhXl6KF|S`D+6yW_jYM z7aEbAni+}Pka-(E14qN6XXe5ox8gd-C^7~R%0LWWa`qeS;>k1vX@U~qEPA1JQxriC z7~(gS#wt~4rn!7KQ@EZd0!?Y!fHpzPlU(wR4O=_b5}>Pn?}iXp|G?;&syxQxM9_ev z!T+BklFbm4>g_^|P#4iW93_@KCr>S@1?&*ExDccb>KP3{UntMonAz~(*Mg?e?Z+r#I(HkYoXIVci9}hO-E)bs?rrJ}tJS0xK_0NTT-<`& zXf{@|@={4;F;iBNpX)0~P>N>o6|9=xqAzu=ag4CNNYQidEuNSevAqf1fyLUI^pX4l z-{E!HqDuLk;i7X~eU zR&0*<4y`xFtkp?K)LQ20Jp6mBGlHN9Dhm1}S{oM9OVEsi!Jfk(-Gi%Pgid0|E{C}e zT6|X>L5L|xR!0Vovn+9-px*13K9g+8ogU~N%y=3bvy8ayK(uh$MazVPny4O<;GjAC z)7npFwxqH!ZZV+jD=@>$rHB~|L6xgiFF^oPK%BJ3RXR-*sU7NWQPuUWg|yhO$L<6n zXhq)JHHnt4VykB%PIWWg-*9fiwkYOFj(ZIAo`>(2jb;bjVmfVG;DT?4h+E6szYl^} zTo2A)HbS-yPusmmO{Th<)Q65-jePv{soXICfKFy}M`A~4&%I8!4T&JItnu8v`U`Vu zV-H?J2s|6(&iU2=teL0?zlgYqZsG1>3ub@(-ZLFM5j-7?2p%v8rbCbpSQP9MGDP*RI}FcDm@>6?piacP zQo5wojq`7aqMIvxi`{_t53~!_?oH6cpSH+gpz)lf`(J`-azC}7={8jLqMTWu5BFoL zD5J*Iliuk@FT0sgW1x^1$rpKoT|}g&yh{1^`2D@Ir$_g^TS+ZwSu~+xqs;D z6n;`xu2Ij`(_zdcZl-+89n!(lXp^AA63ZbHn^oGuSX}%@j@ZVCCS?Rn=9yD|jkxY9 zlzRO3&$<}JFYSRSL3S73A9r5opBMQ2hYtcJI)1c>xU@hJ_NTW^xtI@n-b+B-hRJ<- zF$^Ps?(I!yk~R z+3Q3rj72Xz@vnuD2H;T#292Q>^Z!Lm5;~2SS)cA#3(kn=`BqyHL~TSRN}cKfR;VZ` zX7nmA)03S=pWK&|y&rU?hQ35^0cB2)+SXot&(Guc)h&ArC)|wWB^b%ajiMX(m#g4M zc6()>U2^Cw!Pt4I3BPP#K5{r|*kKrDzs2=5c9;}k9NP{jAs2M9EB{T5i47)2tR<)+ zm04J>L#j2O+C&&b)~K>SGV~tBnSh}bS(5-^fW?V_d^Y36up!xod+7eLR}*u2Q_-`J zUr@r>m7M{VX_NP8w@D|=SR+J%w6;2+fLA?YY`c!T0&^B>$YKJ0UyR#+8tox&4z~;qvo) z$lta<RPO+ZJ02(Z3I6KySq@DV613rrMs+%urB5dn?%TaQ>;jZDg zqqf@^x)kED6mKhMyB6}9DmL6s`1`Rg`AfuabJYaq;8z3deUfZ*W4+d~OpadCNq^Z? z`$c^+y3hGf=^L+N|69hWQc}114)XNmYxdM4aSYydNiQ38S?$NIcXo|{uE;7+ zGJA~dRQsh_l`TR~Wt(uf`Jsin_FpZc zTD}C7Dk;t{w9;15!X4OZZg8aD55~J>S|3jn=@(uujd$7J&0Y9ZE`7w05mg}AV!#^- zyx6MOj024+m1CCKbiY?Ge}LJU!OmSBC5^?mwiQl7|H%M;Y?W#&=JAn$T+96dEQBJ(pUJIe!U#4(@9AJ+3J=p z(2Zwz=2dQuYR{J{2t6lIaO?!dxvf%D@9Z*X1{R*d9s znO9@Y7_H}vB$pTxb57R0J+{C~clD9(oHy=)-S8#Ct43qYp(6)hb_ zPv?osX7x=!FI}*^bBVsxHTTNfyrhl^&iVXE_(e8T zP~^f8MsPnGfQ~iFe?21S17J)xp$z^97I8gKvPo4vRjyG zA|AlwOLr!87Qiyr`kPoQF|O8z7t4XxUKX~SABD{wui6JE)lTQX=T8Th!e0bm?LRaP znQXsHs7KeR*YzW!=}#DJtr{dCkQez#TC+t73J8}gYqN403O>pxxs^{i=$Ib|(MGLE zgwG7c;dsVLHMZ~4t^VMuYUq4*-kQ%y_pTv@h5owScJp0x1A=;)h;7^732REz!uKJw2%vNn6ciTb#9mfg+W`?uR(!d)+_Q#i{W*X zQ>hZ^#AFq-3xQB%R&*u`8-5yfQxWa-^p-4k<>dHRH@lU{j7ULK8`|njQtX)FE5+A8 z#?^BDn?{E+k;VN0^`3)UwFVv&m;Q`j8AgX4F%j_vaHX;I7S4}D*?spwCv^W z&D+a@`b&p?75~gqOWUO1SFC{-{AKgf6+Ec4iXE0?n1T>brnjok;jr~%(X{Zkz`a46 zmpVjXZn3@4mApb;_I7WZW1fjdOeT(E_vvzWy{~pAVHqR2)oLYcCEbU*&BhT5i{&9J zgL8ePp1oVEH0`kuZ#tik<$!JK)_D^X&XIdf-Fs>_kgeZTopH^QD-gA2`!4?K=lNJ886N<4+&XS;uH2_MdFuNN)7}wH?IX`R!0) zlZcAOA8fo9soVjJqs{%@Y;z53{wYzolzIXcAu_qK8O|qZ$k`GT^8YrnQ(P-24oJ^l zV6{7EL+0{Iwd3$h|7KamjrlIHzz(?d&{`x>@b@UhC)F(jJ_)JHbb-*!oaL@d>Xraw4&J;BxVg#M4qK-2Z%FPv^Tv56kBu%Jc`^21+ zj&eI7hTVG8bQZykJL&5~;p&?&a(x7ZE~!2|#9lWs=Y&INJ;mfH&`sbB?HL!Z}LxX>_QpDr#r?5ygGL2k{_Dr&c-JUV=Cj}XvZ2QiK&4; zh_6hoQee$!xcT_7mA3aHP!ljEeE1BC?347P9Qspp6{|8kx&S1?;kA6ImlK)9&38&0 zjd?~fnCj22fqUAE^5XLSUgC9o|DIjNF4`!1t@9R3BN*alJwhXl6KF|S`D+6yW_jYM z7aEbAni+}Pka-(E14qN6XXe5ox8gd-C^7~R%0LWWa`qeS;>k1vX@U~qEPA1JQxriC z7~(gS#wt~4rn!7KQ@EZd0!?Y!fHpzPlU(wR4O=_b5}>Pn?}iXp|G?;&syxQxM9_ev z!T+BklFbm4>g_^|P#4iW93_@KCr>S@1?&*ExDccb>KP3{UntMonAz~(*Mg?3x?z+H=*zMqKghQBm^_;TWi*BPW%x$r@4M7Eud5My_Qmg)bAuO$TBOr$pyCDBH|;Z zI1|Dm^2srv`r5RdroYrtMYM3d8jLsOdi~6n=GPwcDw-5&Ng2M-Th{HC;6%p1I4k}* z>iNR=?mNBAff3!6(a9NVB<;n)RJf1|wm0J+rEa@KoNUdgOYWf7kA#d==oDCw9E=zlR7* z<;J`(OiKj>$uWVKZH zaph#QHvh^XCRW5Ufm}dT88%7=qNUKuPXa1e-@gdFVDkAUsd1G=Q%h`pdbg+wR`?_d z{PWNrBlw{xOJPl{zAfMUN$`!jiOxbf7k*piRe$2v z?Dp_p;EMC!;pb-1j{d(U*CC^^wp#VRBd~#+*GDQRjBT>B>5PG>0b0bDe>?gFih-%a z8SttLQ%OT-9(={}H1s=%TRlq6So!jc(2IzAt`7D9j)%dH@qm$l@qmed9#hos5~DfW zoiH93dN;GdD+dIX(S{$H4@DDbKh?2Zm5~`tN=N3}D+1HD5IASY1z5>yFNUO693si` zcypy_@vZ20i;*UsJbEvpkTs|m8jl}8>h8NFzoyl5))4J{M;yZ-1fXQvaf>|{iZe!6}Ue~~57MnG)Lqm+%r?e3KMcZB5GimOLSr3nrh zV*X4qIfA*4E{X_Su3hP&{jThfvpIKtbk%hy;2rodx4+u&Ru1P5ZU9Q;bZwlv|B-(C zRE7eI4SJEE=l>DS1m_e5g;Z5V{++J(i(?a<1RDjPNBD%_RS(Z54;NQ-yF4BLD+8=( zPKxhoQFi4NP|LBSSe8ndpk|Y zA)FPP3{3mBj=#_QQWBM^UP*4hmzhD9d_Bw|Sm|xpl^utv_e=S`-mLd{XMo5lI=n)h z_+WskH*x6;x)+4Y1yFYw;VUSPv#fCUv&>H@ndh0}4$~;Xc$V44htEKsDLn01^C-`< zUj7=Bqf5-oC*=~frfhuhN;iaPD_{fbnjN^|aj@%MQ zu0+fvSSsF$1lmyuH_WvD)LCtfbQk$p4~BP_NOigOXTIUp-}!YN4<&TRpVPrU*t$fo z(D2-uNj%=8Zzyg``(8CHZZzHFQl-r#3&dUm92^gw-9&V)euGH zaTM2hmK^hM@nxJyE_F$deW*H~Y%nwXT6F3^f>|1EknEnOzqLn%>4OB$aXu^SAthDA zyM$ENrC&K$sSlY+@RL}|Ez2F`TEQ~dUMXps)IK?KDppox%NO)btH6AhxGLp%CxyH5 zv0dRRs>x%yW;Dxb;71~D&E$>CvCCp`2JDeMoH(&~6=3b4j!2M-$PGw`763aaDwh}LWs zL0U0VVXwu2yr#Tz8H3pUyKgp#^GsL^cyK~qh3JWH>u&(fsq?5SDO1Fe~;KZmVCem#M;3g?yYdx#Yhr3X;#*QFcSxAzF=phegaqhb?4@C+VbU;6& z;m=K(J8S#BApFk`iXgu@HY;zbCSQe^hgYC=$~k4yECin9oV$w>Yl&_J6V#N#9K-`rLCrabl_lYV}C7iF4N^0(n0EUc|J^k{X@OW14ACP zBDVK%D28*q2h=6nuj<^eIuyaXad&n|ImTMS=DD??5BAA$-R@Bj zky3C@8PTvD#kXhXUxis3$(eGqH(M!rqpJUOUfU{JZ{NkANId@{!o@Ru};&^le2w>A^?D<2Dz zoHEm9N7Sr)(NEi&NrHM_0(%ySi&Pp3tkird0WSZ>rLeGOJc;Uljku4@A#j&ZXzJrn zX7en{?&0ZJ9cBGU5~6e-s^)BfoZAscjz@Fwwu0@E<2m#V+-gto>5<<_SZTL>Xv#}1 zHru0XS@`onso|#>F}tPwkEHH<7sZ^o7v6-5<6N+0cZvJI`qi6IUO#b@SfC9!?XH%fQwZ!ODFv>w>ghe6uX`KmlF6PP zhV%!1i!(er{o`qKH)>L1iX)SRqsDU(_m%z*w!KVDJ}8rxb9k=Pb|kfMtZwp> z2pR_6Jl$B&$ksUX$*{$|{WmLjMO=Q;@_WnOpKVzwM-E!qBrRqw6ObzHk%NoFnK8Hl zmIIP)cgcfANj=pMC|ViYwX%6-DrO%q26Kh{k~i>}FBr;OuY&a?z|Pf-uX5X4FRnhV z#0`*ZD4z$xa@mkXWWZwj%np9lr<-qWcjeSCRYqN=8}T~@m8^N4jUfOu+`g2@;WPco z-zA6>ZaL5CyIgC?x*`*7V{+Q!bNHmPOx9xoc`lXTb=LAx7af%{S#hyt|1LYpXXN8J zfiK|*Rj-{$C8t0@ATuCDvqzzT{BE8+XDS3hpr={K;pELjJ_`!60-ftl5aR07#c<3- zY~trc2+J>aO-PoHYZ?lhd;O?u6bteto3FeriIP(sXS%f6jP&aLruw61r)tyD)Kkf9 z>+rmtO9}n1LE>)w5i9H%%|Z?a81XwZPZVslrwosNB*Y|&Uc3uUi;1>P#W*HW-o3d)zf5WUY&Lqbsk_dLL%i0as! zRsmeLL<;!x%_PhQ(JsqO<<4R9S>n7nbws<*bfUg z0tVKO#!}RwxiNEN5MGUP&!7Ybwxh6x{D%#NAzX;JYd2t+Za1r}i(TMzxDcZ(Y$R0S(}|=#?4D8{Tj!y-psz1>lRB4&mxRzZx1635ZL;+-q<<4v+Yqx7rml3jBiYl#&+AZDYRrw z3-S2kR6|+C4B65uhg|P&Ik%d-6NsgI!#wC_$e~hi+_Ec$v>OFfa^FzaiK8jiS__pG zT-$u_)P6Z!3XEc1fU=LW!9l2zy$y0Mewt6<^ilreir&8cA5#HX40e-jtZES#oVKozDZGS)}~hy4`SGkPueh ztC2Myc}Y(JUgXK%t7W-6n~5Z3s5xH5T=BPwC%{c)8#EX*fmHuHUKWGmyd$gJCh=&}ECOEizz{xYxuRsAvql~59RI^v{c7>X zw3tQx4%hiy<9BPswoU(m^8lL`2bdmM7#IB6eRM{gGD#YTn&Yr~emstL1n-9OYqb@` zXZQRxrhD0>RIqMx*ZsB*9&wz?j+wMSyD+hl)Vv=WeN4T0i!k1RKN(3(*r~G!PTC`` zxqA1)tC9)?gn zYviQFD{563Wl}lFvA57gZ0ZOI(gQuDn`$=b#uojDONYz~ssi`p?-FVC8zo5p z1dTtS4WF3#I=$J*CiIquFuRXg+h&>670>kFoyWxzrufuV-qJLjAlvO*^(d*5+d}E@ zG@GPyYYwyyvAXsbJ~ekr+){FCwep$WcyR}K&z!~QkLqtagv|mm{~Sk7m;@t++|v$7 zJ^#e7FI#Pd$5S7u1{j?FWsm@I-i=;j2l;M(rEZJubTe@7@EEi09EMkAXS$GJGh@*x zun~7skvzwaTx_qCc@F$jU0||S;IOCLPUn+anL)zojXuV#@;bKiQ0)-vFL5wVZ-pS7 zb%)$iM!A4yv7RIdi+~ow$=jbJ3Z!3_)+s|Up7&I7BENvxT+sI$wIw$Rq3^AFVA7;J zZ)kAvNSrFT@ei~z+bTvFoI1nsa;K^^`K>{Z_+)d3n?Z;O+Oqkq-T1$4_J2RF9Gq6_ zOl9J*(7`S+Jw!j8aS8V6Zu6{%QTb@*qrH35P2Va)Q_6Rc(DnaVh8K2OOIn6BT}3Hr z&n};$1nusQd_gv_$gnlzb_y52xy!o%0$~B-R6>Wx&aDhXFxHK%n)Gw0P1%zivz3gn z%Nr)%jc`o}T%#E>cI-~~y3iOlT6Ej%?5TP%g4l|&bCC-_L}?}7k8U!V68O>M>1y0c zXF+Xu0kjN-MEvlM3o|}-PiaJT9PIZ=zG5~1UC>Z7n9aFt)XTy5VSSZP7GXhSr~>8{ z+OZyFDS7vu<-zRR_I_5B^T{p*ul|!;39aE)z z$2W5+9s=KB4}UKLr8dA4{N@ijJ^8tzExV18w51v7d zR+{-dgF|vbl6Vpm(9GC>nAoE5HY}IvksE;chv_h}b!ymb zk-@*^w3x0Uo1#;$vjoC_LFoI3+&Yo0|Hc0bu-s|D{?4?zM1iK3N`AIl) zX&4zqJ40?{=NUHtohgf02C{y ADF6Tf literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..eba3a1dca7b876ee74faa87aca8a0dea01a63978 GIT binary patch literal 3144 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..22934c478ae4bb42227bf47d5e1b4bc3367e0242 GIT binary patch literal 6172 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%0Dg z7k5fyBrw!dAoVWI7obD}t_d?_Ya44FJ$qUYB?OeB7T zm*|-8vs}z2$-VGqQ*AC8HA(BhN8iCYq{k-c{CB`5s0Wq6 zBpxrU(jUPhoW7R<8py^W&S;==v3J*V2L}f)*VRt#qbnkBhyzfFi<>)R&k{JCw{PFl zYvVYz`-^~H>Pu=lG~Ho{%Y}j5u+pigP+s7BI1qY(4!$Z2tc105&|mOmBqf+7Z10cL zK)qIYwQ(0`v+^!+ezfKIB9RU>Tt+#H$3F(}9^LcDQSpEks1VJM4ghf_SZx--g~Yjy zTo7RZQ{?gJub>wTPQbeP&)i&RK*$3=B`zdVMh`zZ8A9F7Fd*O{ps`2-HIMPmuOKU| ztEu7i`gq2KgJtW7pk0#a^Fq$?_ff+B%=Z){mSj8ROITA&^Uj>^ysd~n(dE6O{7`3+ z(#Vg+cU`5!!4!bMfz6jXQU+F5RsdYjsJQTkpHc?^f_RlP^3ep~@Iel*%?OwGm}zdv z%gc*>?=d8Uo_y~?Pt_imLLL|nPfaxk?ytfD$s?Y_aVRt_jE)hT)xRGEQ-Y??(m;l7 zJ4pR+L0)tz`+MqXY4F*8y4si{_iVzb-fvMRt-KlLO9f9ql%90aZi?g?I_PhUT_|%I=W?+}oJ?TN=G#FKl=lbOUUkrAm zgCpc<4kQ^YGw$CeCr@h1&?QfjLgRgHPv>upl~|4e&I$4E7lewG)cl^huIw3z?o7z^ z1bWi@6@jJCY|=H3G%d|=W~-zr%)4tr9uk%5!jmE9*2hu%oOJaCy|D{;e&&Fy#tTJe z2?#M?26WFoBMKf!5**!A*pB>5!2KzHR>hZ*G5OjNAooFP85w&UmtiR#2o==klr?sJ z+#%+@GRpIH(F>rX;Om0}!AsI{YF`&X3KG0uWLvVu&Zmdt(T)b>L!oO|kC4D?H!DP^ z%c_sbTcqs8#CkLL!Mjgg3F8I_>zXg{ql=h>m7~+q-t1)|ZDtQ{QSPDlZd2?TXAdwm z5{*Ldjf2j?Whjk6AZ%=Ggebwpf&o_{WE(OPUgKdv_Ia==6EG5M*rki+?D}t&j93@0K|?Lze!K7 z3Kb;25Bv!E^A7)}QJhZfV=?fk!LUi<@jBcu;qZGDB%=`l)sX8pmhctVqCeW;Nn9BF zz1N%$?b0C)K-a!pmjil8SS;=PD@L2v;A1fxIpCol_Xj|wl0kxzak{`Cr}i1wxRl~% zT59!78^Rpf4up?Bm{^9{27LaJ-?%;y5N?631*_2jhpi=+pmKK>bYeq*a*CUm{}~&p zh~QwndX*z^svhQ&7y@&j7*(iLy+5CW$MKug>vho&mL`c z?i{JJQpY%&A#)Tnj#qTTD1qd0#;Z?u%nHCSz>(2qKnhlulRJ2Mqxtz*8KBi_^Iq}) z3rcMU1OU(?167c9(@wWAAh8Q83WK4b#%u>MMO`JkO;^7a7i$?K=XPSU*MGQL=;u1$ZxCG5AlUmarl53%~-P-r4 z;*1^9UrG31%@5cW zy{YmfP4U3NQrlY4c`{KzxVyVgwW7()C4saU+EENxOx@>_HxC~^1nvCVwPlEI5@>pY zA_O-!W;RFoc}T_@wEF3te*?@3E}mld|Js8ly8IBhW6Lpg8j(?`{|oJeX@nHanL;{4-Wp&S{Z zyumAsrDc;}bMasaGXTt_0BWe4rKP2bGeBH`A*q0V^Tz7x+#lf-_Lr{sIsvR|dwvHv`DZ{GtWZO6G+N@0>(_U>1 zWx62x0+J~JP7%a25M5j5BZO^e zCu3&YZ@Ut${U+FFk2-)aIe+E4u^Jh8o4mYb;L+$=UXhf_^D*EJ&Wn%NZT|k+*D_3~ zk?B@M=2JnX{JTg}!sqj5`!h`qt_D&k8=Q zQ1ZdX7~niyRNMNE-gE+_Hrfuji7dErsEUrQ6%=Oa}`Ui z7H|N7rUJ;yluAH6Kg80|ykRFx^4Q~)v1Vt${HLBC$CtSS?x51#?@Z}~X4d|5uR$)u z#Zmm(V;i8pwY7er+8hJTT0o{7>gN6>81GRBTD@feepDL))D$4N<8}3jOc}i^uRqg? zh24zj*P|c9$1f&rtvayTpnPYiavvKAGviz|&77?<$8ap3+(AgOC9m^iUaoI8?TpR= zesNXOLo&)VaFae$^e998OrX7vhG&X6L@I;6XmYXGJtt;_ZYZ-diFK2Qeu~pp#mqSA$sm%L6mS$Go>BM?m#xL>fROcWud&V<1(+Mvi@< zld->W<}lH;#1h@y-Pc!FAICb*bDN@!LBwKY1i3{sAmoix%Kk??XZqFTwYG5_DywRx z7E6jC*rNxmASh7g!L|-XK+4prOde*afC=-I9P1G(6i|Xp0x4iXKtu$Y6mVb=5fBg+ z86qGv2{H!=#03( zln_RdCBxUb-|ewXgrF@(r>49;Jqw=MTjW}fPfjXcJ5kmbJOSc>4);lSDyUUw={-{n zciNWGk4@4WO$HHJWxf90@7|ro`Ddh)Nr*JrClLkVK{R5%-(fOx_}imzj?zq_7nfPa z#6_CMP&#gEQDom-UEvN3(hQ!n%FBnBL|>+r-17fnqC@!dEg}&ej!P^9wS|1YLF!k# z-dv07xcT^+fjE-w>SvZmlM5;)=L&`19vTuaQ8ivF5loYwr!RI5h zsE^oz!X}FvWS}oWaVxS~1AKiIv6Rs|Isb7z+g+b!fUQY>`W{LgV=;iSNTd;&0mM4} zSYZ=080gTD9>S{_K~{(_7%`}EsEfR~P1_AZe~W~oo0nJ15DnM?jG~EG1?0W{H{=G} zZeJvXVm`m$_^FeMl!lk?{7Vbg;)4;ZiKK@eUx^&ZD~v$U%Te&-eg|=IoB{tW)xAc9o20&UCa3&7G|c(qB%9} zJHf|u<)jX{HSRTx&o;Blt|WQL!t<{pD=9JuS963}`yvN5aXvuu4`noGmE`8@8nU#f z-!cO?aY@|k`Jo1q_?jJV&euID7xSX4D>-`%jQ|F-!GqBn!?cRarsoEVHq<#3S?>Qj zs!lToeiS@7)#T^3esP;QVUKI03nHXxPmS>CHty<&VBm_;KU{=UPIMgE)d1!=4`|@G!p@zI}Ci(`6eaBHarkBR)${ z5E026OUqH`l)F3#Q1i@>&8J0bnPh3kNT|twRCcTanU%IDj$d@$%$0M`k1Fok8=2`) zq|kdKfW#t0|r>ef+@6teWTN5`8=zuY9{Cl$kv`6XV4;wfi02-^7S?H{GK zv$EEWeya#|L3QClzlryQnt6r;VA(9C%3jt-S7;_CKOQ@wB(B$kNm%tfMP^manzvDb z*?7ykjLh)^o2>iH8>vc_qnQ8}@+K9vssNMevxE%{K?7!@Yp9ri?umI*PIVj#I8F$h zt=qQMQ$r!+PA;C>eFSo|IySY<|L5PlJQ?oFNkvYBZea{}_um)n?VYAunN2RmAO!4- z9P04<7?y!IEll<1@xDo1X0NeaF(#3q(O2FxO>Ya_sM5lf8exd1Cl^SY%gY0@Oo;|+ z#xHLw{U%7=H=K;!XzAt6H(UQ)?TZTjT}nW$VUMa!`nr`NV>|Vj<@)w%bKj*XZDiV} z<9)}SRFW};zE@VpKp&WP%ELZ`v7E#3aCc9xV)pJeEcNE-W0NOQEMNA0@Y$GjVOuJ*N>Jx{J;G@9l5c|gVQGwOd8wt zvw3@@gshR$f^mfy2^WhEwN$k;#=0oh*hH&YpC&C zH)k_NM6zFTc5(Rqc-KBN>0PW{zhAgZRf{*3?KTs<@abM#`%nFtOv_)Y(}!;&ZB>Zu z!+p-GmtX6u!R_qq;V6571EOoFs;);j>@~cL0R$0;{O7Lvs%-DM=~#xqdu`bJ)ooiH zeSLlF+gFSy4^`qH4K*b)CagIrftw@`M!Ph$Zae#H$m(>oFgSQ6JY!&?jppUj3X;gJ z`{$tUmYD`qZ{hvH<~7znW+S>jXtLqC4$~U6)d@f3a)84x_g5>h`~?wq1$N>I`G=ja-{eU6_r%d!(O{tax)m9c3#jbseh+?ggwgN9le2iGi zAgmPQH9BD)MZU}3gUAB zuElI3V<{1OVv@^E(^8!zlMeyxYzRYP1t2`zso>WLEAZ`IYGciIOCyZZ(CQ(4d74{r zudzz+(sa9TpJZxuR*`uyPFx6ZoJpnx2n<|HJ6o>oOI{bZS5ivK8kYr~K51p4ze2{< z%Yx286p}&0+~T5l*WG^_#<@S(aq!T6B6y&Jfm<=?rgmaWKG=?gOZa&7kDBfH2m`>@TH|uoxR`DOi#zA(c_6VVgeGDb2j>abvNbq$+a$Aiu`q;7HHm1RjpqQEMY3E) zMdjeZFB_)ublEMCp3zxZXY}uj*&#Nt_5m@ENFbi#i0^Y>TR(7;Ib@f87# zmcL?R2(?KeAwP8Go!*-*aV|;+=%??GE033>9a`i`R!}|ZyID$ZCsSzDua0)6l^RQ* zKd;-XVl#QGIW_EXZv`TtcbvqyT`C*c?q`$|{|m{2lf}RZpr>GImNlC?l8W!q8qzl_ zC-+LVLx;1}f(jv=1!D^Cu?#B^Z6BnPv7E*H)+EGxR8-nuzXqui3m=u-Q&?JNejIjW z43gT8=uW{s;`&$nH618B*IP+sdv5M`hH<$o=P~l!-Y`wJ9da#Eh*(->{}3SrOL6>( zwzn4?GNiwEmaq8B&xqmP@FuNQv>kRwd9?SNe6+5%~P_w&XwXDXJq^f9#NFU0c}3IVU$kT z7?lMDp3lNfTjV?lKZsk^iqa z;r5{|L*Xemlr)CQCOVztip6fq$|#sFSCxu=9c%YMz9n9^YG1%yrcLYl!RFX#p~pNw ztb4PR=!qw6nvs(GO~Y}TnNmy#-+e&|xzN;Obu?}BKf|{S^@mmz$ra!Dr&iGEUVFuS z9p|8CyW&?u+fOl_?il0os!`hd zkVzGgeyu-T3RfW=cL9{3YjGFkJjT5?N@Y3$TXPs{D8!`pXs0y0pr;{*AQL(W1=Sul zM*5PtRU1eh^o|iQo_pZ5Y4imSt}b$F;Dcc91V#yq>av(?fd8*y52WO8?|^RwCkI!R zX{wJ#+f*NTbb{jPyRK_T4&SS_sPg*iEUi>5k=JW;s)GFiyi@eWAL7+SWoY3ts@w!@ko&Ef~d?Xh$0xT{=A;kiW0DpYLCXs z*FLO$P-?j!eIZsLwvM*8L(r13Qz%ytJCg5L+P688s=@iy2Eq{iVc+9JZ>D@^{kA?-7f zq>M@_)z)qe)oJ6~8Aa`tX=A0228JJ*Pq~qUZB)`@z~h^PW>gpNbczd4DQ7G3+>D1c zoQKt2DP_((f1ezaOu!u=L3UD=<<~gm6c6AVsE)+=hO~zoK*V#)r5|%GBQqhGj1~Xz z@bcnKPNrha43>kUZ1-S=V&#$utNAkf2eTb$aA6ai8?29pv6=kz21Y(WauQCk9f~xs z{)FBR-a+uT0{E>9bRoiV_VUUxS+IwTX!**rEFN zN{RXU2SwJ8Y!-PKj|`D}%E{%X4z+^!P3zv}*3ut|7+(THR$yHQOTkb!!*c%t+sYE~ zI+0oHMLu4HqK!Doa6L|;64|{Q=S+S2@zZtq^bmYn44-y_PdnnLa`34ve$oX02W=#j a4gZIK{^n+%ZP-La43C~Xa{I@#fBX+8#Li#< literal 45173 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 N002ovPDHLkV1hWO+JOK7 literal 0 HcmV?d00001 diff --git a/Canary/Canary/Assets.xcassets/CameraButton.imageset/camera_stroke@2x.png b/Canary/Canary/Assets.xcassets/CameraButton.imageset/camera_stroke@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..bdfbae04be45d14bc8463c10e92c87ac55a26d14 GIT binary patch literal 1000 zcmV>P)_C$8t@T8^d$H|LJlIR z2P5Jw8e#%L3|=CLPe@{1cRf^1rqh|(%uG*qFZ)A7Pj^-IUo+iZ)jd!~8BUln4K{ZH zTY$~LTx}Z$-UHph>ofs9nlij->EiAg4Mu40I3e(6i^SW)l#o*9RczKYOAPN0t_WjuS^QL>l$e8X z0pT`gcG^?On`sBXxHwP)G#SrlftL~VUaG8zCgN=t6X4>&5u@G@w8hYELr!Huwqn7>fo(?pK?41U zA+}<{#esQ7y+2ec93PE(by5;84*YY_wUL`zBp5CZm^n$D$Qq(?lu`zT@+(#JV)F*T7HY=vSqJtw_|& zQvB(bL-@7elO9!$eg3n?x^w!El6?21+T z2>6Q=_Er}FmyuiHrC9foMMMVpirkW4z?wb~9tVB`caej}a!TvK7bJC`M81qNQow(k WSwr)afBX#q00001$$Wzej2x@cgQuKozCKcbOjxB?*R(?4Eni_xnBH z_ue_@`#t9lDpaUYp+d=0m7?x?U=gqaxKppE_1-MtSKxc#GvEikPMk~{N`aezR$#f7 z2H<9L9dr)(5x4_52D}fPPu$XkX#u){Vc_2)%doa>N!;oLXa@FDyiy zo^!HgOl3R8db-+|aJ5%8rpr7DRe-h|I zoL8SmPQ549zaA)2CmPU}fRNDs0?N!|+Pf#b$B2#-@T2)q%^Glj` z%!Yq~taWVBpfXLm!p6L!D0Gls4734{=-)x@8u5S4zX4VPzeO=H54Qk^fO|ak)4=I~ zHre&m0I*O0j?L4+2+^kEKY=D2Mg-8LI-=M%qJ4|ShKS0uWo=vo3Seza8#hE%d@uQ@ z*06FUs^SP7iDBm?(pMuv!DS+`f$w(7&LS=Oq5v#`ZLwCf>eGA$|5y%+kCjvN@$k!F@?9qnM=%cowm*`_iQBS{CX0EUn(;M@r9=W5#_m6ZrdIcPO-7#VBwaal_~mZ+@N7VHWoi7{w7 z&;wi-aQ^@@5PyvXkN`IV%a8%wWm;~9&ieC40IStgM_hC zpEb!iMYNTBr$^33q|ZKF|HNKFW-Kq;fk%;iP8f&u+3kjVjjAJYY7kQJ);Q{NH8 zt{nz>QxuMmhtqi+lTnQoQpw2mtW z-H$(X7zB=5kbM;R$WymKby~-jgO(ZU4p@|L8r)4fW8=y}i#*rk$T=Tde6BKQ(qK{k z62>^|7y67aXDZrb!8hWgil zO%dd6(zagqt1c(F&Ii2&Trkw{1oj|%>0vC?XH8qf7gUEcT&G-rLbi9_G2B~+Z2Now zyoVf$$c7KIk*WMC;0ffktBLih!x>5Bq`Q$t;+uwh^MMz%i~~Pw{jG?T^06LyJ;5nS zZ5-}Fru{C0_qAqYPS1K71)fJvwK^xseX(BPA!IiqDm^MA5$8biJFCZ$!=P`5Q=e7S-p=HLcx6ra*!#dc{QI=%oyr`UVpg{pmb1z?HLuMv+5U2UE7CLWK$yD#YbKMR9(i(XtH@00000NkvXXu0mjf D-}lyB literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..ca4fa2981418536a1024fbcfc7d972a73ccb9217 GIT binary patch literal 502 zcmVfGFcgNZNLBk5UaGt@ z0uNO-XcnllM>au9CTJF5gop`RIsr@onZWgLUHZ@#+ogM-4dOY%%?Up~T zR{ZY%;ftXR&rnAL&ZhuNguT%S`@_Kq^^>74+9*O_$>?x!sJ;P(bK#6vZ}>s6$b# zLn?t&3`xSWW8JmtfPIjCbIYiWPh2%h79cTA{gVMoeSW}Tp%$wO|j6@=lNF)-8L?V%N%QEeN<2VI46v6UfGw$mkU4V7K zTKL+stmfGLY5;8&tO0i8q}PpG*6OH3%YkizwH0y|SYXAv-R_;W zuCJMG-wymOmz!kyxj8mHJ$+{(vcbN>C zTv%9OFThromxF*}tb7X~FNX1w-SNB54h|2Umy1iz{Ra=F7?y=)254v4V%?P=xW`YP zN&_bvmK^}{N@r|LJS6ezS(~kv)D{<|MdCSqdhEhb!TY>j(*{m7Jhy6ymMnO1IOls= z`}UTVD;0Kfa%%L4U0GRX`}I1@O-_ovbV#OV;LsM~EiNswR_i(wS4TcS$9|rj zc|n{7h<2K?CnWfHo@IC_^aXuF-}upx>pL?B&>bEWvJ5W-Vj=Ai$4UXODE@-4*Vm25 zyeKj8$MLcFaVhCwJ5390@Nc$jUosC5g+8LML3L3M(4E`%r)M~K-LBO_1Nq-SzuB9O z4P|h!Y&-{^D@f=o`pjv@t&_Hy#=v+lY>@nXe%`sU?f(D2D_5kC`LN9XKo8D^=L{Qc zdyEfrIsk5p)3dP3@$ghYi%YVxLNgvdZ#^g$!Q3j7USQesVS{JsLGxk>!LgkLZRnr<6lyUD&hIW!N^&f zdodUm`PDR8Iy^eMJMP!N4}8wQ7ypJTSKjx(D??Z(ZgsDy0wCk(zA_aBqDpwZI^j~O zmSjTI_1m}*5lom=z)Mr7iHPw}%W_~CL{;!ir}W2KkpQM$c!`tG^NV}En5Z(JsI3L! z-W1*NR6|T!r$hj8my`~881uYhq6&zz>qG--n#yHwDo4o#N@p2NXHi0xQu>)l>C>VT zEo&NF)})1X+S*}$YljQ5DsCbVt6&eJ3V1xkDDcocHlbIOut>8%5aY#6?>%lA3)`WU z6@A*;0{h5+)UhmURyROgH=qrcNgZ)dxI367sfX^(Gb!{9eH65t=2kEnyY#Qv5bTro z*{RNB{r; literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..c6e5df439c98b49af55eb59700617e8239aa21fa GIT binary patch literal 2205 zcmV;O2x9k%P);qnxH^ee0Al^IKEO8;`HN3Kc#iuh{tnLmwn)) zj#HlCE~UY}bN74k@h7Lq!i7*b)Dd+}%G=OZ+2;Ctuxw~s_dbR=S65c7Cr_q@VbnTfcLtVxUu#*NiL%Z9yc8Ua+0HfPkQ%z#TF{$K|UR8yqNF-gF$&jtci@zf8 z!7^)JQ+LfPh2UPhwi+9U5-8Luuh4ebK%+Gq3llcFXl2)o`Pw2$72?*98yjzj8w4N^ zy%2Runat28T?up7ZPHzk+(R{{UawbZS?=d2Pu}^QgKl^5;fWL8mI?9QsKcmG$0DBv zn`qRc3|P5AuE<*Mak#!AtKgP_44ddS@==A`)GgNMZT#2k>xrxAt|2GE9SU{M`?(35 z2AUKBw@rC(7K6HiY_l#2UfiYzH#FtFlpwpPLxL9@85f(%qA#odc>m$U!XJnsMLbu1 zsL#AHp0t%+r;J&Rjq4L^beYUV2R4@-pL523Dr{$|ilKde=ih(*soK|tCmc_E_u!Go zHs2BUEt`;QPxkSH(%7nLV&cO@7p)w(-GA^qx>YiOe_6aw|W_~?b#)z?W-sWCr(~v2^O|V&5HV&8dVC9UnI|kg38=uwBb1;4vL~+6tQ$GF7;?Z0F_iQJ;{h!nLJZ0WJ!;&bvJe z1Ezf9TOP=D-^x2BShZGRz?9B{Dl&v1H* zc=@tjS-_n!Lr;ff;UZi1xsflm9ctTkd;%Uzv>J{*IPcuD@7C(l@!FBD)u#u*FzGtE>rT{-x z{qg$hYUSX%hMZudr(^)jwc%~m*sjQsiOSYi+mK-!&G>iQ)Ox$g)g)z?a`VQwxj$$z zN;)V#agYV_Vu7SG!8Z2U=WZ#;zTv7M7ljL3xboh> z0^phGbzZ|9qUV;~z%l zj}w-OGvZJV}sK?rYexdDpOudy~c%BH{^z3Ak2gm{cX)on&M|!HWlv=yRdi^??S*35TbB z)CA!6rENr|0n4<1E(vlSyKA&51@4^fThE+y(IZ{;lHkiJN0X{;7j<#=Y?5ryvhIBt z1f|N)2BGf5c2I!p)Pu8nGG7o~Q4dylvU1!917Rxf=8YSbfs4AL&Ixe)KHz%s;1N&8 z&bjO-4H^&pI10Qwt_I$8xFvft|hQn|#U;HM1kIFgA zPfO$li#nlhDRAR9`hrVFSezJS5H7H|vasN7sSw|taEZooZBnppKnQY6CSNmY{}x?0 z&R52%@}>_SS0zsv_Tb5b6mK1>C*y600gppQJia9~ms=*mMx4fxyk(anG1M7u7R&*4 zD9pXzrgT6JDZnG3XQVf`^8Q}{kB@4IhFLiKG3|Ij9GieA>*1-$f^z~Lsw2I&Cs_Hq zZQT|iB6>xB4^>4#H+5I%c~YC?{em?BjXMmMumtG=>c0H$uu3)yDhzu@#^`zZYTEF9 z8C&rPXQ!(1HK>ty19(%Gh6J89K6WI~uAH1W;Dm|AVzF2(7K_DVu~;k?i^XEGSS%Kc f#bQ-l{|Ybw$s^TDDN3Ji00000NkvXXu0mjf_-;FA literal 0 HcmV?d00001 diff --git a/Canary/Canary/Assets.xcassets/Mraid.imageset/Contents.json b/Canary/Canary/Assets.xcassets/Mraid.imageset/Contents.json new file mode 100644 index 000000000..8b6a1c506 --- /dev/null +++ b/Canary/Canary/Assets.xcassets/Mraid.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/Mraid.imageset/Mraid.png b/Canary/Canary/Assets.xcassets/Mraid.imageset/Mraid.png new file mode 100644 index 0000000000000000000000000000000000000000..a0d5f1e8a9f4ff70835bbf92d068134aa7df7226 GIT binary patch literal 452 zcmV;#0XzPQP)kj*X<;E+*(nwQ1v^m$K@jZ3R3bL%1TBM=AbzWWy{6D6 zqOBH+jgrbD#A5H_W;45!tgo4wch9@~c6N54@#qDzz;J|GYpehjAZ-JiBbbkAyo*p< z743%H3r^<%F`zYy@hea=<+p|=6=29x)FW=SjwVWC+0^9RQZO$rW#M+k<=n(J;im-PKuj!z znH6*67u;jE6;N0JzC(;BR9^@J9t12Hv@&oRP?zNI+ZnF{a9;UI(1<7BpeCxB=ipT3+&%voDcc0_yT_Z uYhsFSoUR`mEe!i1+=v66z!C7=i1{xVOksa~&lqR`0000Q#)VN4 zh}A|=A*A0;2vQVLyMD~FadGD4cR%mknKSp?dlx;c;hyh(zn8i5bIutk4$Z(rUUWFU55N{N z)}y=zJZJsKS=HzsP!^+U6ZKEjSXt;HJq z*p^UxOXzwdYAQlc*`qmqTtfq}Bn;mJH4geYp>4@gR}*^M+NH2}Dt3_hL|7*CnH)zY z@GaE-0P14u8_7ILi(v*QHez1S^6$%a#DfAO5^p-)DCGrJi$@i5%B(zr@wHE!#znfGzse~?* ztZ{N&j|oUU$pSN#Of#)5!%ShV=7396JWOjL-9n-cvuFq~i-_$G{q#b@b+wg&>(Z@7 z)Xq8dsm9~d&5~`gToF;*E9!11^sfM3sUCSRmMwaFVk)BUhN!ue(0fXGd)uC{B-s#p zD#lO5_!VVC$dYRdZY%InjMc|@=`FY`zQbywHa#1Fzv1tT#P0;lP0y%ZF7I@+ew+EE z2=XYQPe3^1Pts|(%G*BP$| zHiyTbfUll>3r-d0b9Vq-EJni(o_v;poxo4v4ransP*wpMVP%(UuT#{Y*^c$O$%C4S=Z=~pBHC_oqgv$`^}s=b7l-5_%6QLbDrm!v*$bW zp7*>1w{TP831AQy0yb9cr55M_z6F}m=RB~WQb#pFFOvfjf1VB{-Uua>6U&urqoKs> zIw;G;_shUfp~UMtC`-imlfa*$#OpXHGl6@=wV#3Op$ybWcSl;M9JESDFEGJm zu=OSRXW|UZ`jHIUK3Cc`BL29|M5JFUECw!+jzW8K^4tMj6ddm;?Rpfg3z3gnU>-0+ z+TmldAJEN$0VbdLkKJ7{~uQN>gf z=yT*dAb2K#u4&r$S@b`3)Gv%f7DI0o;QUN*odVih^x0wYaj1X~C+x8p*_iysYIkSPUEk4(J@Hho4Nl)!K7U}k+@qys}5t!w`a~CGnAia)b z?!yc2Ll2bB14}(=G=MJz|3Cr0r5JzOKXFkZr@(E%8HM~GV0BUQ1mm3Us!@wG5+R=As( z`^$xX3(;Gij7sbkI#+@D9{Jxyzn{!MQCqaS&!b6+BSPzR2^#y*@2h3hYny6EOE4Lu z4LB$CJ}ybiwt=;-U(a9yCfVUD{HHCGxEHu2H1mY_dC-Q*s3K0;CA2Q#VQW7Pa>XezLPxY8 z37vD48z|n}qkw8<C}Hd3^MtU6bfD-Xu{7NYVcsLy%3p@$=$Ci?7nF4q_CoPdO<1ou$|!`?keTu=PNvq|HgVrR^jY*Pt9b*r>PYRauNNJ(Yg-)&`Riz3RodFhLT#vZ9c_#} z71y#X^dhFxIuEpp>oNS*%XmXCaE(cmwyeC&e4QZuDoZQKQ-Ev0<4&#v;6cnxcpW{r z+fKSH^nFLIxslF+NcX121Elv*+bVUWAoNR6i!mi*Noeo#REp4pB43#N3-iRN6+#b+ z+~538!1s-_BIKYnfQ91P2yi8!hfD}LC|#H;$RxfGsGk`j2W27Y@cX-f`k4`OP_$~t zp@90e!~&)!lh07#nBo6~jhNXngxfoYw@?*s0J~co)t>$!S^xk507*qoM6N<$f{3E1 A^Z)<= literal 0 HcmV?d00001 diff --git a/Canary/Canary/Base.lproj/Main.storyboard b/Canary/Canary/Base.lproj/Main.storyboard index 67ddaf443..92dc26769 100644 --- a/Canary/Canary/Base.lproj/Main.storyboard +++ b/Canary/Canary/Base.lproj/Main.storyboard @@ -1,11 +1,11 @@ - + - + @@ -61,10 +61,17 @@ - + + + + + + + + @@ -72,6 +79,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -80,75 +130,47 @@ - + - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + - - - - - - - - - - - - + - + - + - - - - - - + + + + + + + + - - - + @@ -382,10 +404,17 @@ - + + + + + + + + @@ -395,7 +424,12 @@ + + + + + diff --git a/Canary/Canary/Base/AdUnit.swift b/Canary/Canary/Base/AdUnit.swift index 257591a58..b9b14b52a 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,11 @@ public struct AdUnitKey { static let UserDataKeywords: String = "userDataKeywords" static let CustomData: String = "custom_data" static let OverrideClass: String = "override_class" + + /** + Ad Unit ID to use when the current interface idiom is `pad` + */ + static let OverridePadId: String = "overridePadAdUnitId" } /** @@ -58,14 +63,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 +86,30 @@ 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 == "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) + } } diff --git a/Canary/Canary/Base/AdUnitDataSource.swift b/Canary/Canary/Base/AdUnitDataSource.swift index a3c97d6bb..9b9cd68d0 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/ // diff --git a/Canary/Canary/Base/AdUnitTableViewController.swift b/Canary/Canary/Base/AdUnitTableViewController.swift index 230a4f98e..58efb6289 100644 --- a/Canary/Canary/Base/AdUnitTableViewController.swift +++ b/Canary/Canary/Base/AdUnitTableViewController.swift @@ -1,16 +1,18 @@ // // 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 class AdUnitTableViewController: UIViewController { // Outlets from `Main.storyboard` @IBOutlet weak var tableView: UITableView! + @IBOutlet var cameraButton: UIBarButtonItem? // Table data source. fileprivate var dataSource: AdUnitDataSource? = nil @@ -38,6 +40,12 @@ class AdUnitTableViewController: UIViewController { tableView.delegate = self } + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + showCameraButtonBasedOnCameraAccess() + } + // MARK: - Ad Loading public func loadAd(with adUnit: AdUnit) { @@ -46,7 +54,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 +68,28 @@ class AdUnitTableViewController: UIViewController { dataSource?.reloadData() tableView.reloadData() } + + // MARK: - Camera Button + + // Shows or hides the camera button based on whether the user has provided camera access. + fileprivate func showCameraButtonBasedOnCameraAccess() { + guard let cameraButton = cameraButton else { + return + } + + let cameraAuthorizationStatus = AVCaptureDevice.authorizationStatus(for: QRCodeCameraInterfaceViewController.defaultMediaType) + + switch cameraAuthorizationStatus { + case .authorized: + navigationItem.rightBarButtonItem = cameraButton + case .denied: + navigationItem.rightBarButtonItem = nil + case .restricted: + navigationItem.rightBarButtonItem = nil + case .notDetermined: + navigationItem.rightBarButtonItem = cameraButton + } + } } extension AdUnitTableViewController: UITableViewDataSource { 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/Cells/AdActionsTableViewCell.swift b/Canary/Canary/Cells/AdActionsTableViewCell.swift index 827382819..d4186de77 100644 --- a/Canary/Canary/Cells/AdActionsTableViewCell.swift +++ b/Canary/Canary/Cells/AdActionsTableViewCell.swift @@ -1,7 +1,7 @@ // // 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/ // diff --git a/Canary/Canary/Cells/AdUnitTableViewCell.swift b/Canary/Canary/Cells/AdUnitTableViewCell.swift index 8dfa49999..c08e78f0f 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/ // 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..c7edd2161 100644 --- a/Canary/Canary/Cells/BasicMenuTableViewCell.swift +++ b/Canary/Canary/Cells/BasicMenuTableViewCell.swift @@ -1,7 +1,7 @@ // // 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/ // diff --git a/Canary/Canary/Cells/StatusTableViewCell.swift b/Canary/Canary/Cells/StatusTableViewCell.swift index 9d94bdee9..b3aa24feb 100644 --- a/Canary/Canary/Cells/StatusTableViewCell.swift +++ b/Canary/Canary/Cells/StatusTableViewCell.swift @@ -1,7 +1,7 @@ // // 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/ // @@ -30,7 +30,8 @@ class StatusTableViewCell: UITableViewCell { // Update text highlighted state nameLabel.textColor = isHighlighted ? .black : .lightGray - + accessoryType = isHighlighted ? .checkmark : .none + // Update the visible state of the message label messageLabel.isHidden = (error == nil) diff --git a/Canary/Canary/Cells/TextEntryTableViewCell.swift b/Canary/Canary/Cells/TextEntryTableViewCell.swift index 78b1bb650..1d9629a61 100644 --- a/Canary/Canary/Cells/TextEntryTableViewCell.swift +++ b/Canary/Canary/Cells/TextEntryTableViewCell.swift @@ -1,7 +1,7 @@ // // 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/ // diff --git a/Canary/Canary/Cells/TweetCollectionViewCell.swift b/Canary/Canary/Cells/TweetCollectionViewCell.swift index 2ad073962..148be85b3 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! @@ -69,6 +70,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/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/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..4e8137da2 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/ // diff --git a/Canary/Canary/Extensions/MPConsentStatus+Description.swift b/Canary/Canary/Extensions/MPConsentStatus+Description.swift index 837938064..b8d6988cc 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/ // 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/UIView+Nib.swift b/Canary/Canary/Extensions/UIView+Nib.swift index 23eb0a0c2..75c61d859 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/ // diff --git a/Canary/Canary/Formats/AdDataSource.swift b/Canary/Canary/Formats/AdDataSource.swift index 132234dbd..fc1f69264 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/ // diff --git a/Canary/Canary/Formats/AdFormat.swift b/Canary/Canary/Formats/AdFormat.swift index 31453757e..9522da2e6 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/ // diff --git a/Canary/Canary/Formats/AdTableViewController.swift b/Canary/Canary/Formats/AdTableViewController.swift index f68dab60d..2e4a4664d 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/ // 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..3b4568616 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/ // diff --git a/Canary/Canary/Formats/BannerAdViewController.swift b/Canary/Canary/Formats/BannerAdViewController.swift index a99ace220..07eb84788 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/ // diff --git a/Canary/Canary/Formats/BaseNativeAdDataSource.swift b/Canary/Canary/Formats/BaseNativeAdDataSource.swift index 8b9214631..934794981 100644 --- a/Canary/Canary/Formats/BaseNativeAdDataSource.swift +++ b/Canary/Canary/Formats/BaseNativeAdDataSource.swift @@ -1,7 +1,7 @@ // // 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/ // diff --git a/Canary/Canary/Formats/InterstitialAdDataSource.swift b/Canary/Canary/Formats/InterstitialAdDataSource.swift index 468d87760..d1fa1a531 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/ // 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 index d3fc436ce..6a739fb69 100644 --- a/Canary/Canary/Formats/LeaderboardAdViewController.swift +++ b/Canary/Canary/Formats/LeaderboardAdViewController.swift @@ -1,7 +1,7 @@ // // LeaderboardAdViewController.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/MediumRectangleAdViewController.swift b/Canary/Canary/Formats/MediumRectangleAdViewController.swift index 1438c5c3d..6c0a72a89 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/ // diff --git a/Canary/Canary/Formats/NativeAdCollectionDataSource.swift b/Canary/Canary/Formats/NativeAdCollectionDataSource.swift index bf4379470..4f754e0fb 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/ // diff --git a/Canary/Canary/Formats/NativeAdCollectionViewController.swift b/Canary/Canary/Formats/NativeAdCollectionViewController.swift index 0631d580a..d10e10afc 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! @@ -123,7 +128,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 +139,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 +169,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..11a693b0f 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/ // 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..40fbb8a71 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/ // diff --git a/Canary/Canary/Formats/NativeAdView.swift b/Canary/Canary/Formats/NativeAdView.swift index ffe74d1f3..2c57db5ea 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/ // 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..a679af030 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/ // 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/Info.plist b/Canary/Canary/Info.plist index 9028023f0..d4775f945 100644 --- a/Canary/Canary/Info.plist +++ b/Canary/Canary/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 5.4.1 + 5.5.0 CFBundleURLTypes @@ -28,7 +28,7 @@ CFBundleVersion - 5.4.1 + 5.5.0 LSRequiresIPhoneOS NSAppTransportSecurity @@ -67,5 +67,7 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + NSCameraUsageDescription + The MoPub Canary app uses the camera for the ease-of-use feature of reading MoPub ad QR codes. diff --git a/Canary/Canary/LoggingLevel/LogingLevelMenuDataSource.swift b/Canary/Canary/LoggingLevel/LogingLevelMenuDataSource.swift index 1e0954f5b..fa8aefcb7 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 { 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 MPLogLevel.debug + case .info: return MPLogLevel.info + case .none: return MPLogLevel.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 { @@ -61,7 +51,7 @@ extension LogingLevelMenuDataSource: MenuDisplayable { func cell(forItem index: Int, inTableView tableView: UITableView) -> UITableViewCell { let cell: BasicMenuTableViewCell = basicMenuCell(inTableView: tableView) let item: LoggingLevelMenuOptions = items[index] - let currentLogLevel: MPLogLevel = MoPub.sharedInstance().logLevel + let currentLogLevel: MPLogLevel = MPLogging.consoleLogLevel cell.accessoryType = (currentLogLevel == item.logLevel ? .checkmark : .none) cell.title.text = item.rawValue @@ -87,7 +77,7 @@ extension LogingLevelMenuDataSource: MenuDisplayable { */ func didSelect(itemAt index: Int, inTableView tableView: UITableView, presentFrom viewController: UIViewController) -> Swift.Void { let item: LoggingLevelMenuOptions = items[index] - MoPub.sharedInstance().logLevel = item.logLevel + MPLogging.consoleLogLevel = item.logLevel tableView.reloadData() } diff --git a/Canary/Canary/MainTabBarController.swift b/Canary/Canary/MainTabBarController.swift index 6a9ed78f6..7a44184b1 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 { /** @@ -40,9 +39,6 @@ class MainTabBarController: UITabBarController { notificationButton.trailingAnchor.constraint(equalTo: view.trailingAnchor), notificationButton.bottomAnchor.constraint(equalTo: tabBar.topAnchor) ]) - - // 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 +69,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/Menu/MenuDataSource.swift b/Canary/Canary/Menu/MenuDataSource.swift index 124147560..4c68baa9f 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,7 +28,6 @@ class MenuDataSource { // MARK: - Initialization init() { - add(menu: PrivacyMenuDataSource()) add(menu: LogingLevelMenuDataSource()) } diff --git a/Canary/Canary/Menu/MenuDisplayable.swift b/Canary/Canary/Menu/MenuDisplayable.swift index 3f55a0a30..e74e4f5c7 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/ // diff --git a/Canary/Canary/Menu/MenuViewController.swift b/Canary/Canary/Menu/MenuViewController.swift index 4f451a773..d0232e8e7 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/ // @@ -86,7 +86,7 @@ extension MenuViewController: UITableViewDelegate { 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) + container.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..42c23be3e --- /dev/null +++ b/Canary/Canary/QRCodeCameraInterfaceViewController.swift @@ -0,0 +1,204 @@ +// +// 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") + + /** + The shared app delegate. This is used to access deep linking functionality contained in app delegate when + `metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection)` is called. + */ + let appDelegate = UIApplication.shared.delegate as? AppDelegate + + // 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) { + 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), + let appDelegate = appDelegate, + 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(completion: { + // Open `adUnit` via the same path that deep linking uses + _ = appDelegate.openMoPubAdUnit(adUnit: adUnit, + onto: appDelegate.savedAdSplitViewController, + shouldSave: true) + }) + } + break + } + } + +} diff --git a/Canary/Canary/RoundedButton.swift b/Canary/Canary/RoundedButton.swift index 0df331d55..b0f96cb79 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/ // diff --git a/Canary/Canary/Samples/SampleAds.plist b/Canary/Canary/Samples/SampleAds.plist index dfe83e58e..64d4044d6 100644 --- a/Canary/Canary/Samples/SampleAds.plist +++ b/Canary/Canary/Samples/SampleAds.plist @@ -18,7 +18,7 @@ name MRAID Banner adUnitId - 23b49916add211e281c11231392559e4 + ef078b27e11c49bbb87080617a69b970 name @@ -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..3ad3df742 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/ // diff --git a/Canary/Canary/SavedAds/SavedAdsManager.swift b/Canary/Canary/SavedAds/SavedAdsManager.swift index c2ead8ebc..64f3261c5 100644 --- a/Canary/Canary/SavedAds/SavedAdsManager.swift +++ b/Canary/Canary/SavedAds/SavedAdsManager.swift @@ -1,7 +1,7 @@ // // 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/ // diff --git a/Canary/Canary/SavedAds/SavedAdsViewController.swift b/Canary/Canary/SavedAds/SavedAdsViewController.swift index f73bf0118..4d629c2f0 100644 --- a/Canary/Canary/SavedAds/SavedAdsViewController.swift +++ b/Canary/Canary/SavedAds/SavedAdsViewController.swift @@ -1,7 +1,7 @@ // // 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/ // 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..6a9b4a29d 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/ // diff --git a/MoPubResources/Info.plist b/MoPubResources/Info.plist index 310ac5acc..e53aaba71 100644 --- a/MoPubResources/Info.plist +++ b/MoPubResources/Info.plist @@ -13,7 +13,7 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 5.4.1 + 5.5.0 CFBundleSignature ???? CFBundleSupportedPlatforms @@ -21,9 +21,9 @@ iPhoneOS CFBundleVersion - 5.4.1 + 5.5.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 07bf58dae..8d3a0a407 100644 --- a/MoPubSDK.xcodeproj/project.pbxproj +++ b/MoPubSDK.xcodeproj/project.pbxproj @@ -31,7 +31,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 */; }; @@ -118,7 +117,7 @@ 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 */; }; @@ -248,7 +247,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, ); }; }; @@ -377,7 +375,7 @@ 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 */; }; @@ -435,7 +433,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 */; }; @@ -637,9 +635,7 @@ 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 */; }; BC19E33120DC1E7700673D60 /* MPBannerCustomEventAdapter+Testing.m in Sources */ = {isa = PBXBuildFile; fileRef = BC19E33020DC1E7700673D60 /* MPBannerCustomEventAdapter+Testing.m */; }; BC19E33320DC1F9A00673D60 /* MPBannerAdManagerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = BC19E33220DC1F9A00673D60 /* MPBannerAdManagerTests.m */; }; @@ -651,10 +647,9 @@ 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 */; }; @@ -663,13 +658,17 @@ 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 */; }; + 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 */; }; 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 */; }; 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 */; }; @@ -689,6 +688,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 */; }; @@ -703,6 +703,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 */; }; @@ -720,6 +721,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 */; }; + 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 */; }; BCA00AF11EF47A91006FF762 /* MPInternalUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 4678267819903BF300DB1B61 /* MPInternalUtils.m */; }; BCA00AF31EF47A91006FF762 /* MPBannerAdManager.m in Sources */ = {isa = PBXBuildFile; fileRef = AEF9D4CA171CA895005AAA5A /* MPBannerAdManager.m */; }; BCA00AF41EF47A91006FF762 /* MPBannerCustomEventAdapter.m in Sources */ = {isa = PBXBuildFile; fileRef = AEF9D4CD171CA895005AAA5A /* MPBannerCustomEventAdapter.m */; }; @@ -780,7 +784,7 @@ 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 */; }; @@ -804,6 +808,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 */; }; @@ -815,6 +823,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 /* MPLogger.h in Headers */ = {isa = PBXBuildFile; fileRef = BCAD76A6214AE05B00A1B067 /* MPLogger.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 */; }; @@ -827,17 +836,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 */; }; @@ -976,8 +988,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 = ""; }; @@ -1361,11 +1373,8 @@ 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 = ""; }; 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 = ""; }; @@ -1386,14 +1395,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 = ""; }; @@ -1401,15 +1408,18 @@ 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 = ""; }; @@ -1432,22 +1442,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 = ""; }; @@ -1455,6 +1468,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 /* MPLogger.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPLogger.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 = ""; }; @@ -1474,9 +1488,13 @@ 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 = ""; }; @@ -1590,7 +1608,6 @@ B2C423B61FA3B5FE00D8E164 /* MPAdImpressionTimerTests.m */, 2A4A5D211F86DF270082FC4C /* MPAdServerCommunicatorTests.m */, BC181D651ECE4DD6009C752C /* MPAdServerURLBuilderTests.m */, - BC12D247206304AE0073388B /* MPAdvancedBiddingManagerTests.m */, BC7F0F181ECF9BF800BB087E /* MPAdViewTests.m */, BC19E33220DC1F9A00673D60 /* MPBannerAdManagerTests.m */, B2C423BA1FA7A38500D8E164 /* MPBannerCustomEventAdapterTests.m */, @@ -1607,6 +1624,7 @@ BC41F72120DAF23C004BE29C /* MPMemoryCacheTests.m */, 2A75215B1F70720F0099C33C /* MPMoPubNativeAdAdapterTests.m */, BCAED2531DF6194E00D45480 /* MPMoPubRewardedPlayableCustomEventTests.m */, + BC8CDD64218A12DD006DE606 /* MPMoPubConfigurationTests.m */, 2A5C4DB71F6B25F20076C08C /* MPNativeAdConfigValuesTests.m */, BCC54C2B1ECFB81000A4FEF0 /* MPNativeAdRequestTests.m */, BCCE7A2A20768922003027BA /* MPReachabilityTests.m */, @@ -1703,8 +1721,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 */, @@ -1944,6 +1960,7 @@ isa = PBXGroup; children = ( AEF9D4C7171CA895005AAA5A /* Internal */, + BCCEE888214AF8A9003BD130 /* Logging */, A71DB8111A2FE68300D3B229 /* MoPub.h */, A71DB8121A2FE68300D3B229 /* MoPub.m */, 2A2701EE2020FCDC004A72E6 /* MOPUBDisplayAgentType.h */, @@ -1951,12 +1968,16 @@ B2D54B551ED20521004E3C7B /* MOPUBExperimentProvider.m */, AEF9D52E171CA895005AAA5A /* MPAdConversionTracker.h */, AEF9D52F171CA895005AAA5A /* MPAdConversionTracker.m */, - BC64149E1F84137300D067F6 /* MPAdvancedBidder.h */, + BC7F42842141CE7E007EC273 /* MPAdTargeting.h */, + BC7F42852141CE7E007EC273 /* MPAdTargeting.m */, + BC4A4D13217F99ED008A7410 /* MPAdapterConfiguration.h */, AEF9D530171CA895005AAA5A /* MPAdView.h */, AEF9D531171CA895005AAA5A /* MPAdView.m */, AEF9D532171CA895005AAA5A /* MPBannerCustomEvent.h */, AEF9D533171CA895005AAA5A /* MPBannerCustomEvent.m */, AEF9D534171CA895005AAA5A /* MPBannerCustomEventDelegate.h */, + BC4A4D1B21827287008A7410 /* MPBaseAdapterConfiguration.h */, + BC4A4D1C21827287008A7410 /* MPBaseAdapterConfiguration.m */, BC437057208A8AC9001B86D4 /* MPBool.h */, BC119223207D211B005DF26E /* MPConsentChangedNotification.h */, BC119224207D211B005DF26E /* MPConsentChangedNotification.m */, @@ -1971,13 +1992,9 @@ AEF9D538171CA895005AAA5A /* MPInterstitialCustomEvent.h */, AEF9D539171CA895005AAA5A /* MPInterstitialCustomEvent.m */, AEF9D53A171CA895005AAA5A /* MPInterstitialCustomEventDelegate.h */, - BC79EC231F9810BF00FFC893 /* MPLogLevel.h */, - BC64EC5520696F7800CB33A7 /* MPMediationSdkInitializable.h */, BC27343D203F51C900920507 /* MPMediationSettingsProtocol.h */, 2AD9F9E01F8547DF00E0A5F0 /* MPMoPubConfiguration.h */, 2AD9F9D51F8547DE00E0A5F0 /* MPMoPubConfiguration.m */, - BC7F42842141CE7E007EC273 /* MPAdTargeting.h */, - BC7F42852141CE7E007EC273 /* MPAdTargeting.m */, A77FBED818C533B400531E8A /* NativeAds */, B27114991B8549ED0066CE82 /* NativeVideo */, AEF9D53B171CA895005AAA5A /* Resources */, @@ -1994,8 +2011,6 @@ AEF9D4D3171CA895005AAA5A /* Common */, AEF9D4E6171CA895005AAA5A /* HTML */, AEF9D4F1171CA895005AAA5A /* Interstitials */, - BCAAD10F1F85696A00047435 /* MPAdvancedBiddingManager.h */, - BCAAD1041F85696700047435 /* MPAdvancedBiddingManager.m */, 4A5968C018CFE71200D0D1AD /* MPCoreInstanceProvider.h */, 4A5968C118CFE71200D0D1AD /* MPCoreInstanceProvider.m */, 2A4D35DA211CBF5100BE9377 /* MPCoreInstanceProvider+MRAID.h */, @@ -2096,6 +2111,8 @@ children = ( AEF9D4E9171CA895005AAA5A /* MPAdWebViewAgent.h */, AEF9D4EA171CA895005AAA5A /* MPAdWebViewAgent.m */, + BCDECB11219B4628002C1E7A /* MPContentBlocker.h */, + BCDECB10219B4627002C1E7A /* MPContentBlocker.m */, AEF9D4EB171CA895005AAA5A /* MPHTMLBannerCustomEvent.h */, AEF9D4EC171CA895005AAA5A /* MPHTMLBannerCustomEvent.m */, AEF9D4ED171CA895005AAA5A /* MPHTMLInterstitialCustomEvent.h */, @@ -2163,8 +2180,6 @@ AEF9D513171CA895005AAA5A /* Utility */ = { isa = PBXGroup; children = ( - 4AB5CCCE19F55596005ABBC1 /* MPLogProvider.h */, - 4AB5CCCF19F55596005ABBC1 /* MPLogProvider.m */, AEF9D514171CA895005AAA5A /* Categories */, AEF9D51B171CA895005AAA5A /* MPAnalyticsTracker.h */, AEF9D51C171CA895005AAA5A /* MPAnalyticsTracker.m */, @@ -2174,8 +2189,6 @@ AEF9D520171CA895005AAA5A /* MPGlobal.m */, AEF9D521171CA895005AAA5A /* MPIdentityProvider.h */, AEF9D522171CA895005AAA5A /* MPIdentityProvider.m */, - AEF9D523171CA895005AAA5A /* MPLogging.h */, - AEF9D524171CA895005AAA5A /* MPLogging.m */, AEF9D525171CA895005AAA5A /* MPReachability.h */, AEF9D526171CA895005AAA5A /* MPReachability.m */, AEF9D527171CA895005AAA5A /* MPSessionTracker.h */, @@ -2252,6 +2265,7 @@ 57401A431B7AAEF0000EEA64 /* MPDAAIcon@2x.png */, 57401A441B7AAEF0000EEA64 /* MPDAAIcon@3x.png */, BCF0FA8A1DC9512900ADFE4F /* MPCountdownTimer.html */, + BC9EF9DE216811B3005BEA65 /* MPAdapters.plist */, ); path = Resources; sourceTree = ""; @@ -2287,15 +2301,22 @@ 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 = ""; @@ -2316,8 +2337,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 */, @@ -2336,6 +2355,8 @@ 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 */, @@ -2369,16 +2390,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 */, @@ -2391,6 +2415,8 @@ BC246A7E1E567D1500CEFA33 /* MPMockRewardedVideoAdapter.m */, BC0BE6AE20D43B9B00DB0D2C /* MPMockRewardedVideoCustomEvent.h */, BC0BE6AF20D43B9B00DB0D2C /* MPMockRewardedVideoCustomEvent.m */, + BC4A4D15218125D3008A7410 /* MPMockTapjoyAdapterConfiguration.h */, + BC4A4D16218125D3008A7410 /* MPMockTapjoyAdapterConfiguration.m */, BC2C5B581F5727D40025FFF5 /* MPMockViewabilityAdapterAvid.h */, BC2C5B591F5727D40025FFF5 /* MPMockViewabilityAdapterAvid.m */, ); @@ -2422,6 +2448,20 @@ name = "Delegate Handlers"; sourceTree = ""; }; + BCCEE888214AF8A9003BD130 /* Logging */ = { + isa = PBXGroup; + children = ( + BC31026C21598E070007FA69 /* Internal */, + BCA762302149B4B100D55A05 /* MPLogEvent.h */, + BCA762312149B4B100D55A05 /* MPLogEvent.m */, + BCAD76A6214AE05B00A1B067 /* MPLogger.h */, + AEF9D523171CA895005AAA5A /* MPLogging.h */, + AEF9D524171CA895005AAA5A /* MPLogging.m */, + BC79EC231F9810BF00FFC893 /* MPLogLevel.h */, + ); + path = Logging; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -2436,7 +2476,6 @@ 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 */, @@ -2467,6 +2506,7 @@ 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 */, @@ -2487,7 +2527,6 @@ 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 */, @@ -2504,7 +2543,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 */, @@ -2525,6 +2563,7 @@ 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 */, @@ -2577,17 +2616,19 @@ 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 /* MPLogger.h in Headers */, BC437058208A8AC9001B86D4 /* MPBool.h in Headers */, 2AF0325B2016B78800909F29 /* MRCommand.h in Headers */, 2AF0325C2016B78800909F29 /* MRProperty.h in Headers */, 2AF0325D2016B78800909F29 /* MRBridge.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 */, @@ -2614,7 +2655,6 @@ 2AF032742016B78800909F29 /* MPGeolocationProvider.h in Headers */, 2AF032752016B78800909F29 /* MPVASTAd.h in Headers */, 2AF032762016B78800909F29 /* MPVASTCompanionAd.h in Headers */, - BCD1188C2034E04100C03B7D /* MPAdvancedBiddingManager.h in Headers */, 2AF032772016B78800909F29 /* MPVASTCreative.h in Headers */, 2AF032782016B78800909F29 /* MPVASTDurationOffset.h in Headers */, 2A2701EF2020FD68004A72E6 /* MPViewabilityOption.h in Headers */, @@ -2622,7 +2662,9 @@ 2AF0327A2016B78800909F29 /* MPVASTInline.h in Headers */, 2AF0327B2016B78800909F29 /* MPVASTLinearAd.h in Headers */, 2AF0327C2016B78800909F29 /* MPVASTMacroProcessor.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 */, @@ -2780,6 +2822,8 @@ 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 */, 2AA73BA11FCF8AD6001FB787 /* MPDAAIcon@3x.png in Resources */, @@ -2815,6 +2859,7 @@ 2A711FEE202267B6007A2412 /* MPDAAIcon@2x.png in Resources */, 2A711FEF202267B6007A2412 /* MPDAAIcon@3x.png in Resources */, 2A711FF0202267B6007A2412 /* MPCountdownTimer.html in Resources */, + BC9EF9E0216811B3005BEA65 /* MPAdapters.plist in Resources */, 2A711FDC20226737007A2412 /* MPAdBrowserController.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -2843,6 +2888,7 @@ 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; @@ -2887,6 +2933,7 @@ BC11922A207D6D06005DF26E /* MPConsentManagerTests.m in Sources */, BCC79EC4204DC28B00F7ABE6 /* MPURLRequestTests.m in Sources */, BCF9BDBA2119FA7800A2F557 /* NSURLSessionTask+Testing.m in Sources */, + BC4A4D17218125D3008A7410 /* MPMockTapjoyAdapterConfiguration.m in Sources */, BC41F72220DAF23C004BE29C /* MPMemoryCacheTests.m in Sources */, 2AFF1BA71EC289E600495994 /* MPRealTimeTimerTests.m in Sources */, 2A922D3A1E9C4CF900F83923 /* MPInterstitialCustomEventAdapterTests.m in Sources */, @@ -2895,7 +2942,7 @@ BCDE9ED71DF0AF970034A444 /* MPAdConfigurationTests.m in Sources */, 2AF177331E9846A000A600BD /* MPRewardedVideoAdapterTests.m in Sources */, BC41F72020DAD554004BE29C /* MPMockAdDestinationDisplayAgent.m in Sources */, - BC12D24B206304FA0073388B /* MPAdvancedBiddingManager+Testing.m in Sources */, + BC12D24B206304FA0073388B /* MPMediationManager+Testing.m in Sources */, BC86211C1DCBBD5A0012275D /* MPCountdownTimerViewTests.m in Sources */, BCC54C241ECFACE200A4FEF0 /* MPInterstitialAdControllerTests.m in Sources */, BC7F0F1F1ECF9E5100BB087E /* MPMockAdServerCommunicator.m in Sources */, @@ -2904,7 +2951,6 @@ 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 */, @@ -2928,10 +2974,10 @@ 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 */, 2AA73B9C1FCF7EDB001FB787 /* MOPUBNativeVideoAdConfigValuesTests.m in Sources */, @@ -2941,7 +2987,6 @@ 2A7F96FA1E6646DC00114565 /* MPViewabilityTrackerTests.m in Sources */, BC0BE6B320D4699100DB0D2C /* MPMockNativeCustomEvent.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 */, @@ -2965,16 +3010,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 */, @@ -3005,9 +3051,9 @@ 2A27021C20214502004A72E6 /* MPMoPubRewardedPlayableCustomEvent.m in Sources */, 2A27021D20214502004A72E6 /* MPRewardedVideoAdapter.m in Sources */, 2A27021E20214502004A72E6 /* MPRewardedVideoAdManager.m in Sources */, + BC4A4D2021827287008A7410 /* MPBaseAdapterConfiguration.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 */, @@ -3038,6 +3084,7 @@ 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 */, @@ -3061,6 +3108,7 @@ 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 */, @@ -3100,7 +3148,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 */, @@ -3111,7 +3158,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 */, @@ -3152,6 +3199,7 @@ 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 */, 2A2702A820214502004A72E6 /* MPCoreInstanceProvider.m in Sources */, 2A2702AA20214502004A72E6 /* MPViewabilityTracker.m in Sources */, @@ -3199,6 +3247,7 @@ AE515F49171F1B110086B464 /* MPAdBrowserController.m in Sources */, AE515F4A171F1B110086B464 /* MPAdConfiguration.m in Sources */, A7A1CDDD197478E20082A6FA /* MPNativeAdData.m in Sources */, + BC4A4D1E21827287008A7410 /* MPBaseAdapterConfiguration.m in Sources */, A776A54E1B5DDFCA00095706 /* MPVASTCreative.m in Sources */, AE515F4B171F1B110086B464 /* MPAdDestinationDisplayAgent.m in Sources */, AE515F4C171F1B110086B464 /* MPAdServerCommunicator.m in Sources */, @@ -3232,6 +3281,7 @@ 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 */, @@ -3255,6 +3305,7 @@ AE515F58171F1B110086B464 /* MPInterstitialCustomEventAdapter.m in Sources */, A7A1CDE71974904A0082A6FA /* MPStreamAdPlacementData.m in Sources */, A714FE641B699587000EAEC4 /* MPVASTStringUtilities.m in Sources */, + BCA762332149B4B100D55A05 /* MPLogEvent.m in Sources */, 57AC69621BB21FA20053C556 /* MOPUBNativeVideoImpressionAgent.m in Sources */, AE515F59171F1B110086B464 /* MPInterstitialViewController.m in Sources */, 57AC698A1BB21FFC0053C556 /* MPNativeAdRendererImageHandler.m in Sources */, @@ -3263,7 +3314,6 @@ 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 */, @@ -3300,7 +3350,6 @@ 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 */, BC0ADDF820810B3C000ADEA4 /* MPConsentDialogViewController.m in Sources */, @@ -3320,7 +3369,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 */, @@ -3346,6 +3395,7 @@ 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 */, 57AC69561BB21FA20053C556 /* MOPUBNativeVideoAdConfigValues.m in Sources */, AEAFC85F1798615C007F5911 /* MRBundleManager.m in Sources */, @@ -3405,6 +3455,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 */, @@ -3418,6 +3469,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 */, @@ -3428,14 +3480,15 @@ 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 */, 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 */, 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 */, @@ -3445,7 +3498,7 @@ BCA00B611EF47A91006FF762 /* MPMoPubRewardedPlayableCustomEvent.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 */, @@ -3471,7 +3524,6 @@ 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 */, BCA00B911EF47A91006FF762 /* MPAdAlertManager.m in Sources */, @@ -3503,7 +3555,7 @@ CLANG_WARN_SUSPICIOUS_MOVES = YES; CLANG_WARN_UNREACHABLE_CODE = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 5.4.1; + CURRENT_PROJECT_VERSION = 5.5.0; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 4S7XS533V3; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -3544,7 +3596,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 5.4.1; + CURRENT_PROJECT_VERSION = 5.5.0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 4S7XS533V3; ENABLE_NS_ASSERTIONS = NO; @@ -3574,6 +3626,7 @@ 2AF030C62016723700909F29 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + BITCODE_GENERATION_MODE = bitcode; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -3585,12 +3638,12 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 5.4.1; + CURRENT_PROJECT_VERSION = 5.5.0; DEBUG_INFORMATION_FORMAT = dwarf; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 4S7XS533V3; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 5.4.1; + DYLIB_CURRENT_VERSION = 5.5.0; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_PREPROCESSOR_DEFINITIONS = ( @@ -3610,7 +3663,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.mopub.MoPubSDKFramework; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 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; @@ -3618,6 +3671,7 @@ 2AF030C72016723700909F29 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + BITCODE_GENERATION_MODE = bitcode; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -3630,12 +3684,12 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 5.4.1; + CURRENT_PROJECT_VERSION = 5.5.0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 4S7XS533V3; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 5.4.1; + DYLIB_CURRENT_VERSION = 5.5.0; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_NS_ASSERTIONS = NO; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -3655,7 +3709,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.mopub.MoPubSDKFramework; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; TARGETED_DEVICE_FAMILY = "1,2"; - VALID_ARCHS = "$(ARCHS_STANDARD) x86_64 i386"; + VALID_ARCHS = "$(ARCHS_STANDARD) armv7s"; VERSIONING_SYSTEM = "apple-generic"; }; name = Release; @@ -3668,7 +3722,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.1; + CURRENT_PROJECT_VERSION = 5.5.0; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_GENERATE_DEBUGGING_SYMBOLS = NO; GCC_PREPROCESSOR_DEFINITIONS = ( @@ -3702,7 +3756,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.1; + CURRENT_PROJECT_VERSION = 5.5.0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -3729,7 +3783,7 @@ AE515F9A171F1B110086B464 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - CURRENT_PROJECT_VERSION = 5.4.1; + CURRENT_PROJECT_VERSION = 5.5.0; DSTROOT = /tmp/MoPubSDK.dst; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -3749,7 +3803,7 @@ AE515F9B171F1B110086B464 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - CURRENT_PROJECT_VERSION = 5.4.1; + CURRENT_PROJECT_VERSION = 5.5.0; DSTROOT = /tmp/MoPubSDK.dst; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -3866,7 +3920,7 @@ BCA00B951EF47A91006FF762 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - CURRENT_PROJECT_VERSION = 5.4.1; + CURRENT_PROJECT_VERSION = 5.5.0; DSTROOT = /tmp/MoPubSDK.dst; GCC_PRECOMPILE_PREFIX_HEADER = NO; GCC_TREAT_WARNINGS_AS_ERRORS = YES; @@ -3883,7 +3937,7 @@ BCA00B961EF47A91006FF762 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - CURRENT_PROJECT_VERSION = 5.4.1; + CURRENT_PROJECT_VERSION = 5.5.0; DSTROOT = /tmp/MoPubSDK.dst; GCC_PRECOMPILE_PREFIX_HEADER = NO; GCC_TREAT_WARNINGS_AS_ERRORS = YES; diff --git a/MoPubSDK.xcodeproj/xcshareddata/xcschemes/MoPubSDKFramework.xcscheme b/MoPubSDK.xcodeproj/xcshareddata/xcschemes/MoPubSDKFramework.xcscheme index 8b9805eb1..59d29355d 100644 --- a/MoPubSDK.xcodeproj/xcshareddata/xcschemes/MoPubSDKFramework.xcscheme +++ b/MoPubSDK.xcodeproj/xcshareddata/xcschemes/MoPubSDKFramework.xcscheme @@ -1,7 +1,7 @@ + version = "1.3"> @@ -76,23 +76,5 @@ - - - - - - - - - - diff --git a/MoPubSDK/Internal/Banners/MPBannerAdManager.h b/MoPubSDK/Internal/Banners/MPBannerAdManager.h index 0bf1fac2f..5df6bc8b6 100644 --- a/MoPubSDK/Internal/Banners/MPBannerAdManager.h +++ b/MoPubSDK/Internal/Banners/MPBannerAdManager.h @@ -1,7 +1,7 @@ // // MPBannerAdManager.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/MPBannerAdManager.m b/MoPubSDK/Internal/Banners/MPBannerAdManager.m index 0e648611e..8a3f1a040 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/ // @@ -99,12 +99,14 @@ - (BOOL)loading - (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; } @@ -214,25 +216,25 @@ - (void)fetchAdWithConfiguration:(MPAdConfiguration *)configuration { MPLogInfo(@"Banner ad view is fetching ad network type: %@", configuration.networkType); if (configuration.adType == MPAdTypeUnknown) { - [self didFailToLoadAdapterWithError:[MOPUBError errorWithCode:MOPUBErrorServerError]]; + [self didFailToLoadAdapterWithError:[NSError 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]]; + MPLogInfo(@"Could not load ad: banner object received an interstitial ad unit ID."); + [self didFailToLoadAdapterWithError:[NSError 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; } @@ -262,7 +264,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; } @@ -278,8 +280,6 @@ - (void)didFailToLoadAdapterWithError:(NSError *)error { [self.delegate managerDidFailToLoadAd]; [self scheduleRefreshTimer]; - - MPLogError(@"Banner view (%@) failed. Error: %@", [self.delegate adUnitId], error); } #pragma mark - @@ -343,6 +343,7 @@ - (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]; } } @@ -367,8 +368,9 @@ - (void)adapter:(MPBaseBannerAdapter *)adapter didFailToLoadAdWithError:(NSError } // 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]; } } @@ -401,6 +403,9 @@ - (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 +413,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,6 +424,8 @@ - (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]; } } diff --git a/MoPubSDK/Internal/Banners/MPBannerAdManagerDelegate.h b/MoPubSDK/Internal/Banners/MPBannerAdManagerDelegate.h index 1744f4ed0..8a98c4d2b 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/ // 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..eeedfabf0 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/ // diff --git a/MoPubSDK/Internal/Banners/MPBannerCustomEventAdapter.m b/MoPubSDK/Internal/Banners/MPBannerCustomEventAdapter.m index eefefc11e..55ca892c8 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" @@ -63,8 +64,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 +74,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]; } diff --git a/MoPubSDK/Internal/Banners/MPBaseBannerAdapter.h b/MoPubSDK/Internal/Banners/MPBaseBannerAdapter.h index 59c0513b9..2a350642c 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/ // diff --git a/MoPubSDK/Internal/Banners/MPBaseBannerAdapter.m b/MoPubSDK/Internal/Banners/MPBaseBannerAdapter.m index 1128543a4..541d655a5 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/ // @@ -91,7 +91,7 @@ - (void)startTimeoutTimer - (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 +101,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..3ed7fa0f5 100644 --- a/MoPubSDK/Internal/Common/AdAlerts/MPAdAlertGestureRecognizer.h +++ b/MoPubSDK/Internal/Common/AdAlerts/MPAdAlertGestureRecognizer.h @@ -1,7 +1,7 @@ // // 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/ // diff --git a/MoPubSDK/Internal/Common/AdAlerts/MPAdAlertGestureRecognizer.m b/MoPubSDK/Internal/Common/AdAlerts/MPAdAlertGestureRecognizer.m index 339bb1247..b96acddb6 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/ // 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..83d82d9d9 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 diff --git a/MoPubSDK/Internal/Common/MPAPIEndpoints.h b/MoPubSDK/Internal/Common/MPAPIEndpoints.h index afc1cddc6..f575be546 100644 --- a/MoPubSDK/Internal/Common/MPAPIEndpoints.h +++ b/MoPubSDK/Internal/Common/MPAPIEndpoints.h @@ -1,7 +1,7 @@ // // MPAPIEndpoints.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/MPAPIEndpoints.m b/MoPubSDK/Internal/Common/MPAPIEndpoints.m index 962640a0c..6aa7f6e5a 100644 --- a/MoPubSDK/Internal/Common/MPAPIEndpoints.m +++ b/MoPubSDK/Internal/Common/MPAPIEndpoints.m @@ -1,7 +1,7 @@ // // MPAPIEndpoints.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/MPActivityViewControllerHelper+TweetShare.h b/MoPubSDK/Internal/Common/MPActivityViewControllerHelper+TweetShare.h index 450b6579b..664a2acce 100644 --- a/MoPubSDK/Internal/Common/MPActivityViewControllerHelper+TweetShare.h +++ b/MoPubSDK/Internal/Common/MPActivityViewControllerHelper+TweetShare.h @@ -1,7 +1,7 @@ // // MPActivityViewControllerHelper+TweetShare.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/MPActivityViewControllerHelper+TweetShare.m b/MoPubSDK/Internal/Common/MPActivityViewControllerHelper+TweetShare.m index 003681a77..d89b556d2 100644 --- a/MoPubSDK/Internal/Common/MPActivityViewControllerHelper+TweetShare.m +++ b/MoPubSDK/Internal/Common/MPActivityViewControllerHelper+TweetShare.m @@ -1,7 +1,7 @@ // // MPActivityViewControllerHelper+TweetShare.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/MPActivityViewControllerHelper.h b/MoPubSDK/Internal/Common/MPActivityViewControllerHelper.h index 6256bb53c..36dca44fa 100644 --- a/MoPubSDK/Internal/Common/MPActivityViewControllerHelper.h +++ b/MoPubSDK/Internal/Common/MPActivityViewControllerHelper.h @@ -1,7 +1,7 @@ // // MPActivityViewControllerHelper.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/MPActivityViewControllerHelper.m b/MoPubSDK/Internal/Common/MPActivityViewControllerHelper.m index c7e7c9016..16ceb2abe 100644 --- a/MoPubSDK/Internal/Common/MPActivityViewControllerHelper.m +++ b/MoPubSDK/Internal/Common/MPActivityViewControllerHelper.m @@ -1,7 +1,7 @@ // // MPActivityViewControllerHelper.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/MPAdBrowserController.h b/MoPubSDK/Internal/Common/MPAdBrowserController.h index d209a7f1e..829977226 100644 --- a/MoPubSDK/Internal/Common/MPAdBrowserController.h +++ b/MoPubSDK/Internal/Common/MPAdBrowserController.h @@ -1,7 +1,7 @@ // // MPAdBrowserController.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/MPAdBrowserController.m b/MoPubSDK/Internal/Common/MPAdBrowserController.m index f00a3123c..ddb82397e 100644 --- a/MoPubSDK/Internal/Common/MPAdBrowserController.m +++ b/MoPubSDK/Internal/Common/MPAdBrowserController.m @@ -1,7 +1,7 @@ // // MPAdBrowserController.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/ // @@ -285,7 +285,7 @@ - (void)webView:(MPWebView *)webView didFailLoadWithError:(NSError *)error // 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]); + MPLogEvent([MPLogEvent error:error message:nil]); } #pragma mark - diff --git a/MoPubSDK/Internal/Common/MPAdConfiguration.h b/MoPubSDK/Internal/Common/MPAdConfiguration.h index a5acbad09..d3b5b1a61 100644 --- a/MoPubSDK/Internal/Common/MPAdConfiguration.h +++ b/MoPubSDK/Internal/Common/MPAdConfiguration.h @@ -1,7 +1,7 @@ // // 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/ // diff --git a/MoPubSDK/Internal/Common/MPAdConfiguration.m b/MoPubSDK/Internal/Common/MPAdConfiguration.m index 2af4e215b..cffdc8eea 100644 --- a/MoPubSDK/Internal/Common/MPAdConfiguration.m +++ b/MoPubSDK/Internal/Common/MPAdConfiguration.m @@ -1,7 +1,7 @@ // // 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/ // @@ -46,7 +46,7 @@ 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 kInterstitialAdTypeMetadataKey = @"x-fulladtype"; @@ -284,7 +284,7 @@ - (Class)setUpCustomEventClassFromMetadata:(NSDictionary *)metadata 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; @@ -591,7 +591,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; } diff --git a/MoPubSDK/Internal/Common/MPAdDestinationDisplayAgent.h b/MoPubSDK/Internal/Common/MPAdDestinationDisplayAgent.h index 234369b15..dda397c85 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/ // diff --git a/MoPubSDK/Internal/Common/MPAdDestinationDisplayAgent.m b/MoPubSDK/Internal/Common/MPAdDestinationDisplayAgent.m index ebe259752..02e2fe12b 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/ // @@ -269,7 +269,7 @@ - (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; } } 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..cecd7d787 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/ // @@ -67,12 +67,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."); + MPLogInfo(@"viewVisibilityTimer is already started."); return; } @@ -86,7 +86,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..d38af7bdf 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/ // diff --git a/MoPubSDK/Internal/Common/MPAdServerCommunicator.m b/MoPubSDK/Internal/Common/MPAdServerCommunicator.m index b9d39f04a..a3c2ff133 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/ // @@ -88,19 +88,13 @@ - (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]; @@ -127,9 +121,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,7 +137,7 @@ - (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); }]; @@ -151,12 +145,8 @@ - (void)sendAfterLoadUrlWithConfiguration:(MPAdConfiguration *)configuration } - (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); - - NSError *error = [NSError errorWithDomain:kMOPUBErrorDomain - code:MOPUBErrorSDKNotInitialized - userInfo:@{ NSLocalizedDescriptionKey : errorString }]; + NSError *error = [NSError adLoadFailedBecauseSdkNotInitialized]; + MPLogEvent([MPLogEvent error:error message:nil]); [self didFailWithError:error]; } @@ -190,25 +180,30 @@ - (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); + NSError * parseError = [NSError adResponseFailedToParseWithError:error]; + MPLogEvent([MPLogEvent error:parseError message:nil]); self.loading = NO; - [self.delegate communicatorDidFailWithError:error]; + [self.delegate communicatorDidFailWithError: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"); + NSError * noResponsesError = [NSError adResponsesNotFound]; + MPLogEvent([MPLogEvent error:noResponsesError message:nil]); self.loading = NO; - [self.delegate communicatorDidFailWithError:[MOPUBError errorWithCode:MOPUBErrorUnableToParseJSONAdResponse]]; + [self.delegate communicatorDidFailWithError: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,7 +211,7 @@ - (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; } @@ -245,8 +240,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 +250,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 = MPLogLevelDebug; + + 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..6b42fc856 100644 --- a/MoPubSDK/Internal/Common/MPAdServerURLBuilder.h +++ b/MoPubSDK/Internal/Common/MPAdServerURLBuilder.h @@ -1,7 +1,7 @@ // // 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/ // diff --git a/MoPubSDK/Internal/Common/MPAdServerURLBuilder.m b/MoPubSDK/Internal/Common/MPAdServerURLBuilder.m index 1d9c25384..45fa52921 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,21 @@ #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 "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"; @@ -323,18 +325,26 @@ + (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]; +} + ++ (NSDictionary *)adapterInformation { + return MPMediationManager.sharedManager.adRequestPayload; } + (NSDictionary *)locationInformationDictionary:(CLLocation *)location { diff --git a/MoPubSDK/Internal/Common/MPClosableView.h b/MoPubSDK/Internal/Common/MPClosableView.h index 37cff36d3..3908211ba 100644 --- a/MoPubSDK/Internal/Common/MPClosableView.h +++ b/MoPubSDK/Internal/Common/MPClosableView.h @@ -1,7 +1,7 @@ // // 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/ // diff --git a/MoPubSDK/Internal/Common/MPClosableView.m b/MoPubSDK/Internal/Common/MPClosableView.m index da4b83b20..757c32157 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/ // diff --git a/MoPubSDK/Internal/Common/MPCountdownTimerView.h b/MoPubSDK/Internal/Common/MPCountdownTimerView.h index 2003465fe..6b06e924c 100644 --- a/MoPubSDK/Internal/Common/MPCountdownTimerView.h +++ b/MoPubSDK/Internal/Common/MPCountdownTimerView.h @@ -1,7 +1,7 @@ // // 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/ // diff --git a/MoPubSDK/Internal/Common/MPCountdownTimerView.m b/MoPubSDK/Internal/Common/MPCountdownTimerView.m index 9b82d9d4b..452454353 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/ // @@ -151,7 +151,7 @@ - (NSString *)timerHtml { html = [NSString stringWithContentsOfFile:filepath encoding:NSUTF8StringEncoding error:nil]; if (html == nil) { - MPLogError(@"Could not find MPCountdownTimer.html in bundle %@", parentBundle.bundlePath); + MPLogInfo(@"Could not find MPCountdownTimer.html in bundle %@", parentBundle.bundlePath); } } 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..89127e8f5 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/ // diff --git a/MoPubSDK/Internal/Common/MPProgressOverlayView.m b/MoPubSDK/Internal/Common/MPProgressOverlayView.m index 1a9ae0447..69f601e97 100644 --- a/MoPubSDK/Internal/Common/MPProgressOverlayView.m +++ b/MoPubSDK/Internal/Common/MPProgressOverlayView.m @@ -1,7 +1,7 @@ // // 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/ // 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..304467514 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/ // diff --git a/MoPubSDK/Internal/Common/MPURLActionInfo.h b/MoPubSDK/Internal/Common/MPURLActionInfo.h index 6b72441c0..28f32692e 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/ // diff --git a/MoPubSDK/Internal/Common/MPURLActionInfo.m b/MoPubSDK/Internal/Common/MPURLActionInfo.m index 00d3b3fdb..7256c9879 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/ // 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..2c14d03c5 100644 --- a/MoPubSDK/Internal/Common/MPURLResolver.m +++ b/MoPubSDK/Internal/Common/MPURLResolver.m @@ -1,7 +1,7 @@ // // 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/ // @@ -257,7 +257,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/Common/MPVideoConfig.h b/MoPubSDK/Internal/Common/MPVideoConfig.h index 3149ffd5b..a92740e77 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/ // diff --git a/MoPubSDK/Internal/Common/MPVideoConfig.m b/MoPubSDK/Internal/Common/MPVideoConfig.m index 963c0a774..d160b6e8f 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/ // @@ -289,7 +289,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..c48f61a11 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/ // diff --git a/MoPubSDK/Internal/HTML/MPAdWebViewAgent.m b/MoPubSDK/Internal/HTML/MPAdWebViewAgent.m index 3adc5fdb4..3636b36af 100644 --- a/MoPubSDK/Internal/HTML/MPAdWebViewAgent.m +++ b/MoPubSDK/Internal/HTML/MPAdWebViewAgent.m @@ -1,7 +1,7 @@ // // 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/ // @@ -267,7 +267,7 @@ - (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; } } 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..707f03abb --- /dev/null +++ b/MoPubSDK/Internal/HTML/MPContentBlocker.m @@ -0,0 +1,66 @@ +// +// 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" + +@interface MPContentBlocker() +@property (class, nonatomic, readonly) NSArray * blockedResources; +@end + +@implementation MPContentBlocker + +#pragma mark - Lazy Initialized Properties + +/** + Current list of blocked resources. + */ ++ (NSArray *)blockedResources { + static NSArray * sBlockedResources = nil; + if (sBlockedResources == nil) { + sBlockedResources = @[@"http.?://ads.mopub.com/mraid.js"]; + } + + return sBlockedResources; +} + +/** + 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; + if (sBlockedResourcesList == nil) { + // 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..8613d21b9 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/ // diff --git a/MoPubSDK/Internal/HTML/MPHTMLBannerCustomEvent.m b/MoPubSDK/Internal/HTML/MPHTMLBannerCustomEvent.m index c174b0bb8..f85f2adbf 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" @@ -29,12 +30,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 @@ -66,14 +68,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 +88,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..30ade61c3 100644 --- a/MoPubSDK/Internal/HTML/MPHTMLInterstitialCustomEvent.h +++ b/MoPubSDK/Internal/HTML/MPHTMLInterstitialCustomEvent.h @@ -1,7 +1,7 @@ // // 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/ // diff --git a/MoPubSDK/Internal/HTML/MPHTMLInterstitialCustomEvent.m b/MoPubSDK/Internal/HTML/MPHTMLInterstitialCustomEvent.m index 18e1bc994..c76624d0d 100644 --- a/MoPubSDK/Internal/HTML/MPHTMLInterstitialCustomEvent.m +++ b/MoPubSDK/Internal/HTML/MPHTMLInterstitialCustomEvent.m @@ -1,14 +1,15 @@ // // 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 "MPAdConfiguration.h" +#import "MPError.h" +#import "MPLogging.h" @interface MPHTMLInterstitialCustomEvent () @@ -32,9 +33,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,7 +45,15 @@ - (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); + } + }]; } #pragma mark - MPInterstitialViewControllerDelegate @@ -62,25 +70,26 @@ - (NSString *)adUnitId - (void)interstitialDidLoadAd:(MPInterstitialViewController *)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 { - 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 { - MPLogInfo(@"MoPub HTML interstitial will appear"); [self.delegate interstitialCustomEventWillAppear:self]; } - (void)interstitialDidAppear:(MPInterstitialViewController *)interstitial { - MPLogInfo(@"MoPub HTML interstitial did appear"); [self.delegate interstitialCustomEventDidAppear:self]; if (!self.trackedImpression) { @@ -91,13 +100,11 @@ - (void)interstitialDidAppear:(MPInterstitialViewController *)interstitial - (void)interstitialWillDisappear:(MPInterstitialViewController *)interstitial { - MPLogInfo(@"MoPub HTML interstitial will disappear"); [self.delegate interstitialCustomEventWillDisappear:self]; } - (void)interstitialDidDisappear:(MPInterstitialViewController *)interstitial { - MPLogInfo(@"MoPub HTML interstitial did disappear"); [self.delegate interstitialCustomEventDidDisappear:self]; // Deallocate the interstitial as we don't need it anymore. If we don't deallocate the interstitial after dismissal, @@ -108,13 +115,11 @@ - (void)interstitialDidDisappear:(MPInterstitialViewController *)interstitial - (void)interstitialDidReceiveTapEvent:(MPInterstitialViewController *)interstitial { - MPLogInfo(@"MoPub HTML interstitial did receive tap event"); [self.delegate interstitialCustomEventDidReceiveTapEvent:self]; } - (void)interstitialWillLeaveApplication:(MPInterstitialViewController *)interstitial { - MPLogInfo(@"MoPub HTML interstitial will leave application"); [self.delegate interstitialCustomEventWillLeaveApplication:self]; } 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..ae5a1a8e1 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/ // diff --git a/MoPubSDK/Internal/HTML/MPWebView.h b/MoPubSDK/Internal/HTML/MPWebView.h index 94e5bbbe9..20c2bf77d 100644 --- a/MoPubSDK/Internal/HTML/MPWebView.h +++ b/MoPubSDK/Internal/HTML/MPWebView.h @@ -1,7 +1,7 @@ // // 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/ // diff --git a/MoPubSDK/Internal/HTML/MPWebView.m b/MoPubSDK/Internal/HTML/MPWebView.m index 76da75d36..18f88a66e 100644 --- a/MoPubSDK/Internal/HTML/MPWebView.m +++ b/MoPubSDK/Internal/HTML/MPWebView.m @@ -1,13 +1,13 @@ // // 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; @@ -82,6 +82,14 @@ - (void)setUpStepsForceUIWebView:(BOOL)forceUIWebView { } config.userContentController = contentController; + if (@available(iOS 11.0, *)) { + [WKContentRuleListStore.defaultStore compileContentRuleListForIdentifier:@"ContentBlockingRules" encodedContentRuleList:MPContentBlocker.blockedResourcesList completionHandler:^(WKContentRuleList * rulesList, NSError * error) { + if (error == nil) { + [config.userContentController addContentRuleList:rulesList]; + } + }]; + } + WKWebView *wkWebView = [[WKWebView alloc] initWithFrame:self.bounds configuration:config]; wkWebView.UIDelegate = self; diff --git a/MoPubSDK/Internal/Interstitials/MPBaseInterstitialAdapter.h b/MoPubSDK/Internal/Interstitials/MPBaseInterstitialAdapter.h index 1bb129aa7..be80594f4 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/ // diff --git a/MoPubSDK/Internal/Interstitials/MPBaseInterstitialAdapter.m b/MoPubSDK/Internal/Interstitials/MPBaseInterstitialAdapter.m index a87dc78c7..1e58dbacf 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/ // @@ -88,7 +88,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; } diff --git a/MoPubSDK/Internal/Interstitials/MPInterstitialAdManager.h b/MoPubSDK/Internal/Interstitials/MPInterstitialAdManager.h index 4a6be0f2a..eeb65e987 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/ // diff --git a/MoPubSDK/Internal/Interstitials/MPInterstitialAdManager.m b/MoPubSDK/Internal/Interstitials/MPInterstitialAdManager.m index 52e9c2c5f..b6b16930b 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" @@ -79,8 +80,7 @@ - (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; } @@ -91,6 +91,8 @@ - (void)loadAdWithURL:(NSURL *)URL - (void)loadInterstitialWithAdUnitID:(NSString *)ID targeting:(MPAdTargeting *)targeting { + MPLogAdEvent(MPLogEvent.adLoadAttempt, ID); + if (self.ready) { [self.delegate managerDidLoadInterstitial:self]; } else { @@ -104,11 +106,11 @@ - (void)loadInterstitialWithAdUnitID:(NSString *)ID targeting:(MPAdTargeting *)t - (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 +142,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 +156,21 @@ - (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]]; + [self.delegate manager:self didFailToLoadInterstitialWithError:[NSError errorWithCode:MOPUBErrorNoInventory]]; return; } if (configuration.adType != MPAdTypeInterstitial) { - MPLogWarn(@"Could not load ad: interstitial object received a non-interstitial ad unit ID."); + MPLogInfo(@"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:MOPUBErrorAdapterInvalid]]; return; } @@ -213,6 +215,7 @@ - (void)adapterDidFinishLoadingAd:(MPBaseInterstitialAdapter *)adapter 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]; } @@ -240,46 +243,55 @@ - (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); } @end diff --git a/MoPubSDK/Internal/Interstitials/MPInterstitialAdManagerDelegate.h b/MoPubSDK/Internal/Interstitials/MPInterstitialAdManagerDelegate.h index ed5e47eda..a31815d52 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/ // 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..bed6009c3 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" @@ -56,21 +57,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..06344da4c 100644 --- a/MoPubSDK/Internal/Interstitials/MPInterstitialViewController.h +++ b/MoPubSDK/Internal/Interstitials/MPInterstitialViewController.h @@ -1,7 +1,7 @@ // // 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/ // @@ -22,7 +22,7 @@ @property (nonatomic, strong) UIButton *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; diff --git a/MoPubSDK/Internal/Interstitials/MPInterstitialViewController.m b/MoPubSDK/Internal/Interstitials/MPInterstitialViewController.m index 9e32143a3..ffe23eda0 100644 --- a/MoPubSDK/Internal/Interstitials/MPInterstitialViewController.m +++ b/MoPubSDK/Internal/Interstitials/MPInterstitialViewController.m @@ -1,13 +1,14 @@ // // 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 "MPError.h" #import "MPGlobal.h" #import "MPLogging.h" #import "UIButton+MPAdditions.h" @@ -52,10 +53,12 @@ - (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; } @@ -68,6 +71,9 @@ - (void)presentInterstitialFromViewController:(UIViewController *)controller [controller presentViewController:self animated:MP_ANIMATED completion:^{ [self didPresentInterstitial]; + if (complete != nil) { + complete(nil); + } }]; } @@ -234,7 +240,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..98759fcfb 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/ // @@ -37,11 +37,15 @@ 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; +#pragma mark - Ad Server Response Keys +extern NSString * const kEnableDebugLogging; + #pragma mark - Open Endpoint Request Keys extern NSString * const kOpenEndpointSessionTrackingKey; diff --git a/MoPubSDK/Internal/MPAdServerKeys.m b/MoPubSDK/Internal/MPAdServerKeys.m index 3188f1d0e..d9baa4d7f 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/ // @@ -37,11 +37,15 @@ 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"; +#pragma mark - Ad Server Response Keys +NSString * const kEnableDebugLogging = @"enable_debug_logging"; + #pragma mark - Open Endpoint Request Keys NSString * const kOpenEndpointSessionTrackingKey = @"st"; 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..188c1cbb4 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/ // diff --git a/MoPubSDK/Internal/MPConsentManager.h b/MoPubSDK/Internal/MPConsentManager.h index fc2ab61ec..bd8803f98 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/ // @@ -19,6 +19,11 @@ */ @property (nonatomic, strong, nonnull) NSString * adUnitIdUsedForConsent; +/** + 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..a674823b7 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" @@ -196,7 +197,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 +227,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 +239,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 +254,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 +271,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 +288,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 +298,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 +349,7 @@ - (void)loadConsentDialogWithCompletion:(void (^)(NSError *error))completion { - (void)showConsentDialogFromViewController:(UIViewController *)viewController didShow:(void (^)(void))didShow didDismiss:(void (^)(void))didDismiss { + MPLogEvent(MPLogEvent.consentDialogShowAttempted); if (self.isConsentDialogLoaded) { [viewController presentViewController:self.consentDialogViewController animated:YES @@ -344,6 +357,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 +381,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:`. }]; } } @@ -397,13 +413,11 @@ - (void)onApplicationWillEnterForeground:(NSNotification *)notification { [self checkForDoNotTrackAndTransition]; // If IDFA changed, status will be set to MPConsentStatusUnknown. [self checkForIfaChange]; + + 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 +427,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 +451,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 +468,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 +478,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,7 +490,7 @@ - (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; } @@ -470,9 +498,9 @@ - (void)synchronizeConsentWithCompletion:(void (^ _Nonnull)(NSError * error))com // 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."); + MPLogInfo(@"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."); } 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 @@ -499,36 +527,36 @@ - (void)synchronizeConsentWithCompletion:(void (^ _Nonnull)(NSError * error))com // ad server. strongSelf.isForcedGDPRAppliesTransition = NO; + // Schedule the next timer. + strongSelf.nextUpdateTimer = [strongSelf newNextUpdateTimer]; + // 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); + MPLogEvent([MPLogEvent consentSyncFailedWithError:deserializationError]); 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); + if (![strongSelf updateConsentStateWithParameters:json]) { + 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); } - // Schedule the next timer and complete. - strongSelf.nextUpdateTimer = [strongSelf newNextUpdateTimer]; - completion(parseError); + // Complete successfully. + MPLogEvent([MPLogEvent consentSyncCompletedWithMessage:nil]); + completion(nil); } errorHandler:^(NSError * _Nonnull error) { __typeof__(self) strongSelf = weakSelf; // Schedule the next timer and complete with error. strongSelf.nextUpdateTimer = [strongSelf newNextUpdateTimer]; - MPLogError(@"%@", error.localizedDescription); + MPLogEvent([MPLogEvent consentSyncFailedWithError:error]); completion(error); }]; } @@ -548,13 +576,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 +648,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 +656,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 +703,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 +723,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 +737,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 +812,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; @@ -824,6 +865,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,19 +877,22 @@ - (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:`. }]; } } @@ -984,7 +1031,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..0f36d0ff9 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/ // diff --git a/MoPubSDK/Internal/MPCoreInstanceProvider.m b/MoPubSDK/Internal/MPCoreInstanceProvider.m index fb323fa84..0ea7a7ea5 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/ // 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..e672d92bc 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 *)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,7 +257,7 @@ - (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) { @@ -69,70 +275,32 @@ - (NSDictionary * _Nullable)cachedInitializationParametersForNetwork:(Class> * _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]; - } - }]; - } - - return cachedNetworks; -} - - (void)clearCache { @synchronized (self) { [[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/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..cb6e7edad 100644 --- a/MoPubSDK/Internal/MPURLRequest.m +++ b/MoPubSDK/Internal/MPURLRequest.m @@ -1,7 +1,7 @@ // // 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/ // @@ -31,7 +31,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); } } @@ -77,7 +77,7 @@ - (instancetype)initWithURL:(NSURL *)URL { [self setHTTPBody:jsonData]; } else { - MPLogError(@"Could not generate JSON body from %@", postData); + MPLogEvent([MPLogEvent error:error message:nil]); } } } @@ -92,7 +92,7 @@ + (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; diff --git a/MoPubSDK/Internal/MPVASTTracking.h b/MoPubSDK/Internal/MPVASTTracking.h index 741835d0d..bb3b88f00 100644 --- a/MoPubSDK/Internal/MPVASTTracking.h +++ b/MoPubSDK/Internal/MPVASTTracking.h @@ -1,7 +1,7 @@ // // 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/ // diff --git a/MoPubSDK/Internal/MPVASTTracking.m b/MoPubSDK/Internal/MPVASTTracking.m index e657ace65..b780c3385 100644 --- a/MoPubSDK/Internal/MPVASTTracking.m +++ b/MoPubSDK/Internal/MPVASTTracking.m @@ -1,7 +1,7 @@ // // 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/ // 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..4c52062f4 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/ // diff --git a/MoPubSDK/Internal/MRAID/MPMRAIDBannerCustomEvent.m b/MoPubSDK/Internal/MRAID/MPMRAIDBannerCustomEvent.m index 25f6f9e7d..ec13c1d9b 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" @@ -23,8 +24,9 @@ @implementation MPMRAIDBannerCustomEvent - (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 +35,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 +65,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 +85,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]; } diff --git a/MoPubSDK/Internal/MRAID/MPMRAIDInterstitialCustomEvent.h b/MoPubSDK/Internal/MRAID/MPMRAIDInterstitialCustomEvent.h index 81b10fabf..2e2321d9f 100644 --- a/MoPubSDK/Internal/MRAID/MPMRAIDInterstitialCustomEvent.h +++ b/MoPubSDK/Internal/MRAID/MPMRAIDInterstitialCustomEvent.h @@ -1,7 +1,7 @@ // // 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/ // diff --git a/MoPubSDK/Internal/MRAID/MPMRAIDInterstitialCustomEvent.m b/MoPubSDK/Internal/MRAID/MPMRAIDInterstitialCustomEvent.m index ee20030a6..32c524915 100644 --- a/MoPubSDK/Internal/MRAID/MPMRAIDInterstitialCustomEvent.m +++ b/MoPubSDK/Internal/MRAID/MPMRAIDInterstitialCustomEvent.m @@ -1,12 +1,14 @@ // // 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 "MPAdConfiguration.h" +#import "MPError.h" #import "MPLogging.h" @interface MPMRAIDInterstitialCustomEvent () @@ -21,8 +23,10 @@ @implementation MPMRAIDInterstitialCustomEvent - (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,7 +36,15 @@ - (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 @@ -49,37 +61,36 @@ - (NSString *)adUnitId - (void)interstitialDidLoadAd:(MPInterstitialViewController *)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 { - 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 { - MPLogInfo(@"MoPub MRAID interstitial will appear"); [self.delegate interstitialCustomEventWillAppear:self]; } - (void)interstitialDidAppear:(MPInterstitialViewController *)interstitial { - MPLogInfo(@"MoPub MRAID interstitial did appear"); [self.delegate interstitialCustomEventDidAppear:self]; } - (void)interstitialWillDisappear:(MPInterstitialViewController *)interstitial { - MPLogInfo(@"MoPub MRAID interstitial will disappear"); [self.delegate interstitialCustomEventWillDisappear:self]; } - (void)interstitialDidDisappear:(MPInterstitialViewController *)interstitial { - MPLogInfo(@"MoPub MRAID interstitial did disappear"); [self.delegate interstitialCustomEventDidDisappear:self]; // Deallocate the interstitial as we don't need it anymore. If we don't deallocate the interstitial after dismissal, @@ -90,13 +101,11 @@ - (void)interstitialDidDisappear:(MPInterstitialViewController *)interstitial - (void)interstitialDidReceiveTapEvent:(MPInterstitialViewController *)interstitial { - MPLogInfo(@"MoPub MRAID interstitial did receive tap event"); [self.delegate interstitialCustomEventDidReceiveTapEvent:self]; } - (void)interstitialWillLeaveApplication:(MPInterstitialViewController *)interstitial { - MPLogInfo(@"MoPub MRAID interstitial will leave application"); [self.delegate interstitialCustomEventWillLeaveApplication:self]; } diff --git a/MoPubSDK/Internal/MRAID/MPMRAIDInterstitialViewController.h b/MoPubSDK/Internal/MRAID/MPMRAIDInterstitialViewController.h index 96b554460..12a2aa6b9 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/ // diff --git a/MoPubSDK/Internal/MRAID/MPMRAIDInterstitialViewController.m b/MoPubSDK/Internal/MRAID/MPMRAIDInterstitialViewController.m index 9715d7e17..067a65918 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]; } @@ -184,7 +185,7 @@ - (UIInterfaceOrientationMask)supportedInterfaceOrientations - (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..d0d32c714 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/ // @@ -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; } 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..7bcb07a76 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; diff --git a/MoPubSDK/Internal/MRAID/MRController.m b/MoPubSDK/Internal/MRAID/MRController.m index 9f4915c6d..d053611e0 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/ // @@ -61,7 +61,7 @@ @interface MRController () )delegate { if (self = [super init]) { _placementType = placementType; _currentState = MRAdViewStateDefault; - _forceOrientationMask = UIInterfaceOrientationMaskAll; + _forceOrientationMask = MPInterstitialOrientationTypeToUIInterfaceOrientationMask(orientationType); _isAnimatingAdSize = NO; - _firedReadyEventForDefaultAd = NO; + _didConfigureOrientationNotificationObservers = NO; _currentAdSize = CGSizeZero; _mraidDefaultAdFrame = adViewFrame; @@ -180,11 +181,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 @@ -357,7 +366,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."); @@ -665,8 +674,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 +689,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 +723,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]); } } @@ -949,7 +958,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 +977,7 @@ - (void)closableView:(MPClosableView *)closableView didMoveToWindow:(UIWindow *) object:nil]; [self.adPropertyUpdateTimer scheduleNow]; - [self initializeLoadedAdForBridge:bridge]; - self.firedReadyEventForDefaultAd = YES; + self.didConfigureOrientationNotificationObservers = YES; } } } @@ -1055,7 +1063,7 @@ - (void)updateCurrentPosition MRBridge *activeBridge = [self bridgeForActiveAdView]; [activeBridge fireSetCurrentPositionWithPositionRect:frame]; - MPLogTrace(@"Current Position: %@", NSStringFromCGRect(frame)); + MPLogDebug(@"Current Position: %@", NSStringFromCGRect(frame)); } - (void)updateDefaultPosition @@ -1066,7 +1074,7 @@ - (void)updateDefaultPosition [self.mraidBridge fireSetDefaultPositionWithPositionRect:defaultFrame]; [self.mraidBridgeTwoPart fireSetDefaultPositionWithPositionRect:defaultFrame]; - MPLogTrace(@"Default Position: %@", NSStringFromCGRect(defaultFrame)); + MPLogDebug(@"Default Position: %@", NSStringFromCGRect(defaultFrame)); } - (void)updateScreenSize @@ -1078,7 +1086,7 @@ - (void)updateScreenSize [self.mraidBridge fireSetScreenSize:screenSize]; [self.mraidBridgeTwoPart fireSetScreenSize:screenSize]; - MPLogTrace(@"Screen Size: %@", NSStringFromCGSize(screenSize)); + MPLogDebug(@"Screen Size: %@", NSStringFromCGSize(screenSize)); } - (void)updateMaxSize @@ -1090,7 +1098,15 @@ - (void)updateMaxSize [self.mraidBridge fireSetMaxSize:maxSize]; [self.mraidBridgeTwoPart fireSetMaxSize:maxSize]; - MPLogTrace(@"Max Size: %@", NSStringFromCGSize(maxSize)); + MPLogDebug(@"Max Size: %@", NSStringFromCGSize(maxSize)); +} + +- (void)updateOrientation +{ + self.expandModalViewController.supportedOrientationMask = self.forceOrientationMask; + self.interstitialViewController.supportedOrientationMask = self.forceOrientationMask; + + MPLogDebug(@"Orientation: %ud", (unsigned int)self.forceOrientationMask); } #pragma mark - MRAID events @@ -1165,6 +1181,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]; } 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..545495272 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/ // diff --git a/MoPubSDK/Internal/MRAID/MRExpandModalViewController.m b/MoPubSDK/Internal/MRAID/MRExpandModalViewController.m index 17a613737..b0717b6b1 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/ // 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..e134955b7 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/ // 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..2be69cac0 100644 --- a/MoPubSDK/Internal/MRAID/MRVideoPlayerManager.m +++ b/MoPubSDK/Internal/MRAID/MRVideoPlayerManager.m @@ -1,7 +1,7 @@ // // 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/ // 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..386406d2c 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/ // 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..35140e043 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/ // diff --git a/MoPubSDK/Internal/Utility/Categories/NSURL+MPAdditions.m b/MoPubSDK/Internal/Utility/Categories/NSURL+MPAdditions.m index 5eecc6928..e5e6cd59e 100644 --- a/MoPubSDK/Internal/Utility/Categories/NSURL+MPAdditions.m +++ b/MoPubSDK/Internal/Utility/Categories/NSURL+MPAdditions.m @@ -1,7 +1,7 @@ // // 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/ // diff --git a/MoPubSDK/Internal/Utility/Categories/UIButton+MPAdditions.h b/MoPubSDK/Internal/Utility/Categories/UIButton+MPAdditions.h index e5c98aeb4..11abfd2d3 100644 --- a/MoPubSDK/Internal/Utility/Categories/UIButton+MPAdditions.h +++ b/MoPubSDK/Internal/Utility/Categories/UIButton+MPAdditions.h @@ -1,7 +1,7 @@ // // UIButton+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/UIButton+MPAdditions.m b/MoPubSDK/Internal/Utility/Categories/UIButton+MPAdditions.m index 4aafc2d76..3747d757c 100644 --- a/MoPubSDK/Internal/Utility/Categories/UIButton+MPAdditions.m +++ b/MoPubSDK/Internal/Utility/Categories/UIButton+MPAdditions.m @@ -1,7 +1,7 @@ // // UIButton+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/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..4c9ae20b3 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/ // diff --git a/MoPubSDK/Internal/Utility/Categories/UIView+MPAdditions.m b/MoPubSDK/Internal/Utility/Categories/UIView+MPAdditions.m index 8ecd51aa7..d04a07e68 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/ // diff --git a/MoPubSDK/Internal/Utility/Categories/UIWebView+MPAdditions.h b/MoPubSDK/Internal/Utility/Categories/UIWebView+MPAdditions.h index b8cb7b7e1..959d4ffda 100644 --- a/MoPubSDK/Internal/Utility/Categories/UIWebView+MPAdditions.h +++ b/MoPubSDK/Internal/Utility/Categories/UIWebView+MPAdditions.h @@ -1,7 +1,7 @@ // // UIWebView+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/UIWebView+MPAdditions.m b/MoPubSDK/Internal/Utility/Categories/UIWebView+MPAdditions.m index 111d15a4b..0d2bad141 100644 --- a/MoPubSDK/Internal/Utility/Categories/UIWebView+MPAdditions.m +++ b/MoPubSDK/Internal/Utility/Categories/UIWebView+MPAdditions.m @@ -1,7 +1,7 @@ // // UIWebView+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/MOPUBExperimentProvider.h b/MoPubSDK/Internal/Utility/MOPUBExperimentProvider.h index 73d11abf3..906d749ec 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/ // diff --git a/MoPubSDK/Internal/Utility/MOPUBExperimentProvider.m b/MoPubSDK/Internal/Utility/MOPUBExperimentProvider.m index 861d8718c..f8af71f71 100644 --- a/MoPubSDK/Internal/Utility/MOPUBExperimentProvider.m +++ b/MoPubSDK/Internal/Utility/MOPUBExperimentProvider.m @@ -1,7 +1,7 @@ // // 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/ // diff --git a/MoPubSDK/Internal/Utility/MPAnalyticsTracker.h b/MoPubSDK/Internal/Utility/MPAnalyticsTracker.h index c9624ce20..20bed6cd0 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/ // diff --git a/MoPubSDK/Internal/Utility/MPAnalyticsTracker.m b/MoPubSDK/Internal/Utility/MPAnalyticsTracker.m index 5e0b86073..61dba4c6e 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/ // diff --git a/MoPubSDK/Internal/Utility/MPError.h b/MoPubSDK/Internal/Utility/MPError.h index 6c805270a..9fcece425 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,41 @@ typedef enum { MOPUBErrorHTTPResponseNot200, MOPUBErrorNoNetworkData, MOPUBErrorSDKNotInitialized, + MOPUBErrorSDKInitializationInProgress, MOPUBErrorAdRequestTimedOut, MOPUBErrorNoRenderer, + MOPUBErrorAdLoadAlreadyInProgress, + MOPUBErrorInvalidCustomEventClass, + MOPUBErrorJSONSerializationFailed, + MOPUBErrorUnableToParseAdResponse, + MOPUBErrorNoConsentDialogLoaded, + MOPUBErrorAdapterFailedToLoadAd, + MOPUBErrorFullScreenAdAlreadyOnScreen, } 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)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; +@end + +@interface NSError (Consent) ++ (instancetype)noConsentDialogLoaded; +@end diff --git a/MoPubSDK/Internal/Utility/MPError.m b/MoPubSDK/Internal/Utility/MPError.m index 9ba5e2e4b..ce5625472 100644 --- a/MoPubSDK/Internal/Utility/MPError.m +++ b/MoPubSDK/Internal/Utility/MPError.m @@ -1,28 +1,87 @@ // // 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)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."]; +} + +@end + +@implementation NSError (Consent) + ++ (instancetype)noConsentDialogLoaded { + return [NSError errorWithCode:MOPUBErrorNoConsentDialogLoaded localizedDescription:@"Consent dialog has not been loaded."]; } @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..34175d704 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/ // diff --git a/MoPubSDK/Internal/Utility/MPGlobal.h b/MoPubSDK/Internal/Utility/MPGlobal.h index 4514a41c0..cd6e3930b 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/ // @@ -63,6 +63,8 @@ typedef NS_ENUM(NSUInteger, MPInterstitialOrientationType) { MPInterstitialOrientationTypeAll, }; +UIInterfaceOrientationMask MPInterstitialOrientationTypeToUIInterfaceOrientationMask(MPInterstitialOrientationType type); + //////////////////////////////////////////////////////////////////////////////////////////////////// @interface UIDevice (MPAdditions) diff --git a/MoPubSDK/Internal/Utility/MPGlobal.m b/MoPubSDK/Internal/Utility/MPGlobal.m index 7b185bec1..dd8ff2c0e 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/ // @@ -223,6 +223,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) @@ -325,7 +334,7 @@ - (id)initWithURL:(NSURL *)url clickHandler:(MPTelephoneConfirmationControllerCl { 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); + MPLogInfo(@"Processing URL as a telephone URL when %@ doesn't follow the tel:// or telprompt:// schemes", url.absoluteString); return nil; } @@ -336,7 +345,7 @@ - (id)initWithURL:(NSURL *)url clickHandler:(MPTelephoneConfirmationControllerCl if (!phoneNumber) { phoneNumber = [url resourceSpecifier]; if ([phoneNumber length] == 0) { - MPLogError(@"Invalid telelphone URL: %@.", url.absoluteString); + MPLogInfo(@"Invalid telelphone URL: %@.", url.absoluteString); return nil; } } diff --git a/MoPubSDK/Internal/Utility/MPIdentityProvider.h b/MoPubSDK/Internal/Utility/MPIdentityProvider.h index d3e73648b..759a8d443 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/ // diff --git a/MoPubSDK/Internal/Utility/MPIdentityProvider.m b/MoPubSDK/Internal/Utility/MPIdentityProvider.m index b94fab45c..41b5ec2ba 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/ // diff --git a/MoPubSDK/Internal/Utility/MPInternalUtils.h b/MoPubSDK/Internal/Utility/MPInternalUtils.h index 9520286f1..31fffd1d9 100644 --- a/MoPubSDK/Internal/Utility/MPInternalUtils.h +++ b/MoPubSDK/Internal/Utility/MPInternalUtils.h @@ -1,7 +1,7 @@ // // MPInternalUtils.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/MPInternalUtils.m b/MoPubSDK/Internal/Utility/MPInternalUtils.m index be87981ee..083bccfc0 100644 --- a/MoPubSDK/Internal/Utility/MPInternalUtils.m +++ b/MoPubSDK/Internal/Utility/MPInternalUtils.m @@ -1,7 +1,7 @@ // // MPInternalUtils.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/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 index 84e45e7c5..e96af4d3e 100644 --- a/MoPubSDK/Internal/Utility/MPStoreKitProvider.h +++ b/MoPubSDK/Internal/Utility/MPStoreKitProvider.h @@ -1,7 +1,7 @@ // // MPStoreKitProvider.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/MPStoreKitProvider.m b/MoPubSDK/Internal/Utility/MPStoreKitProvider.m index 5b6541dd0..aa4fb1deb 100644 --- a/MoPubSDK/Internal/Utility/MPStoreKitProvider.m +++ b/MoPubSDK/Internal/Utility/MPStoreKitProvider.m @@ -1,7 +1,7 @@ // // MPStoreKitProvider.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/MPTimer.h b/MoPubSDK/Internal/Utility/MPTimer.h index 6bfba7c5a..b0b2efb16 100644 --- a/MoPubSDK/Internal/Utility/MPTimer.h +++ b/MoPubSDK/Internal/Utility/MPTimer.h @@ -1,7 +1,7 @@ // // 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/ // diff --git a/MoPubSDK/Internal/Utility/MPTimer.m b/MoPubSDK/Internal/Utility/MPTimer.m index cf9b2ecc8..9251299cf 100644 --- a/MoPubSDK/Internal/Utility/MPTimer.m +++ b/MoPubSDK/Internal/Utility/MPTimer.m @@ -1,7 +1,7 @@ // // 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/ // @@ -130,7 +130,7 @@ - (BOOL)pause self.pauseDate = [NSDate date]; secondsLeft = [fireDate timeIntervalSinceDate:self.pauseDate]; if (secondsLeft <= 0) { - MPLogWarn(@"An MPTimer was somehow paused after it was supposed to fire."); + MPLogInfo(@"An MPTimer was somehow paused after it was supposed to fire."); } else { MPLogDebug(@"Paused MPTimer (%p) %.1f seconds left before firing.", self, secondsLeft); } @@ -154,7 +154,7 @@ - (BOOL)resume return NO; } - MPLogDebug(@"Resumed MPTimer (%p), should fire in %.1f seconds.", self.timeInterval); + MPLogDebug(@"Resumed MPTimer (%p), should fire in %.1f seconds.", self, self.timeInterval); // Resume the timer. NSDate *newFireDate = [NSDate dateWithTimeInterval:self.timeInterval sinceDate:[NSDate date]]; 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/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..0d2630cfc 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/ // diff --git a/MoPubSDK/Internal/VAST/MPVASTCompanionAd.m b/MoPubSDK/Internal/VAST/MPVASTCompanionAd.m index 2c10671f9..2e54a0ffe 100644 --- a/MoPubSDK/Internal/VAST/MPVASTCompanionAd.m +++ b/MoPubSDK/Internal/VAST/MPVASTCompanionAd.m @@ -1,7 +1,7 @@ // // MPVASTCompanionAd.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/VAST/MPVASTCreative.h b/MoPubSDK/Internal/VAST/MPVASTCreative.h index 33013badc..68547716b 100644 --- a/MoPubSDK/Internal/VAST/MPVASTCreative.h +++ b/MoPubSDK/Internal/VAST/MPVASTCreative.h @@ -1,7 +1,7 @@ // // MPVASTCreative.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/MPVASTCreative.m b/MoPubSDK/Internal/VAST/MPVASTCreative.m index 624d59463..e537ccb32 100644 --- a/MoPubSDK/Internal/VAST/MPVASTCreative.m +++ b/MoPubSDK/Internal/VAST/MPVASTCreative.m @@ -1,7 +1,7 @@ // // MPVASTCreative.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/VAST/MPVASTDurationOffset.h b/MoPubSDK/Internal/VAST/MPVASTDurationOffset.h index 40963a297..4ca6ca5da 100644 --- a/MoPubSDK/Internal/VAST/MPVASTDurationOffset.h +++ b/MoPubSDK/Internal/VAST/MPVASTDurationOffset.h @@ -1,7 +1,7 @@ // // MPVASTDurationOffset.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/MPVASTDurationOffset.m b/MoPubSDK/Internal/VAST/MPVASTDurationOffset.m index ec78ae55e..1c6c03954 100644 --- a/MoPubSDK/Internal/VAST/MPVASTDurationOffset.m +++ b/MoPubSDK/Internal/VAST/MPVASTDurationOffset.m @@ -1,7 +1,7 @@ // // MPVASTDurationOffset.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/VAST/MPVASTIndustryIcon.h b/MoPubSDK/Internal/VAST/MPVASTIndustryIcon.h index ca4f33e0c..000ce685c 100644 --- a/MoPubSDK/Internal/VAST/MPVASTIndustryIcon.h +++ b/MoPubSDK/Internal/VAST/MPVASTIndustryIcon.h @@ -1,7 +1,7 @@ // // MPVASTIndustryIcon.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/MPVASTIndustryIcon.m b/MoPubSDK/Internal/VAST/MPVASTIndustryIcon.m index 407b85bc3..4c35f02c5 100644 --- a/MoPubSDK/Internal/VAST/MPVASTIndustryIcon.m +++ b/MoPubSDK/Internal/VAST/MPVASTIndustryIcon.m @@ -1,7 +1,7 @@ // // MPVASTIndustryIcon.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/VAST/MPVASTInline.h b/MoPubSDK/Internal/VAST/MPVASTInline.h index 43da537b0..0c5db4968 100644 --- a/MoPubSDK/Internal/VAST/MPVASTInline.h +++ b/MoPubSDK/Internal/VAST/MPVASTInline.h @@ -1,7 +1,7 @@ // // MPVASTInline.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/MPVASTInline.m b/MoPubSDK/Internal/VAST/MPVASTInline.m index 878bc4c18..f683838dc 100644 --- a/MoPubSDK/Internal/VAST/MPVASTInline.m +++ b/MoPubSDK/Internal/VAST/MPVASTInline.m @@ -1,7 +1,7 @@ // // MPVASTInline.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/VAST/MPVASTLinearAd.h b/MoPubSDK/Internal/VAST/MPVASTLinearAd.h index 00f3d5f34..90ae3073b 100644 --- a/MoPubSDK/Internal/VAST/MPVASTLinearAd.h +++ b/MoPubSDK/Internal/VAST/MPVASTLinearAd.h @@ -1,7 +1,7 @@ // // MPVASTLinearAd.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/MPVASTLinearAd.m b/MoPubSDK/Internal/VAST/MPVASTLinearAd.m index bd5dae41e..687afcd9e 100644 --- a/MoPubSDK/Internal/VAST/MPVASTLinearAd.m +++ b/MoPubSDK/Internal/VAST/MPVASTLinearAd.m @@ -1,7 +1,7 @@ // // MPVASTLinearAd.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/VAST/MPVASTMacroProcessor.h b/MoPubSDK/Internal/VAST/MPVASTMacroProcessor.h index 21a12206d..2843e5e71 100644 --- a/MoPubSDK/Internal/VAST/MPVASTMacroProcessor.h +++ b/MoPubSDK/Internal/VAST/MPVASTMacroProcessor.h @@ -1,7 +1,7 @@ // // MPVASTMacroProcessor.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/MPVASTMacroProcessor.m b/MoPubSDK/Internal/VAST/MPVASTMacroProcessor.m index bf834fab3..5afee74e3 100644 --- a/MoPubSDK/Internal/VAST/MPVASTMacroProcessor.m +++ b/MoPubSDK/Internal/VAST/MPVASTMacroProcessor.m @@ -1,7 +1,7 @@ // // MPVASTMacroProcessor.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/VAST/MPVASTManager.h b/MoPubSDK/Internal/VAST/MPVASTManager.h index cbb7f9569..58e402c0c 100644 --- a/MoPubSDK/Internal/VAST/MPVASTManager.h +++ b/MoPubSDK/Internal/VAST/MPVASTManager.h @@ -1,7 +1,7 @@ // // MPVASTManager.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/MPVASTManager.m b/MoPubSDK/Internal/VAST/MPVASTManager.m index 8bd7649df..6b7598e03 100644 --- a/MoPubSDK/Internal/VAST/MPVASTManager.m +++ b/MoPubSDK/Internal/VAST/MPVASTManager.m @@ -1,7 +1,7 @@ // // MPVASTManager.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/VAST/MPVASTMediaFile.h b/MoPubSDK/Internal/VAST/MPVASTMediaFile.h index ecd883c81..07b28aa65 100644 --- a/MoPubSDK/Internal/VAST/MPVASTMediaFile.h +++ b/MoPubSDK/Internal/VAST/MPVASTMediaFile.h @@ -1,7 +1,7 @@ // // MPVASTMediaFile.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/MPVASTMediaFile.m b/MoPubSDK/Internal/VAST/MPVASTMediaFile.m index f12d3142e..ff0b1f42d 100644 --- a/MoPubSDK/Internal/VAST/MPVASTMediaFile.m +++ b/MoPubSDK/Internal/VAST/MPVASTMediaFile.m @@ -1,7 +1,7 @@ // // MPVASTMediaFile.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/VAST/MPVASTModel.h b/MoPubSDK/Internal/VAST/MPVASTModel.h index eed4d0966..1d9cd8883 100644 --- a/MoPubSDK/Internal/VAST/MPVASTModel.h +++ b/MoPubSDK/Internal/VAST/MPVASTModel.h @@ -1,7 +1,7 @@ // // MPVASTModel.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/MPVASTModel.m b/MoPubSDK/Internal/VAST/MPVASTModel.m index 78e34adea..5048f50e9 100644 --- a/MoPubSDK/Internal/VAST/MPVASTModel.m +++ b/MoPubSDK/Internal/VAST/MPVASTModel.m @@ -1,7 +1,7 @@ // // MPVASTModel.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/ // @@ -272,7 +272,7 @@ - (instancetype)initWithDictionary:(NSDictionary *)dictionary } } } else { - MPLogError(@"Could not populate %@ of class %@ because its mapper is invalid.", + MPLogInfo(@"Could not populate %@ of class %@ because its mapper is invalid.", key, NSStringFromClass([self class])); } } diff --git a/MoPubSDK/Internal/VAST/MPVASTResource.h b/MoPubSDK/Internal/VAST/MPVASTResource.h index 4363a283e..70b0afbb7 100644 --- a/MoPubSDK/Internal/VAST/MPVASTResource.h +++ b/MoPubSDK/Internal/VAST/MPVASTResource.h @@ -1,7 +1,7 @@ // // MPVASTResource.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/MPVASTResource.m b/MoPubSDK/Internal/VAST/MPVASTResource.m index dd5253a87..6331d2cc4 100644 --- a/MoPubSDK/Internal/VAST/MPVASTResource.m +++ b/MoPubSDK/Internal/VAST/MPVASTResource.m @@ -1,7 +1,7 @@ // // MPVASTResource.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/VAST/MPVASTResponse.h b/MoPubSDK/Internal/VAST/MPVASTResponse.h index 1d44695a9..bf7583338 100644 --- a/MoPubSDK/Internal/VAST/MPVASTResponse.h +++ b/MoPubSDK/Internal/VAST/MPVASTResponse.h @@ -1,7 +1,7 @@ // // MPVASTResponse.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/MPVASTResponse.m b/MoPubSDK/Internal/VAST/MPVASTResponse.m index 59b6c4c47..208cd0199 100644 --- a/MoPubSDK/Internal/VAST/MPVASTResponse.m +++ b/MoPubSDK/Internal/VAST/MPVASTResponse.m @@ -1,7 +1,7 @@ // // MPVASTResponse.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/VAST/MPVASTStringUtilities.h b/MoPubSDK/Internal/VAST/MPVASTStringUtilities.h index b1ca4fec5..81051e731 100644 --- a/MoPubSDK/Internal/VAST/MPVASTStringUtilities.h +++ b/MoPubSDK/Internal/VAST/MPVASTStringUtilities.h @@ -1,7 +1,7 @@ // // MPVASTStringUtilities.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/MPVASTStringUtilities.m b/MoPubSDK/Internal/VAST/MPVASTStringUtilities.m index e61cfc2df..77c988149 100644 --- a/MoPubSDK/Internal/VAST/MPVASTStringUtilities.m +++ b/MoPubSDK/Internal/VAST/MPVASTStringUtilities.m @@ -1,7 +1,7 @@ // // MPVASTStringUtilities.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/VAST/MPVASTTrackingEvent.h b/MoPubSDK/Internal/VAST/MPVASTTrackingEvent.h index 160d35917..4d6ba19e1 100644 --- a/MoPubSDK/Internal/VAST/MPVASTTrackingEvent.h +++ b/MoPubSDK/Internal/VAST/MPVASTTrackingEvent.h @@ -1,7 +1,7 @@ // // MPVASTTrackingEvent.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/MPVASTTrackingEvent.m b/MoPubSDK/Internal/VAST/MPVASTTrackingEvent.m index 5e17873b0..c4edb2575 100644 --- a/MoPubSDK/Internal/VAST/MPVASTTrackingEvent.m +++ b/MoPubSDK/Internal/VAST/MPVASTTrackingEvent.m @@ -1,7 +1,7 @@ // // MPVASTTrackingEvent.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/VAST/MPVASTWrapper.h b/MoPubSDK/Internal/VAST/MPVASTWrapper.h index 4a2e865d8..98e5866aa 100644 --- a/MoPubSDK/Internal/VAST/MPVASTWrapper.h +++ b/MoPubSDK/Internal/VAST/MPVASTWrapper.h @@ -1,7 +1,7 @@ // // MPVASTWrapper.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/MPVASTWrapper.m b/MoPubSDK/Internal/VAST/MPVASTWrapper.m index 38a2d4f8b..26b6326bb 100644 --- a/MoPubSDK/Internal/VAST/MPVASTWrapper.m +++ b/MoPubSDK/Internal/VAST/MPVASTWrapper.m @@ -1,7 +1,7 @@ // // MPVASTWrapper.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/Logging/Internal/MPConsoleLogger.h b/MoPubSDK/Logging/Internal/MPConsoleLogger.h new file mode 100644 index 000000000..f5c6f1a6a --- /dev/null +++ b/MoPubSDK/Logging/Internal/MPConsoleLogger.h @@ -0,0 +1,22 @@ +// +// MPConsoleLogger.h +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import +#import "MPLogger.h" + +/** + Console logging destination routes all log messages to @c NSLog. + */ +@interface MPConsoleLogger : NSObject + +/** + Log level. By default, this is set to @c MPLogLevelInfo. + */ +@property (nonatomic, assign) MPLogLevel logLevel; + +@end diff --git a/MoPubSDK/Logging/Internal/MPConsoleLogger.m b/MoPubSDK/Logging/Internal/MPConsoleLogger.m new file mode 100644 index 000000000..02576d895 --- /dev/null +++ b/MoPubSDK/Logging/Internal/MPConsoleLogger.m @@ -0,0 +1,28 @@ +// +// MPConsoleLogger.m +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import "MPConsoleLogger.h" + +@implementation MPConsoleLogger + +- (instancetype)init { + if (self = [super init]) { + // The console logging level is set to info by default in the event + // that an error needs to be logged to the console prior to SDK + // initialization. + _logLevel = MPLogLevelInfo; + } + + return self; +} + +- (void)logMessage:(NSString *)message { + NSLog(@"%@", message); +} + +@end diff --git a/MoPubSDK/Logging/Internal/MPLogManager.h b/MoPubSDK/Logging/Internal/MPLogManager.h new file mode 100644 index 000000000..24a374762 --- /dev/null +++ b/MoPubSDK/Logging/Internal/MPLogManager.h @@ -0,0 +1,67 @@ +// +// MPLogManager.h +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import +#import "MPLogEvent.h" +#import "MPLogger.h" +#import "MPLogging.h" +#import "MPLogLevel.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + Manages all logging sources for the MoPub SDK. By default, the manager will always + contain a console logger destination. + */ +@interface MPLogManager : NSObject + +/** + Current log level of the console logger. + */ +@property (nonatomic, assign) MPLogLevel consoleLogLevel; + +/** + Retrieves the singleton instance of @c MPLogManager. + */ ++ (instancetype)sharedInstance; + +/** + Registers a logging destination. + @param logger Logger to receive log events. + */ +- (void)addLogger:(id)logger; + +/** + Removes a logger from receiving log events. + @param logger Logger to remove. + */ +- (void)removeLogger:(id)logger; + +/** + Logs the message to all available logging destinations at the + specified log level. + @param message Message to log. + @param level Log level. + */ +- (void)logMessage:(NSString *)message atLogLevel:(MPLogLevel)level; + +/** + Logs the event generated from the calling class. The format of the log message + will be: + @code + className | source | logEvent.message + @endcode + @param event Event to log. + @param source Optional source of the event. This will generally be ad unit ID for ad-related events. + @param className Name of the class invoking @c logEvent:fromClass: + */ +- (void)logEvent:(MPLogEvent *)event source:(NSString * _Nullable)source fromClass:(NSString *)className; + +@end + +NS_ASSUME_NONNULL_END diff --git a/MoPubSDK/Logging/Internal/MPLogManager.m b/MoPubSDK/Logging/Internal/MPLogManager.m new file mode 100644 index 000000000..5822a3bb3 --- /dev/null +++ b/MoPubSDK/Logging/Internal/MPLogManager.m @@ -0,0 +1,117 @@ +// +// MPLogManager.m +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import "MPLogManager.h" +#import "MPConsoleLogger.h" +#import "MPIdentityProvider.h" + +// Log format constants +static NSString * const kInfoFormat = @"%@][%@"; +static NSString * const kLogFormat = @"\n\t[MoPub][%@] %@"; + +// Cached IDFA strings used to obfuscate the real IDFA with a sanitized version. +static NSString * sIdentifier; +static NSString * sObfuscatedIdentifier; + +@interface MPLogManager() + +/** + Console logger. + */ +@property (nonatomic, strong) MPConsoleLogger * consoleLogger; + +/** + Currently registered loggers. + */ +@property (nonatomic, strong) NSMutableArray> * loggers; + +/** + Serial dispatch queue to perform logging operations. + */ +@property (nonatomic, strong) dispatch_queue_t queue; + +@end + +@implementation MPLogManager + +#pragma mark - Initialization + ++ (MPLogManager *)sharedInstance { + static dispatch_once_t once; + static MPLogManager * sharedManager; + dispatch_once(&once, ^{ + sharedManager = [[self alloc] init]; + }); + + return sharedManager; +} + +- (instancetype)init { + if (self = [super init]) { + _consoleLogger = [[MPConsoleLogger alloc] init]; + _loggers = [NSMutableArray arrayWithObject:_consoleLogger]; + _queue = dispatch_queue_create("com.mopub-ios-sdk.queue", DISPATCH_QUEUE_SERIAL); + } + + return self; +} + +#pragma mark - Computed Properties + +- (MPLogLevel)consoleLogLevel { + return self.consoleLogger.logLevel; +} + +- (void)setConsoleLogLevel:(MPLogLevel)consoleLogLevel { + self.consoleLogger.logLevel = consoleLogLevel; +} + +#pragma mark - Logger Management + +- (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)level { + // Lazily retrieve the IDFA + if (sIdentifier == nil) { + sIdentifier = [[MPIdentityProvider identifier] copy]; + } + + // Lazily retrieve the sanitized IDFA + if (sObfuscatedIdentifier == nil) { + sObfuscatedIdentifier = [[MPIdentityProvider obfuscatedIdentifier] copy]; + } + + // Replace identifier with a obfuscated version when logging. + NSString * logMessage = [message stringByReplacingOccurrencesOfString:sIdentifier withString:sObfuscatedIdentifier]; + + // Queue up the message for logging. + __weak __typeof__(self) weakSelf = self; + dispatch_async(self.queue, ^{ + [weakSelf.loggers enumerateObjectsUsingBlock:^(id logger, NSUInteger idx, BOOL *stop) { + if (logger.logLevel <= level) { + [logger logMessage:logMessage]; + } + }]; + }); +} + +- (void)logEvent:(MPLogEvent *)event source:(NSString *)source fromClass:(NSString *)className { + NSString * info = (source != nil ? [NSString stringWithFormat:kInfoFormat, className, source] : className); + NSString * message = [NSString stringWithFormat:kLogFormat, info, event.message]; + [self logMessage:message atLogLevel:event.logLevel]; +} + +@end diff --git a/MoPubSDK/Logging/MPLogEvent.h b/MoPubSDK/Logging/MPLogEvent.h new file mode 100644 index 000000000..1a0f21323 --- /dev/null +++ b/MoPubSDK/Logging/MPLogEvent.h @@ -0,0 +1,134 @@ +// +// MPLogEvent.h +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import +#import "MPConsentStatus.h" +#import "MPLogLevel.h" + +@protocol MPAdapterConfiguration; +@class MPRewardedVideoReward; +@class MPURLRequest; + +NS_ASSUME_NONNULL_BEGIN + +/** + Logging event used to construct pre-formatted messages. + */ +@interface MPLogEvent : NSObject +/** + Message to be logged. + */ +@property (nonatomic, copy, readonly) NSString * message; + +/** + Level at which the message should be logged. + */ +@property (nonatomic, assign, readonly) MPLogLevel logLevel; + +/** + Default initialization is disallowed. + */ +- (instancetype)init NS_UNAVAILABLE; + +/** + Initializes the log event with the specified message to be logged at Debug level. + @param message Message to log + @return Log event + */ +- (instancetype)initWithMessage:(NSString *)message; + +/** + Initializes the log event with the specified message to be logged at the desired + log level. + @param message Message to log + @param level Level at which the message should be logged + @return Log event + */ +- (instancetype)initWithMessage:(NSString *)message level:(MPLogLevel)level NS_DESIGNATED_INITIALIZER; + +/** + Initializes a generic error log event with optional message. The message and error + will be logged at Debug level. + @param error Error to log + @param message Optional message that will prefix the error message. + @return Log event + */ ++ (instancetype)error:(NSError *)error message:(NSString * _Nullable)message; + +/** + Initializes the log event with the specified message to be logged at the desired + log level. + @param message Message to log + @param level Level at which the message should be logged + @return Log event + */ ++ (instancetype)eventWithMessage:(NSString *)message level:(MPLogLevel)level; + +@end + +@interface MPLogEvent (AdLifeCycle) ++ (instancetype)adRequestedWithRequest:(MPURLRequest *)request; ++ (instancetype)adRequestReceivedResponse:(NSDictionary *)response; ++ (instancetype)adLoadAttempt; ++ (instancetype)adShowAttempt; ++ (instancetype)adShowSuccess; ++ (instancetype)adShowFailedWithError:(NSError *)error; ++ (instancetype)adDidLoad; ++ (instancetype)adFailedToLoadWithError:(NSError *)error; ++ (instancetype)adExpiredWithTimeInterval:(NSTimeInterval)expirationInterval; ++ (instancetype)adWillPresentModal; ++ (instancetype)adDidDismissModal; ++ (instancetype)adTapped; ++ (instancetype)adWillAppear; ++ (instancetype)adDidAppear; ++ (instancetype)adWillDisappear; ++ (instancetype)adDidDisappear; ++ (instancetype)adShouldRewardUserWithReward:(MPRewardedVideoReward *)reward; ++ (instancetype)adWillLeaveApplication; +@end + +@interface MPLogEvent (AdapterAdLifeCycle) ++ (instancetype)adLoadAttemptForAdapter:(NSString *)name dspCreativeId:(NSString * _Nullable)creativeId dspName:(NSString * _Nullable)dspName; ++ (instancetype)adLoadSuccessForAdapter:(NSString *)name; ++ (instancetype)adLoadFailedForAdapter:(NSString *)name error:(NSError *)error; ++ (instancetype)adShowAttemptForAdapter:(NSString *)name; ++ (instancetype)adShowSuccessForAdapter:(NSString *)name; ++ (instancetype)adShowFailedForAdapter:(NSString *)name error:(NSError *)error; ++ (instancetype)adWillPresentModalForAdapter:(NSString *)name; ++ (instancetype)adDidDismissModalForAdapter:(NSString *)name; ++ (instancetype)adTappedForAdapter:(NSString *)name; ++ (instancetype)adWillAppearForAdapter:(NSString *)name; ++ (instancetype)adDidAppearForAdapter:(NSString *)name; ++ (instancetype)adWillDisappearForAdapter:(NSString *)name; ++ (instancetype)adDidDisappearForAdapter:(NSString *)name; ++ (instancetype)adWillLeaveApplicationForAdapter:(NSString *)name; +@end + +@interface MPLogEvent (Initialization) ++ (instancetype)sdkInitializedWithNetworks:(NSArray> *)networks; +@end + +@interface MPLogEvent (Consent) ++ (instancetype)consentSyncAttempted; ++ (instancetype)consentSyncCompletedWithMessage:(NSString * _Nullable)message; ++ (instancetype)consentSyncFailedWithError:(NSError *)error; ++ (instancetype)consentUpdatedTo:(MPConsentStatus)newStatus from:(MPConsentStatus)oldStatus reason:(NSString * _Nullable)reason canCollectPersonalInfo:(BOOL)canCollectPII; ++ (instancetype)consentShouldShowDialog; ++ (instancetype)consentDialogLoadAttempted; ++ (instancetype)consentDialogLoadSuccess; ++ (instancetype)consentDialogLoadFailedWithError:(NSError *)error; ++ (instancetype)consentDialogShowAttempted; ++ (instancetype)consentDialogShowSuccess; ++ (instancetype)consentDialogShowFailedWithError:(NSError *)error; +@end + +@interface MPLogEvent (Javascript) ++ (instancetype)javascriptConsoleLogWithMessage:(NSString *)message; +@end + +NS_ASSUME_NONNULL_END diff --git a/MoPubSDK/Logging/MPLogEvent.m b/MoPubSDK/Logging/MPLogEvent.m new file mode 100644 index 000000000..133868473 --- /dev/null +++ b/MoPubSDK/Logging/MPLogEvent.m @@ -0,0 +1,306 @@ +// +// MPLogEvent.m +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import "MPLogEvent.h" +#import "MPAdapterConfiguration.h" +#import "MPRewardedVideoReward.h" +#import "MPURLRequest.h" +#import "NSString+MPConsentStatus.h" + +@implementation MPLogEvent + +- (instancetype)initWithMessage:(NSString *)message { + return [self initWithMessage:message level:MPLogLevelDebug]; +} + +- (instancetype)initWithMessage:(NSString *)message level:(MPLogLevel)level { + if (self = [super init]) { + _message = message; + _logLevel = level; + } + + return self; +} + ++ (instancetype)error:(NSError *)error message:(NSString * _Nullable)message { + NSString * formattedMessage = (message != nil ? [NSString stringWithFormat:@"%@: ", message] : @""); + NSString * logMessage = [NSString stringWithFormat:@"%@(%@) %@", formattedMessage, @(error.code), error.localizedDescription]; + return [[MPLogEvent alloc] initWithMessage:logMessage]; +} + ++ (instancetype)eventWithMessage:(NSString *)message level:(MPLogLevel)level { + return [[MPLogEvent alloc] initWithMessage:message level:level]; +} + +@end + +#pragma mark - AdLifeCycle + +@implementation MPLogEvent (AdLifeCycle) + ++ (instancetype)adRequestedWithRequest:(MPURLRequest *)request { + NSString * message = [NSString stringWithFormat:@"Requesting an ad from Adserver: %@", request]; + return [[MPLogEvent alloc] initWithMessage:message]; +} + ++ (instancetype)adRequestReceivedResponse:(NSDictionary *)response { + NSString * message = [NSString stringWithFormat:@"Adserver responded with:\n%@", response]; + return [[MPLogEvent alloc] initWithMessage:message]; +} + ++ (instancetype)adLoadAttempt { + static NSString * const message = @"Attempting to load ad"; + return [[MPLogEvent alloc] initWithMessage:message level:MPLogLevelInfo]; +} + ++ (instancetype)adShowAttempt { + static NSString * const message = @"Attempting to show ad"; + return [[MPLogEvent alloc] initWithMessage:message level:MPLogLevelInfo]; +} + ++ (instancetype)adShowSuccess { + static NSString * const message = @"Ad shown"; + return [[MPLogEvent alloc] initWithMessage:message level:MPLogLevelInfo]; +} + ++ (instancetype)adShowFailedWithError:(NSError *)error { + NSString * message = [NSString stringWithFormat:@"Ad failed to show: (%@) %@", @(error.code), error.localizedDescription]; + return [[MPLogEvent alloc] initWithMessage:message level:MPLogLevelInfo]; +} + ++ (instancetype)adDidLoad { + static NSString * const message = @"Ad loaded"; + return [[MPLogEvent alloc] initWithMessage:message level:MPLogLevelInfo]; +} + ++ (instancetype)adFailedToLoadWithError:(NSError *)error { + NSString * message = [NSString stringWithFormat:@"Ad failed to load: (%@) %@", @(error.code), error.localizedDescription]; + return [[MPLogEvent alloc] initWithMessage:message level:MPLogLevelInfo]; +} + ++ (instancetype)adExpiredWithTimeInterval:(NSTimeInterval)expirationInterval { + NSString * message = [NSString stringWithFormat:@"Ad expired since it was not shown within %@ minutes of it being loaded", @(expirationInterval / 60)]; + return [[MPLogEvent alloc] initWithMessage:message]; +} + ++ (instancetype)adWillPresentModal { + static NSString * const message = @"Ad will present modal"; + return [[MPLogEvent alloc] initWithMessage:message]; +} + ++ (instancetype)adDidDismissModal { + static NSString * const message = @"Ad did dismiss modal"; + return [[MPLogEvent alloc] initWithMessage:message]; +} + ++ (instancetype)adTapped { + static NSString * const message = @"Ad tapped"; + return [[MPLogEvent alloc] initWithMessage:message]; +} + ++ (instancetype)adWillAppear { + static NSString * const message = @"Ad will appear"; + return [[MPLogEvent alloc] initWithMessage:message]; +} + ++ (instancetype)adDidAppear { + static NSString * const message = @"Ad did appear"; + return [[MPLogEvent alloc] initWithMessage:message]; +} + ++ (instancetype)adWillDisappear { + static NSString * const message = @"Ad will disappear"; + return [[MPLogEvent alloc] initWithMessage:message]; +} + ++ (instancetype)adDidDisappear { + static NSString * const message = @"Ad did disappear"; + return [[MPLogEvent alloc] initWithMessage:message]; +} + ++ (instancetype)adShouldRewardUserWithReward:(MPRewardedVideoReward *)reward { + NSString * message = [NSString stringWithFormat:@"Should rewarded user with %@ %@", reward.amount, reward.currencyType]; + return [[MPLogEvent alloc] initWithMessage:message]; +} + ++ (instancetype)adWillLeaveApplication { + static NSString * const message = @"Ad will leave application"; + return [[MPLogEvent alloc] initWithMessage:message]; +} + +@end + +#pragma mark - AdapterAdLifeCycle + +@implementation MPLogEvent (AdapterAdLifeCycle) + ++ (instancetype)adLoadAttemptForAdapter:(NSString *)name dspCreativeId:(NSString *)creativeId dspName:(NSString *)dspName { + NSString * creativeIdMessage = (creativeId != nil ? [NSString stringWithFormat:@" with DSP creative ID %@", creativeId] : @""); + NSString * dspMessage = (dspName != nil ? [NSString stringWithFormat:@" and DSP Name %@", dspName] : @""); + NSString * message = [NSString stringWithFormat:@"Adapter %@ attempting to load ad%@%@", name, creativeIdMessage, dspMessage]; + return [[MPLogEvent alloc] initWithMessage:message]; +} + ++ (instancetype)adLoadSuccessForAdapter:(NSString *)name { + NSString * message = [NSString stringWithFormat:@"Adapter %@ sucessfully loaded ad", name]; + return [[MPLogEvent alloc] initWithMessage:message]; +} + ++ (instancetype)adLoadFailedForAdapter:(NSString *)name error:(NSError *)error { + NSString * message = [NSString stringWithFormat:@"Adapter %@ failed to load ad: (%@) %@", name, @(error.code), error.localizedDescription]; + return [[MPLogEvent alloc] initWithMessage:message]; +} + ++ (instancetype)adShowAttemptForAdapter:(NSString *)name { + NSString * message = [NSString stringWithFormat:@"Adapter %@ attempting to show ad", name]; + return [[MPLogEvent alloc] initWithMessage:message]; +} + ++ (instancetype)adShowSuccessForAdapter:(NSString *)name { + NSString * message = [NSString stringWithFormat:@"Adapter %@ sucessfully showed ad", name]; + return [[MPLogEvent alloc] initWithMessage:message]; +} + ++ (instancetype)adShowFailedForAdapter:(NSString *)name error:(NSError *)error { + NSString * message = [NSString stringWithFormat:@"Adapter %@ failed to show ad: (%@) %@", name, @(error.code), error.localizedDescription]; + return [[MPLogEvent alloc] initWithMessage:message]; +} + ++ (instancetype)adWillPresentModalForAdapter:(NSString *)name { + NSString * message = [NSString stringWithFormat:@"Adapter ad from %@ will present modal", name]; + return [[MPLogEvent alloc] initWithMessage:message]; +} + ++ (instancetype)adDidDismissModalForAdapter:(NSString *)name { + NSString * message = [NSString stringWithFormat:@"Adapter ad from %@ did dismiss modal", name]; + return [[MPLogEvent alloc] initWithMessage:message]; +} + ++ (instancetype)adTappedForAdapter:(NSString *)name { + NSString * message = [NSString stringWithFormat:@"Adapter ad from %@ received tap event", name]; + return [[MPLogEvent alloc] initWithMessage:message]; +} + ++ (instancetype)adWillAppearForAdapter:(NSString *)name { + NSString * message = [NSString stringWithFormat:@"Adapter ad from %@ will appear", name]; + return [[MPLogEvent alloc] initWithMessage:message]; +} + ++ (instancetype)adDidAppearForAdapter:(NSString *)name { + NSString * message = [NSString stringWithFormat:@"Adapter ad from %@ did appear", name]; + return [[MPLogEvent alloc] initWithMessage:message]; +} + ++ (instancetype)adWillDisappearForAdapter:(NSString *)name { + NSString * message = [NSString stringWithFormat:@"Adapter ad from %@ will disappear", name]; + return [[MPLogEvent alloc] initWithMessage:message]; +} + ++ (instancetype)adDidDisappearForAdapter:(NSString *)name { + NSString * message = [NSString stringWithFormat:@"Adapter ad from %@ did disappear", name]; + return [[MPLogEvent alloc] initWithMessage:message]; +} + ++ (instancetype)adWillLeaveApplicationForAdapter:(NSString *)name { + NSString * message = [NSString stringWithFormat:@"Adapter ad from %@ will leave application", name]; + return [[MPLogEvent alloc] initWithMessage:message]; +} + +@end + +#pragma mark - Initialization + +@implementation MPLogEvent (Initialization) + ++ (instancetype)sdkInitializedWithNetworks:(NSArray> *)networks { + // Compile the network adapter versions and underlying SDK versions into a human + // readable format string. + NSMutableArray * networkVersions = [NSMutableArray arrayWithCapacity:networks.count]; + [networks enumerateObjectsUsingBlock:^(id _Nonnull adapter, NSUInteger idx, BOOL * _Nonnull stop) { + NSString * message = [NSString stringWithFormat:@"%@: Adapter version %@, SDK version %@", NSStringFromClass(adapter.class), adapter.adapterVersion, adapter.networkSdkVersion]; + [networkVersions addObject:message]; + }]; + NSString * networksMessage = (networkVersions.count > 0 ? [networkVersions componentsJoinedByString:@"\n\t"] : @"No adapters initialized"); + + NSString * message = [NSString stringWithFormat:@"SDK initialized and ready to display ads.\n\tInitialized adapters:\n\t%@\n", networksMessage]; + return [[MPLogEvent alloc] initWithMessage:message level:MPLogLevelInfo]; +} + +@end + +#pragma mark - Consent + +@implementation MPLogEvent (Consent) + ++ (instancetype)consentSyncAttempted { + static NSString * const message = @"Attempting to synchronize consent state"; + return [[MPLogEvent alloc] initWithMessage:message]; +} + ++ (instancetype)consentSyncCompletedWithMessage:(NSString * _Nullable)message { + NSString * formattedMessage = (message != nil ? [NSString stringWithFormat:@": %@", message] : @""); + NSString * logMessage = [NSString stringWithFormat:@"Consent synchronization complete%@", formattedMessage]; + return [[MPLogEvent alloc] initWithMessage:logMessage]; +} + ++ (instancetype)consentSyncFailedWithError:(NSError *)error { + NSString * message = [NSString stringWithFormat:@"Consent synchronization failed: (%@) %@", @(error.code), error.localizedDescription]; + return [[MPLogEvent alloc] initWithMessage:message]; +} + ++ (instancetype)consentUpdatedTo:(MPConsentStatus)newStatus from:(MPConsentStatus)oldStatus reason:(NSString * _Nullable)reason canCollectPersonalInfo:(BOOL)canCollectPII { + NSString * reasonMessage = (reason != nil ? [NSString stringWithFormat:@" Reason: %@", reason] : @""); + NSString * message = [NSString stringWithFormat:@"Consent changed to %@ from %@; PII can%@ be collected.%@", [NSString stringFromConsentStatus:newStatus], [NSString stringFromConsentStatus:oldStatus], (canCollectPII ? @"" : @"not"), reasonMessage]; + return [[MPLogEvent alloc] initWithMessage:message]; +} + ++ (instancetype)consentShouldShowDialog { + static NSString * const message = @"Consent dialog should be shown"; + return [[MPLogEvent alloc] initWithMessage:message]; +} + ++ (instancetype)consentDialogLoadAttempted { + static NSString * const message = @"Attempting to load consent dialog"; + return [[MPLogEvent alloc] initWithMessage:message]; +} + ++ (instancetype)consentDialogLoadSuccess { + static NSString * const message = @"Consent dialog loaded"; + return [[MPLogEvent alloc] initWithMessage:message]; +} + ++ (instancetype)consentDialogLoadFailedWithError:(NSError *)error { + NSString * message = [NSString stringWithFormat:@"Consent dialog failed to load: (%@) %@", @(error.code), error.localizedDescription]; + return [[MPLogEvent alloc] initWithMessage:message]; +} + ++ (instancetype)consentDialogShowAttempted { + static NSString * const message = @"Attempting to show consent dialog"; + return [[MPLogEvent alloc] initWithMessage:message]; +} + ++ (instancetype)consentDialogShowSuccess { + static NSString * const message = @"Consent dialog shown"; + return [[MPLogEvent alloc] initWithMessage:message]; +} + ++ (instancetype)consentDialogShowFailedWithError:(NSError *)error { + NSString * message = [NSString stringWithFormat:@"Consent dialog failed to show: (%@) %@", @(error.code), error.localizedDescription]; + return [[MPLogEvent alloc] initWithMessage:message]; +} + +@end + +@implementation MPLogEvent (Javascript) + ++ (instancetype)javascriptConsoleLogWithMessage:(NSString *)message { + NSString * scrubbedMessage = [message stringByReplacingOccurrencesOfString:@"ios-log: " withString:@""]; + return [[MPLogEvent alloc] initWithMessage:[NSString stringWithFormat:@"Javascript console logged: %@", scrubbedMessage]]; +} + +@end diff --git a/MoPubSDK/Logging/MPLogLevel.h b/MoPubSDK/Logging/MPLogLevel.h new file mode 100644 index 000000000..64acc1883 --- /dev/null +++ b/MoPubSDK/Logging/MPLogLevel.h @@ -0,0 +1,19 @@ +// +// MPLogLevel.h +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import + +/** + SDK logging level. + @remark Lower values equate to more detailed logs. + */ +typedef NS_ENUM(NSUInteger, MPLogLevel) { + MPLogLevelDebug = 20, + MPLogLevelInfo = 30, + MPLogLevelNone = 70 +}; diff --git a/MoPubSDK/Logging/MPLogger.h b/MoPubSDK/Logging/MPLogger.h new file mode 100644 index 000000000..a4cdbff3b --- /dev/null +++ b/MoPubSDK/Logging/MPLogger.h @@ -0,0 +1,28 @@ +// +// MPLogger.h +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import +#import "MPLogLevel.h" + +/** + Objects which are capable of consuming log messages. + */ +@protocol MPLogger + +/** + Current logging level. + */ +@property (nonatomic, readonly) MPLogLevel logLevel; + +/** + Message to be logged. + @param message Message to be logged. + */ +- (void)logMessage:(NSString * _Nullable)message; + +@end diff --git a/MoPubSDK/Logging/MPLogging.h b/MoPubSDK/Logging/MPLogging.h new file mode 100644 index 000000000..a1eea97a1 --- /dev/null +++ b/MoPubSDK/Logging/MPLogging.h @@ -0,0 +1,70 @@ +// +// MPLogging.h +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import +#import "MPLogEvent.h" +#import "MPLogger.h" +#import "MPLogLevel.h" + +NS_ASSUME_NONNULL_BEGIN + +extern NSString * const kMPClearErrorLogFormatWithAdUnitID; +extern NSString * const kMPWarmingUpErrorLogFormatWithAdUnitID; + +#define MPLogDebug(...) [MPLogging logEvent:[MPLogEvent eventWithMessage:[NSString stringWithFormat:__VA_ARGS__] level:MPLogLevelDebug] source:nil fromClass:self.class] +#define MPLogInfo(...) [MPLogging logEvent:[MPLogEvent eventWithMessage:[NSString stringWithFormat:__VA_ARGS__] level:MPLogLevelInfo] source:nil fromClass:self.class] + +// MPLogTrace, MPLogWarn, MPLogError, and MPLogFatal will be deprecated in +// future SDK versions. Please use MPLogInfo or MPLogDebug +#define MPLogTrace(...) MPLogDebug(__VA_ARGS__) +#define MPLogWarn(...) MPLogDebug(__VA_ARGS__) +#define MPLogError(...) MPLogDebug(__VA_ARGS__) +#define MPLogFatal(...) MPLogDebug(__VA_ARGS__) + +// Logs ad lifecycle events +#define MPLogAdEvent(event, adUnitId) [MPLogging logEvent:event source:adUnitId fromClass:self.class] + +// Logs general events +#define MPLogEvent(event) [MPLogging logEvent:event source:nil fromClass:self.class] + +/** + SDK logging support. + */ +@interface MPLogging : NSObject +/** + Current log level of the SDK console logger. The default value is @c MPLogLevelNone. + */ +@property (class, nonatomic, assign) MPLogLevel consoleLogLevel; + +/** +Registers a logging destination. +@param logger Logger to receive log events. +*/ ++ (void)addLogger:(id)logger; + +/** + Removes a logger from receiving log events. + @param logger Logger to remove. + */ ++ (void)removeLogger:(id)logger; + +/** + Logs the event generated from the calling class. The format of the log message + will be: + @code + className | source | logEvent.message + @endcode + @param event Event to log. + @param source Optional source of the event. This will generally be ad unit ID for ad-related events. + @param aClass Class that generated the event. + */ ++ (void)logEvent:(MPLogEvent *)event source:(NSString * _Nullable)source fromClass:(Class)aClass; + +@end + +NS_ASSUME_NONNULL_END diff --git a/MoPubSDK/Logging/MPLogging.m b/MoPubSDK/Logging/MPLogging.m new file mode 100644 index 000000000..85e92868d --- /dev/null +++ b/MoPubSDK/Logging/MPLogging.m @@ -0,0 +1,41 @@ +// +// MPLogging.m +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import "MPLogging.h" +#import "MPLogManager.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."; + +@implementation MPLogging + +#pragma mark - Class Properties + ++ (MPLogLevel)consoleLogLevel { + return MPLogManager.sharedInstance.consoleLogLevel; +} + ++ (void)setConsoleLogLevel:(MPLogLevel)level { + MPLogManager.sharedInstance.consoleLogLevel = level; +} + +#pragma mark - Class Methods + ++ (void)addLogger:(id)logger { + [MPLogManager.sharedInstance addLogger:logger]; +} + ++ (void)removeLogger:(id)logger { + [MPLogManager.sharedInstance removeLogger:logger]; +} + ++ (void)logEvent:(MPLogEvent *)event source:(NSString *)source fromClass:(Class)aClass { + [MPLogManager.sharedInstance logEvent:event source:source fromClass:NSStringFromClass(aClass)]; +} + +@end diff --git a/MoPubSDK/MOPUBDisplayAgentType.h b/MoPubSDK/MOPUBDisplayAgentType.h index 01a99c544..aec2bbcb1 100644 --- a/MoPubSDK/MOPUBDisplayAgentType.h +++ b/MoPubSDK/MOPUBDisplayAgentType.h @@ -1,7 +1,7 @@ // // MOPUBDisplayAgentType.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/MPAdConversionTracker.h b/MoPubSDK/MPAdConversionTracker.h index 5b1d6a36c..df7fb33b2 100644 --- a/MoPubSDK/MPAdConversionTracker.h +++ b/MoPubSDK/MPAdConversionTracker.h @@ -1,7 +1,7 @@ // // MPAdConversionTracker.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/MPAdConversionTracker.m b/MoPubSDK/MPAdConversionTracker.m index aecc8d3b0..bada904f3 100644 --- a/MoPubSDK/MPAdConversionTracker.m +++ b/MoPubSDK/MPAdConversionTracker.m @@ -1,7 +1,7 @@ // // MPAdConversionTracker.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/MPAdTargeting.h b/MoPubSDK/MPAdTargeting.h index 32a0e88d1..f763c855d 100644 --- a/MoPubSDK/MPAdTargeting.h +++ b/MoPubSDK/MPAdTargeting.h @@ -1,7 +1,7 @@ // // MPAdTargeting.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/MPAdTargeting.m b/MoPubSDK/MPAdTargeting.m index 30094410c..e7277f7fb 100644 --- a/MoPubSDK/MPAdTargeting.m +++ b/MoPubSDK/MPAdTargeting.m @@ -1,7 +1,7 @@ // // MPAdTargeting.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/MPAdView.h b/MoPubSDK/MPAdView.h index 162460b33..c95a9391a 100644 --- a/MoPubSDK/MPAdView.h +++ b/MoPubSDK/MPAdView.h @@ -1,7 +1,7 @@ // // MPAdView.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/MPAdView.m b/MoPubSDK/MPAdView.m index 492240054..5264ab312 100644 --- a/MoPubSDK/MPAdView.m +++ b/MoPubSDK/MPAdView.m @@ -1,7 +1,7 @@ // // MPAdView.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/MPAdapterConfiguration.h b/MoPubSDK/MPAdapterConfiguration.h new file mode 100644 index 000000000..09277b108 --- /dev/null +++ b/MoPubSDK/MPAdapterConfiguration.h @@ -0,0 +1,78 @@ +// +// MPAdapterConfiguration.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 + +@protocol MPAdapterConfiguration +@required +/** + The version of the adapter. + */ +@property (nonatomic, copy, readonly) NSString * adapterVersion; + +/** + An optional identity token used for ORTB bidding requests required for Advanced Bidding. + */ +@property (nonatomic, copy, readonly, nullable) NSString * biddingToken; + +/** + MoPub-specific name of the network. + @remark This value should correspond to `creative_network_name` in the dashboard. + */ +@property (nonatomic, copy, readonly) NSString * moPubNetworkName; + +/** + Optional dictionary of additional values to send along with every MoPub ad request + on behalf of the adapter. + */ +@property (nonatomic, readonly, nullable) NSDictionary * moPubRequestOptions; + +/** + The version of the underlying network SDK. + */ +@property (nonatomic, copy, readonly) NSString * networkSdkVersion; + +#pragma mark - Initialization + +/** + Initializes the underlying network SDK with a given set of initialization parameters. + @param configuration Optional set of JSON-codable configuration parameters that correspond specifically to the network. Only @c NSString, @c NSNumber, @c NSArray, and @c NSDictionary types are allowed. This value may be @c nil. + @param complete Optional completion block that is invoked when the underlying network SDK has completed initialization. This value may be @c nil. + @remarks Classes that implement this protocol must account for the possibility of @c initializeNetworkWithConfiguration:complete: being called multiple times. It is up to each individual adapter to determine whether re-initialization is allowed or not. + */ +- (void)initializeNetworkWithConfiguration:(NSDictionary * _Nullable)configuration + complete:(void(^ _Nullable)(NSError * _Nullable))complete; + +#pragma mark - MoPub Request Options + +/** + Adds entries into the managed @c moPubRequestOptions dictionary, overwriting any previously set + entries. + @param options Entries to add into @c moPubRequestOptions. This should not be @c nil. + */ +- (void)addMoPubRequestOptions:(NSDictionary *)options; + +#pragma mark - Caching + +/** + Updates the initialization parameters for the current network. + @param parameters New set of initialization parameters. Only @c NSString, @c NSNumber, @c NSArray, and @c NSDictionary types are allowed. Nothing will be done if @c nil is passed in. + */ ++ (void)setCachedInitializationParameters:(NSDictionary * _Nullable)parameters; + +/** + Retrieves the initialization parameters for the current network (if any). + @return The cached initialization parameters for the network. This may be @c nil if not parameters were found. + */ ++ (NSDictionary * _Nullable)cachedInitializationParameters; + +@end + +NS_ASSUME_NONNULL_END diff --git a/MoPubSDK/MPAdvancedBidder.h b/MoPubSDK/MPAdvancedBidder.h deleted file mode 100644 index 3d7310056..000000000 --- a/MoPubSDK/MPAdvancedBidder.h +++ /dev/null @@ -1,23 +0,0 @@ -// -// MPAdvancedBidder.h -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import - -@protocol MPAdvancedBidder -@required -/** - * The name of the network that generated the token. - * @remark This value should correspond to `creative_network_name` in the dashboard. - */ -@property (nonatomic, copy, readonly) NSString * _Nonnull creativeNetworkName; - -/** - * An identity token needed for ORTB requests to the bidder. - */ -@property (nonatomic, copy, readonly) NSString * _Nonnull token; -@end diff --git a/MoPubSDK/MPBannerCustomEvent.h b/MoPubSDK/MPBannerCustomEvent.h index f3f16ed7e..5786949c9 100644 --- a/MoPubSDK/MPBannerCustomEvent.h +++ b/MoPubSDK/MPBannerCustomEvent.h @@ -1,7 +1,7 @@ // // MPBannerCustomEvent.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/MPBannerCustomEvent.m b/MoPubSDK/MPBannerCustomEvent.m index 3bb6d3002..2859d0b5d 100644 --- a/MoPubSDK/MPBannerCustomEvent.m +++ b/MoPubSDK/MPBannerCustomEvent.m @@ -1,7 +1,7 @@ // // MPBannerCustomEvent.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/MPBannerCustomEventDelegate.h b/MoPubSDK/MPBannerCustomEventDelegate.h index ea97d69c8..8518a52e0 100644 --- a/MoPubSDK/MPBannerCustomEventDelegate.h +++ b/MoPubSDK/MPBannerCustomEventDelegate.h @@ -1,7 +1,7 @@ // // MPBannerCustomEventDelegate.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/MPBaseAdapterConfiguration.h b/MoPubSDK/MPBaseAdapterConfiguration.h new file mode 100644 index 000000000..3dcb50401 --- /dev/null +++ b/MoPubSDK/MPBaseAdapterConfiguration.h @@ -0,0 +1,81 @@ +// +// MPBaseAdapterConfiguration.h +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import +#import "MPAdapterConfiguration.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + Base class for adapter information providers that provides built-in initialization + caching support. + @remarks Subclasses MUST implement all required fields from the @c MPAdapterConfiguration protocol. + */ +@interface MPBaseAdapterConfiguration : NSObject + +/** + The version of the adapter. + */ +@property (nonatomic, copy, readonly) NSString * adapterVersion; + +/** + An optional identity token used for ORTB bidding requests required for Advanced Bidding. + */ +@property (nonatomic, copy, readonly, nullable) NSString * biddingToken; + +/** + MoPub-specific name of the network. + @remark This value should correspond to `creative_network_name` in the dashboard. + */ +@property (nonatomic, copy, readonly) NSString * moPubNetworkName; + +/** + Optional dictionary of additional values to send along with every MoPub ad request + on behalf of the adapter. + @remarks This base class will manage this property and will provide accessors methods @c addMoPubRequestOption:value: + and @c removeMoPubRequestOption: to add and remove entries from the options. Overriding this property may + cause unintended data loss. + */ +@property (nonatomic, readonly, nullable) NSDictionary * moPubRequestOptions; + +/** + The version of the underlying network SDK. + */ +@property (nonatomic, copy, readonly) NSString * networkSdkVersion; + +/** + Initializes the underlying network SDK with a given set of initialization parameters. + @param configuration Optional set of JSON-codable configuration parameters that correspond specifically to the network. Only @c NSString, @c NSNumber, @c NSArray, and @c NSDictionary types are allowed. This value may be @c nil. + @param complete Optional completion block that is invoked when the underlying network SDK has completed initialization. This value may be @c nil. + @remarks Classes that implement this protocol must account for the possibility of @c initializeNetworkWithConfiguration:complete: being called multiple times. It is up to each individual adapter to determine whether re-initialization is allowed or not. + */ +- (void)initializeNetworkWithConfiguration:(NSDictionary * _Nullable)configuration + complete:(void(^ _Nullable)(NSError * _Nullable))complete; + +/** + Adds entries into the managed @c moPubRequestOptions dictionary, overwriting any previously set + entries. + @param options Entries to add into @c moPubRequestOptions. This should not be @c nil. + */ +- (void)addMoPubRequestOptions:(NSDictionary *)options; + +/** + Updates the initialization parameters for the current network. + @param parameters New set of initialization parameters. Only @c NSString, @c NSNumber, @c NSArray, and @c NSDictionary types are allowed. Nothing will be done if @c nil is passed in. + */ ++ (void)setCachedInitializationParameters:(NSDictionary * _Nullable)parameters; + +/** + Retrieves the initialization parameters for the current network (if any). + @return The cached initialization parameters for the network. This may be @c nil if not parameters were found. + */ ++ (NSDictionary * _Nullable)cachedInitializationParameters; + +@end + +NS_ASSUME_NONNULL_END diff --git a/MoPubSDK/MPBaseAdapterConfiguration.m b/MoPubSDK/MPBaseAdapterConfiguration.m new file mode 100644 index 000000000..7c230b096 --- /dev/null +++ b/MoPubSDK/MPBaseAdapterConfiguration.m @@ -0,0 +1,62 @@ +// +// MPBaseAdapterConfiguration.m +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import "MPBaseAdapterConfiguration.h" +#import "MPMediationManager.h" + +@interface MPBaseAdapterConfiguration() +@property (nonatomic, readonly) NSMutableDictionary * internalMopubRequestOptions; +@end + +@implementation MPBaseAdapterConfiguration +@dynamic adapterVersion; +@dynamic biddingToken; +@dynamic moPubNetworkName; +@dynamic networkSdkVersion; + +#pragma mark - Initialization + +- (instancetype)init { + if (self = [super init]) { + _internalMopubRequestOptions = [NSMutableDictionary dictionary]; + } + + return self; +} + +#pragma mark - MPAdapterConfiguration Default Implementations + +- (NSDictionary *)moPubRequestOptions { + return self.internalMopubRequestOptions; +} + +- (void)initializeNetworkWithConfiguration:(NSDictionary * _Nullable)configuration + complete:(void(^ _Nullable)(NSError * _Nullable))complete { + if (complete != nil) { + complete(nil); + } +} + +- (void)addMoPubRequestOptions:(NSDictionary *)options { + // No entries to add + if (options == nil) { + return; + } + + [self.internalMopubRequestOptions addEntriesFromDictionary:options]; +} + ++ (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/MPBool.h b/MoPubSDK/MPBool.h index 3e3c57c97..4a7907b01 100644 --- a/MoPubSDK/MPBool.h +++ b/MoPubSDK/MPBool.h @@ -1,7 +1,7 @@ // // MPBool.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/MPConsentChangedNotification.h b/MoPubSDK/MPConsentChangedNotification.h index 70284261c..ce434fc9c 100644 --- a/MoPubSDK/MPConsentChangedNotification.h +++ b/MoPubSDK/MPConsentChangedNotification.h @@ -1,7 +1,7 @@ // // MPConsentChangedNotification.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,7 +9,7 @@ #import /** - Notification fire whenever the current consent status has changed. The payload + Notification fired whenever the current consent status has changed. The payload will be included in the @NSNotification.userInfo dictionary. */ extern NSString * const kMPConsentChangedNotification; diff --git a/MoPubSDK/MPConsentChangedNotification.m b/MoPubSDK/MPConsentChangedNotification.m index 1393d4d7e..deb3d8d4b 100644 --- a/MoPubSDK/MPConsentChangedNotification.m +++ b/MoPubSDK/MPConsentChangedNotification.m @@ -1,7 +1,7 @@ // // MPConsentChangedNotification.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/MPConsentChangedReason.h b/MoPubSDK/MPConsentChangedReason.h index 3bbcee027..814713ef9 100644 --- a/MoPubSDK/MPConsentChangedReason.h +++ b/MoPubSDK/MPConsentChangedReason.h @@ -1,7 +1,7 @@ // // MPConsentChangedReason.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/MPConsentChangedReason.m b/MoPubSDK/MPConsentChangedReason.m index 0c6193528..04afec6a3 100644 --- a/MoPubSDK/MPConsentChangedReason.m +++ b/MoPubSDK/MPConsentChangedReason.m @@ -1,7 +1,7 @@ // // MPConsentChangedReason.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/MPConsentError.h b/MoPubSDK/MPConsentError.h index 0a449ff30..fa693ec28 100644 --- a/MoPubSDK/MPConsentError.h +++ b/MoPubSDK/MPConsentError.h @@ -1,7 +1,7 @@ // // MPConsentError.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/MPConsentStatus.h b/MoPubSDK/MPConsentStatus.h index 6d840f347..1662ceb62 100644 --- a/MoPubSDK/MPConsentStatus.h +++ b/MoPubSDK/MPConsentStatus.h @@ -1,7 +1,7 @@ // // 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/MPConstants.h b/MoPubSDK/MPConstants.h index 34f668303..31464c17a 100644 --- a/MoPubSDK/MPConstants.h +++ b/MoPubSDK/MPConstants.h @@ -1,22 +1,20 @@ // // MPConstants.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 -#define MP_DEBUG_MODE 1 - #define MP_HAS_NATIVE_PACKAGE 1 #define DEFAULT_PUB_ID @"agltb3B1Yi1pbmNyDAsSBFNpdGUYkaoMDA" #define MP_SERVER_VERSION @"8" #define MP_REWARDED_API_VERSION @"1" #define MP_BUNDLE_IDENTIFIER @"com.mopub.mopub" -#define MP_SDK_VERSION @"5.4.1" +#define MP_SDK_VERSION @"5.5.0" // Sizing constants. extern CGSize const MOPUB_BANNER_SIZE; diff --git a/MoPubSDK/MPConstants.m b/MoPubSDK/MPConstants.m index 0243d0fb2..9ac7962ef 100644 --- a/MoPubSDK/MPConstants.m +++ b/MoPubSDK/MPConstants.m @@ -1,7 +1,7 @@ // // MPConstants.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/MPInterstitialAdController.h b/MoPubSDK/MPInterstitialAdController.h index 070d2369f..9cb9f068f 100644 --- a/MoPubSDK/MPInterstitialAdController.h +++ b/MoPubSDK/MPInterstitialAdController.h @@ -1,7 +1,7 @@ // // MPInterstitialAdController.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/MPInterstitialAdController.m b/MoPubSDK/MPInterstitialAdController.m index da3691ef2..cf700bcce 100644 --- a/MoPubSDK/MPInterstitialAdController.m +++ b/MoPubSDK/MPInterstitialAdController.m @@ -1,7 +1,7 @@ // // MPInterstitialAdController.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/ // @@ -76,13 +76,13 @@ - (void)loadAd - (void)showFromViewController:(UIViewController *)controller { if (!controller) { - MPLogWarn(@"The interstitial could not be shown: " + MPLogInfo(@"The interstitial could not be shown: " @"a nil view controller was passed to -showFromViewController:."); return; } if (![controller.view.window isKeyWindow]) { - MPLogWarn(@"Attempted to present an interstitial ad in non-key window. The ad may not render properly"); + MPLogInfo(@"Attempted to present an interstitial ad in non-key window. The ad may not render properly"); } [self.manager presentInterstitialFromViewController:controller]; diff --git a/MoPubSDK/MPInterstitialCustomEvent.h b/MoPubSDK/MPInterstitialCustomEvent.h index 20dd25373..9e606109e 100644 --- a/MoPubSDK/MPInterstitialCustomEvent.h +++ b/MoPubSDK/MPInterstitialCustomEvent.h @@ -1,7 +1,7 @@ // // MPInterstitialCustomEvent.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/MPInterstitialCustomEvent.m b/MoPubSDK/MPInterstitialCustomEvent.m index 93a68c556..b01692e8f 100644 --- a/MoPubSDK/MPInterstitialCustomEvent.m +++ b/MoPubSDK/MPInterstitialCustomEvent.m @@ -1,7 +1,7 @@ // // MPInterstitialCustomEvent.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/MPInterstitialCustomEventDelegate.h b/MoPubSDK/MPInterstitialCustomEventDelegate.h index 770aa1dae..1f5a0f825 100644 --- a/MoPubSDK/MPInterstitialCustomEventDelegate.h +++ b/MoPubSDK/MPInterstitialCustomEventDelegate.h @@ -1,7 +1,7 @@ // // MPInterstitialCustomEventDelegate.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/MPLogLevel.h b/MoPubSDK/MPLogLevel.h deleted file mode 100644 index dc7d8a67d..000000000 --- a/MoPubSDK/MPLogLevel.h +++ /dev/null @@ -1,24 +0,0 @@ -// -// MPLogLevel.h -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import - -/** - * SDK logging level. - * @remark Lower values equate to more detailed logs. - */ -typedef enum { - MPLogLevelAll = 0, - MPLogLevelTrace = 10, - MPLogLevelDebug = 20, - MPLogLevelInfo = 30, - MPLogLevelWarn = 40, - MPLogLevelError = 50, - MPLogLevelFatal = 60, - MPLogLevelOff = 70 -} MPLogLevel; diff --git a/MoPubSDK/MPMediationSdkInitializable.h b/MoPubSDK/MPMediationSdkInitializable.h deleted file mode 100644 index 0a1ff50b5..000000000 --- a/MoPubSDK/MPMediationSdkInitializable.h +++ /dev/null @@ -1,26 +0,0 @@ -// -// MPMediationSdkInitializable.h -// -// Copyright 2018 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import - -/** - Indicates that the implementer is initializable by the MoPub SDK when - @c initializeSdkWithConfiguration:complete: is called, or whenever the - mediated network needs to be initialized. - */ -@protocol MPMediationSdkInitializable - -/** - Called when the MoPub SDK requires the underlying mediation SDK to be initialized. - - @param parameters A dictionary containing any mediation SDK-specific information - needed for initialization, such as app IDs and placement IDs. - */ -- (void)initializeSdkWithParameters:(NSDictionary * _Nullable)parameters; - -@end diff --git a/MoPubSDK/MPMediationSettingsProtocol.h b/MoPubSDK/MPMediationSettingsProtocol.h index 073e01432..bf8bb4629 100644 --- a/MoPubSDK/MPMediationSettingsProtocol.h +++ b/MoPubSDK/MPMediationSettingsProtocol.h @@ -1,7 +1,7 @@ // // MPMediationSettingsProtocol.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/MPMoPubConfiguration.h b/MoPubSDK/MPMoPubConfiguration.h index 86b040cf4..5275f40ff 100644 --- a/MoPubSDK/MPMoPubConfiguration.h +++ b/MoPubSDK/MPMoPubConfiguration.h @@ -1,20 +1,28 @@ // // MPMoPubConfiguration.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 "MPAdvancedBidder.h" -#import "MPMediationSdkInitializable.h" +#import "MPAdapterConfiguration.h" +#import "MPLogLevel.h" #import "MPMediationSettingsProtocol.h" #import "MPRewardedVideo.h" NS_ASSUME_NONNULL_BEGIN @interface MPMoPubConfiguration : NSObject +/** + Optional list of additional mediated network SDKs to pre-initialize from the cache. If the mediated network + SDK has no cache entry, nothing will be done. + @remarks All certified mediated networks will be pre-initialized. This property is meant to pre-initialize + any custom adapters that have not been certified by MoPub. + */ +@property (nonatomic, strong, nullable) NSArray> * additionalNetworks; + /** Any valid ad unit ID used within the app used for app initialization. @remark This is a required field. @@ -22,9 +30,9 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, strong, nonnull) NSString * adUnitIdForAppInitialization; /** - Optional list of advanced bidders to initialize. + 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, strong, nullable) NSArray> * advancedBidders; +@property (nonatomic, assign) BOOL allowLegitimateInterest; /** Optional global configurations for all ad networks your app supports. @@ -32,13 +40,21 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, strong, nullable) NSArray> * globalMediationSettings; /** - Optional list of mediated network SDKs to pre-initialize from the cache. If the mediated network - SDK has no cache entry, nothing will be done. If set to @c nil or empty array, no network - SDKs will be preinitialized. + Optional logging level. By default, this value is set to @c MPLogLevelNone. + */ +@property (nonatomic, assign) MPLogLevel loggingLevel; + +/** + Optional configuration settings for mediated networks during initialization. To add entries + to this dictionary, use the convenience method @c setNetworkConfiguration:forMediationAdapter: + */ +@property (nonatomic, strong, nullable) NSMutableDictionary *> * mediatedNetworkConfigurations; - To initialize all existing cached networks use @c MoPub.sharedInstance.allCachedNetworks +/** + Optional MoPub request options for mediated networks. To add entries + to this dictionary, use the convenience method @c setMoPubRequestOptions:forMediationAdapter: */ -@property (nonatomic, strong, nullable) NSArray> * mediatedNetworks; +@property (nonatomic, strong, nullable) NSMutableDictionary *> * moPubRequestOptions; /** Initializes the @c MPMoPubConfiguration object with the required fields. @@ -47,6 +63,22 @@ NS_ASSUME_NONNULL_BEGIN */ - (instancetype)initWithAdUnitIdForAppInitialization:(NSString *)adUnitId NS_DESIGNATED_INITIALIZER; +/** + Sets the network configuration options for a given mediated network class name. + @param configuration Configuration parameters specific to the network. Only @c NSString, @c NSNumber, @c NSArray, and @c NSDictionary types are allowed. This value may be @c nil. + @param adapterClassName The class name of the mediated adapter that will receive the inputted configuration. The adapter class must implement the @c MPAdapterConfiguration protocol. + */ +- (void)setNetworkConfiguration:(NSDictionary * _Nullable)configuration + forMediationAdapter:(NSString *)adapterClassName; + +/** + Sets the mediated network's MoPub request options. + @param options MoPub request options for the mediated network. + @param adapterClassName The class name of the mediated adapter that will receive the inputted configuration. The adapter class must implement the @c MPAdapterConfiguration protocol. + */ +- (void)setMoPubRequestOptions:(NSDictionary * _Nullable)options + forMediationAdapter:(NSString *)adapterClassName; + /** Usage of default initializer is disallowed. Use @c initWithAdUnitIdForAppInitialization: instead. */ diff --git a/MoPubSDK/MPMoPubConfiguration.m b/MoPubSDK/MPMoPubConfiguration.m index 305477e7b..0c4238dd4 100644 --- a/MoPubSDK/MPMoPubConfiguration.m +++ b/MoPubSDK/MPMoPubConfiguration.m @@ -1,24 +1,87 @@ // // MPMoPubConfiguration.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 "MPMoPubConfiguration.h" +#import "MPAdapterConfiguration.h" +#import "MPLogging.h" @implementation MPMoPubConfiguration - (instancetype)initWithAdUnitIdForAppInitialization:(NSString * _Nonnull)adUnitId { if (self = [super init]) { + _additionalNetworks = nil; _adUnitIdForAppInitialization = adUnitId; - _advancedBidders = nil; + _allowLegitimateInterest = NO; _globalMediationSettings = nil; - _mediatedNetworks = nil; + _loggingLevel = MPLogLevelNone; + _mediatedNetworkConfigurations = nil; + _moPubRequestOptions = nil; } return self; } +- (void)setNetworkConfiguration:(NSDictionary *)configuration + forMediationAdapter:(NSString *)adapterClassName { + // Invalid adapter class name + if (adapterClassName == nil) { + return; + } + + // Adapter class name does not exist in the runtime. + Class adapterClass = NSClassFromString(adapterClassName); + if (adapterClass == Nil) { + MPLogDebug(@"%@ is not a valid class name.", adapterClassName); + return; + } + + // Adapter class name does not conform to `MPAdapterConfiguration` + if (![adapterClass conformsToProtocol:@protocol(MPAdapterConfiguration)]) { + MPLogDebug(@"%@ does not conform to MPAdapterConfiguration protocol.", adapterClassName); + return; + } + + // Lazy initialization + if (self.mediatedNetworkConfigurations == nil) { + self.mediatedNetworkConfigurations = [NSMutableDictionary dictionaryWithCapacity:1]; + } + + // Add the entry + self.mediatedNetworkConfigurations[adapterClassName] = configuration; +} + +- (void)setMoPubRequestOptions:(NSDictionary * _Nullable)options + forMediationAdapter:(NSString *)adapterClassName { + // Invalid adapter class name + if (adapterClassName == nil) { + return; + } + + // Adapter class name does not exist in the runtime. + Class adapterClass = NSClassFromString(adapterClassName); + if (adapterClass == Nil) { + MPLogDebug(@"%@ is not a valid class name.", adapterClassName); + return; + } + + // Adapter class name does not conform to `MPAdapterConfiguration` + if (![adapterClass conformsToProtocol:@protocol(MPAdapterConfiguration)]) { + MPLogDebug(@"%@ does not conform to MPAdapterConfiguration protocol.", adapterClassName); + return; + } + + // Lazy initialization + if (self.moPubRequestOptions == nil) { + self.moPubRequestOptions = [NSMutableDictionary dictionaryWithCapacity:1]; + } + + // Add the entry + self.moPubRequestOptions[adapterClassName] = options; +} + @end diff --git a/MoPubSDK/MoPub.h b/MoPubSDK/MoPub.h index 05cb246bc..a263d7436 100644 --- a/MoPubSDK/MoPub.h +++ b/MoPubSDK/MoPub.h @@ -1,7 +1,7 @@ // // MoPub.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,14 @@ #import "MPConstants.h" #import "MOPUBDisplayAgentType.h" +#import "MPAdapterConfiguration.h" #import "MPAdConversionTracker.h" #import "MPAdImpressionTimer.h" #import "MPAdTargeting.h" -#import "MPAdvancedBidder.h" #import "MPAdView.h" #import "MPBannerCustomEvent.h" #import "MPBannerCustomEventDelegate.h" +#import "MPBaseAdapterConfiguration.h" #import "MPBool.h" #import "MPConsentChangedNotification.h" #import "MPConsentChangedReason.h" @@ -29,15 +30,12 @@ #import "MPInterstitialCustomEventDelegate.h" #import "MPLogging.h" #import "MPLogLevel.h" -#import "MPLogProvider.h" -#import "MPMediationSdkInitializable.h" #import "MPMediationSettingsProtocol.h" #import "MPMoPubConfiguration.h" #import "MPRealTimeTimer.h" #import "MPRewardedVideo.h" #import "MPRewardedVideoReward.h" #import "MPRewardedVideoCustomEvent.h" -#import "MPRewardedVideoCustomEvent+Caching.h" #import "MPRewardedVideoError.h" #import "MPViewabilityAdapter.h" #import "MPViewabilityOption.h" @@ -132,15 +130,9 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, assign) BOOL forceWKWebView; /** - * SDK log level. The default value is `MPLogLevelInfo`. + * SDK log level. The default value is `MPLogLevelNone`. */ -@property (nonatomic, assign) MPLogLevel logLevel; - -/** - * 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 enableAdvancedBidding; +@property (nonatomic, assign) MPLogLevel logLevel __attribute((deprecated("Use the MPMoPubConfiguration.loggingLevel instead."))); /** * Initializes the MoPub SDK asynchronously on a background thread. @@ -189,11 +181,6 @@ NS_ASSUME_NONNULL_BEGIN @end @interface MoPub (Mediation) -/** - * Retrieves all currently cached mediated networks. - * @return A list of all cached networks or @c nil. - */ -- (NSArray> * _Nullable)allCachedNetworks; /** * Clears all currently cached mediated networks. @@ -237,6 +224,11 @@ NS_ASSUME_NONNULL_BEGIN * Consent Acquisition */ +/** + 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; + /** * `YES` if a consent dialog is presently loaded and ready to be shown; `NO` otherwise */ diff --git a/MoPubSDK/MoPub.m b/MoPubSDK/MoPub.m index 1ded71584..ccc1d99e4 100644 --- a/MoPubSDK/MoPub.m +++ b/MoPubSDK/MoPub.m @@ -1,13 +1,12 @@ // // MoPub.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 "MoPub.h" -#import "MPAdvancedBiddingManager.h" #import "MPConsentManager.h" #import "MPConstants.h" #import "MPCoreInstanceProvider.h" @@ -15,7 +14,6 @@ #import "MPLogging.h" #import "MPMediationManager.h" #import "MPRewardedVideo.h" -#import "MPRewardedVideoCustomEvent+Caching.h" #import "MPIdentityProvider.h" #import "MPWebView.h" #import "MOPUBExperimentProvider.h" @@ -81,22 +79,12 @@ - (BOOL)forceWKWebView - (void)setLogLevel:(MPLogLevel)level { - MPLogSetLevel(level); + MPLogging.consoleLogLevel = level; } - (MPLogLevel)logLevel { - return MPLogGetLevel(); -} - -- (void)setEnableAdvancedBidding:(BOOL)enableAdvancedBidding -{ - [MPAdvancedBiddingManager sharedManager].advancedBiddingEnabled = enableAdvancedBidding; -} - -- (BOOL)enableAdvancedBidding -{ - return [MPAdvancedBiddingManager sharedManager].advancedBiddingEnabled; + return MPLogging.consoleLogLevel; } - (void)setClickthroughDisplayAgentType:(MOPUBDisplayAgentType)displayAgentType @@ -133,6 +121,9 @@ - (void)setSdkWithConfiguration:(MPMoPubConfiguration *)configuration completion:(void(^_Nullable)(void))completionBlock { @synchronized (self) { + // Set the console logging level. + MPLogging.consoleLogLevel = configuration.loggingLevel; + // Store the global mediation settings self.globalMediationSettings = configuration.globalMediationSettings; @@ -143,6 +134,7 @@ - (void)setSdkWithConfiguration:(MPMoPubConfiguration *)configuration // of `checkForDoNotTrackAndTransition`. dispatch_group_enter(initializationGroup); MPConsentManager.sharedManager.adUnitIdUsedForConsent = configuration.adUnitIdForAppInitialization; + MPConsentManager.sharedManager.allowLegitimateInterest = configuration.allowLegitimateInterest; [MPConsentManager.sharedManager checkForDoNotTrackAndTransition]; [MPConsentManager.sharedManager synchronizeConsentWithCompletion:^(NSError * _Nullable error) { dispatch_group_leave(initializationGroup); @@ -152,21 +144,20 @@ - (void)setSdkWithConfiguration:(MPMoPubConfiguration *)configuration [MPSessionTracker initializeNotificationObservers]; // Configure mediated network SDKs + __block NSArray> * initializedNetworks = nil; dispatch_group_enter(initializationGroup); - NSArray> * mediatedNetworks = configuration.mediatedNetworks; - [MPMediationManager.sharedManager initializeMediatedNetworks:mediatedNetworks completion:^(NSError * _Nullable error) { - dispatch_group_leave(initializationGroup); - }]; - - // Configure advanced bidders - dispatch_group_enter(initializationGroup); - [MPAdvancedBiddingManager.sharedManager initializeBidders:configuration.advancedBidders complete:^{ + [MPMediationManager.sharedManager initializeWithAdditionalProviders:configuration.additionalNetworks + configurations:configuration.mediatedNetworkConfigurations + requestOptions:configuration.moPubRequestOptions + complete:^(NSError * error, NSArray> * initializedAdapters) { + initializedNetworks = initializedAdapters; dispatch_group_leave(initializationGroup); }]; // Once all of the asynchronous tasks have completed, notify the // completion handler. dispatch_group_notify(initializationGroup, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ + MPLogEvent([MPLogEvent sdkInitializedWithNetworks:initializedNetworks]); self.isSdkInitialized = YES; if (completionBlock) { completionBlock(); @@ -202,8 +193,21 @@ - (void)disableViewability:(MPViewabilityOption)vendors @implementation MoPub (Mediation) -- (NSArray> * _Nullable)allCachedNetworks { - return [MPMediationManager.sharedManager allCachedNetworks]; +- (id)adapterConfigurationNamed:(NSString *)className { + // No class name + if (className == nil) { + return nil; + } + + // Class doesn't exist. + Class classToFind = NSClassFromString(className); + if (classToFind == Nil) { + return nil; + } + + NSPredicate * predicate = [NSPredicate predicateWithFormat:@"self isKindOfClass: %@", classToFind]; + NSArray * adapters = [MPMediationManager.sharedManager.adapters.allValues filteredArrayUsingPredicate:predicate]; + return adapters.firstObject; } - (void)clearCachedNetworks { @@ -276,6 +280,14 @@ - (void)showConsentDialogFromViewController:(UIViewController *)viewController c didDismiss:nil]; } +- (BOOL)allowLegitimateInterest { + return [MPConsentManager sharedManager].allowLegitimateInterest; +} + +- (void)setAllowLegitimateInterest:(BOOL)allowLegitimateInterest { + [MPConsentManager sharedManager].allowLegitimateInterest = allowLegitimateInterest; +} + - (BOOL)isConsentDialogReady { return [MPConsentManager sharedManager].isConsentDialogLoaded; } diff --git a/MoPubSDK/NativeAds/Internal/Categories/MPNativeAdRequest+MPNativeAdSource.h b/MoPubSDK/NativeAds/Internal/Categories/MPNativeAdRequest+MPNativeAdSource.h index 91651a6fb..f444198cc 100644 --- a/MoPubSDK/NativeAds/Internal/Categories/MPNativeAdRequest+MPNativeAdSource.h +++ b/MoPubSDK/NativeAds/Internal/Categories/MPNativeAdRequest+MPNativeAdSource.h @@ -1,7 +1,7 @@ // // MPNativeAdRequest+MPNativeAdSource.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/NativeAds/Internal/MPAdPlacerInvocation.h b/MoPubSDK/NativeAds/Internal/MPAdPlacerInvocation.h index 045aed791..960cf6d58 100644 --- a/MoPubSDK/NativeAds/Internal/MPAdPlacerInvocation.h +++ b/MoPubSDK/NativeAds/Internal/MPAdPlacerInvocation.h @@ -1,7 +1,7 @@ // // MPAdPlacerInvocation.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/NativeAds/Internal/MPAdPlacerInvocation.m b/MoPubSDK/NativeAds/Internal/MPAdPlacerInvocation.m index 5ffa5aa7c..9c692f96e 100644 --- a/MoPubSDK/NativeAds/Internal/MPAdPlacerInvocation.m +++ b/MoPubSDK/NativeAds/Internal/MPAdPlacerInvocation.m @@ -1,7 +1,7 @@ // // MPAdPlacerInvocation.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/NativeAds/Internal/MPCollectionViewAdPlacerCell.h b/MoPubSDK/NativeAds/Internal/MPCollectionViewAdPlacerCell.h index 46b48ce29..854cc0257 100644 --- a/MoPubSDK/NativeAds/Internal/MPCollectionViewAdPlacerCell.h +++ b/MoPubSDK/NativeAds/Internal/MPCollectionViewAdPlacerCell.h @@ -1,7 +1,7 @@ // // MPCollectionViewAdPlacerCell.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/NativeAds/Internal/MPCollectionViewAdPlacerCell.m b/MoPubSDK/NativeAds/Internal/MPCollectionViewAdPlacerCell.m index 21386f17a..fd9014f0d 100644 --- a/MoPubSDK/NativeAds/Internal/MPCollectionViewAdPlacerCell.m +++ b/MoPubSDK/NativeAds/Internal/MPCollectionViewAdPlacerCell.m @@ -1,7 +1,7 @@ // // MPCollectionViewAdPlacerCell.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/NativeAds/Internal/MPDiskLRUCache.h b/MoPubSDK/NativeAds/Internal/MPDiskLRUCache.h index a43800c09..fd79746ea 100755 --- a/MoPubSDK/NativeAds/Internal/MPDiskLRUCache.h +++ b/MoPubSDK/NativeAds/Internal/MPDiskLRUCache.h @@ -1,7 +1,7 @@ // // MPDiskLRUCache.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/NativeAds/Internal/MPDiskLRUCache.m b/MoPubSDK/NativeAds/Internal/MPDiskLRUCache.m index a8bbd6bbc..ee9dd69a7 100755 --- a/MoPubSDK/NativeAds/Internal/MPDiskLRUCache.m +++ b/MoPubSDK/NativeAds/Internal/MPDiskLRUCache.m @@ -1,7 +1,7 @@ // // MPDiskLRUCache.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/NativeAds/Internal/MPImageDownloadQueue.h b/MoPubSDK/NativeAds/Internal/MPImageDownloadQueue.h index 75af35f18..b617ceafb 100755 --- a/MoPubSDK/NativeAds/Internal/MPImageDownloadQueue.h +++ b/MoPubSDK/NativeAds/Internal/MPImageDownloadQueue.h @@ -1,7 +1,7 @@ // // MPImageDownloadQueue.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/NativeAds/Internal/MPImageDownloadQueue.m b/MoPubSDK/NativeAds/Internal/MPImageDownloadQueue.m index 48f35a5de..37abe90b6 100755 --- a/MoPubSDK/NativeAds/Internal/MPImageDownloadQueue.m +++ b/MoPubSDK/NativeAds/Internal/MPImageDownloadQueue.m @@ -1,7 +1,7 @@ // // MPImageDownloadQueue.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/NativeAds/Internal/MPMoPubNativeAdAdapter.h b/MoPubSDK/NativeAds/Internal/MPMoPubNativeAdAdapter.h index 8e97533f2..dce15a2d9 100644 --- a/MoPubSDK/NativeAds/Internal/MPMoPubNativeAdAdapter.h +++ b/MoPubSDK/NativeAds/Internal/MPMoPubNativeAdAdapter.h @@ -1,7 +1,7 @@ // // MPMoPubNativeAdAdapter.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/NativeAds/Internal/MPMoPubNativeAdAdapter.m b/MoPubSDK/NativeAds/Internal/MPMoPubNativeAdAdapter.m index 8f5c12712..1e358b911 100644 --- a/MoPubSDK/NativeAds/Internal/MPMoPubNativeAdAdapter.m +++ b/MoPubSDK/NativeAds/Internal/MPMoPubNativeAdAdapter.m @@ -1,7 +1,7 @@ // // MPMoPubNativeAdAdapter.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/NativeAds/Internal/MPMoPubNativeCustomEvent.h b/MoPubSDK/NativeAds/Internal/MPMoPubNativeCustomEvent.h index cd94873ed..8b8b07aa7 100644 --- a/MoPubSDK/NativeAds/Internal/MPMoPubNativeCustomEvent.h +++ b/MoPubSDK/NativeAds/Internal/MPMoPubNativeCustomEvent.h @@ -1,7 +1,7 @@ // // MPMoPubNativeCustomEvent.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/NativeAds/Internal/MPMoPubNativeCustomEvent.m b/MoPubSDK/NativeAds/Internal/MPMoPubNativeCustomEvent.m index f70b375b8..555b9a2f8 100644 --- a/MoPubSDK/NativeAds/Internal/MPMoPubNativeCustomEvent.m +++ b/MoPubSDK/NativeAds/Internal/MPMoPubNativeCustomEvent.m @@ -1,7 +1,7 @@ // // MPMoPubNativeCustomEvent.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 "MPMoPubNativeAdAdapter.h" #import "MPNativeAd+Internal.h" #import "MPNativeAdError.h" +#import "MPNativeAdConstants.h" #import "MPLogging.h" #import "MPNativeAdUtils.h" @@ -17,6 +18,9 @@ @implementation MPMoPubNativeCustomEvent - (void)requestAdWithCustomEventInfo:(NSDictionary *)info { + NSString * adUnitId = info[kNativeAdUnitId]; + MPLogAdEvent([MPLogEvent adLoadAttemptForAdapter:NSStringFromClass(self.class) dspCreativeId:info[kNativeAdDspCreativeId] dspName:info[kNativeAdDspName]], adUnitId); + MPMoPubNativeAdAdapter *adAdapter = [[MPMoPubNativeAdAdapter alloc] initWithAdProperties:[info mutableCopy]]; if (adAdapter.properties) { @@ -29,21 +33,27 @@ - (void)requestAdWithCustomEventInfo:(NSDictionary *)info for (NSString *key in [info allKeys]) { if ([[key lowercaseString] hasSuffix:@"image"] && [[info objectForKey:key] isKindOfClass:[NSString class]]) { if (![MPNativeAdUtils addURLString:[info objectForKey:key] toURLArray:imageURLs]) { - [self.delegate nativeCustomEvent:self didFailToLoadAdWithError:MPNativeAdNSErrorForInvalidImageURL()]; + NSError * error = MPNativeAdNSErrorForInvalidImageURL(); + MPLogAdEvent([MPLogEvent adLoadFailedForAdapter:NSStringFromClass(self.class) error:error], adUnitId); + [self.delegate nativeCustomEvent:self didFailToLoadAdWithError:error]; } } } [super precacheImagesWithURLs:imageURLs completionBlock:^(NSArray *errors) { if (errors) { - MPLogDebug(@"%@", errors); - [self.delegate nativeCustomEvent:self didFailToLoadAdWithError:MPNativeAdNSErrorForImageDownloadFailure()]; + NSError * error = MPNativeAdNSErrorForImageDownloadFailure(); + MPLogAdEvent([MPLogEvent adLoadFailedForAdapter:NSStringFromClass(self.class) error:error], adUnitId); + [self.delegate nativeCustomEvent:self didFailToLoadAdWithError:error]; } else { + MPLogAdEvent([MPLogEvent adLoadSuccessForAdapter:NSStringFromClass(self.class)], adUnitId); [self.delegate nativeCustomEvent:self didLoadAd:interfaceAd]; } }]; } else { - [self.delegate nativeCustomEvent:self didFailToLoadAdWithError:MPNativeAdNSErrorForInvalidAdServerResponse(nil)]; + NSError * error = MPNativeAdNSErrorForInvalidAdServerResponse(nil); + MPLogAdEvent([MPLogEvent adLoadFailedForAdapter:NSStringFromClass(self.class) error:error], adUnitId); + [self.delegate nativeCustomEvent:self didFailToLoadAdWithError:error]; } } diff --git a/MoPubSDK/NativeAds/Internal/MPNativeAd+Internal.h b/MoPubSDK/NativeAds/Internal/MPNativeAd+Internal.h index df3e572ab..b295db946 100644 --- a/MoPubSDK/NativeAds/Internal/MPNativeAd+Internal.h +++ b/MoPubSDK/NativeAds/Internal/MPNativeAd+Internal.h @@ -1,7 +1,7 @@ // // MPNativeAd+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/NativeAds/Internal/MPNativeAd+Internal.m b/MoPubSDK/NativeAds/Internal/MPNativeAd+Internal.m index b90156460..01af32c18 100644 --- a/MoPubSDK/NativeAds/Internal/MPNativeAd+Internal.m +++ b/MoPubSDK/NativeAds/Internal/MPNativeAd+Internal.m @@ -1,7 +1,7 @@ // // MPNativeAd+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/NativeAds/Internal/MPNativeAdConfigValues+Internal.h b/MoPubSDK/NativeAds/Internal/MPNativeAdConfigValues+Internal.h index d5c38c2b6..8c1b9079b 100644 --- a/MoPubSDK/NativeAds/Internal/MPNativeAdConfigValues+Internal.h +++ b/MoPubSDK/NativeAds/Internal/MPNativeAdConfigValues+Internal.h @@ -1,7 +1,7 @@ // // MPNativeAdConfigValues+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/NativeAds/Internal/MPNativeAdConfigValues+Internal.m b/MoPubSDK/NativeAds/Internal/MPNativeAdConfigValues+Internal.m index d74f3a800..85dcf04b5 100644 --- a/MoPubSDK/NativeAds/Internal/MPNativeAdConfigValues+Internal.m +++ b/MoPubSDK/NativeAds/Internal/MPNativeAdConfigValues+Internal.m @@ -1,7 +1,7 @@ // // MPNativeAdConfigValues+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/NativeAds/Internal/MPNativeAdConfigValues.h b/MoPubSDK/NativeAds/Internal/MPNativeAdConfigValues.h index 284ec4e21..8479f01f6 100644 --- a/MoPubSDK/NativeAds/Internal/MPNativeAdConfigValues.h +++ b/MoPubSDK/NativeAds/Internal/MPNativeAdConfigValues.h @@ -1,7 +1,7 @@ // // MPNativeAdConfigValues.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/NativeAds/Internal/MPNativeAdConfigValues.m b/MoPubSDK/NativeAds/Internal/MPNativeAdConfigValues.m index c8f49332d..8410187d4 100644 --- a/MoPubSDK/NativeAds/Internal/MPNativeAdConfigValues.m +++ b/MoPubSDK/NativeAds/Internal/MPNativeAdConfigValues.m @@ -1,7 +1,7 @@ // // MPNativeAdConfigValues.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/NativeAds/Internal/MPNativeAdRendererConstants.h b/MoPubSDK/NativeAds/Internal/MPNativeAdRendererConstants.h index 2c4fa731b..7be9d3542 100644 --- a/MoPubSDK/NativeAds/Internal/MPNativeAdRendererConstants.h +++ b/MoPubSDK/NativeAds/Internal/MPNativeAdRendererConstants.h @@ -1,7 +1,7 @@ // // MPNativeAdRendererConstants.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/NativeAds/Internal/MPNativeAdRendererImageHandler.h b/MoPubSDK/NativeAds/Internal/MPNativeAdRendererImageHandler.h index 6b3a728a6..124b64939 100644 --- a/MoPubSDK/NativeAds/Internal/MPNativeAdRendererImageHandler.h +++ b/MoPubSDK/NativeAds/Internal/MPNativeAdRendererImageHandler.h @@ -1,7 +1,7 @@ // // MPNativeAdRendererImageHandler.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/NativeAds/Internal/MPNativeAdRendererImageHandler.m b/MoPubSDK/NativeAds/Internal/MPNativeAdRendererImageHandler.m index a6550897e..c093fe3f1 100644 --- a/MoPubSDK/NativeAds/Internal/MPNativeAdRendererImageHandler.m +++ b/MoPubSDK/NativeAds/Internal/MPNativeAdRendererImageHandler.m @@ -1,7 +1,7 @@ // // MPNativeAdRendererImageHandler.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/ // @@ -71,11 +71,7 @@ - (void)loadImageForURL:(NSURL *)imageURL intoImageView:(UIImageView *)imageView UIImage *image = [UIImage imageWithData:[[MPNativeCache sharedCache] retrieveDataForKey:imageURL.absoluteString]]; [strongSelf safeMainQueueSetImage:image intoImageView:imageView]; - } else { - MPLogDebug(@"Failed to download %@ on cache miss. Giving up for now.", imageURL); } - } else { - MPLogInfo(@"MPNativeAd deallocated before loadImageForURL:intoImageView: download completion block was called"); } }]; } diff --git a/MoPubSDK/NativeAds/Internal/MPNativeAdSourceQueue.h b/MoPubSDK/NativeAds/Internal/MPNativeAdSourceQueue.h index 9ae845a11..10b4b527c 100644 --- a/MoPubSDK/NativeAds/Internal/MPNativeAdSourceQueue.h +++ b/MoPubSDK/NativeAds/Internal/MPNativeAdSourceQueue.h @@ -1,7 +1,7 @@ // // MPNativeAdSourceQueue.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/NativeAds/Internal/MPNativeAdSourceQueue.m b/MoPubSDK/NativeAds/Internal/MPNativeAdSourceQueue.m index 86f8b5a39..e191fb3b6 100644 --- a/MoPubSDK/NativeAds/Internal/MPNativeAdSourceQueue.m +++ b/MoPubSDK/NativeAds/Internal/MPNativeAdSourceQueue.m @@ -1,7 +1,7 @@ // // MPNativeAdSourceQueue.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/NativeAds/Internal/MPNativeAdUtils.h b/MoPubSDK/NativeAds/Internal/MPNativeAdUtils.h index 2da65d656..fb37fcaec 100644 --- a/MoPubSDK/NativeAds/Internal/MPNativeAdUtils.h +++ b/MoPubSDK/NativeAds/Internal/MPNativeAdUtils.h @@ -1,7 +1,7 @@ // // MPNativeAdUtils.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/NativeAds/Internal/MPNativeAdUtils.m b/MoPubSDK/NativeAds/Internal/MPNativeAdUtils.m index 9adcc1998..64acfaee1 100644 --- a/MoPubSDK/NativeAds/Internal/MPNativeAdUtils.m +++ b/MoPubSDK/NativeAds/Internal/MPNativeAdUtils.m @@ -1,7 +1,7 @@ // // MPNativeAdUtils.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/NativeAds/Internal/MPNativeCache.h b/MoPubSDK/NativeAds/Internal/MPNativeCache.h index 0abc85f4a..c270d2b30 100644 --- a/MoPubSDK/NativeAds/Internal/MPNativeCache.h +++ b/MoPubSDK/NativeAds/Internal/MPNativeCache.h @@ -1,7 +1,7 @@ // // MPNativeCache.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/NativeAds/Internal/MPNativeCache.m b/MoPubSDK/NativeAds/Internal/MPNativeCache.m index 5155724aa..b86b2d942 100644 --- a/MoPubSDK/NativeAds/Internal/MPNativeCache.m +++ b/MoPubSDK/NativeAds/Internal/MPNativeCache.m @@ -1,7 +1,7 @@ // // MPNativeCache.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,6 +9,7 @@ #import "MPNativeCache.h" #import "MPDiskLRUCache.h" #import "MPLogging.h" +#import typedef enum { MPNativeCacheMethodDisk = 0, diff --git a/MoPubSDK/NativeAds/Internal/MPNativePositionResponseDeserializer.h b/MoPubSDK/NativeAds/Internal/MPNativePositionResponseDeserializer.h index 7c00399a3..5d15cd220 100644 --- a/MoPubSDK/NativeAds/Internal/MPNativePositionResponseDeserializer.h +++ b/MoPubSDK/NativeAds/Internal/MPNativePositionResponseDeserializer.h @@ -1,7 +1,7 @@ // // MPNativePositionResponseDeserializer.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/NativeAds/Internal/MPNativePositionResponseDeserializer.m b/MoPubSDK/NativeAds/Internal/MPNativePositionResponseDeserializer.m index f82c0f285..491b62534 100644 --- a/MoPubSDK/NativeAds/Internal/MPNativePositionResponseDeserializer.m +++ b/MoPubSDK/NativeAds/Internal/MPNativePositionResponseDeserializer.m @@ -1,7 +1,7 @@ // // MPNativePositionResponseDeserializer.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/NativeAds/Internal/MPNativePositionSource.h b/MoPubSDK/NativeAds/Internal/MPNativePositionSource.h index d1439a822..1baf3192c 100644 --- a/MoPubSDK/NativeAds/Internal/MPNativePositionSource.h +++ b/MoPubSDK/NativeAds/Internal/MPNativePositionSource.h @@ -1,7 +1,7 @@ // // MPNativePositionSource.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/NativeAds/Internal/MPNativePositionSource.m b/MoPubSDK/NativeAds/Internal/MPNativePositionSource.m index 3fe9e73ed..dd32f61c8 100644 --- a/MoPubSDK/NativeAds/Internal/MPNativePositionSource.m +++ b/MoPubSDK/NativeAds/Internal/MPNativePositionSource.m @@ -1,7 +1,7 @@ // // MPNativePositionSource.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/NativeAds/Internal/MPNativeView.h b/MoPubSDK/NativeAds/Internal/MPNativeView.h index f975fae56..c9da02b85 100644 --- a/MoPubSDK/NativeAds/Internal/MPNativeView.h +++ b/MoPubSDK/NativeAds/Internal/MPNativeView.h @@ -1,7 +1,7 @@ // // MPNativeView.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/NativeAds/Internal/MPNativeView.m b/MoPubSDK/NativeAds/Internal/MPNativeView.m index 1816befbd..09c3cc08b 100644 --- a/MoPubSDK/NativeAds/Internal/MPNativeView.m +++ b/MoPubSDK/NativeAds/Internal/MPNativeView.m @@ -1,7 +1,7 @@ // // MPNativeView.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/NativeAds/Internal/MPTableViewAdPlacerCell.h b/MoPubSDK/NativeAds/Internal/MPTableViewAdPlacerCell.h index 5095687e0..9ab975374 100644 --- a/MoPubSDK/NativeAds/Internal/MPTableViewAdPlacerCell.h +++ b/MoPubSDK/NativeAds/Internal/MPTableViewAdPlacerCell.h @@ -1,7 +1,7 @@ // // MPTableViewAdPlacerCell.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/NativeAds/Internal/MPTableViewAdPlacerCell.m b/MoPubSDK/NativeAds/Internal/MPTableViewAdPlacerCell.m index 3f2f55a42..b6ab1dfa5 100644 --- a/MoPubSDK/NativeAds/Internal/MPTableViewAdPlacerCell.m +++ b/MoPubSDK/NativeAds/Internal/MPTableViewAdPlacerCell.m @@ -1,7 +1,7 @@ // // MPTableViewAdPlacerCell.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/NativeAds/Internal/MPTableViewCellImpressionTracker.h b/MoPubSDK/NativeAds/Internal/MPTableViewCellImpressionTracker.h index a8426f87b..5b7b24629 100644 --- a/MoPubSDK/NativeAds/Internal/MPTableViewCellImpressionTracker.h +++ b/MoPubSDK/NativeAds/Internal/MPTableViewCellImpressionTracker.h @@ -1,7 +1,7 @@ // // MPTableViewCellImpressionTracker.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/NativeAds/Internal/MPTableViewCellImpressionTracker.m b/MoPubSDK/NativeAds/Internal/MPTableViewCellImpressionTracker.m index adb88273b..7ee922025 100644 --- a/MoPubSDK/NativeAds/Internal/MPTableViewCellImpressionTracker.m +++ b/MoPubSDK/NativeAds/Internal/MPTableViewCellImpressionTracker.m @@ -1,7 +1,7 @@ // // MPTableViewCellImpressionTracker.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/NativeAds/MPAdPositioning.h b/MoPubSDK/NativeAds/MPAdPositioning.h index ac9d877d7..67856ee97 100644 --- a/MoPubSDK/NativeAds/MPAdPositioning.h +++ b/MoPubSDK/NativeAds/MPAdPositioning.h @@ -1,7 +1,7 @@ // // MPAdPositioning.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/NativeAds/MPAdPositioning.m b/MoPubSDK/NativeAds/MPAdPositioning.m index 2164d9af8..1c167352d 100644 --- a/MoPubSDK/NativeAds/MPAdPositioning.m +++ b/MoPubSDK/NativeAds/MPAdPositioning.m @@ -1,7 +1,7 @@ // // MPAdPositioning.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/NativeAds/MPClientAdPositioning.h b/MoPubSDK/NativeAds/MPClientAdPositioning.h index 5f0ae087a..3b2c370da 100644 --- a/MoPubSDK/NativeAds/MPClientAdPositioning.h +++ b/MoPubSDK/NativeAds/MPClientAdPositioning.h @@ -1,7 +1,7 @@ // // MPClientAdPositioning.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/NativeAds/MPClientAdPositioning.m b/MoPubSDK/NativeAds/MPClientAdPositioning.m index de0823fd3..06c8de472 100644 --- a/MoPubSDK/NativeAds/MPClientAdPositioning.m +++ b/MoPubSDK/NativeAds/MPClientAdPositioning.m @@ -1,7 +1,7 @@ // // MPClientAdPositioning.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,7 +26,7 @@ - (void)enableRepeatingPositionsWithInterval:(NSUInteger)interval if (interval > 1) { self.repeatingInterval = interval; } else { - MPLogWarn(@"Repeating positions will not be enabled: the provided interval must be greater " + MPLogInfo(@"Repeating positions will not be enabled: the provided interval must be greater " @"than 1."); } } diff --git a/MoPubSDK/NativeAds/MPCollectionViewAdPlacer.h b/MoPubSDK/NativeAds/MPCollectionViewAdPlacer.h index c016a8ee8..6f35b67bd 100644 --- a/MoPubSDK/NativeAds/MPCollectionViewAdPlacer.h +++ b/MoPubSDK/NativeAds/MPCollectionViewAdPlacer.h @@ -1,7 +1,7 @@ // // MPCollectionViewAdPlacer.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/NativeAds/MPCollectionViewAdPlacer.m b/MoPubSDK/NativeAds/MPCollectionViewAdPlacer.m index 5d6995610..cb32072b5 100644 --- a/MoPubSDK/NativeAds/MPCollectionViewAdPlacer.m +++ b/MoPubSDK/NativeAds/MPCollectionViewAdPlacer.m @@ -1,7 +1,7 @@ // // MPCollectionViewAdPlacer.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/NativeAds/MPNativeAd.h b/MoPubSDK/NativeAds/MPNativeAd.h index 3dfbe80d0..1286e5dfb 100644 --- a/MoPubSDK/NativeAds/MPNativeAd.h +++ b/MoPubSDK/NativeAds/MPNativeAd.h @@ -1,7 +1,7 @@ // // MPNativeAd.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/NativeAds/MPNativeAd.m b/MoPubSDK/NativeAds/MPNativeAd.m index 832383a4b..5ef6c8185 100644 --- a/MoPubSDK/NativeAds/MPNativeAd.m +++ b/MoPubSDK/NativeAds/MPNativeAd.m @@ -1,7 +1,7 @@ // // MPNativeAd.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/ // @@ -181,7 +181,7 @@ - (void)displayAdContent } else { // If this method is called, that means that the backing adapter should implement -displayContentForURL:rootViewController:completion:. // If it doesn't, we'll log a warning. - MPLogWarn(@"Cannot display native ad content. -displayContentForURL:rootViewController:completion: not implemented by native ad adapter: %@", [self.adAdapter class]); + MPLogInfo(@"Cannot display native ad content. -displayContentForURL:rootViewController:completion: not implemented by native ad adapter: %@", [self.adAdapter class]); } } @@ -220,11 +220,13 @@ - (void)nativeAdWillLogImpression:(id)adAdapter - (void)nativeAdDidClick:(id)adAdapter { + MPLogAdEvent(MPLogEvent.adTapped, self.adIdentifier); [self trackClick]; } - (void)nativeAdWillPresentModalForAdapter:(id)adapter { + MPLogAdEvent(MPLogEvent.adWillPresentModal, self.adIdentifier); if ([self.delegate respondsToSelector:@selector(willPresentModalForNativeAd:)]) { [self.delegate willPresentModalForNativeAd:self]; } @@ -232,6 +234,7 @@ - (void)nativeAdWillPresentModalForAdapter:(id)adapter - (void)nativeAdDidDismissModalForAdapter:(id)adapter { + MPLogAdEvent(MPLogEvent.adDidDismissModal, self.adIdentifier); if ([self.delegate respondsToSelector:@selector(didDismissModalForNativeAd:)]) { [self.delegate didDismissModalForNativeAd:self]; } @@ -239,6 +242,7 @@ - (void)nativeAdDidDismissModalForAdapter:(id)adapter - (void)nativeAdWillLeaveApplicationFromAdapter:(id)adapter { + MPLogAdEvent(MPLogEvent.adWillLeaveApplication, self.adIdentifier); if ([self.delegate respondsToSelector:@selector(willLeaveApplicationFromNativeAd:)]) { [self.delegate willLeaveApplicationFromNativeAd:self]; } diff --git a/MoPubSDK/NativeAds/MPNativeAdAdapter.h b/MoPubSDK/NativeAds/MPNativeAdAdapter.h index 091dbc0fc..c96c6b91d 100644 --- a/MoPubSDK/NativeAds/MPNativeAdAdapter.h +++ b/MoPubSDK/NativeAds/MPNativeAdAdapter.h @@ -1,7 +1,7 @@ // // MPNativeAdAdapter.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/NativeAds/MPNativeAdConstants.h b/MoPubSDK/NativeAds/MPNativeAdConstants.h index e628cd02a..556b62d1c 100644 --- a/MoPubSDK/NativeAds/MPNativeAdConstants.h +++ b/MoPubSDK/NativeAds/MPNativeAdConstants.h @@ -1,7 +1,7 @@ // // MPNativeAdConstants.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/ // @@ -36,3 +36,7 @@ extern NSString *const kPrivacyIconTapDestinationURL; extern NSString *const kImpressionTrackerURLsKey; extern NSString *const kDefaultActionURLKey; extern NSString *const kClickTrackerURLKey; + +extern NSString *const kNativeAdUnitId; +extern NSString *const kNativeAdDspCreativeId; +extern NSString *const kNativeAdDspName; diff --git a/MoPubSDK/NativeAds/MPNativeAdConstants.m b/MoPubSDK/NativeAds/MPNativeAdConstants.m index 3b1ccc0fd..becbec053 100644 --- a/MoPubSDK/NativeAds/MPNativeAdConstants.m +++ b/MoPubSDK/NativeAds/MPNativeAdConstants.m @@ -1,7 +1,7 @@ // // MPNativeAdConstants.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/ // @@ -33,3 +33,7 @@ NSString *const kImpressionTrackerURLsKey = @"imptracker"; NSString *const kDefaultActionURLKey = @"clk"; NSString *const kClickTrackerURLKey = @"clktracker"; + +NSString *const kNativeAdUnitId = @"adUnitId"; +NSString *const kNativeAdDspCreativeId = @"dspCreativeId"; +NSString *const kNativeAdDspName = @"dspName"; diff --git a/MoPubSDK/NativeAds/MPNativeAdData.h b/MoPubSDK/NativeAds/MPNativeAdData.h index 2cfbe833d..952112e2d 100644 --- a/MoPubSDK/NativeAds/MPNativeAdData.h +++ b/MoPubSDK/NativeAds/MPNativeAdData.h @@ -1,7 +1,7 @@ // // MPNativeAdData.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/NativeAds/MPNativeAdData.m b/MoPubSDK/NativeAds/MPNativeAdData.m index 920b8d5a2..860da435a 100644 --- a/MoPubSDK/NativeAds/MPNativeAdData.m +++ b/MoPubSDK/NativeAds/MPNativeAdData.m @@ -1,7 +1,7 @@ // // MPNativeAdData.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/NativeAds/MPNativeAdDelegate.h b/MoPubSDK/NativeAds/MPNativeAdDelegate.h index 13c967bd0..cbe45e1d2 100644 --- a/MoPubSDK/NativeAds/MPNativeAdDelegate.h +++ b/MoPubSDK/NativeAds/MPNativeAdDelegate.h @@ -1,7 +1,7 @@ // // MPNativeAdDelegate.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/NativeAds/MPNativeAdError.h b/MoPubSDK/NativeAds/MPNativeAdError.h index ed2b46026..13f875691 100644 --- a/MoPubSDK/NativeAds/MPNativeAdError.h +++ b/MoPubSDK/NativeAds/MPNativeAdError.h @@ -1,7 +1,7 @@ // // MPNativeAdError.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/NativeAds/MPNativeAdError.m b/MoPubSDK/NativeAds/MPNativeAdError.m index 10e1a080d..149aec432 100644 --- a/MoPubSDK/NativeAds/MPNativeAdError.m +++ b/MoPubSDK/NativeAds/MPNativeAdError.m @@ -1,7 +1,7 @@ // // MPNativeAdError.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/NativeAds/MPNativeAdRenderer.h b/MoPubSDK/NativeAds/MPNativeAdRenderer.h index 5d530b594..2a12ff3c2 100644 --- a/MoPubSDK/NativeAds/MPNativeAdRenderer.h +++ b/MoPubSDK/NativeAds/MPNativeAdRenderer.h @@ -1,7 +1,7 @@ // // MPNativeAdRenderer.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/NativeAds/MPNativeAdRendererConfiguration.h b/MoPubSDK/NativeAds/MPNativeAdRendererConfiguration.h index 0bca54c81..d5c22724c 100644 --- a/MoPubSDK/NativeAds/MPNativeAdRendererConfiguration.h +++ b/MoPubSDK/NativeAds/MPNativeAdRendererConfiguration.h @@ -1,7 +1,7 @@ // // MPNativeAdRendererConfiguration.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/NativeAds/MPNativeAdRendererConfiguration.m b/MoPubSDK/NativeAds/MPNativeAdRendererConfiguration.m index 5a35bedab..4a6904032 100644 --- a/MoPubSDK/NativeAds/MPNativeAdRendererConfiguration.m +++ b/MoPubSDK/NativeAds/MPNativeAdRendererConfiguration.m @@ -1,7 +1,7 @@ // // MPNativeAdRendererConfiguration.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/NativeAds/MPNativeAdRendererSettings.h b/MoPubSDK/NativeAds/MPNativeAdRendererSettings.h index e44ca61e5..bea4c020b 100644 --- a/MoPubSDK/NativeAds/MPNativeAdRendererSettings.h +++ b/MoPubSDK/NativeAds/MPNativeAdRendererSettings.h @@ -1,7 +1,7 @@ // // MPNativeAdRendererSettings.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/NativeAds/MPNativeAdRendering.h b/MoPubSDK/NativeAds/MPNativeAdRendering.h index d0f567f0c..fbb5ccb88 100644 --- a/MoPubSDK/NativeAds/MPNativeAdRendering.h +++ b/MoPubSDK/NativeAds/MPNativeAdRendering.h @@ -1,7 +1,7 @@ // // MPNativeAdRendering.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/NativeAds/MPNativeAdRenderingImageLoader.h b/MoPubSDK/NativeAds/MPNativeAdRenderingImageLoader.h index 4b3d8e7ae..d516284ca 100644 --- a/MoPubSDK/NativeAds/MPNativeAdRenderingImageLoader.h +++ b/MoPubSDK/NativeAds/MPNativeAdRenderingImageLoader.h @@ -1,7 +1,7 @@ // // MPNativeAdRenderingImageLoader.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/NativeAds/MPNativeAdRenderingImageLoader.m b/MoPubSDK/NativeAds/MPNativeAdRenderingImageLoader.m index 9bc3ee238..5846415fd 100644 --- a/MoPubSDK/NativeAds/MPNativeAdRenderingImageLoader.m +++ b/MoPubSDK/NativeAds/MPNativeAdRenderingImageLoader.m @@ -1,7 +1,7 @@ // // MPNativeAdRenderingImageLoader.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/NativeAds/MPNativeAdRequest.h b/MoPubSDK/NativeAds/MPNativeAdRequest.h index ec1bbd698..393d75710 100644 --- a/MoPubSDK/NativeAds/MPNativeAdRequest.h +++ b/MoPubSDK/NativeAds/MPNativeAdRequest.h @@ -1,7 +1,7 @@ // // MPNativeAdRequest.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/NativeAds/MPNativeAdRequest.m b/MoPubSDK/NativeAds/MPNativeAdRequest.m index c34980992..a8d9a65fa 100644 --- a/MoPubSDK/NativeAds/MPNativeAdRequest.m +++ b/MoPubSDK/NativeAds/MPNativeAdRequest.m @@ -1,7 +1,7 @@ // // MPNativeAdRequest.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/ // @@ -94,7 +94,7 @@ - (void)startWithCompletionHandler:(MPNativeAdRequestHandler)handler [self loadAdWithURL:self.URL]; } else { - MPLogWarn(@"Native Ad Request did not start - requires completion handler block."); + MPLogInfo(@"Native Ad Request did not start - requires completion handler block."); } } @@ -113,7 +113,7 @@ - (void)startForAdSequence:(NSInteger)adSequence withCompletionHandler:(MPNative [self loadAdWithURL:self.URL]; } else { - MPLogWarn(@"Native Ad Request did not start - requires completion handler block."); + MPLogInfo(@"Native Ad Request did not start - requires completion handler block."); } } @@ -135,8 +135,10 @@ - (void)assignCompletionHandler:(MPNativeAdRequestHandler)handler - (void)loadAdWithURL:(NSURL *)URL { + MPLogAdEvent(MPLogEvent.adLoadAttempt, self.adUnitIdentifier); + if (self.loading) { - MPLogWarn(@"Native ad request is already loading an ad. Wait for previous load to finish."); + MPLogInfo(@"Native ad request is already loading an ad. Wait for previous load to finish."); return; } @@ -173,6 +175,12 @@ - (void)getAdWithConfiguration:(MPAdConfiguration *)configuration impressionMinVisibleSeconds:configuration.nativeImpressionMinVisibleTimeInterval]; } + // Additional information to be passed to the MoPub native custom events + // for the purposes of logging. + classData[kNativeAdUnitId] = self.adUnitIdentifier; + classData[kNativeAdDspName] = nil; // Placeholder for future feature + classData[kNativeAdDspCreativeId] = configuration.dspCreativeId; + configuration.customEventClassData = classData; } @@ -191,8 +199,8 @@ - (void)getAdWithConfiguration:(MPAdConfiguration *)configuration if (customEventRendererConfig == nil) { NSString * noRendererErrorMessage = [NSString stringWithFormat:@"Could not find renderer configuration for custom event class: %@", NSStringFromClass(configuration.customEventClass)]; - NSError * noRendererError = [MOPUBError errorWithCode:MOPUBErrorNoRenderer localizedDescription:noRendererErrorMessage]; - MPLogError(noRendererErrorMessage); + NSError * noRendererError = [NSError errorWithCode:MOPUBErrorNoRenderer localizedDescription:noRendererErrorMessage]; + MPLogEvent([MPLogEvent error:noRendererError message:nil]); [self nativeCustomEvent:nil didFailToLoadAdWithError:noRendererError]; return; @@ -203,8 +211,8 @@ - (void)getAdWithConfiguration:(MPAdConfiguration *)configuration MPNativeCustomEvent *customEvent = [[configuration.customEventClass alloc] init]; if (![customEvent isKindOfClass:[MPNativeCustomEvent class]]) { NSString * invalidCustomEventErrorMessage = [NSString stringWithFormat:@"Custom Event Class: %@ does not extend MPNativeCustomEvent", NSStringFromClass(configuration.customEventClass)]; - NSError * invalidCustomEventError = [MOPUBError errorWithCode:MOPUBErrorNoRenderer localizedDescription:invalidCustomEventErrorMessage]; - MPLogError(invalidCustomEventErrorMessage); + NSError * invalidCustomEventError = [NSError errorWithCode:MOPUBErrorNoRenderer localizedDescription:invalidCustomEventErrorMessage]; + MPLogEvent([MPLogEvent error:invalidCustomEventError message:nil]); [self nativeCustomEvent:nil didFailToLoadAdWithError:invalidCustomEventError]; return; @@ -239,10 +247,10 @@ - (void)completeAdRequestWithAdObject:(MPNativeAd *)adObject error:(NSError *)er [(id)adObject.adAdapter performSelector:@selector(setAdConfiguration:) withObject:self.adConfiguration]; } - if (!error) { - MPLogInfo(@"Successfully loaded native ad."); + if (error == nil) { + MPLogAdEvent(MPLogEvent.adDidLoad, self.adUnitIdentifier); } else { - MPLogError(@"Native ad failed to load with error: %@", error); + MPLogAdEvent([MPLogEvent adFailedToLoadWithError:error], self.adUnitIdentifier); } if (self.completionHandler) { @@ -369,7 +377,7 @@ - (void)startTimeoutTimer - (void)timeout { - NSError * error = [MOPUBError errorWithCode:MOPUBErrorAdRequestTimedOut localizedDescription:@"Native ad request timed out"]; + NSError * error = [NSError errorWithCode:MOPUBErrorAdRequestTimedOut localizedDescription:@"Native ad request timed out"]; [self nativeCustomEvent:self.nativeCustomEvent didFailToLoadAdWithError:error]; } diff --git a/MoPubSDK/NativeAds/MPNativeAdRequestTargeting.h b/MoPubSDK/NativeAds/MPNativeAdRequestTargeting.h index e4dcf9765..109fa80a7 100644 --- a/MoPubSDK/NativeAds/MPNativeAdRequestTargeting.h +++ b/MoPubSDK/NativeAds/MPNativeAdRequestTargeting.h @@ -1,7 +1,7 @@ // // MPNativeAdRequestTargeting.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/NativeAds/MPNativeAdRequestTargeting.m b/MoPubSDK/NativeAds/MPNativeAdRequestTargeting.m index 71c2461a6..dd84c8e8e 100644 --- a/MoPubSDK/NativeAds/MPNativeAdRequestTargeting.m +++ b/MoPubSDK/NativeAds/MPNativeAdRequestTargeting.m @@ -1,7 +1,7 @@ // // MPNativeAdRequestTargeting.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/NativeAds/MPNativeAdSource.h b/MoPubSDK/NativeAds/MPNativeAdSource.h index 59da9b468..7c647cb61 100644 --- a/MoPubSDK/NativeAds/MPNativeAdSource.h +++ b/MoPubSDK/NativeAds/MPNativeAdSource.h @@ -1,7 +1,7 @@ // // MPNativeAdSource.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/NativeAds/MPNativeAdSource.m b/MoPubSDK/NativeAds/MPNativeAdSource.m index d964f2eaf..c27c742db 100644 --- a/MoPubSDK/NativeAds/MPNativeAdSource.m +++ b/MoPubSDK/NativeAds/MPNativeAdSource.m @@ -1,7 +1,7 @@ // // MPNativeAdSource.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/NativeAds/MPNativeAdSourceDelegate.h b/MoPubSDK/NativeAds/MPNativeAdSourceDelegate.h index 38d963cf9..da56fa3b7 100644 --- a/MoPubSDK/NativeAds/MPNativeAdSourceDelegate.h +++ b/MoPubSDK/NativeAds/MPNativeAdSourceDelegate.h @@ -1,7 +1,7 @@ // // MPNativeAdSourceDelegate.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/NativeAds/MPNativeCustomEvent.h b/MoPubSDK/NativeAds/MPNativeCustomEvent.h index e0dfa9819..7b68e39e6 100644 --- a/MoPubSDK/NativeAds/MPNativeCustomEvent.h +++ b/MoPubSDK/NativeAds/MPNativeCustomEvent.h @@ -1,7 +1,7 @@ // // MPNativeCustomEvent.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/NativeAds/MPNativeCustomEvent.m b/MoPubSDK/NativeAds/MPNativeCustomEvent.m index 1307a22e7..b5c0dc5aa 100644 --- a/MoPubSDK/NativeAds/MPNativeCustomEvent.m +++ b/MoPubSDK/NativeAds/MPNativeCustomEvent.m @@ -1,7 +1,7 @@ // // MPNativeCustomEvent.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/NativeAds/MPNativeCustomEventDelegate.h b/MoPubSDK/NativeAds/MPNativeCustomEventDelegate.h index 82eeb271e..a2f612784 100644 --- a/MoPubSDK/NativeAds/MPNativeCustomEventDelegate.h +++ b/MoPubSDK/NativeAds/MPNativeCustomEventDelegate.h @@ -1,7 +1,7 @@ // // MPNativeCustomEventDelegate.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/NativeAds/MPServerAdPositioning.h b/MoPubSDK/NativeAds/MPServerAdPositioning.h index cfd8eca6f..19b110645 100644 --- a/MoPubSDK/NativeAds/MPServerAdPositioning.h +++ b/MoPubSDK/NativeAds/MPServerAdPositioning.h @@ -1,7 +1,7 @@ // // MPServerAdPositioning.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/NativeAds/MPServerAdPositioning.m b/MoPubSDK/NativeAds/MPServerAdPositioning.m index 7103cbbd2..d521e264a 100644 --- a/MoPubSDK/NativeAds/MPServerAdPositioning.m +++ b/MoPubSDK/NativeAds/MPServerAdPositioning.m @@ -1,7 +1,7 @@ // // MPServerAdPositioning.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/NativeAds/MPStaticNativeAdRenderer.h b/MoPubSDK/NativeAds/MPStaticNativeAdRenderer.h index b960c491e..04d3756c0 100644 --- a/MoPubSDK/NativeAds/MPStaticNativeAdRenderer.h +++ b/MoPubSDK/NativeAds/MPStaticNativeAdRenderer.h @@ -1,7 +1,7 @@ // // MPStaticNativeAdRenderer.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/NativeAds/MPStaticNativeAdRenderer.m b/MoPubSDK/NativeAds/MPStaticNativeAdRenderer.m index a38183806..9f56a5f5c 100644 --- a/MoPubSDK/NativeAds/MPStaticNativeAdRenderer.m +++ b/MoPubSDK/NativeAds/MPStaticNativeAdRenderer.m @@ -1,7 +1,7 @@ // // MPStaticNativeAdRenderer.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/NativeAds/MPStaticNativeAdRendererSettings.h b/MoPubSDK/NativeAds/MPStaticNativeAdRendererSettings.h index 256609f85..42a9b36d7 100644 --- a/MoPubSDK/NativeAds/MPStaticNativeAdRendererSettings.h +++ b/MoPubSDK/NativeAds/MPStaticNativeAdRendererSettings.h @@ -1,7 +1,7 @@ // // MPStaticNativeAdRendererSettings.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/NativeAds/MPStaticNativeAdRendererSettings.m b/MoPubSDK/NativeAds/MPStaticNativeAdRendererSettings.m index d2c87accc..f39dae85c 100644 --- a/MoPubSDK/NativeAds/MPStaticNativeAdRendererSettings.m +++ b/MoPubSDK/NativeAds/MPStaticNativeAdRendererSettings.m @@ -1,7 +1,7 @@ // // MPStaticNativeAdRendererSettings.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/NativeAds/MPStreamAdPlacementData.h b/MoPubSDK/NativeAds/MPStreamAdPlacementData.h index 83b7717f0..2221f675b 100644 --- a/MoPubSDK/NativeAds/MPStreamAdPlacementData.h +++ b/MoPubSDK/NativeAds/MPStreamAdPlacementData.h @@ -1,7 +1,7 @@ // // MPStreamAdPlacementData.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/NativeAds/MPStreamAdPlacementData.m b/MoPubSDK/NativeAds/MPStreamAdPlacementData.m index 7c1aee658..b507cfa3a 100644 --- a/MoPubSDK/NativeAds/MPStreamAdPlacementData.m +++ b/MoPubSDK/NativeAds/MPStreamAdPlacementData.m @@ -1,7 +1,7 @@ // // MPStreamAdPlacementData.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/ // @@ -170,7 +170,7 @@ - (void)insertAdData:(MPNativeAdData *)data atIndexPath:(NSIndexPath *)adjustedI NSUInteger indexInDesiredArrays = [self indexOfIndexPath:adjustedIndexPath inSortedArray:desiredInsertionPositions options:NSBinarySearchingFirstEqual]; if (indexInDesiredArrays == NSNotFound) { - MPLogWarn(@"Attempted to insert an ad at position %@, which is not in the desired array.", adjustedIndexPath); + MPLogInfo(@"Attempted to insert an ad at position %@, which is not in the desired array.", adjustedIndexPath); return; } diff --git a/MoPubSDK/NativeAds/MPStreamAdPlacer.h b/MoPubSDK/NativeAds/MPStreamAdPlacer.h index 2c1d0817b..84a103c57 100644 --- a/MoPubSDK/NativeAds/MPStreamAdPlacer.h +++ b/MoPubSDK/NativeAds/MPStreamAdPlacer.h @@ -1,7 +1,7 @@ // // MPStreamAdPlacer.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/NativeAds/MPStreamAdPlacer.m b/MoPubSDK/NativeAds/MPStreamAdPlacer.m index da64e7e2b..49f149556 100644 --- a/MoPubSDK/NativeAds/MPStreamAdPlacer.m +++ b/MoPubSDK/NativeAds/MPStreamAdPlacer.m @@ -1,7 +1,7 @@ // // MPStreamAdPlacer.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/ // @@ -103,7 +103,7 @@ - (void)renderAdAtIndexPath:(NSIndexPath *)indexPath inView:(UIView *)view MPNativeAdData *adData = [self.adPlacementData adDataAtAdjustedIndexPath:indexPath]; if (!adData) { - MPLogError(@"-renderAdAtIndexPath: An ad does not exist at indexPath"); + MPLogInfo(@"-renderAdAtIndexPath: An ad does not exist at indexPath"); return; } @@ -169,12 +169,12 @@ - (void)loadAdsForAdUnitID:(NSString *)adUnitID targeting:(MPNativeAdRequestTarg if (error) { if ([error code] == MPNativePositionSourceEmptyResponse) { - MPLogError(@"ERROR: Ad placer cannot show any ads because ad positions have " + MPLogInfo(@"ERROR: Ad placer cannot show any ads because ad positions have " @"not been configured for your ad unit %@. You must assign positions " @"by editing the ad unit's settings on the MoPub website.", strongSelf.adUnitID); } else { - MPLogError(@"ERROR: Ad placer failed to get positions from the ad server for " + MPLogInfo(@"ERROR: Ad placer failed to get positions from the ad server for " @"ad unit ID %@. Error: %@", strongSelf.adUnitID, error); } } else { @@ -188,7 +188,7 @@ - (void)loadAdsForAdUnitID:(NSString *)adUnitID targeting:(MPNativeAdRequestTarg } if (!adUnitID) { - MPLogError(@"Ad placer cannot load ads with a nil ad unit ID."); + MPLogInfo(@"Ad placer cannot load ads with a nil ad unit ID."); return; } @@ -574,7 +574,7 @@ - (CGSize)sizeForAd:(MPNativeAd *)ad withMaximumWidth:(CGFloat)maxWidth andIndex } adSize = CGSizeMake(maxWidth, 44.0f); - MPLogWarn(@"WARNING: + (CGSize)viewSizeHandler is NOT implemented for native ad renderer %@ at index path %@. You MUST implement this method to ensure that ad placer native ad cells are correctly sized. Returning a default size of %@ for now.", NSStringFromClass([(id)renderer class]), indexPath, NSStringFromCGSize(adSize)); + MPLogInfo(@"WARNING: + (CGSize)viewSizeHandler is NOT implemented for native ad renderer %@ at index path %@. You MUST implement this method to ensure that ad placer native ad cells are correctly sized. Returning a default size of %@ for now.", NSStringFromClass([(id)renderer class]), indexPath, NSStringFromCGSize(adSize)); return adSize; } diff --git a/MoPubSDK/NativeAds/MPTableViewAdPlacer.h b/MoPubSDK/NativeAds/MPTableViewAdPlacer.h index b18bb3753..a9b975bb4 100644 --- a/MoPubSDK/NativeAds/MPTableViewAdPlacer.h +++ b/MoPubSDK/NativeAds/MPTableViewAdPlacer.h @@ -1,7 +1,7 @@ // // MPTableViewAdPlacer.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/NativeAds/MPTableViewAdPlacer.m b/MoPubSDK/NativeAds/MPTableViewAdPlacer.m index 387fa0d20..9eba91e7d 100644 --- a/MoPubSDK/NativeAds/MPTableViewAdPlacer.m +++ b/MoPubSDK/NativeAds/MPTableViewAdPlacer.m @@ -1,7 +1,7 @@ // // MPTableViewAdPlacer.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/NativeVideo/Internal/MOPUBAVPlayer.h b/MoPubSDK/NativeVideo/Internal/MOPUBAVPlayer.h index 7a0c8f73e..29f4351bb 100644 --- a/MoPubSDK/NativeVideo/Internal/MOPUBAVPlayer.h +++ b/MoPubSDK/NativeVideo/Internal/MOPUBAVPlayer.h @@ -1,7 +1,7 @@ // // MOPUBAVPlayer.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/NativeVideo/Internal/MOPUBAVPlayer.m b/MoPubSDK/NativeVideo/Internal/MOPUBAVPlayer.m index 28eb18669..cb7daf570 100644 --- a/MoPubSDK/NativeVideo/Internal/MOPUBAVPlayer.m +++ b/MoPubSDK/NativeVideo/Internal/MOPUBAVPlayer.m @@ -1,7 +1,7 @@ // // MOPUBAVPlayer.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/NativeVideo/Internal/MOPUBAVPlayerView.h b/MoPubSDK/NativeVideo/Internal/MOPUBAVPlayerView.h index 0518aed4a..1937c1724 100644 --- a/MoPubSDK/NativeVideo/Internal/MOPUBAVPlayerView.h +++ b/MoPubSDK/NativeVideo/Internal/MOPUBAVPlayerView.h @@ -1,7 +1,7 @@ // // MOPUBAVPlayerView.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/NativeVideo/Internal/MOPUBAVPlayerView.m b/MoPubSDK/NativeVideo/Internal/MOPUBAVPlayerView.m index b54ee9034..6d9c4160d 100644 --- a/MoPubSDK/NativeVideo/Internal/MOPUBAVPlayerView.m +++ b/MoPubSDK/NativeVideo/Internal/MOPUBAVPlayerView.m @@ -1,7 +1,7 @@ // // MOPUBAVPlayerView.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/NativeVideo/Internal/MOPUBActivityIndicatorView.h b/MoPubSDK/NativeVideo/Internal/MOPUBActivityIndicatorView.h index 88e077c7c..2b8eecc80 100644 --- a/MoPubSDK/NativeVideo/Internal/MOPUBActivityIndicatorView.h +++ b/MoPubSDK/NativeVideo/Internal/MOPUBActivityIndicatorView.h @@ -1,7 +1,7 @@ // // MOPUBActivityIndicatorView.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/NativeVideo/Internal/MOPUBActivityIndicatorView.m b/MoPubSDK/NativeVideo/Internal/MOPUBActivityIndicatorView.m index 864e3ef20..eba25f0af 100644 --- a/MoPubSDK/NativeVideo/Internal/MOPUBActivityIndicatorView.m +++ b/MoPubSDK/NativeVideo/Internal/MOPUBActivityIndicatorView.m @@ -1,7 +1,7 @@ // // MOPUBActivityIndicatorView.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/NativeVideo/Internal/MOPUBFullscreenPlayerViewController.h b/MoPubSDK/NativeVideo/Internal/MOPUBFullscreenPlayerViewController.h index e32fdb618..69bf53fbd 100644 --- a/MoPubSDK/NativeVideo/Internal/MOPUBFullscreenPlayerViewController.h +++ b/MoPubSDK/NativeVideo/Internal/MOPUBFullscreenPlayerViewController.h @@ -1,7 +1,7 @@ // // MOPUBFullscreenPlayerViewController.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/NativeVideo/Internal/MOPUBFullscreenPlayerViewController.m b/MoPubSDK/NativeVideo/Internal/MOPUBFullscreenPlayerViewController.m index 191283f26..8fd21064c 100644 --- a/MoPubSDK/NativeVideo/Internal/MOPUBFullscreenPlayerViewController.m +++ b/MoPubSDK/NativeVideo/Internal/MOPUBFullscreenPlayerViewController.m @@ -1,7 +1,7 @@ // // MOPUBFullscreenPlayerViewController.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/NativeVideo/Internal/MOPUBNativeVideoAdAdapter.h b/MoPubSDK/NativeVideo/Internal/MOPUBNativeVideoAdAdapter.h index a0cc2209c..00e0ecdb7 100644 --- a/MoPubSDK/NativeVideo/Internal/MOPUBNativeVideoAdAdapter.h +++ b/MoPubSDK/NativeVideo/Internal/MOPUBNativeVideoAdAdapter.h @@ -1,7 +1,7 @@ // // MOPUBNativeVideoAdAdapter.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/NativeVideo/Internal/MOPUBNativeVideoAdAdapter.m b/MoPubSDK/NativeVideo/Internal/MOPUBNativeVideoAdAdapter.m index a48b020f5..f6e8fe634 100644 --- a/MoPubSDK/NativeVideo/Internal/MOPUBNativeVideoAdAdapter.m +++ b/MoPubSDK/NativeVideo/Internal/MOPUBNativeVideoAdAdapter.m @@ -1,7 +1,7 @@ // // MOPUBNativeVideoAdAdapter.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/NativeVideo/Internal/MOPUBNativeVideoAdConfigValues.h b/MoPubSDK/NativeVideo/Internal/MOPUBNativeVideoAdConfigValues.h index 7d73fddd5..86971678b 100644 --- a/MoPubSDK/NativeVideo/Internal/MOPUBNativeVideoAdConfigValues.h +++ b/MoPubSDK/NativeVideo/Internal/MOPUBNativeVideoAdConfigValues.h @@ -1,7 +1,7 @@ // // MOPUBNativeVideoAdConfigValues.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/NativeVideo/Internal/MOPUBNativeVideoAdConfigValues.m b/MoPubSDK/NativeVideo/Internal/MOPUBNativeVideoAdConfigValues.m index 5fb8d6526..edb634402 100644 --- a/MoPubSDK/NativeVideo/Internal/MOPUBNativeVideoAdConfigValues.m +++ b/MoPubSDK/NativeVideo/Internal/MOPUBNativeVideoAdConfigValues.m @@ -1,7 +1,7 @@ // // MOPUBNativeVideoAdConfigValues.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/NativeVideo/Internal/MOPUBNativeVideoCustomEvent.h b/MoPubSDK/NativeVideo/Internal/MOPUBNativeVideoCustomEvent.h index e07c33f79..3dd330c69 100644 --- a/MoPubSDK/NativeVideo/Internal/MOPUBNativeVideoCustomEvent.h +++ b/MoPubSDK/NativeVideo/Internal/MOPUBNativeVideoCustomEvent.h @@ -1,7 +1,7 @@ // // MOPUBNativeVideoCustomEvent.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/NativeVideo/Internal/MOPUBNativeVideoCustomEvent.m b/MoPubSDK/NativeVideo/Internal/MOPUBNativeVideoCustomEvent.m index 07c68c414..ee349d57f 100644 --- a/MoPubSDK/NativeVideo/Internal/MOPUBNativeVideoCustomEvent.m +++ b/MoPubSDK/NativeVideo/Internal/MOPUBNativeVideoCustomEvent.m @@ -1,7 +1,7 @@ // // MOPUBNativeVideoCustomEvent.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/ // @@ -22,6 +22,7 @@ @implementation MOPUBNativeVideoCustomEvent - (void)handleSuccessfulVastParsing:(MPVASTResponse *)mpVastResponse info:(NSDictionary *)info { + NSString * adUnitId = info[kNativeAdUnitId]; NSMutableDictionary *infoMutableCopy = [info mutableCopy]; [infoMutableCopy setObject:[[MPVideoConfig alloc] initWithVASTResponse:mpVastResponse additionalTrackers:((MOPUBNativeVideoAdConfigValues *)info[kNativeAdConfigKey]).trackers] forKey:kVideoConfigKey]; MOPUBNativeVideoAdAdapter *adAdapter = [[MOPUBNativeVideoAdAdapter alloc] initWithAdProperties:infoMutableCopy]; @@ -34,26 +35,35 @@ - (void)handleSuccessfulVastParsing:(MPVASTResponse *)mpVastResponse info:(NSDic for (NSString *key in [info allKeys]) { if ([[key lowercaseString] hasSuffix:@"image"] && [[info objectForKey:key] isKindOfClass:[NSString class]]) { if (![MPNativeAdUtils addURLString:[info objectForKey:key] toURLArray:imageURLs]) { - [self.delegate nativeCustomEvent:self didFailToLoadAdWithError:MPNativeAdNSErrorForInvalidImageURL()]; + NSError * error = MPNativeAdNSErrorForInvalidImageURL(); + MPLogAdEvent([MPLogEvent adLoadFailedForAdapter:NSStringFromClass(self.class) error:error], adUnitId); + [self.delegate nativeCustomEvent:self didFailToLoadAdWithError:error]; } } } [super precacheImagesWithURLs:imageURLs completionBlock:^(NSArray *errors) { if (errors) { - MPLogDebug(@"%@", errors); - [self.delegate nativeCustomEvent:self didFailToLoadAdWithError:MPNativeAdNSErrorForImageDownloadFailure()]; + NSError * error = MPNativeAdNSErrorForImageDownloadFailure(); + MPLogAdEvent([MPLogEvent adLoadFailedForAdapter:NSStringFromClass(self.class) error:error], adUnitId); + [self.delegate nativeCustomEvent:self didFailToLoadAdWithError:error]; } else { + MPLogAdEvent([MPLogEvent adLoadSuccessForAdapter:NSStringFromClass(self.class)], adUnitId); [self.delegate nativeCustomEvent:self didLoadAd:interfaceAd]; } }]; } else { - [self.delegate nativeCustomEvent:self didFailToLoadAdWithError:MPNativeAdNSErrorForInvalidAdServerResponse(nil)]; + NSError * error = MPNativeAdNSErrorForInvalidAdServerResponse(nil); + MPLogAdEvent([MPLogEvent adLoadFailedForAdapter:NSStringFromClass(self.class) error:error], adUnitId); + [self.delegate nativeCustomEvent:self didFailToLoadAdWithError:error]; } } - (void)requestAdWithCustomEventInfo:(NSDictionary *)info { + NSString * adUnitId = info[kNativeAdUnitId]; + MPLogAdEvent([MPLogEvent adLoadAttemptForAdapter:NSStringFromClass(self.class) dspCreativeId:info[kNativeAdDspCreativeId] dspName:info[kNativeAdDspName]], adUnitId); + MOPUBNativeVideoAdConfigValues *nativeVideoAdConfigValues = [info objectForKey:kNativeAdConfigKey]; if (nativeVideoAdConfigValues && [nativeVideoAdConfigValues isValid]) { NSString *vastString = [info objectForKey:kVASTVideoKey]; @@ -61,16 +71,21 @@ - (void)requestAdWithCustomEventInfo:(NSDictionary *)info [MPVASTManager fetchVASTWithData:[vastString dataUsingEncoding:NSUTF8StringEncoding] completion: ^(MPVASTResponse *mpVastResponse, NSError *error) { if (error) { + MPLogAdEvent([MPLogEvent adLoadFailedForAdapter:NSStringFromClass(self.class) error:error], adUnitId); [self.delegate nativeCustomEvent:self didFailToLoadAdWithError:MPNativeAdNSErrorForVASTParsingFailure()]; } else { [self handleSuccessfulVastParsing:mpVastResponse info:info]; } }]; } else { - [self.delegate nativeCustomEvent:self didFailToLoadAdWithError:MPNativeAdNSErrorForVASTParsingFailure()]; + NSError * error = MPNativeAdNSErrorForVASTParsingFailure(); + MPLogAdEvent([MPLogEvent adLoadFailedForAdapter:NSStringFromClass(self.class) error:error], adUnitId); + [self.delegate nativeCustomEvent:self didFailToLoadAdWithError:error]; } } else { - [self.delegate nativeCustomEvent:self didFailToLoadAdWithError:MPNativeAdNSErrorForVideoConfigInvalid()]; + NSError * error = MPNativeAdNSErrorForVideoConfigInvalid(); + MPLogAdEvent([MPLogEvent adLoadFailedForAdapter:NSStringFromClass(self.class) error:error], adUnitId); + [self.delegate nativeCustomEvent:self didFailToLoadAdWithError:error]; } } diff --git a/MoPubSDK/NativeVideo/Internal/MOPUBNativeVideoImpressionAgent.h b/MoPubSDK/NativeVideo/Internal/MOPUBNativeVideoImpressionAgent.h index cbfefe760..11d519390 100644 --- a/MoPubSDK/NativeVideo/Internal/MOPUBNativeVideoImpressionAgent.h +++ b/MoPubSDK/NativeVideo/Internal/MOPUBNativeVideoImpressionAgent.h @@ -1,7 +1,7 @@ // // MOPUBNativeVideoImpressionAgent.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/NativeVideo/Internal/MOPUBNativeVideoImpressionAgent.m b/MoPubSDK/NativeVideo/Internal/MOPUBNativeVideoImpressionAgent.m index 054593339..b4f5776b2 100644 --- a/MoPubSDK/NativeVideo/Internal/MOPUBNativeVideoImpressionAgent.m +++ b/MoPubSDK/NativeVideo/Internal/MOPUBNativeVideoImpressionAgent.m @@ -1,7 +1,7 @@ // // MOPUBNativeVideoImpressionAgent.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/NativeVideo/Internal/MOPUBPlayerManager.h b/MoPubSDK/NativeVideo/Internal/MOPUBPlayerManager.h index 3a7e847ad..e8891e25c 100644 --- a/MoPubSDK/NativeVideo/Internal/MOPUBPlayerManager.h +++ b/MoPubSDK/NativeVideo/Internal/MOPUBPlayerManager.h @@ -1,7 +1,7 @@ // // MOPUBPlayerManager.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/NativeVideo/Internal/MOPUBPlayerManager.m b/MoPubSDK/NativeVideo/Internal/MOPUBPlayerManager.m index f4c8ec887..799dd103e 100644 --- a/MoPubSDK/NativeVideo/Internal/MOPUBPlayerManager.m +++ b/MoPubSDK/NativeVideo/Internal/MOPUBPlayerManager.m @@ -1,7 +1,7 @@ // // MOPUBPlayerManager.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/NativeVideo/Internal/MOPUBPlayerView.h b/MoPubSDK/NativeVideo/Internal/MOPUBPlayerView.h index 29d29ef0f..f2613917c 100644 --- a/MoPubSDK/NativeVideo/Internal/MOPUBPlayerView.h +++ b/MoPubSDK/NativeVideo/Internal/MOPUBPlayerView.h @@ -1,7 +1,7 @@ // // MOPUBPlayerView.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/NativeVideo/Internal/MOPUBPlayerView.m b/MoPubSDK/NativeVideo/Internal/MOPUBPlayerView.m index c29008f06..600ca896a 100644 --- a/MoPubSDK/NativeVideo/Internal/MOPUBPlayerView.m +++ b/MoPubSDK/NativeVideo/Internal/MOPUBPlayerView.m @@ -1,7 +1,7 @@ // // MOPUBPlayerView.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/ // @@ -53,6 +53,8 @@ - (instancetype)initWithFrame:(CGRect)frame delegate:(id 1) { currentProgress = 1; - MPLogError(@"Progress shouldn't be > 1"); + MPLogInfo(@"Progress shouldn't be > 1"); } self.progressBar.frame = CGRectMake(0, CGRectGetMaxY(self.avView.frame)- kVideoProgressBarHeight, vcWidth * currentProgress, kVideoProgressBarHeight); diff --git a/MoPubSDK/NativeVideo/Internal/MOPUBPlayerViewController.h b/MoPubSDK/NativeVideo/Internal/MOPUBPlayerViewController.h index 45cf178d7..7ee73dbd9 100644 --- a/MoPubSDK/NativeVideo/Internal/MOPUBPlayerViewController.h +++ b/MoPubSDK/NativeVideo/Internal/MOPUBPlayerViewController.h @@ -1,7 +1,7 @@ // // MOPUBPlayerViewController.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/NativeVideo/Internal/MOPUBPlayerViewController.m b/MoPubSDK/NativeVideo/Internal/MOPUBPlayerViewController.m index 5fc3a7357..f6dfbc439 100644 --- a/MoPubSDK/NativeVideo/Internal/MOPUBPlayerViewController.m +++ b/MoPubSDK/NativeVideo/Internal/MOPUBPlayerViewController.m @@ -1,7 +1,7 @@ // // MOPUBPlayerViewController.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/ // @@ -160,7 +160,7 @@ - (void)loadAndPlayVideo AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:self.mediaURL options:nil]; if (asset == nil) { - MPLogError(@"failed to initialize video asset for URL %@", self.mediaURL); + MPLogInfo(@"failed to initialize video asset for URL %@", self.mediaURL); [self handleVideoInitError]; return; @@ -195,7 +195,7 @@ - (void)prepareToPlayAsset:(AVURLAsset *)asset withKeys:(NSArray *)requestedKeys NSError *error = nil; if (!asset.playable) { - MPLogError(@"asset is not playable"); + MPLogInfo(@"asset is not playable"); [self handleVideoInitError]; return; @@ -203,7 +203,7 @@ - (void)prepareToPlayAsset:(AVURLAsset *)asset withKeys:(NSArray *)requestedKeys AVKeyValueStatus status = [asset statusOfValueForKey:kTracksKey error:&error]; if (status == AVKeyValueStatusFailed) { - MPLogError(@"AVKeyValueStatusFailed"); + MPLogInfo(@"AVKeyValueStatusFailed"); [self handleVideoInitError]; return; @@ -373,7 +373,7 @@ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(N { if (object == self.avPlayer) { if (self.avPlayer.status == AVPlayerItemStatusFailed) { - MPLogError(@"avPlayer status failed"); + MPLogInfo(@"avPlayer status failed"); [self.vastTracking handleVideoEvent:MPVideoEventTypeError videoTimeOffset:self.avPlayer.currentPlaybackTime]; } } else if (object == self.playerItem) { @@ -400,7 +400,7 @@ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(N break; case AVPlayerItemStatusFailed: { - MPLogError(@"avPlayerItem status failed"); + MPLogInfo(@"avPlayerItem status failed"); [self.vastTracking handleVideoEvent:MPVideoEventTypeError videoTimeOffset:self.avPlayer.currentPlaybackTime]; break; } diff --git a/MoPubSDK/NativeVideo/Internal/MOPUBReplayView.h b/MoPubSDK/NativeVideo/Internal/MOPUBReplayView.h index 6d6cbc438..efa781f53 100644 --- a/MoPubSDK/NativeVideo/Internal/MOPUBReplayView.h +++ b/MoPubSDK/NativeVideo/Internal/MOPUBReplayView.h @@ -1,7 +1,7 @@ // // MOPUBReplayView.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/NativeVideo/Internal/MOPUBReplayView.m b/MoPubSDK/NativeVideo/Internal/MOPUBReplayView.m index e2bcbc899..32e2d3abe 100644 --- a/MoPubSDK/NativeVideo/Internal/MOPUBReplayView.m +++ b/MoPubSDK/NativeVideo/Internal/MOPUBReplayView.m @@ -1,7 +1,7 @@ // // MOPUBReplayView.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/NativeVideo/MOPUBNativeVideoAdRenderer.h b/MoPubSDK/NativeVideo/MOPUBNativeVideoAdRenderer.h index 10583d98d..568ec85a6 100644 --- a/MoPubSDK/NativeVideo/MOPUBNativeVideoAdRenderer.h +++ b/MoPubSDK/NativeVideo/MOPUBNativeVideoAdRenderer.h @@ -1,7 +1,7 @@ // // MOPUBNativeVideoAdRenderer.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/NativeVideo/MOPUBNativeVideoAdRenderer.m b/MoPubSDK/NativeVideo/MOPUBNativeVideoAdRenderer.m index f46c92182..1e20758df 100644 --- a/MoPubSDK/NativeVideo/MOPUBNativeVideoAdRenderer.m +++ b/MoPubSDK/NativeVideo/MOPUBNativeVideoAdRenderer.m @@ -1,7 +1,7 @@ // // MOPUBNativeVideoAdRenderer.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/NativeVideo/MOPUBNativeVideoAdRendererSettings.h b/MoPubSDK/NativeVideo/MOPUBNativeVideoAdRendererSettings.h index 4cc3a81fc..07aee7028 100644 --- a/MoPubSDK/NativeVideo/MOPUBNativeVideoAdRendererSettings.h +++ b/MoPubSDK/NativeVideo/MOPUBNativeVideoAdRendererSettings.h @@ -1,7 +1,7 @@ // // MOPUBNativeVideoAdRendererSettings.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/NativeVideo/MOPUBNativeVideoAdRendererSettings.m b/MoPubSDK/NativeVideo/MOPUBNativeVideoAdRendererSettings.m index 2e4b65a29..cd15b49be 100644 --- a/MoPubSDK/NativeVideo/MOPUBNativeVideoAdRendererSettings.m +++ b/MoPubSDK/NativeVideo/MOPUBNativeVideoAdRendererSettings.m @@ -1,7 +1,7 @@ // // MOPUBNativeVideoAdRendererSettings.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/Resources/MPAdapters.plist b/MoPubSDK/Resources/MPAdapters.plist new file mode 100644 index 000000000..ef3802bcf --- /dev/null +++ b/MoPubSDK/Resources/MPAdapters.plist @@ -0,0 +1,17 @@ + + + + + AdColonyAdapterConfiguration + AppLovinAdapterConfiguration + ChartboostAdapterConfiguration + FacebookAdapterConfiguration + FlurryAdapterConfiguration + GoogleAdMobAdapterConfiguration + IronSourceAdapterConfiguration + MillennialAdapterConfiguration + TapjoyAdapterConfiguration + UnityAdsAdapterConfiguration + VungleAdapterConfiguration + + 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..8013d0a58 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,6 +9,7 @@ #import "MPMoPubRewardedPlayableCustomEvent.h" #import "MPMRAIDInterstitialViewController.h" #import "MPAdConfiguration.h" +#import "MPError.h" #import "MPLogging.h" #import "MPRewardedVideoError.h" #import "MPCountdownTimerView.h" @@ -73,7 +74,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,47 +96,71 @@ - (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] initWithFrame:viewController.view.bounds duration:self.countdownDuration]; + [self.interstitial.view addSubview:self.timerView]; + + [self.timerView startWithTimerCompletion:^(BOOL hasElapsed) { + __typeof__(self) strongSelf = weakSelf; + if (strongSelf != nil) { + [strongSelf rewardUserWithConfiguration:strongSelf.configuration timerHasElapsed:hasElapsed]; + [strongSelf showCloseButton]; + } + }]; + + [self.interstitial presentInterstitialFromViewController:viewController complete:^(NSError * error) { + if (error != nil) { + onShowError(error); + } + else { + MPLogAdEvent([MPLogEvent adShowSuccessForAdapter:NSStringFromClass(self.class)], self.adUnitId); + } + }]; } #pragma mark - MPInterstitialViewControllerDelegate - (void)interstitialDidLoadAd:(MPInterstitialViewController *)interstitial { - MPLogInfo(@"MoPub rewarded playable did load"); + 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"); [self.delegate rewardedVideoDidAppearForCustomEvent:self]; } - (void)interstitialWillAppear:(MPInterstitialViewController *)interstitial { - MPLogInfo(@"MoPub rewarded playable will appear"); [self.delegate rewardedVideoWillAppearForCustomEvent:self]; } - (void)interstitialDidFailToLoadAd:(MPInterstitialViewController *)interstitial { - MPLogInfo(@"MoPub rewarded playable failed to load"); + 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]; } 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..096fc84b7 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" @@ -28,8 +29,10 @@ @implementation MPMoPubRewardedVideoCustomEvent - (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,39 +56,62 @@ - (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 - (void)interstitialDidLoadAd:(MPInterstitialViewController *)interstitial { - MPLogInfo(@"MoPub rewarded video did load"); + MPLogAdEvent([MPLogEvent adLoadSuccessForAdapter:NSStringFromClass(self.class)], self.adUnitId); + self.adAvailable = YES; [self.delegate rewardedVideoDidLoadAdForCustomEvent:self]; } - (void)interstitialDidAppear:(MPInterstitialViewController *)interstitial { - MPLogInfo(@"MoPub rewarded video did appear"); [self.delegate rewardedVideoDidAppearForCustomEvent:self]; } - (void)interstitialWillAppear:(MPInterstitialViewController *)interstitial { - MPLogInfo(@"MoPub rewarded video will appear"); [self.delegate rewardedVideoWillAppearForCustomEvent:self]; } - (void)interstitialDidFailToLoadAd:(MPInterstitialViewController *)interstitial { - MPLogInfo(@"MoPub rewarded video failed to load"); + 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]; } 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..4237ec7af 100644 --- a/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdManager.h +++ b/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdManager.h @@ -1,7 +1,7 @@ // // 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/ // diff --git a/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdManager.m b/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdManager.m index 68c86c933..274312206 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/ // @@ -81,6 +81,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. @@ -102,6 +104,8 @@ - (void)loadRewardedVideoAdWithCustomerId:(NSString *)customerId targeting:(MPAd - (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,8 +170,7 @@ - (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; } @@ -262,6 +265,7 @@ - (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]; } @@ -289,15 +293,17 @@ - (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]; } @@ -308,21 +314,25 @@ - (void)rewardedVideoDidFailToPlayForAdapter:(MPRewardedVideoAdapter *)adapter e self.ready = NO; self.playedAd = YES; + 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]; } @@ -331,21 +341,26 @@ - (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)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]; } diff --git a/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdapter.h b/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdapter.h index 50a3bea8e..fca18776b 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/ // diff --git a/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdapter.m b/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdapter.m index 9d21d0696..6fca4f5dc 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; @@ -134,7 +135,7 @@ - (void)startTimeoutTimer - (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; } 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..5d085861c 100644 --- a/MoPubSDK/RewardedVideo/MPRewardedVideo.h +++ b/MoPubSDK/RewardedVideo/MPRewardedVideo.h @@ -1,7 +1,7 @@ // // 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/ // diff --git a/MoPubSDK/RewardedVideo/MPRewardedVideo.m b/MoPubSDK/RewardedVideo/MPRewardedVideo.m index 616260172..47ae9157a 100644 --- a/MoPubSDK/RewardedVideo/MPRewardedVideo.m +++ b/MoPubSDK/RewardedVideo/MPRewardedVideo.m @@ -1,7 +1,7 @@ // // 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/ // @@ -13,7 +13,6 @@ #import "MPRewardedVideoError.h" #import "MPRewardedVideoConnection.h" #import "MPRewardedVideoCustomEvent.h" -#import "MPRewardedVideoCustomEvent+Caching.h" static MPRewardedVideo *gSharedInstance = nil; @@ -157,21 +156,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]; 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..6c2027cc7 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/ // 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..d3e8a15d3 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. 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..079f8664e 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/ // diff --git a/MoPubSDK/Viewability/MPWebView+Viewability.m b/MoPubSDK/Viewability/MPWebView+Viewability.m index a37e1b4fc..2be745dbf 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/ // diff --git a/MoPubSDKFramework/Info.plist b/MoPubSDKFramework/Info.plist index 317468cfe..7e5d3ad53 100644 --- a/MoPubSDKFramework/Info.plist +++ b/MoPubSDKFramework/Info.plist @@ -15,9 +15,9 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 5.4.1 + 5.5.0 CFBundleVersion - 5.4.1 + 5.5.0 NSPrincipalClass diff --git a/MoPubSDKTests/Info.plist b/MoPubSDKTests/Info.plist index 6716c5758..8a43305e3 100644 --- a/MoPubSDKTests/Info.plist +++ b/MoPubSDKTests/Info.plist @@ -15,8 +15,8 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 5.4.1 + 5.5.0 CFBundleVersion - 5.4.1 + 5.5.0 diff --git a/MoPubSDKTests/MOPUBExperimentProvider+Testing.h b/MoPubSDKTests/MOPUBExperimentProvider+Testing.h index 8a436952a..b98440b0f 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/ // diff --git a/MoPubSDKTests/MOPUBExperimentProviderTests.m b/MoPubSDKTests/MOPUBExperimentProviderTests.m index 125d6bc7c..0611fd76f 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/ // 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..c0cd56da2 100644 --- a/MoPubSDKTests/MPAdConfiguration+Testing.h +++ b/MoPubSDKTests/MPAdConfiguration+Testing.h @@ -1,7 +1,7 @@ // // 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/ // diff --git a/MoPubSDKTests/MPAdConfiguration+Testing.m b/MoPubSDKTests/MPAdConfiguration+Testing.m index 6cbfe38b1..8c3ea29fe 100644 --- a/MoPubSDKTests/MPAdConfiguration+Testing.m +++ b/MoPubSDKTests/MPAdConfiguration+Testing.m @@ -1,15 +1,18 @@ // // 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" @implementation MPAdConfiguration (Testing) @dynamic clickthroughExperimentBrowserAgent; @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..3e28c4f7e 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/ // diff --git a/MoPubSDKTests/MPAdConfigurationTests.m b/MoPubSDKTests/MPAdConfigurationTests.m index 9f241f600..181d564d0 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/ // 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..03bb036c3 100644 --- a/MoPubSDKTests/MPAdServerCommunicator+Testing.h +++ b/MoPubSDKTests/MPAdServerCommunicator+Testing.h @@ -1,7 +1,7 @@ // // 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/ // @@ -14,5 +14,6 @@ // Expose private methods from `MPAdServerCommunicator` - (void)didFinishLoadingWithData:(NSData *)data; +- (NSArray *)getFlattenJsonResponses:(NSDictionary *)json keys:(NSArray *)keys; @end diff --git a/MoPubSDKTests/MPAdServerCommunicator+Testing.m b/MoPubSDKTests/MPAdServerCommunicator+Testing.m index 7e6da0c4d..93cf2ed4e 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/ // diff --git a/MoPubSDKTests/MPAdServerCommunicatorTests.m b/MoPubSDKTests/MPAdServerCommunicatorTests.m index 0c36f2111..71899d012 100644 --- a/MoPubSDKTests/MPAdServerCommunicatorTests.m +++ b/MoPubSDKTests/MPAdServerCommunicatorTests.m @@ -1,7 +1,7 @@ // // 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/ // @@ -47,6 +47,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 { 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..3627b2f6f 100644 --- a/MoPubSDKTests/MPAdServerURLBuilderTests.m +++ b/MoPubSDKTests/MPAdServerURLBuilderTests.m @@ -1,29 +1,27 @@ // // 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 "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" -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"; @@ -74,29 +72,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 diff --git a/MoPubSDKTests/MPAdView+Testing.h b/MoPubSDKTests/MPAdView+Testing.h index e54280d1f..b91fdf381 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/ // diff --git a/MoPubSDKTests/MPAdView+Testing.m b/MoPubSDKTests/MPAdView+Testing.m index 742081a1d..cd768f9ba 100644 --- a/MoPubSDKTests/MPAdView+Testing.m +++ b/MoPubSDKTests/MPAdView+Testing.m @@ -1,7 +1,7 @@ // // 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/ // diff --git a/MoPubSDKTests/MPAdViewTests.m b/MoPubSDKTests/MPAdViewTests.m index a56ad243b..e1be2cc3a 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/ // diff --git a/MoPubSDKTests/MPAdserverCommunicatorDelegateHandler.h b/MoPubSDKTests/MPAdserverCommunicatorDelegateHandler.h index 48fdadb0f..618d43474 100644 --- a/MoPubSDKTests/MPAdserverCommunicatorDelegateHandler.h +++ b/MoPubSDKTests/MPAdserverCommunicatorDelegateHandler.h @@ -1,7 +1,7 @@ // // 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/ // diff --git a/MoPubSDKTests/MPAdserverCommunicatorDelegateHandler.m b/MoPubSDKTests/MPAdserverCommunicatorDelegateHandler.m index 849038710..2979e8b62 100644 --- a/MoPubSDKTests/MPAdserverCommunicatorDelegateHandler.m +++ b/MoPubSDKTests/MPAdserverCommunicatorDelegateHandler.m @@ -1,7 +1,7 @@ // // 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/ // 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..085470f95 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/ // diff --git a/MoPubSDKTests/MPBannerAdManagerDelegateHandler.m b/MoPubSDKTests/MPBannerAdManagerDelegateHandler.m index 21aedc126..768c776e1 100644 --- a/MoPubSDKTests/MPBannerAdManagerDelegateHandler.m +++ b/MoPubSDKTests/MPBannerAdManagerDelegateHandler.m @@ -1,7 +1,7 @@ // // 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/ // diff --git a/MoPubSDKTests/MPBannerAdManagerTests.m b/MoPubSDKTests/MPBannerAdManagerTests.m index c81fcc62a..f58c49438 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/ // diff --git a/MoPubSDKTests/MPBannerAdapterDelegateHandler.h b/MoPubSDKTests/MPBannerAdapterDelegateHandler.h index 9544654a3..c992a3a02 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/ // diff --git a/MoPubSDKTests/MPBannerAdapterDelegateHandler.m b/MoPubSDKTests/MPBannerAdapterDelegateHandler.m index de838e1f9..9f212685c 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/ // diff --git a/MoPubSDKTests/MPBannerCustomEventAdapter+Testing.h b/MoPubSDKTests/MPBannerCustomEventAdapter+Testing.h index d4de53314..8c51a801e 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/ // 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..30cc0dabf 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/ // 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..b2ec81988 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/ // diff --git a/MoPubSDKTests/MPConsentManager+Testing.m b/MoPubSDKTests/MPConsentManager+Testing.m index ec43e7d46..2cc8fce73 100644 --- a/MoPubSDKTests/MPConsentManager+Testing.m +++ b/MoPubSDKTests/MPConsentManager+Testing.m @@ -1,7 +1,7 @@ // // 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/ // diff --git a/MoPubSDKTests/MPConsentManagerTests.m b/MoPubSDKTests/MPConsentManagerTests.m index 235af0e47..9d15f8c80 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/ // 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/MPCountdownTimerViewTests.m b/MoPubSDKTests/MPCountdownTimerViewTests.m index bb24e101d..f1eadd177 100644 --- a/MoPubSDKTests/MPCountdownTimerViewTests.m +++ b/MoPubSDKTests/MPCountdownTimerViewTests.m @@ -1,7 +1,7 @@ // // 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/ // 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/MPGeolocationProviderTest.m b/MoPubSDKTests/MPGeolocationProviderTest.m index cf526e524..88f1b53d2 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/ // diff --git a/MoPubSDKTests/MPHTTPNetworkSession+Testing.h b/MoPubSDKTests/MPHTTPNetworkSession+Testing.h index d8ae8a1b9..55d660e46 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/ // 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..2fcef3fac 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/ // diff --git a/MoPubSDKTests/MPInterstitialAdController+Testing.h b/MoPubSDKTests/MPInterstitialAdController+Testing.h index b02145326..25bc976a8 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/ // diff --git a/MoPubSDKTests/MPInterstitialAdController+Testing.m b/MoPubSDKTests/MPInterstitialAdController+Testing.m index a14e798f7..adb1dc582 100644 --- a/MoPubSDKTests/MPInterstitialAdController+Testing.m +++ b/MoPubSDKTests/MPInterstitialAdController+Testing.m @@ -1,7 +1,7 @@ // // 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/ // diff --git a/MoPubSDKTests/MPInterstitialAdControllerTests.m b/MoPubSDKTests/MPInterstitialAdControllerTests.m index ab8ce749b..a30171ff0 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/ // 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..130ceeb72 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/ // diff --git a/MoPubSDKTests/MPInterstitialAdManagerDelegateHandler.m b/MoPubSDKTests/MPInterstitialAdManagerDelegateHandler.m index a181e0bf8..2623b66b8 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/ // diff --git a/MoPubSDKTests/MPInterstitialAdManagerTests.m b/MoPubSDKTests/MPInterstitialAdManagerTests.m index 48cb4fe56..9e08d9cd7 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/ // diff --git a/MoPubSDKTests/MPInterstitialAdapterDelegateHandler.h b/MoPubSDKTests/MPInterstitialAdapterDelegateHandler.h index 7d3e8f264..fc142737f 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/ // diff --git a/MoPubSDKTests/MPInterstitialAdapterDelegateHandler.m b/MoPubSDKTests/MPInterstitialAdapterDelegateHandler.m index b81824d39..f32147cc9 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/ // 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/MPMediationManager+Testing.h b/MoPubSDKTests/MPMediationManager+Testing.h new file mode 100644 index 000000000..bd8819e49 --- /dev/null +++ b/MoPubSDKTests/MPMediationManager+Testing.h @@ -0,0 +1,19 @@ +// +// 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" + +@interface MPMediationManager (Testing) +@property (class, nonatomic, copy) NSString * adapterInformationProvidersFilePath; +@property (nonatomic, strong) NSMutableDictionary> * adapters; +@property (nonatomic, strong, readonly) NSSet> * certifiedAdapterClasses; + ++ (NSSet> * _Nonnull)certifiedAdapterInformationProviderClasses; +- (NSDictionary *)parametersForAdapter:(id)adapter + overrideConfiguration:(NSDictionary *)configuration; +@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..def60080a 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,88 @@ - (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]; + NSDictionary * params = [MPMediationManager.sharedManager parametersForAdapter:adapter overrideConfiguration:nil]; - 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 { + MPMediationManager.adapterInformationProvidersFilePath = nil; + + 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..fd8ba6d12 100644 --- a/MoPubSDKTests/MPMoPubRewardedPlayableCustomEvent+Testing.m +++ b/MoPubSDKTests/MPMoPubRewardedPlayableCustomEvent+Testing.m @@ -1,7 +1,7 @@ // // 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/ // 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/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/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/MPNativeAdRequestTests.m b/MoPubSDKTests/MPNativeAdRequestTests.m index 8900b7f0e..787d2de66 100644 --- a/MoPubSDKTests/MPNativeAdRequestTests.m +++ b/MoPubSDKTests/MPNativeAdRequestTests.m @@ -1,7 +1,7 @@ // // 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/ // 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/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/MPRealTimeTimerTests.m b/MoPubSDKTests/MPRealTimeTimerTests.m index 4c8d9c502..7f31090a4 100644 --- a/MoPubSDKTests/MPRealTimeTimerTests.m +++ b/MoPubSDKTests/MPRealTimeTimerTests.m @@ -1,7 +1,7 @@ // // 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/ // diff --git a/MoPubSDKTests/MPRewardedVideo+Testing.h b/MoPubSDKTests/MPRewardedVideo+Testing.h index 33a4330f0..413a7b873 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/ // diff --git a/MoPubSDKTests/MPRewardedVideo+Testing.m b/MoPubSDKTests/MPRewardedVideo+Testing.m index 32a80efef..e0dcb0388 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/ // 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..11bf35790 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/ // 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..b5532384e 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/ // diff --git a/MoPubSDKTests/MPRewardedVideoAdapterDelegateHandler.m b/MoPubSDKTests/MPRewardedVideoAdapterDelegateHandler.m index edc1e4d43..856170aa2 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/ // 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..b72224769 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/ // diff --git a/MoPubSDKTests/MPRewardedVideoDelegateHandler.m b/MoPubSDKTests/MPRewardedVideoDelegateHandler.m index b5fde001c..6e140e8a4 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/ // 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..6877ed34b 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/ // @@ -32,9 +32,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]; }); } 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/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..cd9d928b6 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/ // diff --git a/MoPubSDKTests/MPURLResolverTests.m b/MoPubSDKTests/MPURLResolverTests.m index 812acef86..a399bd75b 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/ // diff --git a/MoPubSDKTests/MPVASTLinearAdTests.m b/MoPubSDKTests/MPVASTLinearAdTests.m index 269a8d7ed..b00a96348 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/ // 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/MPVideoConfigTests.m b/MoPubSDKTests/MPVideoConfigTests.m index 73ffe9944..f62bc0f08 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/ // 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/MPWebView+Testing.h b/MoPubSDKTests/MPWebView+Testing.h index 69ebb12cd..25cf50349 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/ // diff --git a/MoPubSDKTests/MPWebView+Testing.m b/MoPubSDKTests/MPWebView+Testing.m index 64496a69b..b14900e97 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/ // diff --git a/MoPubSDKTests/MPWebViewTests.m b/MoPubSDKTests/MPWebViewTests.m index 671d4cadb..4d863f07b 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/ // diff --git a/MoPubSDKTests/MRController+Testing.h b/MoPubSDKTests/MRController+Testing.h index 9284bb340..53f452a74 100644 --- a/MoPubSDKTests/MRController+Testing.h +++ b/MoPubSDKTests/MRController+Testing.h @@ -1,7 +1,7 @@ // // 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/ // diff --git a/MoPubSDKTests/MRController+Testing.m b/MoPubSDKTests/MRController+Testing.m index 225c22b18..986b3deea 100644 --- a/MoPubSDKTests/MRController+Testing.m +++ b/MoPubSDKTests/MRController+Testing.m @@ -1,7 +1,7 @@ // // 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/ // diff --git a/MoPubSDKTests/MoPub+Testing.h b/MoPubSDKTests/MoPub+Testing.h index 4ce599c82..097260cdf 100644 --- a/MoPubSDKTests/MoPub+Testing.h +++ b/MoPubSDKTests/MoPub+Testing.h @@ -1,7 +1,7 @@ // // 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/ // @@ -17,6 +17,13 @@ NS_ASSUME_NONNULL_BEGIN - (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; + @end NS_ASSUME_NONNULL_END diff --git a/MoPubSDKTests/MoPub+Testing.m b/MoPubSDKTests/MoPub+Testing.m index b53ad7d57..25f02c1bb 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/ // diff --git a/MoPubSDKTests/MoPubTests.m b/MoPubSDKTests/MoPubTests.m index ba2759b9e..2baa8fc8c 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" @@ -30,27 +32,29 @@ - (void)setUp { [MPMediationManager.sharedManager clearCache]; [MoPub sharedInstance].forceWKWebView = NO; - [MoPub sharedInstance].logLevel = MPLogLevelInfo; + MPLogging.consoleLogLevel = MPLogLevelInfo; } -#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 +67,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 +107,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 +145,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 +170,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,11 +188,11 @@ - (void)testBadInitializingNetworkFromCache { XCTAssertNil(error); }]; - // Verify initialized sdks - XCTAssertFalse([MPMockAdColonyRewardedVideoCustomEvent isSdkInitialized]); - XCTAssertFalse([MPMockChartboostRewardedVideoCustomEvent isSdkInitialized]); + // Verify legitimate interest is not set by default + XCTAssertFalse(MoPub.sharedInstance.allowLegitimateInterest); } + #pragma mark - WKWebView - (void)testNoForceWKWebView { @@ -209,7 +207,7 @@ - (void)testNoForceWKWebView { MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; - MRController * controller = [[MRController alloc] initWithAdViewFrame:CGRectZero adPlacementType:MRAdViewPlacementTypeInterstitial delegate:nil]; + MRController * controller = [[MRController alloc] initWithAdViewFrame:CGRectZero supportedOrientations:MPInterstitialOrientationTypeAll adPlacementType:MRAdViewPlacementTypeInterstitial delegate:nil]; [controller loadAdWithConfiguration:config]; XCTAssertNil(controller.mraidWebView.wkWebView); @@ -228,7 +226,7 @@ - (void)testForceWKWebView { MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; - MRController * controller = [[MRController alloc] initWithAdViewFrame:CGRectZero adPlacementType:MRAdViewPlacementTypeInterstitial delegate:nil]; + MRController * controller = [[MRController alloc] initWithAdViewFrame:CGRectZero supportedOrientations:MPInterstitialOrientationTypeAll adPlacementType:MRAdViewPlacementTypeInterstitial delegate:nil]; [controller loadAdWithConfiguration:config]; XCTAssertNotNil(controller.mraidWebView.wkWebView); @@ -238,9 +236,9 @@ - (void)testForceWKWebView { #pragma mark - Logging - (void)testSetLogLevel { - [MoPub sharedInstance].logLevel = MPLogLevelFatal; + MPLogging.consoleLogLevel = MPLogLevelDebug; - XCTAssertTrue([MoPub sharedInstance].logLevel == MPLogLevelFatal); + XCTAssertTrue(MPLogging.consoleLogLevel == MPLogLevelDebug); } @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..7df6fd00e 100644 --- a/MoPubSDKTests/XCTestCase+MPAddition.h +++ b/MoPubSDKTests/XCTestCase+MPAddition.h @@ -1,7 +1,7 @@ // // 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/ // diff --git a/MoPubSDKTests/XCTestCase+MPAddition.m b/MoPubSDKTests/XCTestCase+MPAddition.m index a69fb5c73..77802ac7e 100644 --- a/MoPubSDKTests/XCTestCase+MPAddition.m +++ b/MoPubSDKTests/XCTestCase+MPAddition.m @@ -1,7 +1,7 @@ // // 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/ // diff --git a/MoPubSampleApp/AppDelegate.h b/MoPubSampleApp/AppDelegate.h index 314601261..0b6b177cc 100644 --- a/MoPubSampleApp/AppDelegate.h +++ b/MoPubSampleApp/AppDelegate.h @@ -1,7 +1,7 @@ // // AppDelegate.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/MoPubSampleApp/AppDelegate.m b/MoPubSampleApp/AppDelegate.m index 058b7cb7a..c5499fa87 100644 --- a/MoPubSampleApp/AppDelegate.m +++ b/MoPubSampleApp/AppDelegate.m @@ -1,7 +1,7 @@ // // AppDelegate.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,8 +42,7 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( MPMoPubConfiguration * sdkConfig = [[MPMoPubConfiguration alloc] initWithAdUnitIdForAppInitialization: @"0ac59b0996d947309c33f59d6676399f"]; sdkConfig.globalMediationSettings = @[]; - sdkConfig.mediatedNetworks = @[]; - sdkConfig.advancedBidders = nil; + sdkConfig.loggingLevel = MPLogLevelInfo; [[MoPub sharedInstance] initializeSdkWithConfiguration:sdkConfig completion:^{ NSLog(@"SDK initialization complete"); }]; diff --git a/MoPubSampleApp/Controllers/MPAdEntryViewController.h b/MoPubSampleApp/Controllers/MPAdEntryViewController.h index 466638b92..c11ca9855 100644 --- a/MoPubSampleApp/Controllers/MPAdEntryViewController.h +++ b/MoPubSampleApp/Controllers/MPAdEntryViewController.h @@ -1,7 +1,7 @@ // // MPAdEntryViewController.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/MoPubSampleApp/Controllers/MPAdEntryViewController.m b/MoPubSampleApp/Controllers/MPAdEntryViewController.m index 67fb5f650..3003998f5 100644 --- a/MoPubSampleApp/Controllers/MPAdEntryViewController.m +++ b/MoPubSampleApp/Controllers/MPAdEntryViewController.m @@ -1,7 +1,7 @@ // // MPAdEntryViewController.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/MoPubSampleApp/Controllers/MPAdTableViewController.h b/MoPubSampleApp/Controllers/MPAdTableViewController.h index 4b7cf1eb9..bfcbc6b8e 100644 --- a/MoPubSampleApp/Controllers/MPAdTableViewController.h +++ b/MoPubSampleApp/Controllers/MPAdTableViewController.h @@ -1,7 +1,7 @@ // // MPAdTableViewController.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/MoPubSampleApp/Controllers/MPAdTableViewController.m b/MoPubSampleApp/Controllers/MPAdTableViewController.m index 37fb86883..173bee61c 100644 --- a/MoPubSampleApp/Controllers/MPAdTableViewController.m +++ b/MoPubSampleApp/Controllers/MPAdTableViewController.m @@ -1,7 +1,7 @@ // // MPAdTableViewController.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/MoPubSampleApp/Controllers/MPBannerAdDetailViewController.h b/MoPubSampleApp/Controllers/MPBannerAdDetailViewController.h index 1a08eb7c3..2c7103fc3 100644 --- a/MoPubSampleApp/Controllers/MPBannerAdDetailViewController.h +++ b/MoPubSampleApp/Controllers/MPBannerAdDetailViewController.h @@ -1,7 +1,7 @@ // // MPBannerAdDetailViewController.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/MoPubSampleApp/Controllers/MPBannerAdDetailViewController.m b/MoPubSampleApp/Controllers/MPBannerAdDetailViewController.m index 36f6f6d72..b6d65738e 100644 --- a/MoPubSampleApp/Controllers/MPBannerAdDetailViewController.m +++ b/MoPubSampleApp/Controllers/MPBannerAdDetailViewController.m @@ -1,7 +1,7 @@ // // MPBannerAdDetailViewController.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/MoPubSampleApp/Controllers/MPInterstitialAdDetailViewController.h b/MoPubSampleApp/Controllers/MPInterstitialAdDetailViewController.h index f31eb812a..2e0dbe032 100644 --- a/MoPubSampleApp/Controllers/MPInterstitialAdDetailViewController.h +++ b/MoPubSampleApp/Controllers/MPInterstitialAdDetailViewController.h @@ -1,7 +1,7 @@ // // MPInterstitialAdDetailViewController.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/MoPubSampleApp/Controllers/MPInterstitialAdDetailViewController.m b/MoPubSampleApp/Controllers/MPInterstitialAdDetailViewController.m index 60dd2cf10..e23331ee9 100644 --- a/MoPubSampleApp/Controllers/MPInterstitialAdDetailViewController.m +++ b/MoPubSampleApp/Controllers/MPInterstitialAdDetailViewController.m @@ -1,7 +1,7 @@ // // MPInterstitialAdDetailViewController.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/MoPubSampleApp/Controllers/MPLeaderboardBannerAdDetailViewController.h b/MoPubSampleApp/Controllers/MPLeaderboardBannerAdDetailViewController.h index 296c3aa2c..a37246a6c 100644 --- a/MoPubSampleApp/Controllers/MPLeaderboardBannerAdDetailViewController.h +++ b/MoPubSampleApp/Controllers/MPLeaderboardBannerAdDetailViewController.h @@ -1,7 +1,7 @@ // // MPLeaderboardBannerAdDetailViewController.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/MoPubSampleApp/Controllers/MPLeaderboardBannerAdDetailViewController.m b/MoPubSampleApp/Controllers/MPLeaderboardBannerAdDetailViewController.m index 08a00afef..e7a15d8a1 100644 --- a/MoPubSampleApp/Controllers/MPLeaderboardBannerAdDetailViewController.m +++ b/MoPubSampleApp/Controllers/MPLeaderboardBannerAdDetailViewController.m @@ -1,7 +1,7 @@ // // MPLeaderboardBannerAdDetailViewController.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/MoPubSampleApp/Controllers/MPMRectBannerAdDetailViewController.h b/MoPubSampleApp/Controllers/MPMRectBannerAdDetailViewController.h index 9f9cd6fe8..f99769fb1 100644 --- a/MoPubSampleApp/Controllers/MPMRectBannerAdDetailViewController.h +++ b/MoPubSampleApp/Controllers/MPMRectBannerAdDetailViewController.h @@ -1,7 +1,7 @@ // // MPMRectBannerAdDetailViewController.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/MoPubSampleApp/Controllers/MPMRectBannerAdDetailViewController.m b/MoPubSampleApp/Controllers/MPMRectBannerAdDetailViewController.m index 0abdb10aa..b5b1669f8 100644 --- a/MoPubSampleApp/Controllers/MPMRectBannerAdDetailViewController.m +++ b/MoPubSampleApp/Controllers/MPMRectBannerAdDetailViewController.m @@ -1,7 +1,7 @@ // // MPMRectBannerAdDetailViewController.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/MoPubSampleApp/Controllers/MPNativeAdDetailViewController.h b/MoPubSampleApp/Controllers/MPNativeAdDetailViewController.h index f265b6e97..19f61e3af 100644 --- a/MoPubSampleApp/Controllers/MPNativeAdDetailViewController.h +++ b/MoPubSampleApp/Controllers/MPNativeAdDetailViewController.h @@ -1,7 +1,7 @@ // // MPNativeAdDetailViewController.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/MoPubSampleApp/Controllers/MPNativeAdDetailViewController.m b/MoPubSampleApp/Controllers/MPNativeAdDetailViewController.m index 2a09b0d42..419b9999a 100644 --- a/MoPubSampleApp/Controllers/MPNativeAdDetailViewController.m +++ b/MoPubSampleApp/Controllers/MPNativeAdDetailViewController.m @@ -1,7 +1,7 @@ // // MPNativeAdDetailViewController.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/MoPubSampleApp/Controllers/MPNativeAdPlacerCollectionViewController.h b/MoPubSampleApp/Controllers/MPNativeAdPlacerCollectionViewController.h index 40f6de5e7..f8fc81446 100644 --- a/MoPubSampleApp/Controllers/MPNativeAdPlacerCollectionViewController.h +++ b/MoPubSampleApp/Controllers/MPNativeAdPlacerCollectionViewController.h @@ -1,7 +1,7 @@ // // MPNativeAdPlacerCollectionViewController.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/MoPubSampleApp/Controllers/MPNativeAdPlacerCollectionViewController.m b/MoPubSampleApp/Controllers/MPNativeAdPlacerCollectionViewController.m index 2e222ccdb..fdbabe8a9 100644 --- a/MoPubSampleApp/Controllers/MPNativeAdPlacerCollectionViewController.m +++ b/MoPubSampleApp/Controllers/MPNativeAdPlacerCollectionViewController.m @@ -1,7 +1,7 @@ // // MPNativeAdPlacerCollectionViewController.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/MoPubSampleApp/Controllers/MPNativeAdPlacerPageViewController.h b/MoPubSampleApp/Controllers/MPNativeAdPlacerPageViewController.h index e956ba48d..5fddca817 100644 --- a/MoPubSampleApp/Controllers/MPNativeAdPlacerPageViewController.h +++ b/MoPubSampleApp/Controllers/MPNativeAdPlacerPageViewController.h @@ -1,7 +1,7 @@ // // MPNativeAdPlacerPageViewController.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/MoPubSampleApp/Controllers/MPNativeAdPlacerPageViewController.m b/MoPubSampleApp/Controllers/MPNativeAdPlacerPageViewController.m index 5638b1c32..32a60a357 100644 --- a/MoPubSampleApp/Controllers/MPNativeAdPlacerPageViewController.m +++ b/MoPubSampleApp/Controllers/MPNativeAdPlacerPageViewController.m @@ -1,7 +1,7 @@ // // MPNativeAdPlacerPageViewController.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/MoPubSampleApp/Controllers/MPNativeAdPlacerTableViewController.h b/MoPubSampleApp/Controllers/MPNativeAdPlacerTableViewController.h index e16663e38..a69c359e5 100644 --- a/MoPubSampleApp/Controllers/MPNativeAdPlacerTableViewController.h +++ b/MoPubSampleApp/Controllers/MPNativeAdPlacerTableViewController.h @@ -1,7 +1,7 @@ // // MPNativeAdPlacerTableViewController.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/MoPubSampleApp/Controllers/MPNativeAdPlacerTableViewController.m b/MoPubSampleApp/Controllers/MPNativeAdPlacerTableViewController.m index 85be33ead..0b910e2a3 100644 --- a/MoPubSampleApp/Controllers/MPNativeAdPlacerTableViewController.m +++ b/MoPubSampleApp/Controllers/MPNativeAdPlacerTableViewController.m @@ -1,7 +1,7 @@ // // MPNativeAdPlacerTableViewController.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/MoPubSampleApp/Controllers/MPRewardedVideoAdDetailViewController.h b/MoPubSampleApp/Controllers/MPRewardedVideoAdDetailViewController.h index 1f9e8e680..e142cac5b 100644 --- a/MoPubSampleApp/Controllers/MPRewardedVideoAdDetailViewController.h +++ b/MoPubSampleApp/Controllers/MPRewardedVideoAdDetailViewController.h @@ -1,7 +1,7 @@ // // MPRewardedVideoAdDetailViewController.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/MoPubSampleApp/Controllers/MPRewardedVideoAdDetailViewController.m b/MoPubSampleApp/Controllers/MPRewardedVideoAdDetailViewController.m index a06661d54..cd00a3ea3 100644 --- a/MoPubSampleApp/Controllers/MPRewardedVideoAdDetailViewController.m +++ b/MoPubSampleApp/Controllers/MPRewardedVideoAdDetailViewController.m @@ -1,7 +1,7 @@ // // MPRewardedVideoAdDetailViewController.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/MoPubSampleApp/Controllers/MPViewController.h b/MoPubSampleApp/Controllers/MPViewController.h index 1326c9272..be5583c5c 100644 --- a/MoPubSampleApp/Controllers/MPViewController.h +++ b/MoPubSampleApp/Controllers/MPViewController.h @@ -1,7 +1,7 @@ // // MPViewController.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/MoPubSampleApp/Controllers/MPViewController.m b/MoPubSampleApp/Controllers/MPViewController.m index 8764e6dcb..3ed3b6937 100644 --- a/MoPubSampleApp/Controllers/MPViewController.m +++ b/MoPubSampleApp/Controllers/MPViewController.m @@ -1,7 +1,7 @@ // // MPViewController.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/MoPubSampleApp/Domain/MPAdInfo.h b/MoPubSampleApp/Domain/MPAdInfo.h index e0eb94b94..8d2efa2a9 100644 --- a/MoPubSampleApp/Domain/MPAdInfo.h +++ b/MoPubSampleApp/Domain/MPAdInfo.h @@ -1,7 +1,7 @@ // // MPAdInfo.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/MoPubSampleApp/Domain/MPAdInfo.m b/MoPubSampleApp/Domain/MPAdInfo.m index 9dd72cc10..984929dd5 100644 --- a/MoPubSampleApp/Domain/MPAdInfo.m +++ b/MoPubSampleApp/Domain/MPAdInfo.m @@ -1,7 +1,7 @@ // // MPAdInfo.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/MoPubSampleApp/Domain/MPAdSection.h b/MoPubSampleApp/Domain/MPAdSection.h index f4364f5f7..540185a39 100644 --- a/MoPubSampleApp/Domain/MPAdSection.h +++ b/MoPubSampleApp/Domain/MPAdSection.h @@ -1,7 +1,7 @@ // // MPAdSection.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/MoPubSampleApp/Domain/MPAdSection.m b/MoPubSampleApp/Domain/MPAdSection.m index a44cda6bc..a11c0399b 100644 --- a/MoPubSampleApp/Domain/MPAdSection.m +++ b/MoPubSampleApp/Domain/MPAdSection.m @@ -1,7 +1,7 @@ // // MPAdSection.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/MoPubSampleApp/MPAdPersistenceManager.h b/MoPubSampleApp/MPAdPersistenceManager.h index 6a747c173..93a3d2959 100644 --- a/MoPubSampleApp/MPAdPersistenceManager.h +++ b/MoPubSampleApp/MPAdPersistenceManager.h @@ -1,7 +1,7 @@ // // MPAdPersistenceManager.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/MoPubSampleApp/MPAdPersistenceManager.m b/MoPubSampleApp/MPAdPersistenceManager.m index 1d727d53f..04ae3da5e 100644 --- a/MoPubSampleApp/MPAdPersistenceManager.m +++ b/MoPubSampleApp/MPAdPersistenceManager.m @@ -1,7 +1,7 @@ // // MPAdPersistenceManager.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/MoPubSampleApp/MPSampleAppInstanceProvider.h b/MoPubSampleApp/MPSampleAppInstanceProvider.h index 1cc2554a8..909fc85a7 100644 --- a/MoPubSampleApp/MPSampleAppInstanceProvider.h +++ b/MoPubSampleApp/MPSampleAppInstanceProvider.h @@ -1,7 +1,7 @@ // // MPSampleAppInstanceProvider.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/MoPubSampleApp/MPSampleAppInstanceProvider.m b/MoPubSampleApp/MPSampleAppInstanceProvider.m index 77c8ba029..4ce9a3bf1 100644 --- a/MoPubSampleApp/MPSampleAppInstanceProvider.m +++ b/MoPubSampleApp/MPSampleAppInstanceProvider.m @@ -1,7 +1,7 @@ // // MPSampleAppInstanceProvider.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/MoPubSampleApp/MPSampleAppLogReader.h b/MoPubSampleApp/MPSampleAppLogReader.h index 9984e1400..b1937751f 100644 --- a/MoPubSampleApp/MPSampleAppLogReader.h +++ b/MoPubSampleApp/MPSampleAppLogReader.h @@ -1,7 +1,7 @@ // // MPSampleAppLogReader.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/MoPubSampleApp/MPSampleAppLogReader.m b/MoPubSampleApp/MPSampleAppLogReader.m index e05cfcbb3..2569578f2 100644 --- a/MoPubSampleApp/MPSampleAppLogReader.m +++ b/MoPubSampleApp/MPSampleAppLogReader.m @@ -1,13 +1,13 @@ // // MPSampleAppLogReader.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 "MPSampleAppLogReader.h" -#import "MPLogProvider.h" +#import "MPLogging.h" @interface MPSampleAppLogReader () @@ -30,20 +30,20 @@ + (MPSampleAppLogReader *)sharedLogReader - (void)dealloc { - [[MPLogProvider sharedLogProvider] removeLogger:self]; + [MPLogging removeLogger:self]; } - (void)beginReadingLogMessages { - [[MPLogProvider sharedLogProvider] removeLogger:self]; - [[MPLogProvider sharedLogProvider] addLogger:self]; + [MPLogging removeLogger:self]; + [MPLogging addLogger:self]; } #pragma mark - - (MPLogLevel)logLevel { - return MPLogLevelAll; + return MPLogLevelDebug; } - (void)logMessage:(NSString *)message diff --git a/MoPubSampleApp/Views/MPCollectionViewAdPlacerView.h b/MoPubSampleApp/Views/MPCollectionViewAdPlacerView.h index e4de7879c..e8f6d4d97 100644 --- a/MoPubSampleApp/Views/MPCollectionViewAdPlacerView.h +++ b/MoPubSampleApp/Views/MPCollectionViewAdPlacerView.h @@ -1,7 +1,7 @@ // // MPCollectionViewAdPlacerView.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/MoPubSampleApp/Views/MPCollectionViewAdPlacerView.m b/MoPubSampleApp/Views/MPCollectionViewAdPlacerView.m index d89bc4575..29f99f892 100644 --- a/MoPubSampleApp/Views/MPCollectionViewAdPlacerView.m +++ b/MoPubSampleApp/Views/MPCollectionViewAdPlacerView.m @@ -1,7 +1,7 @@ // // MPCollectionViewAdPlacerView.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/MoPubSampleApp/Views/MPIndexPathPickerView.h b/MoPubSampleApp/Views/MPIndexPathPickerView.h index 7e1bcf90e..8d14dcbe0 100644 --- a/MoPubSampleApp/Views/MPIndexPathPickerView.h +++ b/MoPubSampleApp/Views/MPIndexPathPickerView.h @@ -1,7 +1,7 @@ // // MPIndexPathPickerView.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/MoPubSampleApp/Views/MPIndexPathPickerView.m b/MoPubSampleApp/Views/MPIndexPathPickerView.m index 2d48d4889..5ad120e9f 100644 --- a/MoPubSampleApp/Views/MPIndexPathPickerView.m +++ b/MoPubSampleApp/Views/MPIndexPathPickerView.m @@ -1,7 +1,7 @@ // // MPIndexPathPickerView.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/MoPubSampleApp/Views/MPNativeAdCell.h b/MoPubSampleApp/Views/MPNativeAdCell.h index 5be4361cc..59f21db4f 100644 --- a/MoPubSampleApp/Views/MPNativeAdCell.h +++ b/MoPubSampleApp/Views/MPNativeAdCell.h @@ -1,7 +1,7 @@ // // MPNativeAdCell.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/MoPubSampleApp/Views/MPNativeAdCell.m b/MoPubSampleApp/Views/MPNativeAdCell.m index a7ab6b625..664164a3f 100644 --- a/MoPubSampleApp/Views/MPNativeAdCell.m +++ b/MoPubSampleApp/Views/MPNativeAdCell.m @@ -1,7 +1,7 @@ // // MPNativeAdCell.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/MoPubSampleApp/Views/MPNativeAdPageView.h b/MoPubSampleApp/Views/MPNativeAdPageView.h index 2a3008e16..a6d682a70 100644 --- a/MoPubSampleApp/Views/MPNativeAdPageView.h +++ b/MoPubSampleApp/Views/MPNativeAdPageView.h @@ -1,7 +1,7 @@ // // MPNativeAdPageView.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/MoPubSampleApp/Views/MPNativeAdPageView.m b/MoPubSampleApp/Views/MPNativeAdPageView.m index 2f674096e..1aed67e39 100644 --- a/MoPubSampleApp/Views/MPNativeAdPageView.m +++ b/MoPubSampleApp/Views/MPNativeAdPageView.m @@ -1,7 +1,7 @@ // // MPNativeAdPageView.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/MoPubSampleApp/Views/MPNativeAdTableHeaderView.h b/MoPubSampleApp/Views/MPNativeAdTableHeaderView.h index d2f6003b9..527b54cdd 100644 --- a/MoPubSampleApp/Views/MPNativeAdTableHeaderView.h +++ b/MoPubSampleApp/Views/MPNativeAdTableHeaderView.h @@ -1,7 +1,7 @@ // // MPNativeAdTableHeaderView.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/MoPubSampleApp/Views/MPNativeAdTableHeaderView.m b/MoPubSampleApp/Views/MPNativeAdTableHeaderView.m index 9b1779b62..5a64e5344 100644 --- a/MoPubSampleApp/Views/MPNativeAdTableHeaderView.m +++ b/MoPubSampleApp/Views/MPNativeAdTableHeaderView.m @@ -1,7 +1,7 @@ // // MPNativeAdTableHeaderView.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/MoPubSampleApp/Views/MPNativeCollectionViewAdCollectionViewCell.h b/MoPubSampleApp/Views/MPNativeCollectionViewAdCollectionViewCell.h index aa443a233..490414886 100644 --- a/MoPubSampleApp/Views/MPNativeCollectionViewAdCollectionViewCell.h +++ b/MoPubSampleApp/Views/MPNativeCollectionViewAdCollectionViewCell.h @@ -1,7 +1,7 @@ // // MPNativeCollectionViewAdCollectionViewCell.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/MoPubSampleApp/Views/MPNativeCollectionViewAdCollectionViewCell.m b/MoPubSampleApp/Views/MPNativeCollectionViewAdCollectionViewCell.m index b3f5218be..73729cbf1 100644 --- a/MoPubSampleApp/Views/MPNativeCollectionViewAdCollectionViewCell.m +++ b/MoPubSampleApp/Views/MPNativeCollectionViewAdCollectionViewCell.m @@ -1,7 +1,7 @@ // // MPNativeCollectionViewAdCollectionViewCell.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/MoPubSampleApp/Views/MPNativeVideoTableViewAdPlacerView.h b/MoPubSampleApp/Views/MPNativeVideoTableViewAdPlacerView.h index edc224d87..8e257bca5 100644 --- a/MoPubSampleApp/Views/MPNativeVideoTableViewAdPlacerView.h +++ b/MoPubSampleApp/Views/MPNativeVideoTableViewAdPlacerView.h @@ -1,7 +1,7 @@ // // MPNativeVideoTableViewAdPlacerView.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/MoPubSampleApp/Views/MPNativeVideoTableViewAdPlacerView.m b/MoPubSampleApp/Views/MPNativeVideoTableViewAdPlacerView.m index 6a3d728b0..ea4c99f38 100644 --- a/MoPubSampleApp/Views/MPNativeVideoTableViewAdPlacerView.m +++ b/MoPubSampleApp/Views/MPNativeVideoTableViewAdPlacerView.m @@ -1,7 +1,7 @@ // // MPNativeVideoTableViewAdPlacerView.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/MoPubSampleApp/Views/MPNativeVideoView.h b/MoPubSampleApp/Views/MPNativeVideoView.h index bea690014..fd41612cf 100644 --- a/MoPubSampleApp/Views/MPNativeVideoView.h +++ b/MoPubSampleApp/Views/MPNativeVideoView.h @@ -1,7 +1,7 @@ // // MPNativeVideoView.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/MoPubSampleApp/Views/MPNativeVideoView.m b/MoPubSampleApp/Views/MPNativeVideoView.m index 34e7ec538..6f450d1fa 100644 --- a/MoPubSampleApp/Views/MPNativeVideoView.m +++ b/MoPubSampleApp/Views/MPNativeVideoView.m @@ -1,7 +1,7 @@ // // MPNativeVideoView.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/MoPubSampleApp/Views/MPStaticNativeAdView.h b/MoPubSampleApp/Views/MPStaticNativeAdView.h index 0375b747a..52b765ec8 100644 --- a/MoPubSampleApp/Views/MPStaticNativeAdView.h +++ b/MoPubSampleApp/Views/MPStaticNativeAdView.h @@ -1,7 +1,7 @@ // // MPStaticNativeAdView.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/MoPubSampleApp/Views/MPStaticNativeAdView.m b/MoPubSampleApp/Views/MPStaticNativeAdView.m index ae74df585..7c3bbced2 100644 --- a/MoPubSampleApp/Views/MPStaticNativeAdView.m +++ b/MoPubSampleApp/Views/MPStaticNativeAdView.m @@ -1,7 +1,7 @@ // // MPStaticNativeAdView.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/MoPubSampleApp/Views/MPTableViewAdPlacerView.h b/MoPubSampleApp/Views/MPTableViewAdPlacerView.h index c0dfa73fc..a3d0a7f23 100644 --- a/MoPubSampleApp/Views/MPTableViewAdPlacerView.h +++ b/MoPubSampleApp/Views/MPTableViewAdPlacerView.h @@ -1,7 +1,7 @@ // // MPTableViewAdPlacerView.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/MoPubSampleApp/Views/MPTableViewAdPlacerView.m b/MoPubSampleApp/Views/MPTableViewAdPlacerView.m index 95cda40b3..d75f4ea8c 100644 --- a/MoPubSampleApp/Views/MPTableViewAdPlacerView.m +++ b/MoPubSampleApp/Views/MPTableViewAdPlacerView.m @@ -1,7 +1,7 @@ // // MPTableViewAdPlacerView.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/MoPubSampleApp/main.m b/MoPubSampleApp/main.m index a1cef282a..b749fa9db 100644 --- a/MoPubSampleApp/main.m +++ b/MoPubSampleApp/main.m @@ -1,7 +1,7 @@ // // main.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/README.md b/README.md index 9b3ecd40c..706d737be 100644 --- a/README.md +++ b/README.md @@ -23,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.1 +The current version of the SDK is 5.5.0 ### Installation with CocoaPods @@ -82,10 +82,15 @@ 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** + - 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** - - 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. + - 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 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. diff --git a/mopub-ios-sdk.podspec b/mopub-ios-sdk.podspec index e4d41074c..d47cf1b4c 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.1' + spec.version = '5.5.0' spec.license = { :type => 'New BSD', :file => 'LICENSE' } spec.homepage = 'https://github.com/mopub/mopub-ios-sdk' spec.authors = { 'MoPub' => 'support@mopub.com' } @@ -14,7 +14,7 @@ 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.1' } + spec.source = { :git => 'https://github.com/mopub/mopub-ios-sdk.git', :tag => '5.5.0' } spec.requires_arc = true spec.ios.deployment_target = '8.0' spec.frameworks = [ @@ -45,7 +45,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,html}', 'MoPubSDK/**/MPAdapters.plist'] core.exclude_files = ['MoPubSDK/Viewability/Moat', 'MoPubSDK/Viewability/Avid'] end From d02b054ca60d518b26fea6c16ebae791d691acb2 Mon Sep 17 00:00:00 2001 From: Kelly Dun Date: Fri, 15 Mar 2019 12:11:23 -0700 Subject: [PATCH 06/12] 5.6.0 --- CHANGELOG.md | 10 + Canary/Canary.xcodeproj/project.pbxproj | 246 +++++++++++++-- .../xcschemes/Canary (Internal).xcscheme | 8 +- .../xcschemes/Canary Screenshots.xcscheme | 84 +++++ .../xcshareddata/xcschemes/Canary.xcscheme | 10 + .../xcschemes/CanaryUITests.xcscheme | 34 ++ Canary/Canary/AccessibilityIdentifier.swift | 20 ++ .../AdapterVersionsMenuDataSource.swift | 134 ++++++++ Canary/Canary/AppDelegate.swift | 7 +- ...Pub_logo_square_white on blue (1)-1024.png | Bin 48615 -> 42168 bytes ...MoPub_logo_square_white on blue (1)-20.png | Bin 1516 -> 1830 bytes ...ub_logo_square_white on blue (1)-20@2x.png | Bin 2044 -> 2321 bytes ...ub_logo_square_white on blue (1)-20@3x.png | Bin 2663 -> 2893 bytes ...MoPub_logo_square_white on blue (1)-29.png | Bin 1741 -> 2032 bytes ...ub_logo_square_white on blue (1)-29@2x.png | Bin 2607 -> 2821 bytes ...ub_logo_square_white on blue (1)-29@3x.png | Bin 3477 -> 3603 bytes ...MoPub_logo_square_white on blue (1)-40.png | Bin 2044 -> 2321 bytes ...ub_logo_square_white on blue (1)-40@2x.png | Bin 3291 -> 3417 bytes ...ub_logo_square_white on blue (1)-40@3x.png | Bin 4498 -> 4676 bytes ...ub_logo_square_white on blue (1)-60@2x.png | Bin 4498 -> 4676 bytes ...ub_logo_square_white on blue (1)-60@3x.png | Bin 6568 -> 6353 bytes ...MoPub_logo_square_white on blue (1)-76.png | Bin 3144 -> 3303 bytes ...ub_logo_square_white on blue (1)-76@2x.png | Bin 5668 -> 5460 bytes ..._logo_square_white on blue (1)-83.5@2x.png | Bin 6172 -> 6020 bytes .../CameraButton.imageset/Contents.json | 23 -- .../CameraButton.imageset/camera_stroke.png | Bin 471 -> 0 bytes .../camera_stroke@2x.png | Bin 1000 -> 0 bytes .../camera_stroke@3x.png | Bin 1501 -> 0 bytes .../Contents.json | 0 .../Mraid.png | Bin .../Mraid@2x.png | Bin .../Mraid@3x.png | Bin Canary/Canary/Base.lproj/Main.storyboard | 199 +++++++++++- Canary/Canary/Base/AdUnit.swift | 35 ++- Canary/Canary/Base/AdUnitDataSource.swift | 15 +- .../Base/AdUnitTableViewController.swift | 51 +-- .../FilterableAdUnitTableViewController.swift | 71 +++++ .../Base/FilteredAdUnitDataSource.swift | 112 +++++++ .../Canary/Cells/AdActionsTableViewCell.swift | 19 +- .../CollapsibleAdapterInfoTableViewCell.swift | 90 ++++++ .../CollapsibleAdapterInfoTableViewCell.xib | 109 +++++++ .../Extensions/UIAlertController+Picker.swift | 51 +++ Canary/Canary/Formats/AdDataSource.swift | 10 + Canary/Canary/Formats/AdFormat.swift | 2 +- .../Formats/AdTableViewController.swift | 16 +- .../Canary/Formats/BannerAdDataSource.swift | 19 ++ .../Formats/BannerAdViewController.swift | 5 + .../Formats/InterstitialAdDataSource.swift | 19 ++ .../Formats/LeaderboardAdViewController.swift | 5 + .../MediumRectangleAdViewController.swift | 5 + .../Canary/Formats/NativeAdDataSource.swift | 20 ++ Canary/Canary/Formats/NativeAdView.swift | 3 + .../Canary/Formats/RewardedAdDataSource.swift | 58 ++-- Canary/Canary/Info.plist | 6 +- .../LogingLevelMenuDataSource.swift | 18 +- Canary/Canary/MainTabBarController.swift | 1 + .../ManualEntryInterfaceViewController.swift | 124 ++++++++ Canary/Canary/Menu/MenuDataSource.swift | 19 +- Canary/Canary/Menu/MenuDisplayable.swift | 16 +- Canary/Canary/Menu/MenuViewController.swift | 20 +- Canary/Canary/RoundedButton.swift | 47 +++ Gemfile | 11 + Gemfile.lock | 228 ++++++++++++++ MoPubResources/Info.plist | 4 +- MoPubSDK.xcodeproj/project.pbxproj | 98 ++++-- MoPubSDK/Internal/Banners/MPBannerAdManager.m | 40 ++- .../Banners/MPBannerCustomEventAdapter.m | 10 + .../Internal/Banners/MPBaseBannerAdapter.h | 6 + MoPubSDK/Internal/Common/MPAdConfiguration.h | 13 +- MoPubSDK/Internal/Common/MPAdConfiguration.m | 25 +- .../Internal/Common/MPAdServerCommunicator.h | 2 + .../Internal/Common/MPAdServerCommunicator.m | 32 +- .../Internal/Common/MPAdServerURLBuilder.m | 8 + MoPubSDK/Internal/Common/MPClosableView.m | 1 + MoPubSDK/Internal/HTML/MPAdWebViewAgent.m | 4 +- MoPubSDK/Internal/HTML/MPContentBlocker.m | 21 +- MoPubSDK/Internal/HTML/MPWebView.m | 48 +-- .../Interstitials/MPInterstitialAdManager.m | 15 +- .../MPInterstitialAdManagerDelegate.h | 1 + MoPubSDK/Internal/MPAdServerKeys.h | 2 + MoPubSDK/Internal/MPAdServerKeys.m | 2 + MoPubSDK/Internal/MPRateLimitConfiguration.h | 40 +++ MoPubSDK/Internal/MPRateLimitConfiguration.m | 53 ++++ MoPubSDK/Internal/MPRateLimitManager.h | 50 +++ MoPubSDK/Internal/MPRateLimitManager.m | 61 ++++ .../Internal/MRAID/MPMRAIDBannerCustomEvent.m | 14 + MoPubSDK/Internal/MRAID/MRController.h | 6 + MoPubSDK/Internal/MRAID/MRController.m | 203 +++++++++--- MoPubSDK/Internal/Utility/MPError.h | 5 + MoPubSDK/Internal/Utility/MPError.m | 6 + MoPubSDK/Internal/Utility/MPGlobal.h | 2 +- MoPubSDK/Internal/Utility/MPGlobal.m | 15 +- .../Internal/Utility/MPIdentityProvider.m | 40 ++- MoPubSDK/Logging/Internal/MPConsoleLogger.h | 8 +- MoPubSDK/Logging/Internal/MPConsoleLogger.m | 4 +- MoPubSDK/Logging/Internal/MPLogManager.h | 12 +- MoPubSDK/Logging/Internal/MPLogManager.m | 24 +- .../Logging/{MPLogLevel.h => MPBLogLevel.h} | 10 +- MoPubSDK/Logging/{MPLogger.h => MPBLogger.h} | 8 +- MoPubSDK/Logging/MPLogEvent.h | 8 +- MoPubSDK/Logging/MPLogEvent.m | 24 +- MoPubSDK/Logging/MPLogging.h | 16 +- MoPubSDK/Logging/MPLogging.m | 8 +- MoPubSDK/MPBannerCustomEventDelegate.h | 16 + MoPubSDK/MPConstants.h | 2 +- MoPubSDK/MPMoPubConfiguration.h | 6 +- MoPubSDK/MPMoPubConfiguration.m | 2 +- MoPubSDK/MoPub.h | 18 +- MoPubSDK/MoPub.m | 16 +- MoPubSDK/NativeAds/MPNativeAdRequest.m | 8 + MoPubSDK/Resources/MPCountdownTimer.html | 1 + .../MPMoPubRewardedPlayableCustomEvent.m | 10 + .../Internal/MPRewardedVideoAdManager.m | 8 + MoPubSDKFramework/Info.plist | 4 +- MoPubSDKTests/Info.plist | 4 +- MoPubSDKTests/MOPUBExperimentProviderTests.m | 10 +- MoPubSDKTests/MPAdConfigurationFactory.m | 18 +- MoPubSDKTests/MPAdConfigurationTests.m | 142 ++++----- .../MPAdServerCommunicator+Testing.h | 4 + .../MPAdServerCommunicator+Testing.m | 1 + MoPubSDKTests/MPAdServerCommunicatorTests.m | 290 ++++++++++++++++++ MoPubSDKTests/MPAdServerURLBuilderTests.m | 42 +++ .../MPAdserverCommunicatorDelegateHandler.h | 2 + .../MPAdserverCommunicatorDelegateHandler.m | 2 + .../MPBannerAdapterDelegateHandler.h | 3 + .../MPBannerAdapterDelegateHandler.m | 8 + .../MPBannerCustomEventAdapterTests.m | 2 +- MoPubSDKTests/MPIdentityProvider+Testing.h | 17 + MoPubSDKTests/MPIdentityProvider+Testing.m | 16 + MoPubSDKTests/MPIdentityProviderTests.m | 99 ++++++ .../MPRateLimitConfiguration+Testing.h | 20 ++ .../MPRateLimitConfiguration+Testing.m | 13 + MoPubSDKTests/MPRateLimitConfigurationTests.m | 143 +++++++++ MoPubSDKTests/MPRateLimitManager+Testing.h | 20 ++ MoPubSDKTests/MPRateLimitManager+Testing.m | 13 + MoPubSDKTests/MPRateLimitManagerTests.m | 186 +++++++++++ MoPubSDKTests/MPRewardedVideoAdManagerTests.m | 12 +- MoPubSDKTests/MPRewardedVideoTests.m | 32 +- MoPubSDKTests/MoPubTests.m | 10 +- MoPubSampleApp/AppDelegate.m | 2 +- .../MPRewardedVideoAdDetailViewController.m | 2 +- MoPubSampleApp/MPSampleAppLogReader.m | 8 +- README.md | 19 +- mopub-ios-sdk.podspec | 4 +- 144 files changed, 3634 insertions(+), 530 deletions(-) create mode 100644 Canary/Canary.xcodeproj/xcshareddata/xcschemes/Canary Screenshots.xcscheme create mode 100644 Canary/Canary/AccessibilityIdentifier.swift create mode 100644 Canary/Canary/AdapterVersions/AdapterVersionsMenuDataSource.swift delete mode 100644 Canary/Canary/Assets.xcassets/CameraButton.imageset/Contents.json delete mode 100644 Canary/Canary/Assets.xcassets/CameraButton.imageset/camera_stroke.png delete mode 100644 Canary/Canary/Assets.xcassets/CameraButton.imageset/camera_stroke@2x.png delete mode 100644 Canary/Canary/Assets.xcassets/CameraButton.imageset/camera_stroke@3x.png rename Canary/Canary/Assets.xcassets/{Mraid.imageset => ReleaseTesting.imageset}/Contents.json (100%) rename Canary/Canary/Assets.xcassets/{Mraid.imageset => ReleaseTesting.imageset}/Mraid.png (100%) rename Canary/Canary/Assets.xcassets/{Mraid.imageset => ReleaseTesting.imageset}/Mraid@2x.png (100%) rename Canary/Canary/Assets.xcassets/{Mraid.imageset => ReleaseTesting.imageset}/Mraid@3x.png (100%) create mode 100644 Canary/Canary/Base/FilterableAdUnitTableViewController.swift create mode 100644 Canary/Canary/Base/FilteredAdUnitDataSource.swift create mode 100644 Canary/Canary/Cells/CollapsibleAdapterInfoTableViewCell.swift create mode 100644 Canary/Canary/Cells/CollapsibleAdapterInfoTableViewCell.xib create mode 100644 Canary/Canary/Extensions/UIAlertController+Picker.swift create mode 100644 Canary/Canary/ManualEntryInterfaceViewController.swift create mode 100644 Gemfile create mode 100644 Gemfile.lock create mode 100644 MoPubSDK/Internal/MPRateLimitConfiguration.h create mode 100644 MoPubSDK/Internal/MPRateLimitConfiguration.m create mode 100644 MoPubSDK/Internal/MPRateLimitManager.h create mode 100644 MoPubSDK/Internal/MPRateLimitManager.m rename MoPubSDK/Logging/{MPLogLevel.h => MPBLogLevel.h} (65%) rename MoPubSDK/Logging/{MPLogger.h => MPBLogger.h} (76%) create mode 100644 MoPubSDKTests/MPIdentityProvider+Testing.h create mode 100644 MoPubSDKTests/MPIdentityProvider+Testing.m create mode 100644 MoPubSDKTests/MPIdentityProviderTests.m create mode 100644 MoPubSDKTests/MPRateLimitConfiguration+Testing.h create mode 100644 MoPubSDKTests/MPRateLimitConfiguration+Testing.m create mode 100644 MoPubSDKTests/MPRateLimitConfigurationTests.m create mode 100644 MoPubSDKTests/MPRateLimitManager+Testing.h create mode 100644 MoPubSDKTests/MPRateLimitManager+Testing.m create mode 100644 MoPubSDKTests/MPRateLimitManagerTests.m diff --git a/CHANGELOG.md b/CHANGELOG.md index bdf3500fb..4305e2e7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +## 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 diff --git a/Canary/Canary.xcodeproj/project.pbxproj b/Canary/Canary.xcodeproj/project.pbxproj index 21829b418..66a751752 100644 --- a/Canary/Canary.xcodeproj/project.pbxproj +++ b/Canary/Canary.xcodeproj/project.pbxproj @@ -7,6 +7,10 @@ objects = { /* Begin PBXBuildFile section */ + 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 */; }; @@ -21,6 +25,8 @@ 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 */; }; @@ -82,6 +88,12 @@ 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 */; }; @@ -98,12 +110,21 @@ 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 /* MraidSplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC713BAD21700BBC003655B2 /* MraidSplitViewController.swift */; }; - BC713BB121700C9B003655B2 /* MraidTestsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC713BB021700C9B003655B2 /* MraidTestsViewController.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 */; }; BC96A33B20AE306F00AC3266 /* PrivacyMenuDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC96A33920AE306F00AC3266 /* PrivacyMenuDataSource.swift */; }; @@ -149,7 +170,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 /* MraidComplianceAds.plist in Resources */ = {isa = PBXBuildFile; fileRef = BCF6F7E321752DDF00FDE594 /* MraidComplianceAds.plist */; }; + 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 */; }; @@ -179,6 +200,13 @@ /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + BC014495220B6E2F0090F497 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BC33E02B1FBB627B0060ECBE /* Project object */; + proxyType = 1; + remoteGlobalIDString = BC33E0321FBB627B0060ECBE; + remoteInfo = Canary; + }; CE237F23216BF2C800A8134A /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BC33E02B1FBB627B0060ECBE /* Project object */; @@ -202,6 +230,8 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 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 = ""; }; 2A4D35E1211D074800BE9377 /* APIEndpointMenuDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIEndpointMenuDataSource.swift; sourceTree = ""; }; @@ -213,6 +243,10 @@ 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 = ""; }; @@ -236,7 +270,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 = ""; }; @@ -250,6 +284,9 @@ 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 = ""; }; @@ -258,10 +295,15 @@ 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 /* MraidSplitViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MraidSplitViewController.swift; sourceTree = ""; }; - BC713BB021700C9B003655B2 /* MraidTestsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MraidTestsViewController.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 = ""; }; 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 = ""; }; @@ -286,7 +328,7 @@ 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 /* MraidComplianceAds.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = MraidComplianceAds.plist; sourceTree = ""; }; + 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 = ""; }; @@ -317,6 +359,13 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + BC01448D220B6E2F0090F497 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; BC33E0301FBB627B0060ECBE /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -381,6 +430,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 = ( @@ -388,6 +449,8 @@ BC59F25C20F524E30051DA00 /* AdUnitDataSource.swift */, BC04944220F904F200CFD9C2 /* AdUnitTableViewController.swift */, BC647D9920F967C800FAA12C /* BaseSplitViewController.swift */, + BC5F735F2220AF6800EED041 /* FilterableAdUnitTableViewController.swift */, + BC5F735C221F4C1D00EED041 /* FilteredAdUnitDataSource.swift */, ); path = Base; sourceTree = ""; @@ -416,8 +479,9 @@ isa = PBXGroup; children = ( BC33E0331FBB627B0060ECBE /* Canary.app */, - BC33E0571FBB63260060ECBE /* Canary (Internal).app */, + BC33E0571FBB63260060ECBE /* Canary.app */, CE237F1E216BF2C800A8134A /* CanaryUITests.xctest */, + BC014490220B6E2F0090F497 /* CanaryScreenshots.xctest */, ); name = Products; sourceTree = ""; @@ -425,11 +489,10 @@ BC33E0351FBB627B0060ECBE /* Canary */ = { isa = PBXGroup; children = ( + BC4FE3052208C57A00B5D240 /* AdapterVersions */, BC33E0361FBB627B0060ECBE /* AppDelegate.swift */, + BC7892E8220B9C22007A6218 /* AccessibilityIdentifier.swift */, BC00C5B7208FCF46008E0AEB /* AppDelegate+Consent.swift */, - BCB4DF33211CD970005BD171 /* TableViewCellRegisterable.swift */, - BCA042EE211E049D001B1AF5 /* RoundedButton.swift */, - BC525EE82135BA4E007B1761 /* PreferredWidthLabel.swift */, BC33E03F1FBB627B0060ECBE /* Assets.xcassets */, BC04944520F911C900CFD9C2 /* Base */, BCFE5B391FE4469D00D760E9 /* Cells */, @@ -442,38 +505,51 @@ BC003AFF20DC430400CD1FEB /* LoggingLevel */, BC33E03C1FBB627B0060ECBE /* Main.storyboard */, BC95D23B2097B9180030C230 /* MainTabBarController.swift */, + 2A1F52F322160A0300933277 /* ManualEntryInterfaceViewController.swift */, BCB63FE420AB3D8100C22C7F /* Menu */, + BC525EE82135BA4E007B1761 /* PreferredWidthLabel.swift */, 2A35FAAA21B5DA6200DC8805 /* QRCodeCameraInterfaceViewController.swift */, + BCA042EE211E049D001B1AF5 /* RoundedButton.swift */, BCFE5B3D1FE44F6D00D760E9 /* Samples */, B275DB2B200FD5F500F053F8 /* SavedAds */, BC04945920F91C9E00CFD9C2 /* StoryboardInstantiable.swift */, + BCB4DF33211CD970005BD171 /* TableViewCellRegisterable.swift */, ); 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 */, BC04944620F91BF400CFD9C2 /* Internal.storyboard */, BC04945C20F9266A00CFD9C2 /* Internal.swift */, - BC713BAF21700BC6003655B2 /* MRAID */, + BC713BAF21700BC6003655B2 /* ReleaseTesting */, BC04944920F91BFC00CFD9C2 /* NetworkAds */, BCE7078B20B373A500DA4BCB /* Privacy */, ); path = Internal; sourceTree = ""; }; - BC713BAF21700BC6003655B2 /* MRAID */ = { + BC713BAF21700BC6003655B2 /* ReleaseTesting */ = { isa = PBXGroup; children = ( - BC713BAD21700BBC003655B2 /* MraidSplitViewController.swift */, - BC713BB021700C9B003655B2 /* MraidTestsViewController.swift */, - BCF6F7E321752DDF00FDE594 /* MraidComplianceAds.plist */, + BC713BAD21700BBC003655B2 /* ReleaseTestingSplitViewController.swift */, + BC713BB021700C9B003655B2 /* ReleaseTestingViewController.swift */, + BCF6F7E321752DDF00FDE594 /* ReleaseTestingAds.plist */, ); - path = MRAID; + path = ReleaseTesting; sourceTree = ""; }; BCB63FE420AB3D8100C22C7F /* Menu */ = { @@ -507,6 +583,8 @@ BC2B97E920F41D7800D58F79 /* AdUnitTableViewHeader.xib */, BC96A33C20AE32DB00AC3266 /* BasicMenuTableViewCell.swift */, BC96A33F20AE335C00AC3266 /* BasicMenuTableViewCell.xib */, + BC4FE3062208E8E500B5D240 /* CollapsibleAdapterInfoTableViewCell.swift */, + BC4FE3092208E90800B5D240 /* CollapsibleAdapterInfoTableViewCell.xib */, BCB4DF2D211CC265005BD171 /* StatusTableViewCell.swift */, BCB4DF30211CC27D005BD171 /* StatusTableViewCell.xib */, BC2CEFB5211CF12C00EAA99D /* TextEntryTableViewCell.swift */, @@ -565,6 +643,7 @@ BCEB5DA82008279900FE5165 /* UIColor+Random.swift */, BCE7078520B34C3400DA4BCB /* MPBool+Description.swift */, BCE7078820B34C7A00DA4BCB /* MPConsentStatus+Description.swift */, + 2A1F52F52216230300933277 /* UIAlertController+Picker.swift */, ); path = Extensions; sourceTree = ""; @@ -657,6 +736,24 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + BC01448F220B6E2F0090F497 /* CanaryScreenshots */ = { + isa = PBXNativeTarget; + buildConfigurationList = BC014499220B6E2F0090F497 /* Build configuration list for PBXNativeTarget "CanaryScreenshots" */; + buildPhases = ( + 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 /* Canary */ = { isa = PBXNativeTarget; buildConfigurationList = BC33E0471FBB627B0060ECBE /* Build configuration list for PBXNativeTarget "Canary" */; @@ -695,7 +792,7 @@ ); name = "Canary (Internal)"; productName = Canary; - productReference = BC33E0571FBB63260060ECBE /* Canary (Internal).app */; + productReference = BC33E0571FBB63260060ECBE /* Canary.app */; productType = "com.apple.product-type.application"; }; CE237F1D216BF2C800A8134A /* CanaryUITests */ = { @@ -722,10 +819,16 @@ BC33E02B1FBB627B0060ECBE /* Project object */ = { isa = PBXProject; attributes = { - LastSwiftUpdateCheck = 1000; + DefaultBuildSystemTypeForWorkspace = Original; + LastSwiftUpdateCheck = 1010; LastUpgradeCheck = 0940; ORGANIZATIONNAME = MoPub; TargetAttributes = { + BC01448F220B6E2F0090F497 = { + CreatedOnToolsVersion = 10.1; + ProvisioningStyle = Automatic; + TestTargetID = BC33E0321FBB627B0060ECBE; + }; BC33E0321FBB627B0060ECBE = { CreatedOnToolsVersion = 9.1; ProvisioningStyle = Automatic; @@ -757,11 +860,19 @@ BC33E0321FBB627B0060ECBE /* Canary */, BC33E04A1FBB63260060ECBE /* Canary (Internal) */, CE237F1D216BF2C800A8134A /* CanaryUITests */, + BC01448F220B6E2F0090F497 /* CanaryScreenshots */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + BC01448E220B6E2F0090F497 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; BC33E0311FBB627B0060ECBE /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -770,6 +881,7 @@ 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 */, BCB4DF2B211CB9C4005BD171 /* AdTableViewController.xib in Resources */, @@ -791,6 +903,7 @@ 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 */, @@ -799,7 +912,7 @@ BC4CE2A3213733EB00CA0220 /* NativeAdCollectionViewController.xib in Resources */, BC3B0C9C2007DBC8002D28B1 /* TweetCollectionViewCell.xib in Resources */, BCFE5B3C1FE446D600D760E9 /* AdUnitTableViewCell.xib in Resources */, - BCF6F7E421752DDF00FDE594 /* MraidComplianceAds.plist in Resources */, + BCF6F7E421752DDF00FDE594 /* ReleaseTestingAds.plist in Resources */, BC4CE29A213604DA00CA0220 /* NativeAdTableViewController.xib in Resources */, BCB4DF32211CC27D005BD171 /* StatusTableViewCell.xib in Resources */, BC33E0531FBB63260060ECBE /* Main.storyboard in Resources */, @@ -940,6 +1053,18 @@ /* 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; @@ -947,7 +1072,9 @@ BCEB5DA92008279900FE5165 /* UIColor+Random.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 */, BC4CE2A52137343C00CA0220 /* NativeAdCollectionDataSource.swift in Sources */, BC525EE32130A61D007B1761 /* BaseNativeAdDataSource.swift in Sources */, @@ -962,14 +1089,18 @@ BCE7078620B34C3400DA4BCB /* MPBool+Description.swift in Sources */, BC525ED7212F50E3007B1761 /* RewardedAdDataSource.swift in Sources */, BCA042E9211E004D001B1AF5 /* AdActionsTableViewCell.swift in Sources */, + 2A1F52F422160A0300933277 /* ManualEntryInterfaceViewController.swift in Sources */, + BC4FE3032208C57000B5D240 /* AdapterVersionsMenuDataSource.swift in Sources */, BC00C5B8208FCF46008E0AEB /* AppDelegate+Consent.swift in Sources */, BC3B0C952007DAA5002D28B1 /* NativeAdCollectionViewController.swift in Sources */, BCFE5B371FE4469200D760E9 /* AdUnitTableViewCell.swift in Sources */, BC647D9A20F967C800FAA12C /* BaseSplitViewController.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 */, + 2A1F52F62216230300933277 /* UIAlertController+Picker.swift in Sources */, BC4CE29C2136054500CA0220 /* NativeAdTableDataSource.swift in Sources */, BC04945A20F91C9E00CFD9C2 /* StoryboardInstantiable.swift in Sources */, BC525ED4212F2D71007B1761 /* InterstitialAdDataSource.swift in Sources */, @@ -995,6 +1126,7 @@ BC3B0C8C20058D7C002D28B1 /* NativeAdView.swift in Sources */, 2A35FAAB21B5DA6200DC8805 /* QRCodeCameraInterfaceViewController.swift in Sources */, BCB4DF25211B913F005BD171 /* AdDataSource.swift in Sources */, + BC7892E9220B9C22007A6218 /* AccessibilityIdentifier.swift in Sources */, BCB4DF2E211CC265005BD171 /* StatusTableViewCell.swift in Sources */, BC96A33D20AE32DB00AC3266 /* BasicMenuTableViewCell.swift in Sources */, ); @@ -1004,12 +1136,15 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + BC7892EA220B9C22007A6218 /* AccessibilityIdentifier.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 */, + BC5F735E221F4C1D00EED041 /* FilteredAdUnitDataSource.swift in Sources */, BC525EE42130A61D007B1761 /* BaseNativeAdDataSource.swift in Sources */, BCE7078A20B34C7A00DA4BCB /* MPConsentStatus+Description.swift in Sources */, BC01F6512004191400D6898B /* InterstitialAdViewController.swift in Sources */, @@ -1018,6 +1153,7 @@ BC01F65420042BD600D6898B /* RewardedAdViewController.swift in Sources */, BC3B0C8A20058290002D28B1 /* NativeAdViewController.swift in Sources */, BC4E236020A5FE2E000BA519 /* ContainerViewController.swift in Sources */, + BC4FE3082208E8E500B5D240 /* CollapsibleAdapterInfoTableViewCell.swift in Sources */, BC04945020F91BFC00CFD9C2 /* NetworkAdsSplitViewController.swift in Sources */, BC63B9231FBD14A00033ACD6 /* AdUnit.swift in Sources */, BCE7078720B34C3400DA4BCB /* MPBool+Description.swift in Sources */, @@ -1025,12 +1161,12 @@ BCA042EA211E004D001B1AF5 /* AdActionsTableViewCell.swift in Sources */, BC00C5B9208FCF46008E0AEB /* AppDelegate+Consent.swift in Sources */, BC3B0C962007DAA5002D28B1 /* NativeAdCollectionViewController.swift in Sources */, - BC713BB121700C9B003655B2 /* MraidTestsViewController.swift in Sources */, + BC713BB121700C9B003655B2 /* ReleaseTestingViewController.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 /* MraidSplitViewController.swift in Sources */, + BC713BAE21700BBC003655B2 /* ReleaseTestingSplitViewController.swift in Sources */, BC003AFE20DC42FE00CD1FEB /* LogingLevelMenuDataSource.swift in Sources */, BC525EEA2135BA4E007B1761 /* PreferredWidthLabel.swift in Sources */, 2A35FAAC21B5DA6200DC8805 /* QRCodeCameraInterfaceViewController.swift in Sources */, @@ -1038,8 +1174,10 @@ 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 */, 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 */, @@ -1057,6 +1195,7 @@ BC525EC9212F15A5007B1761 /* MediumRectangleAdViewController.swift in Sources */, BC95D23D2097B9180030C230 /* MainTabBarController.swift in Sources */, BC16904E201F8F8E000EA77F /* AdFormat.swift in Sources */, + 2A1F52F7221628A000933277 /* ManualEntryInterfaceViewController.swift in Sources */, BCD0506D2003F20400FFC36D /* UIView+Nib.swift in Sources */, BC3B0C932006E168002D28B1 /* NativeAdTableViewController.swift in Sources */, BC33E04E1FBB63260060ECBE /* SampleAdsViewController.swift in Sources */, @@ -1097,6 +1236,11 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + BC014496220B6E2F0090F497 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = BC33E0321FBB627B0060ECBE /* Canary */; + targetProxy = BC014495220B6E2F0090F497 /* PBXContainerItemProxy */; + }; CE237F24216BF2C800A8134A /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = BC33E0321FBB627B0060ECBE /* Canary */; @@ -1124,6 +1268,43 @@ /* 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 = Canary; + }; + 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 = Canary; + }; + name = Release; + }; BC33E0451FBB627B0060ECBE /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -1241,7 +1422,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 5.5.0; + CURRENT_PROJECT_VERSION = 5.6.0; DEVELOPMENT_TEAM = 4S7XS533V3; INFOPLIST_FILE = Canary/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; @@ -1259,7 +1440,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 5.5.0; + CURRENT_PROJECT_VERSION = 5.6.0; DEVELOPMENT_TEAM = 4S7XS533V3; INFOPLIST_FILE = Canary/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; @@ -1277,13 +1458,13 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon.Internal; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 5.5.0; + CURRENT_PROJECT_VERSION = 5.6.0; DEVELOPMENT_TEAM = 4S7XS533V3; INFOPLIST_FILE = "Canary/Internal/Internal-Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; OTHER_SWIFT_FLAGS = "-DINTERNAL"; PRODUCT_BUNDLE_IDENTIFIER = com.mopub.Canary; - PRODUCT_NAME = "$(TARGET_NAME)"; + PRODUCT_NAME = Canary; SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; @@ -1296,13 +1477,13 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon.Internal; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 5.5.0; + CURRENT_PROJECT_VERSION = 5.6.0; DEVELOPMENT_TEAM = 4S7XS533V3; INFOPLIST_FILE = "Canary/Internal/Internal-Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; OTHER_SWIFT_FLAGS = "-DINTERNAL"; PRODUCT_BUNDLE_IDENTIFIER = com.mopub.Canary; - PRODUCT_NAME = "$(TARGET_NAME)"; + PRODUCT_NAME = Canary; SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; @@ -1347,6 +1528,15 @@ /* 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 = ( diff --git a/Canary/Canary.xcodeproj/xcshareddata/xcschemes/Canary (Internal).xcscheme b/Canary/Canary.xcodeproj/xcshareddata/xcschemes/Canary (Internal).xcscheme index 3dd34bb8c..d00ff75cd 100644 --- a/Canary/Canary.xcodeproj/xcshareddata/xcschemes/Canary (Internal).xcscheme +++ b/Canary/Canary.xcodeproj/xcshareddata/xcschemes/Canary (Internal).xcscheme @@ -15,7 +15,7 @@ @@ -33,7 +33,7 @@ @@ -56,7 +56,7 @@ @@ -75,7 +75,7 @@ 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..22e5e53db --- /dev/null +++ b/Canary/Canary.xcodeproj/xcshareddata/xcschemes/Canary Screenshots.xcscheme @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Canary/Canary.xcodeproj/xcshareddata/xcschemes/Canary.xcscheme b/Canary/Canary.xcodeproj/xcshareddata/xcschemes/Canary.xcscheme index f244b59a6..739e4256a 100644 --- a/Canary/Canary.xcodeproj/xcshareddata/xcschemes/Canary.xcscheme +++ b/Canary/Canary.xcodeproj/xcshareddata/xcschemes/Canary.xcscheme @@ -28,6 +28,16 @@ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" shouldUseLaunchSchemeArgsEnv = "YES"> + + + + + + + + + + + + + + @@ -50,6 +75,15 @@ savedToolIdentifier = "" useCustomWorkingDirectory = "NO" debugDocumentVersioning = "YES"> + + + + 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..cbeaab6b4 --- /dev/null +++ b/Canary/Canary/AdapterVersions/AdapterVersionsMenuDataSource.swift @@ -0,0 +1,134 @@ +// +// 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 + + /** + Alphabetically sorted adapter names. + */ + private var adapterNames: [String] = [] + + /** + Set of adapter names that are currently in an expanded state. + */ + private var expandedAdapters: Set = Set() + + // MARK: - Adapter Cell Retrieval + + /** + Registers and retrieves a reusable instance of a `CollapsibleAdapterInfoTableViewCell`. + - Parameter tableView: Table View rendering the cell. + - Returns: A reusable `CollapsibleAdapterInfoTableViewCell` instance. + */ + func adapterMenuCell(inTableView tableView: UITableView) -> CollapsibleAdapterInfoTableViewCell { + let adapterCellReuseIdentifier: String = "CollapsibleAdapterInfoTableViewCell" + + var cell = tableView.dequeueReusableCell(withIdentifier: adapterCellReuseIdentifier) as? CollapsibleAdapterInfoTableViewCell + if cell == nil { + tableView.register(UINib(nibName: adapterCellReuseIdentifier, bundle: nil), forCellReuseIdentifier: adapterCellReuseIdentifier) + cell = tableView.dequeueReusableCell(withIdentifier: adapterCellReuseIdentifier) as? CollapsibleAdapterInfoTableViewCell + } + + return cell! + } +} + +extension AdapterVersionsMenuDataSource: MenuDisplayable { + /** + Number of menu items available + */ + var count: Int { + // There will always be at least one item. + return max(adapterNames.count, 1) + } + + /** + 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 { + let cell: CollapsibleAdapterInfoTableViewCell = adapterMenuCell(inTableView: tableView) + + // There are no adapters initialized. + guard adapterNames.count > 0 else { + cell.titleLabel.text = "No adapters initialized" + return cell + } + + // There exist some initialized adapters + let name: String = adapterNames[index] + 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 + } + + /** + 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. + return (adapterNames.count > 0) + } + + /** + 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 { + // Verify that there are adapters present + guard adapterNames.count > 0 && indexPath.row < adapterNames.count else { + return false + } + + // Toggle the expanded state for the adapter + let name: String = adapterNames[indexPath.row] + 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 + } + + /** + Updates the data source if needed. + */ + func updateIfNeeded() -> Swift.Void { + adapterNames = MoPub.sharedInstance().availableAdapterClassNames()?.sorted() ?? [] + } +} diff --git a/Canary/Canary/AppDelegate.swift b/Canary/Canary/AppDelegate.swift index 5620dcceb..6ec9bb2d7 100644 --- a/Canary/Canary/AppDelegate.swift +++ b/Canary/Canary/AppDelegate.swift @@ -50,6 +50,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate { sdkConfig.loggingLevel = .info MoPub.sharedInstance().initializeSdk(with: sdkConfig) { + // Update the state of the menu now that the SDK has completed initialization. + if let menuController = self.containerViewController.menuViewController { + menuController.updateIfNeeded() + } + // Request user consent to collect personally identifiable information // used for targeted ads if let tabBarController = self.containerViewController.mainTabBarController { @@ -107,8 +112,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate { 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. + self.containerViewController.mainTabBarController?.selectedIndex = 1 if shouldSave { - self.containerViewController.mainTabBarController?.selectedIndex = 1 SavedAdsManager.sharedInstance.addSavedAd(adUnit: adUnit) } 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 index 76c635e4a9e9af6554622ba69f40b3dd2d3e4e1e..add79ff76b013e00490c9a8efdd2fc3faee4d1ba 100644 GIT binary patch literal 42168 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 PpZmIof0x~Hc=dk(gX2X0 literal 48615 zcmeFZXIPV4*EJfDrl5dGld7nQGywsr!3HQzMS2NRRhsk`5)c)XCQ6kOge{0P=>!Nx zN`L?Xkq!ZY5PIk#qLF)>7m|D z0N{FjyqVn-VY4&Zjec$t7Y0l{uD$aA0z7%~pQ;<6Gczh)sdrT-&o4V4Y!0ON z3|v=gqL3cC~!UiHnmz#V>1DA4c6@jxAWr&``>F+E9UU z*#z%q<8#6`;cj^5W%yUi)TA>Ed>_kX`4#8=q7!4Zq~E=a&gEzMWbr^m+OEiuQN@Hk zsK#dY*7q>b#K;U4R$8Yk!Yl40=2*4#g*_&bVXM##703{Jlq3dRwH7^aT&`2l>5vbt z|K&8+?xzijf9kp@Gd~=|Y-ai_CXFkxuk%osgQW0n6qIZ9Z0+kZHqS9LU?#WXIsP#f z=Gpe-)vjlIAvy!5{SyD!mL#ow5%=pshr!qPFkLd+yt`+3t?=AbYGx8QkmcPp=3{vQ zH~zM5$rS6?X4qe)HE+*Ba4%JMW#(mL(5kpz)3)*AuOB+I+5$W3l&yDmG?-f7G}wIH zM*5OGO>=f|*j43^sIZXRG=7A&I;IQUh6iW%^&9YsFOrQNQI^^GXEVkv$Q7jH5t z3twF$KIk0a2gAUxREi^^%_pmlGha@>8jUz%WO2SDd4@U+`6)B`1{d4ZCa?zp{1W0YYR{8G)As z#ZON}q3sp#9whlFGMo>2t7&xbLAa8~8Gkf>&*P50rs^oADNgBOLFSzqwd%%P2b$6q zLtE3a5}8xAuO(1YBxXAVxeHWbyCS3dpmts`yTM|B2qLbL6%VAz&;FW&E?R8Y`68|R z8Y>Ok*F7x#g3Qi(vMlgz4P|t`=@i-Z*WZ=s8>@=;smO)5{U#4^+`onv}`@;vq+w2&VG*HHN54?O3tQLm87%eg$ozfnb;b+xSVHstwgVAb8Wxj#C{^*xpSL~ znU{|;A7b>m z;eT5@%gY6LQvKL0_P>|+lRiYvYp4IVHuxn1pp$>H*!usmXg)5$7iD3d|F)(}_s>~R z%aQZ{z33YH5T(D;|J&NNE8>8`C+%PP{(I4XT>RTjxqn>zdsqI6;@_M74^aGumOnu8 z2Ppm=cYiX9|72Z%GK$}6-k+rHcT)K$Fa9TQ|5JMWE>`{k#UG&fQ$W*q!yk^~A9Vb~ zQT#^7KQz#9bo|4#{gX!hAv1s18GnG{4^aG}F1rdN&$7i83|0FdR zxw|2Q3*JEc1G}ocvAfpqKTI#)*v(4w#+Yk|jE-i4k)Y!;h7LA+Tax)W;At(=v9WR) zJ$z%Oa0NkM&0*uMeeP4oXEyNQg<9Xu5hd(;v1>2ZC+l%!1>4PyXEF}Y3X+GCHX>iW zvMZf^_JFQZ=`=T!{9o%jw-_gwdy@u%_4tpzecF`lw$Lu#Dy*rS_lAq;`ZLy&PnYhe z+~FEOEhr<@Ws$6h@oLiiRbSzo7^R1Ta1S(x3XBI^cHGgUG6TBC$-lBl*Ay7~pxNEylb-&=F_8lxI>?(9W9_mp4U z-J>59l8?+N@jM$`PcD^7QbD3&>%k#Zgn63A?3LG8+=EjS-zqhw7@3Iyz0AW{@0M%a zkr6A3d@YdSA_g^O*R_)w%t$fKhdDvtKUM~sNwpkA#y+=&Adwruhs}OHhE~cgkc0QnoX=8o6nbfeoYtl}fyixU*cvrDK9wh@=@WV3sZHJ;JRIysV)$r@45t>@EaYC+LYrR6D)Vv3?kyn$mVR@acE`3&S!y0YC${vf z@jv7jQ)-5etNS!<)Cn7qXlnskYZpTG0CTRmtFMz?B`LXD=5*in9r@mj>&1KZ%yrS? zIbP%+*jrI&3ssP8w72#uZO8K4X}O0D{q{hEu)~MW9N?pMDR#?ExzMN3vd6UNBU~hYh(0R74IDIIP@_xlwx7F|LckPOAvn^U);qa-*##v(d)Q$o& z?hX=KT_R>PWOJs9YA@>j_7xLte(_EE>HDD>098tJi@bLv?A4ppx~R#236ie<#i)j+ ze$&$HrEtz_H)lw%w-C42P$K#HS+V&2s>KQ41S$yVF_RZW#jX0RX<1-AaKIMYTY2N; zVTj_$8W~r1EaS}}C=y=0hZCpkwm2M%>o5J@AoSHN$-eXZg%2HkGT|Pu#NZ(xt#?GR zfpJM}v6f$fBkWsxQ?HLHZK~Ug2KbkJAH(e&m3v8ft9w?~_0KKUCv2x*dQM9h{U%54 zbBMU918DX8O*_$Z$jDE8-DX?fUI7#i#wN;}HPo#HYqQE*O+Ulu`fgvprpOJEUs@$p zIZx7H;KzQ%VYH{*np1IGBa$x)y2tL`i-c8t%Ho=0uy>Pgg{UBPV5qZzV>6m^3BPfN zZX)}*b1&uG&_oN|xAmmoHf?>E2fHzwBa^@FBmp!yQx6AOZM9&{!6xuwOSQ`d;(Num zEde$m$e_AZS<}x=+ZAglJE#80`~%Syqg#izGA|F0cI)zq{_D*W695O!XOFE|8EA157v{u_EcXWc7;n?Elfh2S+ba{myU8IC< zvTS84V8xFK1rVvcN>~~MY}&(o-1`V|gcFvRkId9GO0~d_r2cD7zxcZuC`r5;Vxv%V z|KwSUv{3JMbX-?*#6#cJR>zd0>BDk4?hmM)2DOaS}Z=U`p;1jVD zb7`6~w^2mct&h7%N@CC3rM1w)oYZ!AG8CI1`H7^iuWD^Qq-*zNYQ7;Qf7c1(Iu3W4 zUGsLsxA%APUFC_@ME;o|BVsvd=Duk7dayi?gKsFI7KI96^Qr^YDV3mOs%dUbP$%MO zr;a^iq*&ox@^{wIQ30c_3(G=hArC|NF5V;%SqLmm&n0&}XH$=|v2 z5jr~Z5)Ragm#`d#v2)jhDFcP)$b0;J#5pCLaY`XVUYA;)K1u{RPo;_)+kW{x`iTEO zagVz1VCLW4642wH5#vu)&TqsV2X4ohVI%A8&6J22}C?7F)<@$p|H-Z_(eu`743iVu7M2KikKV(6}6Yq)ID zS;oSC65{WJ7?{Xz{cbNZV^^;9FAF+zIHrO;0wn=&D0dxjA2_#hAR<-Qh}I|0@R+CJ zJAS@E!!+!g!EKNJ8bc=+U#d;fv|wR0@0yMh-a|{VnO2-?GfPq)ywl@e|GD)x|JXuS zl(abDYQ5(|?{Ain9{1^mI%UByiG$gC=YXCRDt~?)vVD3XcL-x4RgAh*QEKnX3dUDv z>_oA#*go&~fcp=7z#)nnw4_(h161j|=SwltQuOabDL;GU{Xa3TGt8)Vq9tUlcIWK} z)wCeZkXwQ_FR%lg_AQEhM>bZ*ar=x*y ztTyymF+^zB5Wc|*GToNH6~iI_8iPewT#u$2SAH4(3N(PCk>ow4@Q0g=t+uldLESYK za*<3CpUu4%wv8B`me9wFXda%8K4NF5Om)l*R zvH6Dg!x^PS&$yZm7cYW>XADr2}wK-6CK4=V)SJoMAUaG6MK-OV%e3 zRjQ9N)Q6Q!!$4NPo)M{a-6&>&e3O@r;8XZ}MCu-_lj|ExtK;QRj(@qKfhWe88sy~O zoGD#Oh?S2oJuV+x%I3s>`&FwkT^$>EZ;|z1F(Tj`Ikb$0@k!BaoUXL`O1mK&SjUXL zq~Tn7HyzyQH(!HiC;VTdvsgP^<0ES9zRlR_kChOEWTbf8D%useg&LfbtNK{Hd6S6= zz?Zia^luXak;3GhI9UK~cdzuApAn`0-TDD6@Rwm94Ue42%4cRUN#V18Ao8ED$oNHW zr!&!#l0lN#;cqo^bdyz9#a=JqG+>zx^YmXDWJ}G+tYa-wdj^hv(VM7 zB9{jyDYpM#hlk9&8xdoWkUTPJ<%0;Q=SL3py#SF0+b!%(r_fUQ8Y=Yo0Z&S9m_`35 zuzdNtnHhH15`k54^6uDFKhvRq92Mk?*rQ>|wjli(=Kq(gEF-gcT4YnZp)0scy}Q7Q zqD99L*_4$p17R(7%1czYFSpKJh0>#sXhXdFuepGKKH?I^y- z7kk?)OO=fM$zF&WlYcux%ZHqZwBzOijPuF@4gQ7HgKW-R4%eu=`SjSwL&2sYb2eqN z(5<3=?BZ@A>Sy2JqXk|5kLTi4p-u+2v|7xgXrb_75Zn4~2Y<;~g6F^H!N!rT6S!q^ z-J_a3aJ(3GQGr=}c*zgZgcvKiOe(c|V18=l{_>dPB27tw2ISXx#J2vGe$p!&|k1uEqc}R zpWIbkC--#s9t}k|0&2z#1V+Fp+mCuiXaUsX*Map0^oVZkz()ToZUp!u4L{J(mP}hD z3yzhSv=gDjKWlf!3JsGdYE`a%dA9sbjQ%Cc{lHWIn&Xk&J1fALabGHsIK5;)37(aA z;5Iqz>UKVK?W2iR?adTp6;!n~y?P?((=;sxpvAKPw^N zG(M`0ipkW);xB;K_d}r$|1!jK!(sm`FIKi90E0Tuc2&dqK}5VwT?#+c)ejCqdGy4w zylUNKV*{)wgbN|}M5iC6CNzcr@DNCvE1vyScc5-n6 zEIIBZ-uPD--DfL#Bi?FdF_7#=BXsR75^O;j5~UPd*&kI`O?C5|d4cwplSm?zlYnPT zNblQd^FzPMa(v~Nj+Hz zs-xL!>r(AjbaM%0e9guI&ZpMB7HI;bg@A`~P%60A9gl`-(QpuX7<)*GRpZlPVwUn0 zd<=pBLJu%$5;vznV^iLzET$J(d^VM{@yu1*;zWUofk}w=D&#f2W;y?EjnNtII9$6^ zA?773rdE|)IP21+d6aIJ&7B3AFeH|8ddgUMVO&uy1J<8rc-R5eQ?<81RZUjen*sOg z_Ps2}x~2;$qh&Izx-Sm~wRF!vq!-Jlkzu7)RtNSX(1h}3#N7nUmaaDIyX*;BZY@Nh zuMJ$04-8j-*fh}MRcm_&#NRP?aSWa6Ugsfg>XVkGI!q*~hQz?5&yEA#i`UP1u*KSx z&TjfI=SZV|g_X#VN^Kq-z}ub$H0|u-Rux6|XVt4_R#vy_RZ8SA*KFiY!PwhZ=Hsgn zoOwnSr#C#Zp1b&~=q@O{?*Cf0n=9tV>ZM*%6}}q+*Q;J*P`Kjryc&0In+&{0od!-R z+w&92Afb(hD?iSzy-!q76WI_%(>%tC=ZVnA)hlc`<3R!&(Jt5zr(p&UZ1Oggx(CCw+@9Z`3L4%TxEG=0#)v@o1`i{`l(A%_ zu+r#64S4_i%8rfLgaCd-otCY9b3u|1@T4g+x-^u?qRETM&RVGsn%)vcp!MOXNTRZ} zSKX|>+I4lUi3w1Nv0GrUe7x~yVp)#VY=d(FSnEe=v(SUFrW*sNw!dB<$}O;SDSYmJ zz?HYopB?Gcj@x8SOkLnSQLJx;(&?GZ^k?Be0(V#0@@L1a%j5)%cn}9Z4{Mbxm8VOk zb+)){UC{XQ^fUh3ee8yy?C^G0-RadjoZ6rSvvLHqJ(7XEoLf zTs-DsVeG;AGTVn}NL=bv!cL_brip=+gf>KMPIlhiQO>s z0?}iB&4d}L9}f~SZZ1rF=Bew;^5#9YEms(C^4qJL4#b6gn@u3J3_B&R_?3NJVLst1 z(eg~@thUna*9hn5P9zN%sJo&^xsY)zf!vL=36%?k4{7L-vAR&u`Ulw$NEG3FQ=0k( zOc2Z`Z3GJyYiS6AO_=QiLk1p&v`MP$W1^8>?}x@ll-0b$7^P2z^_v-N@r{TkpOwvS zwbfG@?&FLdS`8CXw>EC-|LU8#+$Smu#S>UzEejbw{%hou&dj7n0Pm3~4cF=64JH zsVaxT9Dv^7T8Z2oy%CS_$}=>BNzWHxbRTaEQbS;~+oOo%8K*%HZx?CiSNFlhnKmqV zbE9N2u|v;*xHU|OX|;rg>CV2L8(+Zq%$O7otuI^es{46`G{fXwdm?T^sBS$Dk1u&* zXO&Psa^vrA6!vWzNa9DPO1(B)wri(IeRkaXdb$z&caiZag_x&JX;wjgz}a$W*s$s) z6v||B8r3EIv#k(fbsc^Bc!zBpZ&j7s34lyedGrjt)wa-WRsaFf@edRYYc#_`e0Hbs zr_c#ydq?$=8W;Iqhs+Wr^OdfNJl(EBBuzjP*B7Hn&R|EZ(cw0?K*)~`Wbds++YwaE z_|QIKH*CU(9O%pfKI|*SesD8M5z~`Yj8dmGe_D6T4#L?E{pI35-G)8cJwz&#;cVQn z40+V2+Q%HzbtHy@cYa;GmhoXAQ%>xt%~?(M1;ZG!zX5=j_1)ML@+-$0)&b?z6*yZq zzV3X>?+VSP-e~-VDuI>jn*Y>uGG6B&$li2e=a_O=FH~*1`XdAug4=F(V<1mLJ+I8h zJM=z*lD=w$pTbOYE?)bTMwxHesSoDtB@{qX-W=MJK=`RMkp~&su2a~6leYlkvQPzW z7-)AZAl5bG)62MvE(P~f`+XAPeW35lhqAsatJX+X)j@nW44qZF2k-f#!cC>2-* zSSbpvxkwr%2W4wdgo#v7o7<^t1hGwX0hYxtB|zcqi(f37xy5)_C?*Z)Z<-;{FSghcqnVT5S+mQaxVbZF*ZyMR489fka?kas6e^cOtE_ zKK$=JkKQdGYV8|4A%x!T$7)(vU*;F$e4lX<9pm0tj_=q@C+J>XZ+A1^_fp&~@Euey zjWxq}Lb93|`p0&1Do!EW7YcTzf`k)!3oKGbj!moeDN8~WDl2xRHO{~^-P}X)gxXZ^ z+xNwl%y+b&4}yk?p{Pt!(=U8bkB)tFH0Q0JwZ4@AO5n#heqHuq!&MbyRG<1v9d=sT zBSEGKjG$gEIB-^37?b}fjCJ6ivP3Iheoqjx+NqKn z>#*WqJH)E1>2-uJiJWhRBu3Sll=cRgv3w$<>FlkEQ)bJsgk}2veYQP%iI^+aJ21r4 zt%{A)ot67x16}YcM)4$wtoi4Rl&PS66eJ0}oc5+U;pE_ug|WJ1LTCI|jg4!V*k0q2 z?U`b^LY2 z_&2C=0&kkbo66(Mc~kKyhK&B2pT`6n z=WE}=`q87dm-p2OmLkLF^m_eez#k!p@BO?+52UxEFAAyMPYOpIl zO5~67M#&~w$|FL|HYG`e@K;=TUI|JI6gF~kc=&aV^KELI55+1Pougr9AGDh4yUJ|J zodL3!K*CRxR-bf%Hktj*3E|XLMsWf7<;F4a&s2Jh%)XgdK`F#)e zZkvYG%Pk7?Y};pK_|pWkX@2JoB&zPDuC;dGB$Ef`=~aYGLcSiHInpr1jz8obIw67P zjT~c8c>D7n+{sz73G1_lf_7#REW&;AR8KcF7FW6DUZ@KRqlS;Bf*MhlJ+D}8E?dPK zN(?X$U?_;5ok@9bDI8L>lRW31|5|nSUdH}=hHtF8Ga#bi0van+@?;#EMas-<`o|51BJ~k1v_HG{`0~-r>F;KfW z`nJ2Sr1Qk<+9w6ZObvY9QVJP@??AwWQU_pg*XT~pp{+KqcS2~Cefj7_5QVH3e}Vrt zInmpDrPONY@X#>S)p-INKAD>wub{k;Pv`W!6S!&KUDa`5*tJ#Q<CO_@qd~<3Wl&$yxBbJY}vR=D-+wUOoR=rX60)CcGw_?*z4C%`h zj@heHOjr)=cOsM#Pqm=Cz^N)n-$w;CfrzIF?;`@7fCxj9+Gvfm)g5;3E$dSkgG%Z_ z&N2vj_0v#(s|UchPtPIdFgppyWTOi#kmvZjOa9`Y4ht|u3FBRunl74uLd!nhMTCuG zT6Q-su|8Hossp^=MC_5AK2E;TOVAyCT2kjb{=|DGTkhl9bh_8}FH3!8d=P}ImN-#w zjW3QZcs_8i)%4r<=k?ba1z+{UE#w=}A2G{gn+b2_2h3zQ@)ybDV?akcZ}#@I@W**| zN1I^N2HyuAhS}jml(8;b+6W?qW)N0013Wq>WPZ>jdgf^dl=`Y4`(v#RUp7cd*`V_F z$?ELI34|ZETZWTcyO%BHF4mQYp%%snx*rr<{T|23$)%S-8(K z@GxVq&!mdwL!5uCw3GBqmh0n>KOcS#TmMb2G@3>lJ_b7ClXkvFm&vN>`NLD(Zufc@ zkecTfGJCC5`!*(%ZWt$BcSa}etb(kkGNoJcVV>JzaKdJ`m-0jV!=8RD3TJXMT-{ci zR;8sBE1`5N)v0IdYhg!>LBb2S=Qs(4I|Z*^6?hfNhWSzQXYl;_bMnxw$?-0q5JIru ziQ!_1k0)N2^tqxkpqW!HT6{}Z3rN<8e0;h)J28j1Z4KFpvyclUr+S~lSC{E`k)Mhm zl#F+-kSdI}flyC_b(X6SH-|EtuUT&j5X9ia{hJ=g(qW`DY#DnJac_V89JaVZ<(}&U zTkdQsS+g&pz22O z>O|o|Pg&B_&^3Ko${3PPaGh37Do^l;cvEzs8>KWQp_ggTHLU5|Hh@)GZOO+Cw>$o| zAy+q<-*oMTj#$jku|6h!=~sZrHvVzd$_#s9L65|Eoz`MU>oG~EiUr$A7t1=2Y3Dje$UzbO`5R~9QkwS1&rxQDeF8|~it z7&AaksTeRSn}hl|%h_{Tc>&)HQ4Qt~Cwsa-q*6K4`V-*OI=0{JOM<*Z22E!tLhjSFoY z76?xh4mfNCS&vPr5(7Xv%i$5H4BXDu3H8Ef(T!stcjj4dOMb)lP#X_#8@@T7H~q zLEf#|k!MOx)G=NQb(aYwCs@v22DPi%d8f~|S4jZ*Il9fW-aNghX0}0|=2NKE>vMo$ z!b)Nu#E_MM?MsXRKiK}%eDsbIP^_4sv<)LG)p-uc`tUNgi|67LI7+Xzj-`9Y*~}Nn z)e%Uf=<;D=`Q#M~eOjSjxilxxwS-?ehh&~W2Fmb}NMp#7VIu6-8{n>=lAf^rPHqiC z3%q+QaL^zDfV1W-4;`P}!6hA(??QKFQ`xT~NERx|q>j8`B3bOxLk(HECLeY3}-z3*8!dOPcD zwaO?gi6<>}4_ zVgya|6XLl$!SavTxCG=P^9fc~RLXIoUiQtp0@W+pJPgn25&!E97eHVjP$;H&U|`)F z6CMy^6*vACg7N*}%r$p1(N-+BKQ1+_OMA;Zc(I{UkDTd?ViaTwemw1LsC%|mJdn3m z7Y7_}nH3h9%vvkyGZBxnr*WZapq8msdoBmAsX#sD9zv63Cu212fZpH(m<9#j4yc+% zMcgeT_}rA?%1AxIz}SBAox~gsZ7W1B=)gb{BiVjkA4i0!tw`%sF<0KYS5tCo( z&KPnm!~K;e{w3SiZOP}BUShji_@X2hQiEQJ-Hz;EfkyuIkfXtI#=ZUuvqc1_yOVnN z^OZ(x##o7WjZDs0@hTND<#9Qk3j3Ftj-==h1u@ys9Z2y$AjHd-Q`MKWa9NjKAdGcy zQhss1$pq0aGbFZuq@UOtw3L5*Uzc5JgBBq`gqrrbH5eW1Q^*{fFZ6U*`R&OgVNvQcMNdAn0_@>r)i^v5`B{in0=YFiK7NK19C zFmFD`1aY%+@dY$&`YMiArcP^|IU_hntO8luJ7;Ol>MqoS*QR+sA@4m$LaQplM+#nh}&a7Tojp*hPspG*8In6b{fu9Cm65$gQ< z)V*5FmQ;fyBZYS`cCDOnA13Sl8Y=lPPz?O?4*GS&x#+EVG5@up9h-_2Oa7JbYAR|X z+!4U|PB}3j8#JX8aukOIM~vNO)!=`RVI2D;MSmi)blqVe3Y`n zf`K&qV%A^2w(W{%4iRDHazR?b)%fC5xbrX3Z)fBnyI!?NHeU;8Fhj&JyY*Agmva+(kRcOB}Fy( zG;aBSzz&zIt8+$;Sf13mm&5Vb6(%;0zXe(jwJ04sv%nV+3BJ_^o07w? zT+0vVA{CHZ$Lqw_4BkG*G)-U8h0-pWLqPm(tS&3e#+r`RWNNrmSnDZ5>Hwk94e9>@ ziBz$;2W7Soo*ltk5x*Wj z3MJkeJwWiJOPIm?7eHO`vEIYwpeouPSZ_n?p}nwactPXj&O)KZJYDrN!+n^|wxq47 zShUXZP2(*jt@X#>l~=qwPvS>X*{swLDoj{vjBfLEH#6}dwlq5RJmPAV{i#Ibm=M}b z&0$i7A~|+bEW7pdlR29&=9_n5Z3B(dy-hS5@zp6H5PNC?n?(0A^N+fF>fDiC%)TdD zLE6`A<1y|Xp@8_hn$?rAh`TfGW3J(i^AiG*A%P{3=F#uaaHXI;;_>NFbP9Gzp>k`~ z+skV2m6Y3j2rW&s@4~ytc1-fc0>K$1wcvGrmRW2g{j&WFVr+z#681l z_ea07@`48jmsA6_nZle`E;rV>8}|9r+t18|Wu{d9xpya}o!DoTv31(jMXII7ax15Agjq5+UZ!hJ zVjWOeq~9K`e*74z0?C1E#=&1(4*as8hqMm@MV3abQ9_%KrP1}M;Qt|ZGQQj{n`{=i z$RDAx-K|d%+bT(`D*Gq}qtR*F6*|MqO7GO8RQnJ3bo&@~MeopiK3S-|O$47K;pH@% zI>^Q}tyOCZe$qG8MEI2NQoeoXyGzc{}BrApVLv(Qav z>IEH#9wt$q2~AiNB0<5R@FKOHt*rWw#=;4ey_lg-H;GPQ{~``R_JUBwpD=F z?$kxXf|jao44SR5sx0edRfccp($y1%>oU%`5Tom*37kuav9in29o+=(iY+_!nLt=l zrJny;){1F0^Rok|)*a)y+}zY~kd|dgF-n}}c0t|;!itztN>G>4&oue;D`Q>y(1TcC zn-1Y~{X^936hlASV@=NHpcQ{2+S}w{QcX!JN7Vg_b6{(3iXjrMg9bueQ121^E`zu< zi7D}*bb(agp{|LY4FlKSI=KuvH{CG%GZ|uI9r__w>!VqA<&R;C3Sv;Z0v;5V!1`3k?~132>AKN)=;Se4*X^=5WVcK87$5Q6 z2i0cjJUxw|u*@4TJc%vW(}hIrcoB`^`T1c;oA^{v1DzL+IqW^NGTN(}=l4$Pr`kQR z*I&`zS<~i<2!z|26pC8mp?&Cn7UOdx|I3_jwZics-A_XNZ*Q4sI*#akPH+>#-3*7t zp4slaJi$!{1=-ncLI&p$vJjaZJZ-N=7V8eoNH~}nW%rPEzHl*0OHi^q_eScj#FGO8 z=;Jc;F~~*rHgx=%!yB1LTTSEd@$JC$D`|HMgDSj*cj3)J{_kjb1S@oe#xdyvSe5`z zTQ>)6`@orn%RCd?>g zyHK@f8LklD!o7UkVQbT0Z1LtsC&b z>n~16-~M(*2^e^IX1wR3v)*%9jWnxQBg$}}hN7!6O>&e+*d4Kc;YiQ=4{tGxQ^clj zht_>=;m~%AOTBsn)7JJ}Hghhzt-00Pkx5BAB@0LtDpj0UQ|UZJR+N<9?JxM#ml{xo z-AF3kj#R2mWk3Hxa_VAKx(0N%xX_4PqAZ_)3?l?dqDW58l=SW$;s_g=>}wb@A^##T z%2G7kzhvKM`RmRI8^jYz<(xNbxWowL>DIwAZ>yuEo;8~8C|y?qb)A2nHe7cA;;{?#&9Aq@Ht<5gN$y4 z{Vw(m#>hSJiBc&vsBWcqs1p9F)7N?1Y^bMqXeTYFzCiOy%)`jFC2bA$*m6x51LC38 z6s{9z)V_J$B}m-cd1+~ODf+R0w60yhpj8#v(c;=Um^8oM6`~kjv!JF{sY^X??k$?! zm(HQ@EO{uYfR&8mi+!NDTm@y~&+#KW&=s^oH!@Viohs`FU1|-Q@%?Tekpmv9WB$gh z#1Ck%)$9ee3YW2A{Po()xbX?)^IL=ZGQG;H?A!jADAF+G_Z;MGQU=lX@XO1Tc!@giEArBR*q<9e0G@l#6gA7T6f4$GpMk3vQjLG-PL_7|46TNU}?1xDt z_@kWj$I*l`g+e#igL&Rex89)h5Eck&!AyzlU4Tme*kLgX>@%R4KLA=E5*%@nytokC(R(YzC zVL<=zVe0zafG%JWJCp>u`7dQyzb{rX>>H}54-fmZ?>1zrx(;*);uXbh#&_D0^=ui_lz1My!CF!edS z`R(Xn+CH{Q;j=SWuc;#<`j%6}fMJtvP~D=R3p$+yvl5GXq=GyTfgZeigK?MUZ$>3R zfk%x*O%x%ayfcB_Bpknw^){J#=sVPNLyCwYuLap4=Ee?lf3^p3jf+H*Jx@*F2`FS{ZM?9R5=#;rO0itHC$? zVpRBdVY`s-q?z*9l-glQ6gv=JWY~28@{>SQ-k~_hgndY9biH?Z%u|LaTSc91H|~;P zBbj-=w5BITL%T(O0|tGi6Vy4L%+T2P{kWjiRX5*+LlH+*ZxQotz|PWB&$wJoCG(H? zhD8;x2_kKdAX&%Sq~4qhp0VS}H$oE!yD)i~A|T@l;L;c1xuvfrayOmj#?(UeN08i$-ZtH!aI(TvRvQUv^MW7_G2e1wlG-kN5aYPV{1gKW}kScMcsg3 z8oQC}9tkdAX5Kn4@J=jJEqrZ)hA7h9wi_exo!mG&^4cUziYi!d`gDJNKjB4Z6=vvp zv;TtjJGcI%np=+cL2?J9E<}gF(rG?_QE;p_1voMsw$gJ)ZTGv0r(7WtjFdwiulO3`>b?G~C&8gW0&_9&<#OfXleO4+HQJEn zOYfo|2>ypHO%+)&Oc6L_1E8=Y@4a0bC|+$ftg}0u#VfFI@q09U3zwUmB~LC^*n!-e zZgL|9ZHMMmb&Ef7JqEspiH3;^isQO&=0@H3<*t?#RprZmJ>x;X@g>e({2LQv2wqXN z&ReiaOSUpZF=_`cHuFfa{<{9fLJ^lit)Oc9lVX>3bE7uRRCxnNv)LEfR#GKvuS1T=9?yNG==VZ5NX;~aKqbI z!Y#0ytnqv1y0h3_y<0$K^%Ohpv7L`JmB}5gig&-n1E@Hes`&E6QWdDG*9AX|7jgz= zqqGRiJ)a=;NVV_bg>1eURr6G_m%i2>D}~4a3@3p^92&eiQ+Z!LH@|Tr9N>A$tgSwiToLn*frl8^+;4yP^&JUqr^7o-Z`CI z+TU_JRk3j;*Fp^f82@#VJjSo5r#PI@C_rp^il|!fdvJRI#J~HPwa8gghMz3-2GA+) z&+psYL9ZdMcmolQLUB&>x{fe??%9-t8<=3>iZT6=`!ZMQ(Q!Coiroica(afa0qe`t z83_=&kPFKUo3{1Mej$hY;3Z~rX0wY2s-ANp5k3qj1U$UuNZW$S zXPlu8V(j^C(C1TRa*xm{>k>!h%4`wqyO$@N_BTydK9)zpBy{Hf))51%EJyW1 zoU&{@MJ9%{m(#pH%6e94^_Hlry$7-$SFC}+REX-rz2-hz_4 zQg=Fc8*Mlp5(U&k9h^O7oEI+_s?Eq2#4gra*`>HC@AG2>$ss)_ z+rG+`6Q~ug$Vj1`8VuI#x@zRF)7=0+7N6O(_*v*-hS4!@rfw;_jDQ9W(}vf@ytcBD zZFD)UKvwPY-n}J0z%+2lBJcr*f2zlhOX6|VwXa&ZchXK!#U*rra)VZo$V6TEFumW9 z9Cj5C_InH?=mNwGef=MJ390hze(o7skXXRsM}Ot?NM@_rVfgK%uHnPnHd0lSCbkD; zlI6z0-NgL-?S|=*_vpmvnOYL};#e1BK1@4oO~<279n?<$p^nCAQE=G$;18Fc8S%|< z%k`skjDs#p*9lxF7ByU(6KGm2*X#oy5*mfE`R!EH8|DMf{pYt<}zLogFrc#hZU-vAr_i2>wTP? zZ&_&BRmx^UrB-DsQtVm-%2Zo1q854B``(nTK?Zcx<4;76yI#{}wpZ*(NGjux z0zdco^@U6P+f}+mwQ0Nh%}xPBw+DEx)FOoLgexCon+V2YZxRd( zl7h^%ck_#UpZPPfK1IaZRdjpq{hefC8xodY;*2jM#f0-)ca4ZKy)L6j1yZSuo^Ebc z@%sq{C^vhs*BcQfmTMUJt&be9?YW0*s5^^qLfod-w;1>j`+3fL1}oG?+0Ly{&i?%y ztFRc3C5p?sum9?+8L&N?fi6yjlak|d)VQIimlJhVwVJ6v`>g_Rh_f1VPE=(Uw)0Ey zs(Z(y>|D}uBw z&nw;$o1Hj2!NYyHa(?7l(v|ECc351tv^MXJlCs`k+g1Tn;pTEr&UKfSO@tihc&~%G zXV_tIJ;;95*9YB{AEM*Tuzph}=$(Kad*mnHnG!fEwI0ue ztc_&t8ep(XF7Mi zc1JYc%(@ilo02!v3Qpq&EED!&tGDH!Pu;P5e^p6G@e7@PriT`p<7nXGFt!G?66lsj z>i8&%n4^6E`m2tvL5|Q-4Qgetzu!{n<jX$DbRnTRabW+l!it~=LyQT zXsg%o2UhbRBvzV=G;%KnaQIpY;xAj6^rcSEm(04@Iq#Lnep<=iKkm!j*?0%%lY?TG z301~WW|psU26v&rSF+u;oz45whJ7a%Nb2scPRzTk_zoo$=;#ogjt?S`72v z>!-+Q5iQo8-FYGI?o~y0@nbr6h}S{R3t>#J@8;GiwW`)7<84PDggQB3y#Uec^D52l z&g<64Ir9EKbZYOlluLOf?7o)MyHB!{o2o9gRaQ6@ney*Zdxq;YX0}WAbBaYgPM1hS z=Nd{k)ko>vEj!Voq67$3yj-uCcRA>dTC`m>A*I z^!aNl^5URHBxX*x35YoOnVvd2rbJtJ7CQf7t)8Omyho1f;Eue=PgkD%&i}f8BT#>| z>tsL+N?E~LvqD)r@CdqUSN1I7{L-}ux!yZNpDFkxq^$bojB!yM+sCXQu2Xp)^Jg5m3+Ti)rQ{{l&Zw5`dHs5&QM{l;ozkY3_m*e)g&Vyb9e8N zOghiEgYZvSVg3yU&U<}01lZEhPBQWa|MGI+lR>N0v3-|lDGd|&_y5!0oBu=INB_f2 zgc7+FvPb1AdoIfuhElXBl|tDgOP0yL&m^VAUP4)>C{tMn*%`ZGm`Tdc*aw4Q24l>a zx!+yi?|psm&;1wN_v67&^YEC*d*1K!e!b4?d7kH-=Lx{zexpAJx|#@^06i`V>Fov+ zYTZF1ALY_sXD{Ov3-X93nZF?9$+<%v>^@IyV5?d;Dpzsn)}l{J*mx!N`z1a~t}6;k z+M*P?){zLoH>qP34%;9tQPeJD<#g=hOW*E3B)wMX)UguJ@2h#CI;AdAY}k^Du0u*eCHiE zzPJpEulLW7)EbHMmW_U;CKy7>V!T^_7J$0q3mdxF6(sLgce#5hw-$dHph5lJUlX#l z?x-y|?KoI1VH?cCkvPcqwnJHMgcAq{v}-$oD|dQ-fhpIdfW`9e{SGRPH+V}L&&yTb zKg3x@cxQ^sG-sXvc`?8DR%IBB+!^PlKm1yiv_Yu}?>uPaHuw)pcMjffpZdlnv!ARO zchk_@z@B5lyv68&<-! zTmZD{w8&dqqj+y89J~)`Eb_JbP>?veh`!HG>zS<{E|8o+^z?1Ybt=5Ax$^*ni?!kL zC4PUm>4$U2i+pC)>>JqP%&4g=mn<4l$Ez=K{j6eiA}TdT|Hd6iWPnkIa7@=;s62 zV~B^^!0IP?k54fdZ~Z=v+Hf@`Fete>I%=S;B1tG13^;qlBZhg8&q|d!2cN#e zDM}O*hWtH1!(NX&|F~)OpsS8G{CW`j1#G`)K=?%BlbSk5lO*HEO<9kV@L&PNU-Ccd zn=bVPjGM`LlabvZ??P8Y`Q2V$4;@P6g zJ~xV=8|pZ6G&4YZ5SngiUGjH9z@ZmxWGz8ENFQ`nJ(*%zqZ;5=5Cj-vvYyLWoQkiJ z2HMxsTnY-bq96R79QAZ;@vK=q(_Y7PiSs!{f#`3x)+yu4YH&N$sS4AvaoWA6Lrn@;t5^;w!6JJh6A=jq?0SuhqKQB0Vef z*|l*a`byEhz;o4Zh6ICnc1|8PY(`6{N++dG!PCjPG~zVqaX?}XcKl8z08w0(9+~MT z41>os271lx_g}mx*LQ_kq{%zGbV+74q{~gl@jJ~&o*8A2H*>P#HJv_0ZW%i8=vzz` z&Ct5DB^MH|C1-Ve@R}1Bux6Ddjrr3rJT@K$4I92>BUB^@IFNYhU1Z|9w^lIUTN^t? zqxvzjU0%+$JL1dU6mJ|XjMpvnQpl(R zmxb@xceo@v6p&{(Mt?H+m(88PW0}i6O?1G5`$1t4EwROxnf2e4GO&K9Okv7E)k8Nk z74hiIRe7QzDVp--A}zv8i`q|mO{gO0j$2nqZhkc=FFwY#6w+n2BA+uzd8jWJ-6$~Y zb%uzm3pFGK=CDFnIgrc5(CimYO#mSvXl_}37{rryPabkmW`pAbo7sXr^gmJ^CskGN zJbrgES2#y(cop|K@aCYjYR}^wZLTz2xw&EbJ$a%9O2O6#w{Z*EH6tqj<1;WbBz*^e z?cWY3$-@$4r_A;qy~UL>4dmZ9_H}&wk=^t@!}M1sv{LX%xR%Jx%q98Eg1UhqHxSMO za8HcyHIkw>l_2n~g+{kw9n35B6yq%n{l!-a`_u#eaZ$YI*&J8I;x{G)@wt9ci1FvnU7IhF)ous1hca5uDWANLd2#Y(xLzp<-4 zj2`fD?B2b?So7Jcx@o}9+Q$s|bEJjPFP5@80I4OnJFP*REszJ$D;Uk*m%u z4Y%I|7a2PKe%>SxBt8WF$<;a_93C%j0nsLXMqq&YBe=4YTmy7RG ziy*Oc?b%b;^QUMrJZ0BfW^pIHTa%MrPwj7Mq_`d1*P&B+PG@K`iuk~`tf=9)YA@5F z@0`x-Z*{+;@I|A9%THau1`@uaRvYv;jlLHyv4a(!b0{z5q6*hFq?8e)NnUE5n{7qd zU}rZ!*Fe1Ly~B!Fs}8Fg2Fz;E44y+HF02y9g05vJ8FN76oVamBwmmmD=-_R6y0RQ) zYJUAkwO<^NTVO3!HRS^lsM0$sMLK3eY9kGAmwXS4Po71NXZ$pY3cB z!E#L|u8i|>c`0z|DV#4h;QTFj7h__jqE-+-q#qO!KmT4jX!0hU_Qlo`1bUqR9@qYt zyoOiw0&;e#1MFH)cJT>J9*A%6bs1DzU5LC}qByWYBXrqAOz(Wp>wsYU&G&Qm4XCL- zFIjHft!f*H0nS?CG9h+21XN=@-XRNmET}6N=&_$`oWtyUKz8M0VYk%?ssVM-)iIG{ z3=dlHwWUa&E#)7(m?37v5yT%A2Zkti^j(%wICEf?qTvtU5GgPX+4h*IkP+4GjFPVM z9UaGEW1vg=@uZ%&+j3#Zlj!r|bn7bT(PD`{QIps@NXYJ+xK?iBYRaCAHo!Q!vT=_b|WQv2xl z49Z%iXwH3TLtd^b*zg0q2NF5y!Bmoyc@rC<=Q~ybIp46luR-?PQmWArY$2%wsFK+U zTi#9Ept{!MItUsoZOkoNSfgqqE`e*I?0dG?abWF=`I8GE(a%mDj(SFps155undOBB zycsWfi+1|8{+y)1H!eHVCtR(XvtMnCzuItqV?Ii4HqR z2EVX)Z?X=X_+`Ph_JygUN>7+t&wCG^3t_w;qOA3a1aA1bei-c3KO6PyH&|c;&>~%^ zkc-3M&=+1L$WR1+| zWiz$Y!mA%wL}$N2P6bKCQnNJ?;@|bcU+7-o`qAmD22LPr_JLPjq*@rFyM4-}BQ-8A z+Z?~=L2_KK+mBo-3N+|uD^u1y^Oj9M{gF%IKi zZmV=_^7iJgxzjm;YS_ISy+vg_VwJ`dc4LQIV|Ck?rtIZvZiTC*rCHoHUBr4GBIxBK z!FhvY=mPI?PmyZqghfP-nA&Lq#5YV1rC!xJoiy0#9;To7a3}O$mduD?`@OpZ-Z#zj z78ToY^6saIFTtbkE)`Ki;*^=~#pf!@bg4d1F6A_}GX#W(QGb|kqK2;c!qNAe>&3zN zXF#2R&5NR9JdfCyKX)XlD+&HpN(R{RESL0zU%3zdEhE!5(8`hA^t1CHoyDpZ4m~HC z$IMy0+&JQQ&o}VdOW^wFm02&2-JKED z1UbEj<*B6E8aaeV7+Kbw#YSC^2Mvi(I5+h>VrYGI$6ME5eSH`uZQbFKGd2)VONoIN z_kjiCUq9w@O1)=k&wp6&18|Stvg&g`5ABY}!rURPDNCSYy`(?u&fq~2`O+V}r(Aj#Ltu%$tr2>%V#;$yj4z*Kz$J!R zaJ)qg#5bBRps9|cn_ATORGg?U!X(fiQNiinWZsYt_XTZ>dpB1b%8ZzlF+I&9JR%kz z;!QynY$cCLmK)`IV!rxIup6vZ)#yndSM23NI>sn6V35;(CH!~0vet}q#&VsaNp|;g zj#_e4Yt5#x#+2&qs>Nr*6nA|KNw_*lhQ=|Q8vi6z#2?7%>TzA5|EvyZ zxZmnpb29(&hTrUW_PnTdTR*i=f;Tyi(=h!B?QbJ&X!cbz zC_%TK=_DEEfcsUP-E>2MpDXqo*iMieL{rrWqGebdX6jj~+un9yY@?46H@5tWm=O11 z9ytkGrXxFpZK@SX8(BQWO>~rDv3q#RGPOipiiz8WZ%~yTW8-tXB9e`n{&aK>D?2ZU8nj=ZMcDUnJEk>2%#=XGQ}Evwg@3D4 z)-O9-{e!eZ2LHx~?ie*x5;C*u?i04hG(V6POV+@e)9$B6$AY2~z<(|JX`*(Y@}Jq! zD}M2th#F1}sjJWG4jKr8M95qY8gq`rkrjBbe|QW3jTUvohhbsO>$|))mqWr?wg%aK z2C9;EZlG3RJe|Zqnx8!ne@|vpnS@tphxYmrG>Y#M_`oDRr}FI5;6%cV6V}7rd0SZ1 zy`p3WqT7CW4T7rTi*y{+yF%&()74*rIk3q2ZxaP?M=|z?<3iOk=f|&J=N=Nf>)7bH zqn8uHD)SFjDb+2EYO~kmnf#3Y7Ii&gX$R{x`z@hndvv2EtDywuIeRp{YeCxI8om-=`WWX;gs> zBj*S{y!Dbmfb4cAS+_2|Q{fhAvqH+)fkH6roFusjc{)a<5`L-R-0f26OR&)SZvPVW zJ7NW4K8v{nK{-GjPEl``x=iqv(KyQJfmYj`s@lOYj^)wa>7x)_z69Rr4b&xZyNeJZ z*NH|88T)d;@yZ?UN^9u#4UV|%2w@FSH@V%hSuRMWjw;a;I7@GL6K1>VmTX(Om{nGKCO?XGEi#`XXDP8`;QtSy!>3$|_a`X`q6?G;?(EK16_a)Z4trQR z8CYp?SPy0z>=8l{QT-N0XN2pm@gl1$XWas)1(o3|e(P-osJ9&2{eW$br(ZuZPfwbr zlx$I0)ye;k_~NT9Y`5k&n!;dZd)1a?6|^JRuh)+msrHmk=Gr`{FPY&ad=)KF)F69} z)<*q~IMWkhAAjNAQN`CgdS_FICCFMmZU>AVU{G!L{io4U>R)!6zQ^H4k(hjG$$|x8 zW(ecgrBqD(k7-YIvNO`m0Y_4oL2d=+Kudnt2*ke!j-6lkZbtzUL21Up@|Z{lzFF)- z|IR_$jfIQ|RW$@V>rM>nUF$FKkoBDaW|dO=p&7Z3ehxIL-(|_vmwTu)c@9b*pR1u= zcH3!O99G65IKy{L`WS>AWYq2j279NAAK{jtLgodI%c!6u7PZ9J&40i0F{yw>#)c_% zh6_c-hLPIO7anWFZ(%*T7;rsIX9_SLmEFdtavB!Vyfn0>DTb5&X{-wwd$TUwD>V}k z<=?YQVPeM({B_gc!E>06z9c1$0|3sx>2Wa#5V7h24L8^5>%ZnG#maAmu`-{lDwjkp zy$T)CXG%>m8rfNN#O?Uop=6qMx=HwuawKIZYL*F!UAnw9F*vYOQXZ3R!u|t*=Nd5e zl`N~}pb0|xRMgc*&J&rwn)eKntLJTnL9XiKm6Bv~sC{Hd(#wl8vCdP3ti(sR1a}cG z1m_8`$bxi_aZV&`{(P_;^Bh94W(#g)sGc8Qf3i{0y5q$w=Y!n-LkPG(dwaM5SAZb>!c1`YPnqM|m{z#}~y-FkTavGJHl)LM&7gZhunT2!%$dc^Xpb0n`J z$&<+U6UwobY=WbWy=?Hdpj-pq?}1t(@1%eG!W>_}SH_H&NnKIf=Uj%PuXIy@ll1UIhMH~2iZ#sqYSQl0bT&0RWh%{ zE?p3;Kdj(9^m7m07K&zl&meOa(~DJ0I9+dJbdXhV@YH8SdV@^Nb(S|N&K<>!&!Hy} zrnX?=1K*nar=aH_M^ZVDO~ld>J0V`(imT(WjC2zWY(g3mChfXE1;GJM8=Sk^V#tZX z2^g8wF_0t|+BZU77+U&HpwL($CkTkhicKaw3@B|A*cE0X{ddk6Vgl|Iayv6Ir$vlJ z7p#oAeiT)ei^ywS0CM~9ET9|)OQyl`_p3|X{^;-47tB;(znBa1%L&PreM3ytCrP}^&V!&AVadP6Y8~1@NAhU%NwK!xIxZ!*{FqSDnxTYdE2(9 z4`=0g3i3p$Z7Z9y`&k>PcszBE*qQEfoFJVB74Q6ciHCC%l^RcOk7NM5flro%%F#1L7#q_Cy$Ec3@r%RD=XPg-^4 zEg*3suK}E*U$H2WbOXTzJ zKDmi;VYt>uvn+~Ko??bYf!St8!4IWT4M+wfKyBx(5$VFH-OBE{z8nkvhj$t8GD=t# zdFeuP{p^TJ*#1C9#kP}G0YNFjx=plGZcP8?^;!g`3d+%pbtWQubE(fA$M5;R3@*8I&AB(v9$=v_Vse^`X!!^!)zH zj%zcng)NO91GW!(XP?j(J$&g~BcW%q;?jy_U*orSGSpd(lvRmEMlM4`1!db4{DL{L zl9hMjtoc2uk`T-giMfzL+x=oQq%KZ}u-Dp6`imnRX>54ZZ<)GozUf6@_$iEHDpZ)+vK>=R#?3BL05uC-6Rz4`P$WSvCM z#Z1@6bPJZ&0$2@7EYb5UtXByRwHc*7DPor4v>&jiAft*Ovf|!T$b#@BuINR8&&$vI z1b|IN)nu7`3kL%Sw#k@Y_F>(*)<>+{2GN|^uIGSYL0v<=lCLBz?qc!0GC#J4T|;fM zOmwhf%Zo)vgc}@_&7Vo7Ri%^fcgEg~+Cf^dh2!u$d@k6v29lF=Wu%dN@*A1LZBBkt zNLG1NY162k!AVE4#qO_Yr!g;Nz5n6ljYDtZ9yTiF4@FX6uTxiHFm+;r&-$rc@pOxq z<>PK=&xaE{k_;^n>1~dY3nFp2tJ7aOC8lPN)FvzgJU7?FhPo76-vq#h?n0&VYT%0^ zty%}1x>Z9g!mcfU^P|3WiP>T=Je!HRc@V7a;#sdAaJ$L81;fH)la)f+u_>BKh^C*^ z*P`j^;-;mk#FMSzk*8aV%ZtZ7&kUC8B$MxHfLepntDJ`Jq;mb~@RTa4tE2#RFcA@q zI-3=^O=5jYM5~rnhwL;CXH+zL6DlS1Y_sN(mEBrdwR6^GzNws`Zq!A{41)Uo`YXdi zN5=Ni7}RSK8!5a}IAePHu zB1$bdC^g;ZuN(u(u3%jUy3+F#vw#qc5c!~NZGBeQI6fn>qC88~>!_U^h1sil-}$V5 z==x2W$boX(bR#O3h`SDe4&>Aww0Wl{x`}9E@aS%sgNn{}Pw2FPiee}=9F;iMJ0%5Z zI^sNX+CS3p{PAV%RvY?1SdIC&Nm2j>l7aST_lm}doOU;TzWwS^SBeKX5>N@kjG{&& zehjRKpx{O^g(fpWiAh|Go@iUM>!dJqL55rkzruCH8Dg*iVzQ zGR{z9{jqMnE&kcHO29Ft#)}61BwmI20V@ z;@uUhK?a*Q3>YHtk4X`IU)kwHswc#4rRQD3A{~uR=Zuh&?CY%4r1BC<#;ObKWXZS3 zDo&3WwAO-sOxr!!k0 z(v{hfC*;?f(P~nz{w}Ed z8v94c#n!y;uLJlilc|)1bgklHFv$n$+>#Cy69kviX#d2UH4?QpaB_Kpg-O{4(482@ zc$Z7qCq+v9k2R9Z#O}?$bG`tTpj4ub0GObKr0JBn(8d+pQNDf|Q#5?(!Uyf>K32YG z(NQ~fZjj0G5_- zH>8&g2F)60eQfVLHxJ#J#`K>Q_isLDpb* z{SQsVEbS3;wR9 z9#aodAqfaM<#1%cE6Tg6SL&b6RL~Yua060%JW9&U-yhRnACaDk;Tow=$+&bjbEY!urh! zN33x+Qw?&uuTspM>UP-|qc z7*z|tlKo~bb;#jIUqVxz;t$-jL;di^B-X&tQN<7J6O}g`w1X}DHk3+_i|Mdt`lpFM z`L~4J`bO9fCwq8IEkDGRO!qytR0i>wTFXP^9BULR=kHZT1uBC-X#t(4uG{57PoETg&Ob`kIQj--gZfb^f(deIvJ;o<`OUA}Pcw!eu=^?Zh7bewOd zwD*IN2OrW8f*iz1yR;0`*#!&Wb)t*xD&Pv0mCF>#tan_|ascPRs(wj44kSvQzAPb< z!mz|w7q_Ix1z2HY8pQ``V}qmUM?7jZQPzwYb)RF)xBA-OwDd^$zIw(L&=&2&2J+u= zx`+T7vn1u%&7on4Ap+^WuvX*Wh-j|pXrQh` zDjHCqWFE$%2(~VFc8NwoS8KreJbLqvlaBXu79#L*HMs|!a2Up*H%YLnBh9@8kT3M7 z&5y^}E7#Uir59-*LkR+mc?*LoJA=)#^|0)pejf@5_BYteUZdSruveN3iFh708>3_W z3zcHo@pvqdb&KbT)Nyb7tN|ofF3Tg&JO0&r3BkE)dsXfFe1=i<7*!o65sFuTuG=jzZ1rcGxa)c@1D)$$yfwjw_nAQMxqKhMH2HhdmH;9AuJlFvwQ}#Tce78=6qVloHN1v1s~5dj(x~k{8cnpke(!5S-3oN zt7H05i0`t_`c{BBZ^l*5`hpk9S4g*0@?ca5NDJHW&yz7Co0aqZ;(S?-19=s6?8~l0 z{QEi$kYRuIs_zVjU+@hOS7+R7{SF}BR1o;Yt{kSkq25Hv89z#z4?RQYLY_U zi9fo{jfeR6t6LHZJKqT2r-`^JqF>CajRXih21Wh_*AVBIdJ;?!S(-r)kmN3q)^=

vs!RvX|#I-YEB8U+i?b_QdGb3U_(=Qig3bCVo+tVu+~2bH>WTwLvrm zAfOYsQor)J>5G7?(sA+qJJAD9pan&|-H$aKtb_Vs<*B zbt6@y>U3TBj4NZD|CGNysB@VI-{6_cO6R{7%aX45w0ErJ6`X;dhO z2;EUyb6T3E4DkBjtajwI%~Kf?3iS{9L&CAn(~e2b(VZ)ncwsc2=coO?PoL47msXp(#J=2ojw;DK~K&->5bc#UHy% zDX9$ek$dq~fTNwyl;A5h`YgxUwZqchBFAnOV_fJzO6F3se+hE4t)FsXXcrZs@Oo_& z9RhKw?2+rZ^s4Fs$%ySN-60~^U-0r4j~E~y?lko${Ve||8Dx39DRUa|y}KHzf#DxW z;|6yfcKWV87*7y285hWM@JW52XdIl&nW~qW8#HSCY8TO2{PFIQRpY^&?ADN+h6dKf z9?#%kylSHwcOBMWUYnyzaBEanIKXYLxN}4R=-!SF{fASsoiP;s=;vfGwk_TBKId_k}c5d^=E(3BMc2E{Ruq zrYyO1Vi{w~{&eb*3dg|Ag2@j%SAW{>)l8Be|@mw|(=NV-~xP zn_pE=LSj`*XvxQo6RpA=y?wS`Wf2>@#G)Ci*uQ7(i4zVEn~S{Qk72u7z^n-7+FU?*qB8=mOvz9I;OEL*zj#u*p~Vfu(~?^{kPPyuq{V(CzrIig z6Gr)4d|Y4C=CF5$H3R8mPkTwy)}%9zq7$ZIP|u5um2J&en+C}iLL7E~iH~afg#Pdb zHgVr?O-#|*wJv=(TMP^BG8cj@%N>JlzW8*P?;FdItzy!EiA#~?9IiX*JtC`?vqLn& zBL4EQ1}Gul(HegsZ+pXOY5&>hMwlHf+isNlG4+y&k#7GMkwX@*vtw!V8H!Qe3`jSz z{o4=S&S{vh7%F<;QOdG(q>flHi@08~@nmS+*>5Asw4paZr1hoEydtV13%+?FtX0c8 z4JE6+Q+2cEJe%0@SndS+iouPv9%Uri%2Kb3 zvhH&3Kln8uxehVQWM)a7+J6wiUdrq2_R6Wwl@7vsL=^)vF0@9YM*zBUH(#mV!grM^ zNY=g0mY$zheBGaRcDZ^AZ$B4=)@gd~65T|6l_F;pF;>>RkYA(MS=!K{3uJyOLRRCG zTX&<4Vk$bteCfx}74tmHgU4-s`>_Q#anIhG7@L`ULG_PHjUtd`X(M!)kwMTBdo zku^6WL{Qie;wxFMBNqFx%1}6Pu10?3F3^j-&N1zY#uyG%*@$H~KdaAnDGoO}md8yM zSQl3ty$hf5i+~Q&?SF5P^oe|sTnA**4T`-qq4ls%KADZ{6cNFSFO`zX0h1rcOsxHzm*GNp@kG`iLekG+FGcdwVW)U*{=x`Q?GtNu$=swId0Wb+hAYb&+H`Jy#rSkdq}2Y-AU@KrhMwk zg55Q`=Taoxce+U1ra3BPW3BuRlGI)5efgh<#gj;k?_ymwt}<(-=4Yt;Q}P>4U>d|w z?l~+Spr+rV+B}eEaY5fBU{s6GHqr;BaR8*^sd?){<$TiN}HX0?U3 z8{|1w9mPp|NhP4$M?NZxz2)nC^T|vWU6FM-J9dWBP%SMJ-6T>VCTzNFzM@lFGB`F! znPaW4Xbx&Ym|<^1?=1VUcZX;@e10*ZVS$JJ!_TwB)L3(;*BW_7Sq*~8X+ow-Ra-@) z`5_KLSA<)T3r_Bb*TyqX;X-mvwu}gOZwn>0gXI1qBV0Q*5Yke}b(S3w0v+^nIWZAv zlGhM)cD5^Qj4|vWhg^wVis%zI8nte7hg6ZqWTS0@x9BR<3k{`J#AdeszMokp$j$+Y z@gS@CQoY@!?3fggSzB&Nr&~m4$hIQiiVXLUk&AQCzKH^bjqF8wXpW2fi9_I5%asd5 z#KU>3#UrJtex#yu$t!1eRXes>@I+)fDh&WY4ZtP^BC$2cx}zBrQjbMZivm5xw;M6} zEsrCg)i*O80;LDqf4wp1^BUiF}NxY;G7l-F>Y* zB%&md-jWm2o#Pc>val_i5!W#DYg5BaVoI~zwj3~} zfOW%|YqIE1q2X~ctW;O~u-_0lSOc=6+sRsy(tTUzWnFk#WYDO3GmbMHaBw^Cz_sxU zZhR+_q^a2${9rG#mo_mojSvVtU0Om2^2Jm_=RxCTVCdlbIlR3%;FQCB_;$QWoe^k4 zaQZd9ynt)GpmknW2}0e>WlTbbPNsZAB#|5?S-smx79f}s%2T;cj`YdiJ>n09;6S-& zQ{d-RMSz1z2C|Hf6J|oDfuwOH@M7nA_an)L?HKqc&ZcW*c#GW<& z|6~n)F`Ke4Gol@fRJp;6hFMk2_xg$exAUjN=5^E0G_`^6|Bg(Fe#fukSol{BI}hnPSfrd${6XtQEZnvHr!zf3WSIDfUdU7lZuS z347t#e>-8%6nm!FqZR&TVD@6^|6uc;DfUdUM=Sg}8TMl7|8~NjDfUdUM=R{n3VTVc zy#VsRl;xf&_Dr#tm;PV5`n{;>KY+AnivO>fqUmSZrTsVDZ$0^+btV6tIlRjre?Xu9 z92dM&`>F3twfFx`i2ZNJ_CcXE;^s@vXwV#o*Q-~Pd;8XaB^U5_`Qr5p#pj)#{9kyB BvcdoW 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 index f3a099d6b91d51a61793774189d0e03d56ab7a4c..b99dd9396c6ae0f26054240f7e28acd11ae1a798 100644 GIT binary patch delta 758 zcmaFEy^L>yBnJ}%0|V3RuBeTQ8Z7mZnIRD+5xzcF$@#f@i7EL>sd^Q;1wa)H3^o;3 zKxS@gNuokUZcbjYRfVk*ScMgk4HDK@QUEI{$+lIB@C{IK&M!(;Fx4~BGf=YQQczH^ zDN0GR3UYCSY6tRcl`=|73as??%gf94%8m8%>xljf`}GN_2}W^GXsc zbn}XpA*R3#a!D;tF3Kz@$;{7F02!Q^lwVq6tE2?72@W6*Ey+wnSd~`{^%u~G`XCv7 zLp=k1sM+;koi-q0EB~U*RG`%$3+xPSz)DfXpcdKagKS4~4aBctQJ`n-xNP*{0cXeM zb4Wdsfq^mE)5S5wLhx;fx8Gq0o~`Llm$)u%nZl}7FY-~)BPa3nH0A>KBcc|}+w7EZn1!dh<14n*!)&K#RVM$wDVHwtCXRn$(}Ei%jv)$SQ|gNh zyI38L1u>XSVZO|=bJJAEeL{|_7}oXjuq<S?yeqGplZAdOBd+y1?-MSmM_3*NX zzpkizl4E+btUfqy-j&>MsYxGCohc2Ek-5G{s?II*Vt+?i&J3*&GH=+cR%PthJ{ zc;l{J-0Fsj$IhRa!1nb|OXgz9Iq!6T3g0b!+PwZ4hitgUqU#RDhc`AQuE_nl*eJb{ z^UCdr=dpLbC9hX|S9?@4ziQ*_1*!@i8H*L?wXaGjDu|ld8hmb2QjXW0DLZn%{#$B# z(WFu1VK$?Z4eyklIetbdb{nhpID{0=6g@q5Czx%6`=d#Wzp$PzKULPa? delta 442 zcmV;r0Y(0%4(tn%83+ad0051N9SyM|C}?)Wla;R(G#(n>5KI;a6^f8-{Xw`@NJ7f@u7}qzpO<6MWAR=3B|_1E&+Vp;P7r}5 zKp9D3V${4BbfP4&FAj;vNqi)RfTKi?(YL_zQ}2IHohi?9AkWK k%Wr$!`|}xNj9mQlH&W|}=q+XfmjD0&07*qoM6N<$f}>iMZq)k(nV8B@w}FfdWk9dNvV1jxdlKa3=B3E zRzPNMYDuC(MQ%=Bu~mhw5?F;5kPQ;nS5g2gDap1~itr6kaLzAERWQ{v(KAr8<5Eyi zuqjGOvkG!?gK7uzY?U%fN(!v>^~=l4^~#O)>hp`z^$jg8E%gnI^o@*kfl73XEAvVc zD|GXUl_93U401^=PA`+VPb^0rI$_qVLj=nB;iJ9w#CPDH8V!?G*$ zjyAJzuWsrC}0Zg75BR?Xnix|6TXm?acolyt9f{ zcvOc;?`lsp_DgG%ep>yiO4-_34ZDe(HA)cyMOb%(tCw?;R- z+9zxAH>TC{X4=0eQ*W*Rt^eJ<@2$aX7N%8O1+#AY+i=U)ir8GqiF*8`{+!0cqboGO zeEDa2&Y{!(loF?zczVF0!cg%^aZyJj=BE8Rygb0`09RJ1#NnHQzDJT2COd}d2Q9B} zWqJJSBEx%|!0SsVStjo|>|YzyHBsGvpSsny3-kP}m(}Ge>{=VPV*BNJ%1O<0%f7Z{ zyWYB*#wKk4?^HPZB8i`m`F3|cm*YF0c<^9c*rha{W3yyJ7Ni_e&tLQCuc4V_$gI^r z1Lrq673?rryRVQZ;tR_Z_QM-za!Ah(J>Z@__d&hMWOKPpvr{h0H=ha}xV)Rw)A7!w zvdPANuO)e?w_p#1BwT~yAb=b1`*EQvX9J2h|s@#P- z1b#dX2!6Yq@$iZymBcMO4=vI@_Jp4~`N^Hv2KQbXF*^#*(y0p#nDfkX|MaKI!WP}P znoLm8dsm;Y#yVZ5e?K)hJ*W#)zf|9OIKFt|XJ0c=Y31qa=d#Wzp$P!z;2IhL delta 975 zcmV;=12FuN68sO483+ad004~sxNWf^Cj@^3o=HSOR9Fe^SWQS&Q562(n>RE5jXG+i znMy@bWMyPQ;-VG>1})kZ<~mxmh$Lv&svwBkw5X^EYG;4+V?t0ASXq%#nWUkhm8SVW zGtM~gy}tAG!Kh;{o`k`>@aEok?>*;!=R4ndTt+E6J78ilHbn(Yqm#djySV_J=Er|! z&}Gmm15WK-4Y~|EWx%PutHJ+W!f}uqPaN5dt~gsoQ+FIaW2VJMMUjTS zu!%R{<5m#DLRr3sjv*Z%dv$DB>Op_VM1RCUd4YyZzXFAal;uyd`01BUUk27M@t|wi zu>5$F(51}0Q41^E3N>8Zn~ssFi3}g9r{i(5An!*Wg>arx`#lPBQWac%AH{#O&Iw%K zmw`~sL~fdjlTX8_+2qHqmKbh-iK3w<6K{XSQIb6gX6Fho>RO^W^*n;2OcnhMR9IFe z0=vz1qmDB#Rja*_2zOi?K_6MD`y9ibo1@6#;m0deEpSygMsVWEB#>hjDfmF9E3XXW zLQ@2Xx2C|aDOQSY@!N+VeH?$nq5EUlU+Txopa?LMq^;x8ZRU5gLw? z6&Z)w%eyvZVFxRHFhxb%fB}_u!ng!!5Sa)+z|^LC74%XXg#i^ovOY#%(i9pq5ms+a zB>73IK`|F&>q!{<=n2Xw6Cr(u}d>!qMec|Zr5617W_ zh;|ygM1KKvj~KYy8pUA5MCXuUF>rZhN< zLD7mFAeAA}we;zhcx8XS`KNBBL>vl};#}rMXL4v`R~+d~r(KP&2u#-GT^`xf&`vwr zYh-awa0!TK0hBe-h2@2vtvQ)b9Q&rexu&V$^oy|dUCbaj0cxHp(0XpT;Wzaii&?y5>?OhGJ3_4}N xsl5idcP++7T&8KLn*7zj81b3B$MK@n^#|)QZvG;r|LOn$002ovPDHLkV1h9F&}aYv 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 index e3cacb458020d69ac3be5bd7d32f0b88d8987153..6d11b1f34b6bbb4c592286494d480be4f6270992 100644 GIT binary patch delta 1831 zcmY*adpy(oA0F!DlG}8!y~2FG{F+Y8=F;&sQ){^sqgdq!*ZcFlpZD{;pU*#^$t{odg)miR2n14B z;A388x@`kbDL6VHh!y1R1(NAhtMEvA1jUL&Wvo&l5G)5=EvXb%ID$hxMq`2;C*TVM zT&>q&6oB|bVI6e>g1mhZd+4zggq@YGl{J7akSexd#g z`)Y(mt-1eWoAs<;(ABUwbu8-J!{OAu?rjT(KsKhi6Ltq2-Y5@A@H(*Hux0X8GYW;$ zQR11UsNscp{vz339A5Bly2jv)FnFKr7OEAscMH2Q-q69Hl)!SoirW2x`+>Py{X zBxF#$;psCc(cd#m7G$u$R;fLFbS7|imBGO>nk??)!;_GPGM zRo~Ck`t$*)#RyK8;0a5kU1bdYUlzPo;k(Nh8-Gvd2YZiniYuF&LOV5udI>VY4gMV@ z$n?}|;MOB}3NKGVnw^kk$n0~Yma_bXthcYc4=xJ5=8-z;Hbe9KEL37bxGE<}xY7O! zC7l3Les}grN4`m~c3~iT=Plox8w=-~vMRvH**-O!=bPaH{UbYQcbrdSBV;xj+*|;> zN;1WPP+p@&NZ@~H!LW`uFs+VO(&H*NDMH$w%F`~+6?7aU z*HLwj>R4UjRX3-C52WjF`=P+>E28~e#u-XejZ;p127>{(v!CDJHb!BVYmCV)x$~@DvF7m zC0;^Ca-s3QVLMN$b+3I({| z-jhwbs|cp&q~jzhs!zFFeAr9JKO()N?T#ux_X zrfWnkN?S$AXv0ydiN@aPUO*|>n0=*{hcM2eAnNnwi?2_b`F5m1O%XbVx}A3%9=2Ul z!7*@^1$QsT$QYIFgybhj2Izd{FeILmUDR%2E5+;Zm4U?^LyJeqwwR2GrE>!E_!fz- zYu^j)Q)Xz%P1|>3%{M{aXu6T<_tm683*%Y8J$@^VDY&s+EzQJx`HVj|YXQ7q$G}JL zE+&j%1W!}P%HK_Hq{V7vo$9wetS)yXFW@cdE#{k-|Q=cw|F8diRE8Tx7zPBsM7`!%K zClYoq>IF2FL}WEOG;ST3g7xg$c0a>E{lZ|pPd+SC8=L)v>oUSc+B;r$+*x8G-#d|E zS#;nJ5-?Gm`OZ`JsfC}W<`n7iq5xWiNOoGOIG^-$Z_Iu>n+ezBn|gRs^V6N%_YxD0 zHA^MS2ANT>&u`QjhWGpsnWn5vFEATJq7I#I|8B-vyXMR|MoFQ6e4(1Qwd?lTc9?Ew z)7fD)dl&DXuEkwF59=uS27SzVvpMGo~EfrvYM>YQMr*vYAWn_Et5Y`d3@#S6FNxAfvq^ckSQEo+~9`^I|+7;&*xPs zi|v_Z_RFxr#2cGFeD+9^)JkeKR0^=69quzi35z(*Qky`_@jk>qEGO HKa});6>~gr delta 1599 zcmV-F2Eh5v7UvX@83+ad0027t*>bTVCj@^62}wjjRA>e5T5D_+WfVO#J3G6}LW>m$ z3Jv99E4C6;FoK8@P^5xC1hg8X5)C!cAChP^nwV((VI)dIFfs9w2qDG@ieOZ%50saZ z@KCe?p<1mVlm=~S_c^=cJ>Tw-hTSf0B0J48H=XUy_qg|-d+xp8bhldNtEX*T45EMO zfSqoY*NaO$z@^T&$FRq6iABIm=@KI}2ad;ZPDn~@z+T1(!#N>wc_5y$kx2FJg!7Rr*QC;YuXAPwEyDz2h<6JnnV2=s zfCJ2NoWK1tPXLwxy}8Oj^$1_@>AZjM2QOejIK;1Eb$u95&k7-Z4q_o5qlf8OIX{f$ z(*k&Z=`cKYeGo}b6cD4p+}SBcFqyUJ*EPtGq?rv{bn9|sN?>J3$=S6sXULqtSJ0{A z>^|9G`ZQ?rJIQnuWr6HHeU4kUjA?3)I+b2oqss>BBvb7@HtI(CaCfb#Z2y1omnD0I z`HU-hU(XAe&P*BY!!1=AHBBu^oa|0xQB43N!y0~WPvPs|l8S+(ICiC^)!Yew1Vr=h zHVbrusmZyKLp02v6u@P}G#ovhM$^#*&LnKyFxHRCavfiap3az!@t5ehX0#z|QVIZT zbE^1IeW?$}&!n;UxTW;Pk~V+n57&QjG>MN7B~;MzBFzpK1WY#SuQ0LZj<8BE?s;^2 z5T|<5%DAKW?R_yk(-c8{wTaaW!YB`B_+|#Yyd#R&c1Kkp-nw%rYI@b#_CHK4+tiH* zrUr1=WD~RB=~Tg-Io43A`usN$m7+}3hN}ZuChI6$1>B0yd(bVx+Sq>_$0h;VBEew> zG5ZQhpEWFd5i~Tcm=RLluW?Hc9{Q+Tr7awB`0Pkhvc!L{>NO^5-y)fExFgfX&j*$|`vxpV%L1B8wS(;zK5S0llV6jP z!j?+Iwc^0Ht(Hn9rtF;YepC(DvEi45a*Ba?eNRlK^P(CP)5jPp04sM!RghlX7DY$I zmhLL)FMu4dbjlHu#Y-3cES-(w!xO?R(|X=SKf=<~I7j>am4bgM?I>ZS*(m6gVnW`~ z2%FWp!g)uWw_Hb19nxeHmgo=^93vyt&2Aq_!UFXb)Gi_I*{ z6gP460Hj}l`=jqrho$OhX`P9k(yTfnX*@2+1fAx6LtOxZ2iIQiQ>ne=Diaj~WNS+T zhIC$b>-(kzP;q}GfhDz>TK(yygN`tF3x=~oqvCglnZNnprZBlg*Y>Xz~)MA zcE)Y&`qNU`GqchV2Q_sRMUt6gX!1y(a-yv>rPyOVv*Gbr+H=gp7lge zqL@(O!?CV3_Ddib6ka^yZkuRggdAPXr&8GVd*-NP`j$&Cxm9X*tT5a9Z(1E+?9c4ci(8^KeUqP7&ODS#N?%tL|POSD;&tV4EcZZ>3$B#a7Y05bp$ zA9Tw)TgElTy2BB-I1eR0QN*5+Z;m#c2NvZ}e_yhI3#K#;o?9as;EpSq=EWx}N0{QF zUiSn-ApkPMlE{~e+`_ELCxzzntuHiI5OAT5{hfbfGwPC(Xx!Wb*11#j{oVh6ueea%zBraWhKmzt_pEpfyBGW7SoRn$PMqDdVyF+O-Lqdj x%knF^v~8z#`R9prYw@hQXGy+1;3vjy{{ihLPRGJv2eJSF002ovPDHLkV1gx`2&Di3 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 index 7b419bd5f953833a106c384dbf514d75d466dd66..8315443e4a865677b6f3bc980e5d5448626bcb66 100644 GIT binary patch delta 963 zcmX@h`+LsS+C#C9DiIJqdZpd>RtPXT0bVp4u-iLH_n$R;>|IJ6`)4PjMYG1Ol`AL@f- z^bPe4^dV-~Lp0iegsuFGGE;$8gDkK!v;iwc5rbM}qYttj$u$taf<=Lzwd1nUhX9Dgr({ zx>%pM_r=2L-xU6_&Z^}^Lumf78i#Y8RSk%&y!Z z|Nd>vaM+hLd{}>OR^;r(MJ}H%3xB%hL4<1_b4hgauVU!O_rlyx~a`TMy`d**IGdSu3r zpv7;ka^DJ7EPbH;ewO+3OLiOir!LsyuVYvEK5mPV=DsjaKhdX`jK9}s_tu$&EM98S zG5I@Ro2>l9ggt+6tlY6>!z-V+;^BTv^43~q#95@<{(W-TzW&OYD4DxAW~%-?b^F=k z{(Da!KdUl*F`uFB?#>bZ&S2v5cFDpLwOn+CPpZta&&DVS1?mD#O z_@<=2Ui)oLC!5Q}=l)UAkc*CPpCQgEV|8P4M=}2tE9=apSJR{GPF-mCJkpXNVGtm7 z@ltZ`y$JRvhq_df1e!mF&yPOQB+{WiYu(-phb38U_gu`HuRQtoo6~Ov|J5FsbE!S; zd`~}M!a}}dv-slw7GY}bP0l+XkK D*+85g delta 670 zcmV;P0%85|56umb83+ad0035#XAiLp)U8hEnTT<0&-CPt#N%ahuzfO{ns_3-q;Qgk4WlD<1Za}yx^c37%ctL z_O4q{WJUD0fpP%9Oa|}N{5pR_x`WgEGq&TG%K@^^#v{}8P10lHu0qOZ%{96t0&Sz0 zgW}*$%`eoUa<4u&Z7l|5;g!F|61n5;rn(Ds)2M_~B>)ocRAvuKq{6eoebDrrrO4>h zn}V$Lp~{Y?^m$v$powIRmWux^wpJm1mN7=q1S|go;mGAZDB}V-000UA07*qoM6N<$ Ef+ea#lmGw# 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 index 656a41e3d51e2119a253269927ff4d788aea0ee0..ca78c2965138eef4eb69f5abb4c9e433a194e9e0 100644 GIT binary patch delta 1758 zcmY*ac|g+H8YQ1-flOw&RFffN+Gs+eHj0S34K6`sn$ScFxa5MUkhl~-m(dv}p5|zp z3u@w)IR;^BmMxl&qobBW~NS*Z9dH2%zO8*@1A?^Ip4W|eD?}GMd#eMjVd4z zC|o`QNDYitfeqAPFAA4JJcwnoStzJ0yijMmVvf0uWXxOVmCj9)dt1a8dq&SWoBgzg$=D?2sq9+z2ch z&ExSaRt4qhk0^A~7gUM_m6LESws z<#mscfX+LnDcGUa1P(2;Dg`%zsuxRAJsdCV=IwFNP}LHf$0ZtRS$%K*M6cC06KSvo zQu>Pv!{n}-xJ=Qb^TRREx_}$2Qw7^bWfceW755!`ox-Aw^1JFefwy9(a^K{4q0I1K zBcUaSFYR$H1siz({MR!G9HpvH(=GlbOP`k545Zu<E2^omW%4Vp1rT7rpZ92X4H zRY|Xbl3m=>YYvP%jy(`TO{VVosV=1bJ2Gi#ez>U|dOjE5ILrTbx}p#66wo_%*0Rws z*}b3oswq1k6YDMQt+jZcs~yr?aBt&pX#C9R&Ep@JR;tBlRf~a{eCc!3?i;ReW`!4o zfJasphAx~IGJC3U(o=iO@~y2=73vRTos9!m{4e+5dH#~8S#JLBmMI;-TJ1Dfk=)zA zBrE0B`}!jr?6{VkuTu9D>bO;LeB_;DlS>jR5QZzy z>?!Iz9d|v%_}v6!qN!*?GE#9ZQ#AT9cX$YXCZ5yg^u}$#H$`l&2uZ19mBm#8yVIz0 z{5v8|bN~D{tIdT1(OlmuOqP>IXe7wTtYwFsD&dLA$0KM`*Eq5Jd*(x2zt*G1-XTf% zsBPN``)xEI#KLco7kgeO!)oMiqNbeaX{;G6m&2IKl^^T?UNmPAqdT+hMXz+Z(7L4~=M;Ix_2ZibNK_wi+Hbqey%%$PX zVpq+D?hk-&XHY2jwbpgE5SB{KF2ZLR`$Ar;4jh{Rdz%Q-Sw8kYGgvw`+P6)qC@8ohjEjs5be4O-b|QVBRNS z$N{BLZ*Az5=SS*WWk8_^}E+p!-7{W#g)$zBKV=chmFs+FyRWFItH-jw0ywO`83h2&i0=$G^#Mt&g1 delta 1542 zcmV+h2Ko7g7OxbL83+ad007~;N+_`*Cj@^5(Md!>RA>e5TU%^YRTN$Gp6LS+c`7I( zsEG0q5HN;@y96snSg{u9ugEmVvr!gC}1h2 zAkY#h*vHIt?##Web=q5|+B?ONVHmh4na(-)oU`{{d+oi?$(=MXa&5DN8@UC7fMb83 z;NP30`9Nk_EIRYn6CdutHYu|Th21+F9O zU_d;8!4&}n1BIbpmq0T)2Qx>8v2A}*KRiA~;!$TMVHbr3V86L_!P|jxCPm#7O9}H)W2MQz$tIb***f=Fdq^lycPE@RVG4 zoS_VgMLZlph*U_X#_EeXIbv8<5O2+?Fne~^TWC%@h!mi$PoO!1nle0u`v!l7(A4hW z#|8_d1_m+hwlLZ<4t_eB#YHNi?TFFXnmZ#HRvp06^EM8g$s$PQVnD!Vac+2Z5cL;x z*mpW>o=+GWGJp;?W)0|jsi!)s^%Te!=KdLEH^y;oa}F1h4xa_Eqt3#j2{E+Nh7W&E zGnvhD&p!o$ipgcu<5>M@qC0;F_MXZb&;t{lh8$|jVadi;BrV{RCoAz7!^v%*?oZ?G zA5tcdK3dRo?(5$(Sh1}gZ_KX1-M0iW>+=@0CvCj=a2)f-M6hOe%82R5T>WxkwaLTd zbkOSWlGsIO|J7pSxlL`TyJ+J~t8F69)!W;*Cr~jtdO#4b&Wz(Xf?0pFJBhhtqF6dP zhWbl3Uf$A<$z=MKnF%~IK4v^GkZbp*u%*_*@)>cwFg1=n4OvVf`~REYA&ZI*+TbjbK2AtUeod6k&|RSQlL=*horqqk7Vx^lgNQsM3W zgC?vt9PYrTBN-FA0=j>-HiHd+bl_lP*eI6~cW`YTLrY_&gc7gQuT_AmX zZZ(CWOS`-tW1&#~$osBQ>~=Tx^-(@0SP`334pIzncdr1dXlZ|q`YK8t1fss=$ysVD zI<5w{`f5#IIjOz*&6CmmMTPn*LMevdj^h?qJd$82$FcR8g=Ne&IYJdt1xKkcZA1tU z3<+c5xG1Wadz+71Cj94+-6ag?(+tO&QDK~B&h28zEBBtC634kV+c@+k$~@O@Gm^5V z+846x-*Z<4R1Sa4Z~0>ZRfSx)FO7*q!zO2yw_@&NKHGLIgHIqZ}1y)ClLQr|3tjRyT-LdAh#vPjr10U*{jM}-lKM!;p3)Z)S z;<^ui=}>=Do?E;GXn%zJbTM$7E=8JC)8(Y=cy*`y{_X-=FmyY^^0F*>HQ$@wW6fNy z7s}x@i;Dn|wEr|YN^JJl1T5q+*V}rpX9a&4XRGg?gVmO}PT!zf>v-r!h? s&vEQvSpWP|icq>%IvvN+Tia9r0R0Ld_-1Ka!~g&Q07*qoM6N<$f{^9_l>h($ 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 index 058084c35f368c1d2379f7b6f034f747f3a41194..a5df872ebaa1a5fa26cf008cca3ab843c61c94f2 100644 GIT binary patch delta 2546 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 GPtreljidGe 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 index b3a22c1997e819151b895140e285e2939ddaa293..9d028818b36153353b119d34115532fa98ce57ee 100644 GIT binary patch delta 1254 zcmeyvKT&9cBnJ}%0|WCMeXEU%>MZq)k(nV8B@w}FfdWk9dNvV1jxdlKa3=B3E zRzPNMYDuC(MQ%=Bu~mhw5?F;5kPQ;nS5g2gDap1~itr6kaLzAERWQ{v(KAr8<5Eyi zuqjGOvkG!?gK7uzY?U%fN(!v>^~=l4^~#O)>hp`z^$jg8E%gnI^o@*kfl73XEAvVc zD|GXUl_93U401^=PA`+VPb^0rI$_qVLj=nB;iJ9w#CPDH8V!?G*$ zjyAJzuWsrC}0Zg75BR?Xnix|6TXm?acolyt9f{ zcvOc;?`lsp_DgG%ep>yiO4-_34ZDe(HA)cyMOb%(tCw?;R- z+9zxAH>TC{X4=0eQ*W*Rt^eJ<@2$aX7N%8O1+#AY+i=U)ir8GqiF*8`{+!0cqboGO zeEDa2&Y{!(loF?zczVF0!cg%^aZyJj=BE8Rygb0`09RJ1#NnHQzDJT2COd}d2Q9B} zWqJJSBEx%|!0SsVStjo|>|YzyHBsGvpSsny3-kP}m(}Ge>{=VPV*BNJ%1O<0%f7Z{ zyWYB*#wKk4?^HPZB8i`m`F3|cm*YF0c<^9c*rha{W3yyJ7Ni_e&tLQCuc4V_$gI^r z1Lrq673?rryRVQZ;tR_Z_QM-za!Ah(J>Z@__d&hMWOKPpvr{h0H=ha}xV)Rw)A7!w zvdPANuO)e?w_p#1BwT~yAb=b1`*EQvX9J2h|s@#P- z1b#dX2!6Yq@$iZymBcMO4=vI@_Jp4~`N^Hv2KQbXF*^#*(y0p#nDfkX|MaKI!WP}P znoLm8dsm;Y#yVZ5e?K)hJ*W#)zf|9OIKFt|XJ0c=Y31qa=d#Wzp$P!z;2IhL delta 975 zcmV;=12FuN68sO483+ad004~sxNWf^Cj@^3o=HSOR9Fe^SWQS&Q562(n>RE5jXG+i znMy@bWMyPQ;-VG>1})kZ<~mxmh$Lv&svwBkw5X^EYG;4+V?t0ASXq%#nWUkhm8SVW zGtM~gy}tAG!Kh;{o`k`>@aEok?>*;!=R4ndTt+E6J78ilHbn(Yqm#djySV_J=Er|! z&}Gmm15WK-4Y~|EWx%PutHJ+W!f}uqPaN5dt~gsoQ+FIaW2VJMMUjTS zu!%R{<5m#DLRr3sjv*Z%dv$DB>Op_VM1RCUd4YyZzXFAal;uyd`01BUUk27M@t|wi zu>5$F(51}0Q41^E3N>8Zn~ssFi3}g9r{i(5An!*Wg>arx`#lPBQWac%AH{#O&Iw%K zmw`~sL~fdjlTX8_+2qHqmKbh-iK3w<6K{XSQIb6gX6Fho>RO^W^*n;2OcnhMR9IFe z0=vz1qmDB#Rja*_2zOi?K_6MD`y9ibo1@6#;m0deEpSygMsVWEB#>hjDfmF9E3XXW zLQ@2Xx2C|aDOQSY@!N+VeH?$nq5EUlU+Txopa?LMq^;x8ZRU5gLw? z6&Z)w%eyvZVFxRHFhxb%fB}_u!ng!!5Sa)+z|^LC74%XXg#i^ovOY#%(i9pq5ms+a zB>73IK`|F&>q!{<=n2Xw6Cr(u}d>!qMec|Zr5617W_ zh;|ygM1KKvj~KYy8pUA5MCXuUF>rZhN< zLD7mFAeAA}we;zhcx8XS`KNBBL>vl};#}rMXL4v`R~+d~r(KP&2u#-GT^`xf&`vwr zYh-awa0!TK0hBe-h2@2vtvQ)b9Q&rexu&V$^oy|dUCbaj0cxHp(0XpT;Wzaii&?y5>?OhGJ3_4}N xsl5idcP++7T&8KLn*7zj81b3B$MK@n^#|)QZvG;r|LOn$002ovPDHLkV1h9F&}aYv 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 index d165cad2f0a939f1100cdc847515b8d1e54db201..690813e77f4b1a37d943fffb0da3a223efb3043c 100644 GIT binary patch delta 2359 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 HaVqv-_Ru+& delta 2232 zcmcK6`6JVh1IKYT=9Z&SM2hKf+uZkA#-~26Tyvkf!^riv+%j7FG)$?aJU(Y8X~kT(Ud%b?9a4#b2ha>lYCCkRa(6X4CvWWp=|GL8vB zR*RT%!&7CKK)JgmXh}m0Hi;!YuwHU5r)Fn7B#!00V0hF%?;eZx#g)z7vzx3@rf(*f z)c&Uc@SlP&9I!0u%Ij|mJPEt&Q4i|L{M|U%A9D~ z)+t9#1W!ukRF&%q5?v(;X3w~{QhB>OtNCK|k?-_eAQ)hx6lt}xaqYnY zn^E=xW~PP4Y)FmvLC6XBkK(@2X?@Y)vNzMLOC242qSgo_M63Kvv>>_r`{40J_;`=t z3K>xJoX1i=hZ>w-+0NrH%ix$AV0$EK2AL}(8asN1FPEQA3O5t(S_+$^KiGiKtztZe zQhj}%7_ZWFneL(If&T6_>1QM3%67C&c%BdoadvpG_X!BRS5xWf}q@H z1CsX{@&v;mb*w;Xg5S@VQqfUn*zu{hE;`QabTl1w%7E5z!o!E~PZN z!M94xMkz3F9;RvL>q;&ENYP^$&xvXug29o#YMuAPgr&j1dJ!-YM2yM03q_0iC?0X@(@chp;%CpB2*5Bx`FJGjVnCVyM$H!O4f)?nW>20b%m`V;- zunOm&qA|c?K#wD@U8WT@wncJlnKiUMWn|00p&T=eQYo+1ow`VR?p$kn!M9beDtxz} z0-HiO=_JZ-)lT}f%E?7sX|rdiVr{^ur~z9K9gkH_LN1XmBL=C=6T~0!Q?Ag2+ z{Q9=h9m*USO?MvARyp}IM_CY$*AYKFoDkM21VsFKY!${79YQ9@O>(iP)WHkT_$K0x zM!FE%zNLl7wzS?0TPg5jdt&yd1$ox??gjjZzQ=XDGmR&;!oJQ&js?gK#~e6SEQ7NHtgs&9Zne25~U7{tiJ^y!2L73eCh_6T7@xi_vj zKOI)Bfb5UtC0r|P)A9G^txFo9YGdNA@?6g+&5X4@Wj~!o13~%!5(^XR;N$Jpov!Cz z|7SpEF0ngP;Ru|Nt*xEHQk!JwMeYMs?{L85dXH=mNZ(L|hUlrN!$tYwayEtpU(NKa zv>!g}=6414WJ_9002d)%JZ{KiE~;gr+RBP-Xq}(56cs4$;Pw!%q&J;T^LTr(9RyF< zS@nDVXMuIuvRm)nfdwFUj>u4wwRMMnz?U}fISo@^XKGjCtULj-t*`BJ06+;Nrgq}o zhZTz-z2(!@47zIHJ?KBRpc!|EY2Ml0HiV69Mn*3h6m9`sH92m6MuV9!(}KoN4gi4O(cX?&AXd0)Kr#+PE=)U zMn}GKBLkm-4odtvMib5fL3iRo98P`Az?jy{FUzVeJCaOFv6umQ zN?>AE$FJb(S~rmV%5!-;%{1OT820xRKnE0e)Dh6TpWgL9(!NqTdi~6_gq8l*(HW8D z7-IisrJEu$2K0J_QG`%p^%uS!Sq@!G7a!K2w;we73k^@4azp`o?aUXA4S;&ifT4An zfXH<@JlE%ZhjKUdR00!EjolEN)c;u@xCck9V0~a5JOKnYz 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 index 9ae752954b0a8f3fba2dafd84c87df5f491a722a..edf9ba91afe5a08622ebff061d137752091cafcb 100644 GIT binary patch delta 3628 zcmc(i`6JY8-^a(kbcpPx(S$5v7$h0P*i&QaNI1ffWyU%&7{WJ6LiTLYMnx2&?8XuX zF~Z5d4UHwnzGZ#tbe{V;&mVB#_x01~dSCDB{rY^a->zhdX30bmc192gRL=T5^4)1^ zW`My4ch#C;ZGzUg;o}8!boOy_ff2p%KP(`SCQ;)@>E%Ljgc7|xz5O(ZNb$cM8b9iv zvZ6TjFAKo~DQ<0I4%PGVb%CCPslt@RwK$ijsYLtL;{Xdkq-`{inm{9TOi6R`Pr1&cv_;pe9r>tS-i*xyb{t3{6 zYyM^Zf7;(5O~s$|e{}O}*1yyr!)kGAD*o%?v^ddC(l#K_Z{f!JI#-Dd^z2)S{4Ef2 zpzBs$AbC{_sAOV}kzu1M9Ou=8qEwA-UdYS2T$GOkBqc3hp$qa8FWnCkHjqCuUoUZb zU<{3xJr|vp1C3$pkZ4qiUrmn5D1m;lr8Uxl37UI!28oJ#U2agZZq3^ z{@Z!L!Gc5gwa02xNq!7xFL7XlO@dHX!^|SH%YpHgs_bz+BmZ%1k;T+QrkX@1~tyUy~uOp$PW59K6$5JJ|eO>;=+f>!YnjqrDCI z4-8TJ0Gq}1#^!tp)^pAyMS{(iZkJbvFm*ww6upX`B2}YmyfKC!;CWKan$@fmi(Gtn zBXsCi4(R>2TI&PBWu2e<<)e?A%*B%Lfr5wDA)f_jPF4cdsEEQ;+rz_4W+l|OvY)(` z@TdOZulKw3^?Xn9M#SqWQk1nE=;rnOLq}uEK z`BMJ*@j*GBotQKz>?U7AutSzo0&U)gC-E+xbYaoefNodtG&C$e9!ZiI!`b~gzM7C- zVdkuNCV5E$!1e{a3*An@<(}Q7(lkU(-0G;)5u8%DtTW?{n5ISVXAK%v3*_r5i}t$j z&P&K`u9dK6h1dy2X9YC)GNxWBu6o3YJh4G=3>Q+peR)gv5i9z5&2jg!V*|R<7B&?Z zrLfFf=SLE}E(bcV>7k}p`_8AhrEu$p7{B}~Oy_EE1rEU^KVRow^IZ8H>e^t??DBFZ zFzk^t;qaKh(QOgLgz#6M7Sp^@JmyB)2#-u8BciUviC{+0oh!u~xGJ31M2nSP!cnos~xRjTUJRijEr`z_{ zWW52*i{R+-ilPMANjg0&j!uagNDiUEPw`5WxEkjapg(q%Ka73lpydqFULLxu=`Ai@ z<#L^9=&!`Pra6O(t(&3$xqA=YJfNpDdM2V(-D*_0CNSW=Tp0(8l$A(I;naW?8af+_Ch2Wl!fIxdqS)sa$zmMOi{v7^I!i%{9M z1r7(zI4!k7uSxQeHmg_GGAZ8_UVqqAo�K$c_dyxtnNoFT$xUr?Nj#hm%f30IVwZ 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 Xay12NQ)~YHu}JhD8EIE(+K2rMt%HoE 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 index 9ae752954b0a8f3fba2dafd84c87df5f491a722a..edf9ba91afe5a08622ebff061d137752091cafcb 100644 GIT binary patch delta 3628 zcmc(i`6JY8-^a(kbcpPx(S$5v7$h0P*i&QaNI1ffWyU%&7{WJ6LiTLYMnx2&?8XuX zF~Z5d4UHwnzGZ#tbe{V;&mVB#_x01~dSCDB{rY^a->zhdX30bmc192gRL=T5^4)1^ zW`My4ch#C;ZGzUg;o}8!boOy_ff2p%KP(`SCQ;)@>E%Ljgc7|xz5O(ZNb$cM8b9iv zvZ6TjFAKo~DQ<0I4%PGVb%CCPslt@RwK$ijsYLtL;{Xdkq-`{inm{9TOi6R`Pr1&cv_;pe9r>tS-i*xyb{t3{6 zYyM^Zf7;(5O~s$|e{}O}*1yyr!)kGAD*o%?v^ddC(l#K_Z{f!JI#-Dd^z2)S{4Ef2 zpzBs$AbC{_sAOV}kzu1M9Ou=8qEwA-UdYS2T$GOkBqc3hp$qa8FWnCkHjqCuUoUZb zU<{3xJr|vp1C3$pkZ4qiUrmn5D1m;lr8Uxl37UI!28oJ#U2agZZq3^ z{@Z!L!Gc5gwa02xNq!7xFL7XlO@dHX!^|SH%YpHgs_bz+BmZ%1k;T+QrkX@1~tyUy~uOp$PW59K6$5JJ|eO>;=+f>!YnjqrDCI z4-8TJ0Gq}1#^!tp)^pAyMS{(iZkJbvFm*ww6upX`B2}YmyfKC!;CWKan$@fmi(Gtn zBXsCi4(R>2TI&PBWu2e<<)e?A%*B%Lfr5wDA)f_jPF4cdsEEQ;+rz_4W+l|OvY)(` z@TdOZulKw3^?Xn9M#SqWQk1nE=;rnOLq}uEK z`BMJ*@j*GBotQKz>?U7AutSzo0&U)gC-E+xbYaoefNodtG&C$e9!ZiI!`b~gzM7C- zVdkuNCV5E$!1e{a3*An@<(}Q7(lkU(-0G;)5u8%DtTW?{n5ISVXAK%v3*_r5i}t$j z&P&K`u9dK6h1dy2X9YC)GNxWBu6o3YJh4G=3>Q+peR)gv5i9z5&2jg!V*|R<7B&?Z zrLfFf=SLE}E(bcV>7k}p`_8AhrEu$p7{B}~Oy_EE1rEU^KVRow^IZ8H>e^t??DBFZ zFzk^t;qaKh(QOgLgz#6M7Sp^@JmyB)2#-u8BciUviC{+0oh!u~xGJ31M2nSP!cnos~xRjTUJRijEr`z_{ zWW52*i{R+-ilPMANjg0&j!uagNDiUEPw`5WxEkjapg(q%Ka73lpydqFULLxu=`Ai@ z<#L^9=&!`Pra6O(t(&3$xqA=YJfNpDdM2V(-D*_0CNSW=Tp0(8l$A(I;naW?8af+_Ch2Wl!fIxdqS)sa$zmMOi{v7^I!i%{9M z1r7(zI4!k7uSxQeHmg_GGAZ8_UVqqAo�K$c_dyxtnNoFT$xUr?Nj#hm%f30IVwZ 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 Xay12NQ)~YHu}JhD8EIE(+K2rMt%HoE 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 index 2f30b52a58caf9ecae3f7c8854d8238f180bd527..62518d70ce41360b2ea267770cf5ee364dac27e0 100644 GIT binary patch delta 5318 zcmchbXH-+`wuTdW6_AdUKu{Ef1SvwKgA|dXbOa$FNDl@`D8dT87XeWb5G>S4Q6Mxi zbWoJuQCbk`ReCwN_rB-eGsgaL?zrRJ_3K;jn&11(IsdIUU#dqkiJOKD1OmM)dE`(g zC_({%Eu4+bz0LJ*BJ5G_63BZfI|m70chA!j5J-Q7IA5Wb2+e^ek& z&%b4u5af>%Z&yVjb3H?dItuLok&%#=fD0+nK_CzX^gTz!Z4J%8uAjas3ORXudm>;k zEEX$)m68BZ==(59d3kvl{0i*K74g#&aW6j)Z=|odhZpoum;W--aPYE6J9~ONqdXwL zjgfXJA8$n=q2Hu`K7Tgq?dHF=zEcUOEokGkP9B==feI&f}SRL7l7pOC1csih!>Z zleEL6=)EoIZKe^r(5m&&&5${ih9(BG`UFlK-wJ*nQ=Xn&aJ(J+x$ zdve@0WP?39!W!f+u7q+32xSY}M%Ip6-wEgtW?`n#J`)Utgd~7Ea-fan5EV2H8=Lp3 z$j6dP2~Jke&d!Fapnpce#c7gw$tWrLPBpDCXH^`D`QpW&ry4@&COw&Oq>8Gl7LL@- zg0y6Y$WzeKAx^afn!XTFhoGThHw#4GTjMFmZ`R+Tfd5C<-=Tp2&|H7V`a2Zxf88PP z5oO;O)ohaGbpoCz^vZZu@O+JFLj_L}Yh^O;>>1ZA3dcCq`!-><-sVU6+D+|C#zBJe zp)`BR>}M=0Z?ga>OK6Ul=18Q}%HWEf99xVQ-p-j>vEp6|Z$-8->e&(vD>X(tD%TF$R z_wnaS*Ah`9bjF@6$NkLrrJ>h5V(Pf19HHI=FR%Bq#`cZ1#MWfI86p77$<6rnsp#eH z5qtl+q#&_0_fH?ni$KNwC!Q7s0!=e{4Q1-EWl{e0sRY*wW#HEIAU*4W(VMbp5X<1Q zm|aM;bR%#v`4!7Wei&bPazI1aT7FO)M_*G|B}l3?-A$PR^~wYLYA2SgFmdSWyG%hL zX4=RVi^tYwuWU!-+?0&RJ+rx-4NGLTzc4GO4J0svtIEchI0Ys$2nho%5K`kEvBsUe-c>VpM zPCus*CVzV}xdgm?fFyS!&V;EyEiU501-@F)c3Gz?L9ZHF;FYyb1jHMalc?j>=7_Fj z7ZPVn=8kJV`|SwlDe}NKtKGv-EK=f`+@Zpn7u7;Lf?;1c##tT&9EYtf(drM;Tw4iC zmIpZdzQrtuR|F_t*kxl=$ZoHoO^hSB*n5sXX42Rw*tOVzAUskUo*gprGhklu6Du|*j6 z!BsKG*kX<_VqT`qCy(gVx1-N5f5=1+_@)A=+47$6b5Sj|iD?)eiDYSw&# zDOYby7O@b2>OWCo#u@G5uF|%RelyzdiGB7pAX;V)T+yu&!?N>RJqg?vXF3m5IN~H5sHgXx?UVOXKdsO1c`7u>zmVEXN zF)18Ld-DbjnHr)WodA>MtC;%`@VPeW#B^7zXJ28dcVt^~MxzyPjV!D^$bR-H&UA z?N4#vGCaHk+=TM-FWOu%sV;!F^|A3JS|3BGTL#?u0N|#kl{F|!cMH)U%EJ0 zYXF~aIjQZ7Kcl(f6K9<@V+ z4Q0->UuJ@UuqKlr+is@V9nojOv;s=m+qQK`w0{>j%kYo;NbBeFU#kOpCOer;%=8k2 zW|Jw#@A~zbBXK&Ej7Ut~PI~1fXw&G>RG$hbGnsN{y-bKa#piQyFJf5;wg2XL)u2Ld ztw<$lM?`mxfkQvG4hnG1K5lf~X3HA2luMNmJo|8VD<3g7^I6$X_2KN-tnbks`%fsv z(^J4F>-7T`yQN%OQu_43Lkxp%vJ(~kYE}8{=s}{bv&z-s%3r^BggsXvln|V`0qTqL z$1h&Nl4h`Uj$m#VFxT4fxjkj~$P($A%*-}^By@{d3^+|Vw*ER1Ym?;D5Pt<;DnGwV@3blNu%*XfK zYD6Z?7!pX+u4C{!QIbz?KF~)Irq*ow*{g-jkE-4Go0U5a<75YTeA$1xS4wDChl8Ry>*L`;|HjZ}l!8CTa&X6Vc#@UZ4+*@k>+g7VZL4Fi;J<_sM_v6ZyF+ofF7Lt!6V{@;=vXTQGU6n>xA4-41ss5tS z?8tso#6ir@lqws~ytw%zXL{M-&6MP17MO~HBczFYapHv@khdAU#|`C&NiOP2Zf$)$ zmW*fD$dVM_ws_SzymJ@pVa%&evQyh{T)jQKn<D-xjwd<-LeuFFtHG4i5`@L#RdC5 zLI~y%QL-dvmwp84AT zu0)l*LaG6YnX&2Mg!iTCP6M~7LdkXU`}}+bUtiocZ5L&;rufothhqrHi2g*&`flo? zu@4!1M`$#n_RLG92Cu81lXoN4b<+8WCDc`t@!cC7>FUf&j}?RGsbd{2Ua`4{fU=hi zX@d2E0oqpaD^<`uVR=!(M$W;FeshLw+wr2Z3vMdgyl;d0XpaKn%hMuM*^d*R#+$#c z$)gOYrM?hDJj(O)|diVS9s7Nj@OwCHK}zHo}^x1=%>bA9V1(Zk8yKP zCRS}lIQn?_^2mJepO8$af_Wo7_`*MgQqA51KAfNR@oL$)IW~@2=uVi5|E29ihP%xB z2;v{eI62$y>vZoVp1O{*^}DLW7piQ93@f*|0iHErfW%B97=an(a#h8Gk5|p;x;Vuq z^L?vbiT2}6Z;x2_Wd;s_kr00wnYT(*U&fp{Yb~6?aKRdr0rh6f_!*{ORU6u^-=bxJ z{2xr=iE-itjBFTGv0P$ZdLsGJgIc$glfoVIGDVJ`ZQkGMi8FYurAWAsG1v3Wxao5` z68h%MttJMgs+Z;FD}a#vLZf`{_lU8ASBRvQjv6@+6Q76o9NETnm3>85r#2gp>K^Dr zcnKIAaC2`OdjVNj{|WMws?Tnt7a|it-5q@C4;_X>Az-O~cQ$D&>5+!$2t=@8vTTXe z6&>1Fg4i~ej70O26|reHv(fON=DTf`!^3Zim@));WuWMi`iFj47NaLF$?0PXdu$MD zCH_#VYX&mCxW4|?%PxFFKfY0euCJ-Lm22Q@IVz`_ZCVx^eB|TRU5YusNBINF3_J7o zOEBw)N8iH`&63XUO6HCHE+~Wf zvLW&**5T-=i4l7@{L>&G3Weef4(o?{=PnlW>`m_DZ>Y)?6`(ztM*DLY3=`zHTQb9E zQ6%_849_8DTXG`iaN{w0_YjD{wmMQh3qB{jOV8r-LTEq|X*;xUGgN_zUuzHJZxP@# zNo$Ue4={C@eDSh2%TFv(3@o?%>+$5k5QlInaVkoHmAKV|FFxKF z7{!9Hn+-xMA(Qo{;s86#&%=e8f;qzMtdhon<-+YZ>_lNE55+Z?z1uXVk*me+c-q#s za?9i}YOk>r=QY~2112uas;&iv%9~CinIn|<$o7YvU?M5CT=fak7R^-ZU6J@+Z?y%+ood%%2Q9O8w2nyW;6WEVzLE+J?aXK#xIZ!qGQB$>p2WH%g(tyA|UGW)1{C z*}JUL72#E^WWY%_r9SqFdnXiedqi?A$aCmNgv|~Sw>ZIFRF{O2o;yZ*9Y?|(JZP_t>Jyk!M+;s4@Kag#h%2Byokl^b>1vpz5sLIrjpnv~p`&5Fv&g3niL&DdC zKXVTsJ14MPHb_9e12bn^N)?-EisFu@&LWMgN6SlqJa0BZh(lbcMAUuA8QBAG>d1|m z(bAMP2`>9vHXn*Wo)y=An%g-V&?uwTBcD=y+&EHJ4iT4Fk)2T@B!Uurn9DVP@`>!4 ztG@O;uT&4NvI<$IZFKMqEye8aq&(=4Tc*Wd?+n0EUqCL%d8Nif6?w-Y3W<~+Hodjb z_*!Zpz<*URzcveOEOY+z8Ei!@BZNAxi5C>pPHtW#6uWA6!_UFxQbfJcGr_mnk)G6i zh@<*&$5agivjyo#2To|h95_zKkZ+69EZhR5xcn+ScPifXn ze-Lt-Cg?M-<*B_FZY${46=~N@o}mB(qSDvm+H*Yo^zVwS-4oKY$B2paaheb)4?3-K z>bx}N^uHOKl}Ig3oS{d{A?eKqN}6q=wz{}z^m%V`!~7TmOt--B%kAJBlLnR~)aOIM zphTom^5(GA<@Vru3gz|)``IlxT04=QCrw1Y9Sl9qGFW&VlXyuu7I1PC4WpFy88k5p z)PW3(;1;Hqx7EOr;A%|_$)UV(Ar&-N4w9-v^$gc(=n>Zq4<()M@-!#Wbb6|aP9oPl z%|M{iosfUA1^z?6|E?|YzwGzl_5C+(6RtQ!#y{)is-zj7O1|S+Gx`k~3sN2r!?`|2 cj5VDwX<@aut=dB4f2WZ;*Y!1u)oerl0Z%lO$^ZZW delta 5535 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 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 index eba3a1dca7b876ee74faa87aca8a0dea01a63978..9b0ce08cdbc12cdc37a585a5a33be70479c416ed 100644 GIT binary patch delta 2244 zcmbVOc_0&fA2*?h+Jl8$YmTKJHph}V4k#`S<<(_5FT+pYP}M{rvv=^-8WvB}?t&<>BEm`S3Wp`UsSd z0>I(V+Y{|AEp$Bzfokqv1P^bu@W9|53J;Hdxb99F=uLD7hX)1(h3bYQAwL+pJM}IM zgMfcfi2g{3y`?qSm=NL(MyP42!67IiFc_>K;^m`z4ukzQzSAKgzC>cME(}H{lhw$Y zY7|1qMVPvdjt&g20n^Y>-Jz(4Mg$Sv!&QSq6@IGxM+oB`>KTF$CgKS};9X&N4+4pZ zgg|zk{ysk!CE|VlYZVmwE7wj!*zOHXT@4QVsZIGgsJ{#Ao(aKw?^y2|pw#t$Q2&Ab zGSY|by8q8MKePTocfz8C^kIKL97@Qd1B&C}*{f)ZL7xu?aGmf0=WWH?2ssh8b!GRj zQ|b$WehT7dDW_Oq0mivUn0Bxq_^>5VY$>j*i=85$cO>)Z_f!nagF(?s@dYsVH#;~8 zQR~Y1XUAh$^|dWo4V&{1R>oLa+Uc<~+7a4A??28*4eKp@ZVO#k%6hayXhe^P2S>XI za3&=41cw0l;*RHHbkiI$-`EZKPDA-e_kT5MSNaRljG$5Zwf1`4M!O${k2&hh+xO-j z4wbkhYRI$6#tPFIGE$1r*|B-H0W|?%sCas=8B!Z<8he5AEb{f;mtGdEDz-_S-{ZZt zt)KzT3zjK}R=u)y3Uhw@6^z2vmpU5ijsT=A;zWBTyxT6wvE=(jHJqH9JusxsPH2%9 z!sO&BKg4pHg6Ih6Lv{>QV3M~%EkCEA%I3yJmt~!63s zWiB!AktnlcT!FJ%5Vy{9MZ&gNT}rUtlI(I1Z9b8a%_*Pa}>(I@H%qR_g94YGm`j9=e49{A;HTZK|JMZ$_s7xId8AyQZs~(xV-#A1fm%usEo$Nf<=ff?JbF)kfD^TG#nsGqL zo&VV17X-%SeI4#ebh6W&o9j2#0Okt$8cbmcSmDBVTR6E_PRNPcx*7gA(T;bb$r?ft zm6?lIJhS(RPwnzoqrw zuReaDt@<9B3NAq3YP3~02dnfv9I)so-#Ocx@)+U8+poE`0g818A<;GFKNiRglKhGT2K8@#&1V? z?e?yDKD-&05%-d{o_0q%dJJIBq|p*Ke9%=Io$6rYFv9V?YLyNa1RY_gKz9H@@YXI0mjghCLnKcyP%`UM9lU&)= zTs<=?C*}CDGPug{p+!D7<%1urV3Vza@k4u8NY*FIGLoNDq0j}8#rO`r!AepQa z+;xjEMxH!cX~mizc{hBFC!+8bT4LXVL%CDXL`1F)LSbNpaC1ZY<;s46@jbqTwg0Bnf@td$T ziVY<-AA}7l6ihCOmoLa;aRpnyOBqLj`U#;$Z$EQ-drvC9=0ALCCQ?zmq-eFZgFM^aArLu_|QTx zwK6r}H(p~JUeLxl=GvSE3aAmB4tOwqJuO>q%l5`$#W>SH;Q-~662YQ!gpT_Ragh}= zO#Z5v_gG?>7r?rbU!+$$LvVYB$1Ru|MxEo-Z8oE?UmUEy7RIl%)Pfcf zO^kz)8S$TFQp`i76dV5-Q?jvXu&~)mq*)x9mV0@c;S4LZhOMPNFRgNIQV?ZQ?#|Uw&_TYSi4i-A4OH;i%^^zdYiK7xoPTvXn6n-|y%+!*#*3%G`87 zN%JgIwbWi#xxB`+1VJ~|tM&$!b{5)(y{dZ-$3yGOJV=>!{fW2!kWuD#v24o@q}e^G zp2+Tm7v7qwiL+I7a!BiY)07{->8dauzPKjUskyn$)5iCLLH^olu=^J=J!OTdFmj{* E1v)VOtN;K2 delta 2084 zcmcK6=_3=21Hkc_xtG|OIeO$?&N&v3Xc}@Q6~z?UA~KYOoMW++BeLcyN0MW13neVf zqg*viNMRv8&CKz*_5A*W->c^j_`d#p7v=llnew8-007{xobx&jDq0|-$WI5GQ*L(! zxCPiWH(MF93(rzQ5Fu)2#YnKRI*tW_@eG7uE&^_I$fi^bkH3(Tqa!*+*?hgeJqkS!J$v-8!pysBeoxbo)g=y0UHiG)T6VO*|v&;iE~PAtYjb$RO}^O_wgx8c1HggoEX(2v|qbkP-u#2_02QJ-UgA5$k0I^|UR?H_+K~Y7)Owd8EhPs`w9rAXCtJh>NbHq-oiG1Wp~3n`f*=z=+fi!dUo^`g zj*z&l*Fb0zROH}hDe%JjJ2ZNZJiA!tE~e6Xzd(8n#yMTypprjMB7%Q&J$fI`otud#FL_9>Y$=Xwqgxi#Xm5 zrW%6gpfb(*zF$S0MnQn0r=Qku54|A%oDc`4HaJORSwLD9(5zYtX42ss!=G_ zXsr+^KY>!`;Yhr?3_^8cnacL}68tvD_w?6&!`eZ3+wPzM)}q7%9X8uBNz9{Q(LH4f zoOIQL2tf2}RWfs^@A%u*WM;xf)9A{fs~$(o4?RTaE+c9Gl=<$L(CG}vvx@MG9w(4L zZboR$U#TgjZT6-B^{S1LpB&n|F-t_r)~i7-fsQU*xh%Q+unNfbUig+Yd*Rv*Y;%A5 zh_ZGry4jupb$!&!TuTciKF8e+KP}5FR_dx?39Y?{#;F?Z9$=h#o))yv*%W30v~j;x zUN?eO-DO5qJ>z@E)E&FV?hSuZiV#ix;~4dPOER4!<}}>jAc2kA8%h~ac2|#76UrlJ zN88LIZPN93kb z9~s6);`qH(ciL^u(%&t-Fk{Y$vl#Zi5d;_7W<&7C!L?@V^*`o@sNCuEtcS8*a(e(S zY+qC`_FNw9i_{K|=HI5a*yJ%rAsJu-T~BG@v7_omV4e&mLnA)8skrfyD`ly3!N;*< z*SL(oOP2^FJ{vile#HXR<0$+1rm9|}&o#{PQ?IH+l^(V5O&~8PrUnK3lV_{~yhG;! z?MKphU*mXQ&%)%I=V>7qwq}@Wu~nqDYv2ts7d1A+=tk)MJzsdhZT5>Xu$0e{u;-9$ ztV6&C)SRJ1>CTGsS5}3uOSyW9;Wn4j*mM>wKB}I0*m}IKd_umzjuO5 z`j8Y`Uwi4z0s~o6p{18*>#;&xT1?Jy&g{lK34$o)VNaWiuXcnzoAUaw#WeENa^Yc6*Z5&5YO z^EyyDsYlV#uy?G^BLLdBeV5Z}MkRjw_NPx&xv#Y#r{~LU&u^%a(W&X5Jk{wLcvW{K zKYQm0H5)(ml47;N?_*Iy`K<`|?p5Ikl)3*I?RA+8`QmFCV(zp$9qG+{U@2s4zBMQm zwJ;YD9OUTbj_=A*ov#Hrn~}Z2S$scHwa{L3pJTSCG5)SoKwxnTD)sdARP_IT0N`MI)}|KY GL;4qcvGykb 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 index 62248c617e42ef89aa86a425f6e98f10abf95dde..254fb3fca749c9b16c724dcc3e481c255c63ad51 100644 GIT binary patch delta 4418 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 delta 5135 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 diff --git a/Canary/Canary/Assets.xcassets/CameraButton.imageset/Contents.json b/Canary/Canary/Assets.xcassets/CameraButton.imageset/Contents.json deleted file mode 100644 index eff3b1dec..000000000 --- a/Canary/Canary/Assets.xcassets/CameraButton.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "camera_stroke.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "camera_stroke@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "camera_stroke@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Canary/Canary/Assets.xcassets/CameraButton.imageset/camera_stroke.png b/Canary/Canary/Assets.xcassets/CameraButton.imageset/camera_stroke.png deleted file mode 100644 index 18441923fb5d2cb412b505947ecc0c345aafe03d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 471 zcmV;|0Vw{7P)?E0ee-b@#x$K>D&$;tvnB_mC(~x%!4>1$J-{Td&{)-{Hc#1<5 z5rg+sjLC|g$qautv`(Op z??HHp&4?!3zlY~|5xqrd^;6PTEUY&WtiyU}Z87?0gPNvk!yWyI*o;~g2B|?2c_Kqx z#0Mjg`c()}Z|3`7?&M-9+q^Mu*WET#fvzp-2^er<&yscJKg8 zxROrL0_Ahl50jnKggG2I`@Xl0gV4%OxrEu6pwJE-TsFMEt1WxakH09VbPQg1XiWeB N002ovPDHLkV1hWO+JOK7 diff --git a/Canary/Canary/Assets.xcassets/CameraButton.imageset/camera_stroke@2x.png b/Canary/Canary/Assets.xcassets/CameraButton.imageset/camera_stroke@2x.png deleted file mode 100644 index bdfbae04be45d14bc8463c10e92c87ac55a26d14..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1000 zcmV>P)_C$8t@T8^d$H|LJlIR z2P5Jw8e#%L3|=CLPe@{1cRf^1rqh|(%uG*qFZ)A7Pj^-IUo+iZ)jd!~8BUln4K{ZH zTY$~LTx}Z$-UHph>ofs9nlij->EiAg4Mu40I3e(6i^SW)l#o*9RczKYOAPN0t_WjuS^QL>l$e8X z0pT`gcG^?On`sBXxHwP)G#SrlftL~VUaG8zCgN=t6X4>&5u@G@w8hYELr!Huwqn7>fo(?pK?41U zA+}<{#esQ7y+2ec93PE(by5;84*YY_wUL`zBp5CZm^n$D$Qq(?lu`zT@+(#JV)F*T7HY=vSqJtw_|& zQvB(bL-@7elO9!$eg3n?x^w!El6?21+T z2>6Q=_Er}FmyuiHrC9foMMMVpirkW4z?wb~9tVB`caej}a!TvK7bJC`M81qNQow(k WSwr)afBX#q00001$$Wzej2x@cgQuKozCKcbOjxB?*R(?4Eni_xnBH z_ue_@`#t9lDpaUYp+d=0m7?x?U=gqaxKppE_1-MtSKxc#GvEikPMk~{N`aezR$#f7 z2H<9L9dr)(5x4_52D}fPPu$XkX#u){Vc_2)%doa>N!;oLXa@FDyiy zo^!HgOl3R8db-+|aJ5%8rpr7DRe-h|I zoL8SmPQ549zaA)2CmPU}fRNDs0?N!|+Pf#b$B2#-@T2)q%^Glj` z%!Yq~taWVBpfXLm!p6L!D0Gls4734{=-)x@8u5S4zX4VPzeO=H54Qk^fO|ak)4=I~ zHre&m0I*O0j?L4+2+^kEKY=D2Mg-8LI-=M%qJ4|ShKS0uWo=vo3Seza8#hE%d@uQ@ z*06FUs^SP7iDBm?(pMuv!DS+`f$w(7&LS=Oq5v#`ZLwCf>eGA$|5y%+kCjvN@$k!F@?9qnM=%cowm*`_iQBS{CX0EUn(;M@r9=W5#_m6ZrdIcPO-7#VBwaal_~mZ+@N7VHWoi7{w7 z&;wi-aQ^@@5PyvXkN`IV%a8%wWm;~9&ieC40IStgM_hC zpEb!iMYNTBr$^33q|ZKF|HNKFW-Kq;fk%;iP8f&u+3kjVjjAJYY7kQJ);Q{NH8 zt{nz>QxuMmhtqi+lTnQoQpw2mtW z-H$(X7zB=5kbM;R$WymKby~-jgO(ZU4p@|L8r)4fW8=y}i#*rk$T=Tde6BKQ(qK{k z62>^|7y67aXDZrb!8hWgil zO%dd6(zagqt1c(F&Ii2&Trkw{1oj|%>0vC?XH8qf7gUEcT&G-rLbi9_G2B~+Z2Now zyoVf$$c7KIk*WMC;0ffktBLih!x>5Bq`Q$t;+uwh^MMz%i~~Pw{jG?T^06LyJ;5nS zZ5-}Fru{C0_qAqYPS1K71)fJvwK^xseX(BPA!IiqDm^MA5$8biJFCZ$!=P`5Q=e7S-p=HLcx6ra*!#dc{QI=%oyr`UVpg{pmb1z?HLuMv+5U2UE7CLWK$yD#YbKMR9(i(XtH@00000NkvXXu0mjf D-}lyB diff --git a/Canary/Canary/Assets.xcassets/Mraid.imageset/Contents.json b/Canary/Canary/Assets.xcassets/ReleaseTesting.imageset/Contents.json similarity index 100% rename from Canary/Canary/Assets.xcassets/Mraid.imageset/Contents.json rename to Canary/Canary/Assets.xcassets/ReleaseTesting.imageset/Contents.json diff --git a/Canary/Canary/Assets.xcassets/Mraid.imageset/Mraid.png b/Canary/Canary/Assets.xcassets/ReleaseTesting.imageset/Mraid.png similarity index 100% rename from Canary/Canary/Assets.xcassets/Mraid.imageset/Mraid.png rename to Canary/Canary/Assets.xcassets/ReleaseTesting.imageset/Mraid.png diff --git a/Canary/Canary/Assets.xcassets/Mraid.imageset/Mraid@2x.png b/Canary/Canary/Assets.xcassets/ReleaseTesting.imageset/Mraid@2x.png similarity index 100% rename from Canary/Canary/Assets.xcassets/Mraid.imageset/Mraid@2x.png rename to Canary/Canary/Assets.xcassets/ReleaseTesting.imageset/Mraid@2x.png diff --git a/Canary/Canary/Assets.xcassets/Mraid.imageset/Mraid@3x.png b/Canary/Canary/Assets.xcassets/ReleaseTesting.imageset/Mraid@3x.png similarity index 100% rename from Canary/Canary/Assets.xcassets/Mraid.imageset/Mraid@3x.png rename to Canary/Canary/Assets.xcassets/ReleaseTesting.imageset/Mraid@3x.png diff --git a/Canary/Canary/Base.lproj/Main.storyboard b/Canary/Canary/Base.lproj/Main.storyboard index 92dc26769..f8b6c9d28 100644 --- a/Canary/Canary/Base.lproj/Main.storyboard +++ b/Canary/Canary/Base.lproj/Main.storyboard @@ -62,17 +62,19 @@ - + - + - + + + @@ -120,7 +122,7 @@ - + @@ -405,31 +407,210 @@ - + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + diff --git a/Canary/Canary/Base/AdUnit.swift b/Canary/Canary/Base/AdUnit.swift index b9b14b52a..898a9cad7 100644 --- a/Canary/Canary/Base/AdUnit.swift +++ b/Canary/Canary/Base/AdUnit.swift @@ -18,6 +18,7 @@ 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` @@ -100,7 +101,7 @@ public class AdUnit : NSObject, Codable { 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 formatString: String = queryItems.filter({ $0.name == AdUnitKey.Format }).first?.value, let format = AdFormat(rawValue: formatString) else { return nil } @@ -112,4 +113,36 @@ public class AdUnit : NSObject, Codable { 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 + } } diff --git a/Canary/Canary/Base/AdUnitDataSource.swift b/Canary/Canary/Base/AdUnitDataSource.swift index 9b9cd68d0..fdd2f3d83 100644 --- a/Canary/Canary/Base/AdUnitDataSource.swift +++ b/Canary/Canary/Base/AdUnitDataSource.swift @@ -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 58efb6289..88f73f1a6 100644 --- a/Canary/Canary/Base/AdUnitTableViewController.swift +++ b/Canary/Canary/Base/AdUnitTableViewController.swift @@ -9,10 +9,15 @@ 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 cameraButton: UIBarButtonItem? + @IBOutlet var addButton: UIBarButtonItem? // Table data source. fileprivate var dataSource: AdUnitDataSource? = nil @@ -40,12 +45,6 @@ class AdUnitTableViewController: UIViewController { tableView.delegate = self } - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - - showCameraButtonBasedOnCameraAccess() - } - // MARK: - Ad Loading public func loadAd(with adUnit: AdUnit) { @@ -69,26 +68,33 @@ class AdUnitTableViewController: UIViewController { tableView.reloadData() } - // MARK: - Camera Button + // MARK: - IBActions - // Shows or hides the camera button based on whether the user has provided camera access. - fileprivate func showCameraButtonBasedOnCameraAccess() { - guard let cameraButton = cameraButton else { + @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 } - let cameraAuthorizationStatus = AVCaptureDevice.authorizationStatus(for: QRCodeCameraInterfaceViewController.defaultMediaType) + // If camera use is authorized, show action sheet with a choice between manual interface and camera interface - switch cameraAuthorizationStatus { - case .authorized: - navigationItem.rightBarButtonItem = cameraButton - case .denied: - navigationItem.rightBarButtonItem = nil - case .restricted: - navigationItem.rightBarButtonItem = nil - case .notDetermined: - navigationItem.rightBarButtonItem = cameraButton - } + 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)) + + present(actionSheet, animated: true, completion: nil) } } @@ -109,6 +115,7 @@ extension AdUnitTableViewController: UITableViewDataSource { return UITableViewCell() } + adUnitCell.accessibilityIdentifier = adUnit.id adUnitCell.refresh(adUnit: adUnit) adUnitCell.setNeedsLayout() return adUnitCell diff --git a/Canary/Canary/Base/FilterableAdUnitTableViewController.swift b/Canary/Canary/Base/FilterableAdUnitTableViewController.swift new file mode 100644 index 000000000..cb6e79ab5 --- /dev/null +++ b/Canary/Canary/Base/FilterableAdUnitTableViewController.swift @@ -0,0 +1,71 @@ +// +// 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 + + if #available(iOS 11.0, *) { + navigationItem.searchController = searchController + } + else { + 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 d4186de77..9eeae940c 100644 --- a/Canary/Canary/Cells/AdActionsTableViewCell.swift +++ b/Canary/Canary/Cells/AdActionsTableViewCell.swift @@ -27,13 +27,30 @@ class AdActionsTableViewCell: UITableViewCell { willShowAd?(sender) } + // MARK: - Life Cycle + + override func awakeFromNib() { + super.awakeFromNib() + + // Accessibility + loadAdButton.accessibilityIdentifier = AccessibilityIdentifier.adActionsLoad + showAdButton.accessibilityIdentifier = AccessibilityIdentifier.adActionsShow + } + // MARK: - Refreshing - func refresh(loadAdHandler: AdActionHandler? = nil, showAdHandler: AdActionHandler? = nil) { + func refresh(isAdLoading: Bool = false, loadAdHandler: AdActionHandler? = nil, showAdHandler: AdActionHandler? = nil, showButtonEnabled: Bool = false) { 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 + // Showing an ad is optional. Hide it if there is no show handler. showAdButton.isHidden = (showAdHandler == nil) + showAdButton.isEnabled = showButtonEnabled } } diff --git a/Canary/Canary/Cells/CollapsibleAdapterInfoTableViewCell.swift b/Canary/Canary/Cells/CollapsibleAdapterInfoTableViewCell.swift new file mode 100644 index 000000000..a1add4f3c --- /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 + +class CollapsibleAdapterInfoTableViewCell: UITableViewCell { + // 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/Extensions/UIAlertController+Picker.swift b/Canary/Canary/Extensions/UIAlertController+Picker.swift new file mode 100644 index 000000000..7d7b6077d --- /dev/null +++ b/Canary/Canary/Extensions/UIAlertController+Picker.swift @@ -0,0 +1,51 @@ +// +// 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.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0), + pickerView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0), + ] + 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/Formats/AdDataSource.swift b/Canary/Canary/Formats/AdDataSource.swift index fc1f69264..1ef0504ae 100644 --- a/Canary/Canary/Formats/AdDataSource.swift +++ b/Canary/Canary/Formats/AdDataSource.swift @@ -105,6 +105,16 @@ 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 } + /** Retrieves the display status for the event. - Parameter event: Status event. diff --git a/Canary/Canary/Formats/AdFormat.swift b/Canary/Canary/Formats/AdFormat.swift index 9522da2e6..ccca6821c 100644 --- a/Canary/Canary/Formats/AdFormat.swift +++ b/Canary/Canary/Formats/AdFormat.swift @@ -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 */ diff --git a/Canary/Canary/Formats/AdTableViewController.swift b/Canary/Canary/Formats/AdTableViewController.swift index 2e4a4664d..53c18cf9a 100644 --- a/Canary/Canary/Formats/AdTableViewController.swift +++ b/Canary/Canary/Formats/AdTableViewController.swift @@ -178,9 +178,11 @@ extension AdTableViewController: UITableViewDataSource { return UITableViewCell() } - cell.name.text = "ID" - cell.adUnitId.text = dataSource.adUnit.id + cell.accessibilityIdentifier = dataSource.adUnit.id cell.accessoryType = .none + cell.adUnitId.text = dataSource.adUnit.id + cell.name.text = "ID" + return cell } @@ -196,6 +198,7 @@ extension AdTableViewController: UITableViewDataSource { self?.dataSource.adUnit.keywords = keywords } + cell.accessibilityIdentifier = dataSource.adUnit.keywords return cell } @@ -207,10 +210,11 @@ extension AdTableViewController: UITableViewDataSource { return UITableViewCell() } - cell.refresh(title: "User Data Keywords", text: dataSource.adUnit.keywords) { [weak self] (piiKeywords: String?) in + 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 } @@ -226,6 +230,7 @@ extension AdTableViewController: UITableViewDataSource { self?.dataSource.adUnit.customData = customData } + cell.accessibilityIdentifier = dataSource.adUnit.customData return cell } @@ -239,9 +244,11 @@ extension AdTableViewController: UITableViewDataSource { return UITableViewCell() } + 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(isAdLoading: isAdLoading, loadAdHandler: loadHandler, showAdHandler: showHandler, showButtonEnabled: showEnabled) return cell } @@ -261,6 +268,7 @@ extension AdTableViewController: UITableViewDataSource { let status = dataSource.status(for: event) cell.update(status: status.title, error: status.message, isHighlighted: status.isHighlighted) + cell.accessibilityIdentifier = status.title return cell } } diff --git a/Canary/Canary/Formats/BannerAdDataSource.swift b/Canary/Canary/Formats/BannerAdDataSource.swift index 3b4568616..e212be537 100644 --- a/Canary/Canary/Formats/BannerAdDataSource.swift +++ b/Canary/Canary/Formats/BannerAdDataSource.swift @@ -116,6 +116,16 @@ class BannerAdDataSource: NSObject, AdDataSource { return adView } + /** + Queries if the data source has an ad loaded. + */ + private(set) var isAdLoaded: Bool = false + + /** + Queries if the data source currently requesting an ad. + */ + private(set) var isAdLoading: Bool = false + /** Retrieves the display status for the event. - Parameter event: Status event. @@ -151,6 +161,11 @@ class BannerAdDataSource: NSObject, AdDataSource { // MARK: - Ad Loading private func loadAd() { + guard !isAdLoading else { + return + } + + isAdLoading = true clearStatus { [weak self] in self?.delegate?.adPresentationTableView.reloadData() } @@ -171,6 +186,8 @@ extension BannerAdDataSource: MPAdViewDelegate { } func adViewDidLoadAd(_ view: MPAdView!) { + isAdLoading = false + isAdLoaded = true setStatus(for: .didLoad) { [weak self] in if let strongSelf = self { strongSelf.loadFailureReason = nil @@ -180,6 +197,8 @@ extension BannerAdDataSource: MPAdViewDelegate { } func adViewDidFail(toLoadAd view: MPAdView!) { + isAdLoading = false + isAdLoaded = false setStatus(for: .didFailToLoad) { [weak self] in if let strongSelf = self { // The banner load failure doesn't give back an error reason; assume clear response diff --git a/Canary/Canary/Formats/BannerAdViewController.swift b/Canary/Canary/Formats/BannerAdViewController.swift index 07eb84788..796293a9d 100644 --- a/Canary/Canary/Formats/BannerAdViewController.swift +++ b/Canary/Canary/Formats/BannerAdViewController.swift @@ -37,6 +37,11 @@ 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 { + header.heightAnchor.constraint(equalToConstant: MOPUB_BANNER_SIZE.height).isActive = true + } } } diff --git a/Canary/Canary/Formats/InterstitialAdDataSource.swift b/Canary/Canary/Formats/InterstitialAdDataSource.swift index d1fa1a531..556bd5b69 100644 --- a/Canary/Canary/Formats/InterstitialAdDataSource.swift +++ b/Canary/Canary/Formats/InterstitialAdDataSource.swift @@ -117,6 +117,18 @@ class InterstitialAdDataSource: NSObject, AdDataSource { return nil } + /** + Queries if the data source has an ad loaded. + */ + var isAdLoaded: Bool { + return interstitialAd.ready + } + + /** + Queries if the data source currently requesting an ad. + */ + private(set) var isAdLoading: Bool = false + /** Retrieves the display status for the event. - Parameter event: Status event. @@ -152,6 +164,11 @@ class InterstitialAdDataSource: NSObject, AdDataSource { // MARK: - Ad Loading private func loadAd() { + guard !isAdLoading else { + return + } + + isAdLoading = true clearStatus { [weak self] in self?.delegate?.adPresentationTableView.reloadData() } @@ -177,6 +194,7 @@ extension InterstitialAdDataSource: MPInterstitialAdControllerDelegate { // MARK: - MPInterstitialAdControllerDelegate func interstitialDidLoadAd(_ interstitial: MPInterstitialAdController!) { + isAdLoading = false setStatus(for: .didLoad) { [weak self] in if let strongSelf = self { strongSelf.loadFailureReason = nil @@ -186,6 +204,7 @@ extension InterstitialAdDataSource: MPInterstitialAdControllerDelegate { } func interstitialDidFail(toLoadAd interstitial: MPInterstitialAdController!) { + isAdLoading = false setStatus(for: .didFailToLoad) { [weak self] in if let strongSelf = self { // The interstitial load failure doesn't give back an error reason; assume clear response diff --git a/Canary/Canary/Formats/LeaderboardAdViewController.swift b/Canary/Canary/Formats/LeaderboardAdViewController.swift index 6a739fb69..14a5f625a 100644 --- a/Canary/Canary/Formats/LeaderboardAdViewController.swift +++ b/Canary/Canary/Formats/LeaderboardAdViewController.swift @@ -37,6 +37,11 @@ class LeaderboardAdViewController: AdTableViewController { // Invoke the super class to finish loading the view. super.viewDidLoad() + + // Fix the leaderboard height so that Auto Layout will correctly resize the table header. + if let header = tableView.tableHeaderView { + header.heightAnchor.constraint(equalToConstant: MOPUB_LEADERBOARD_SIZE.height).isActive = true + } } } diff --git a/Canary/Canary/Formats/MediumRectangleAdViewController.swift b/Canary/Canary/Formats/MediumRectangleAdViewController.swift index 6c0a72a89..44eacb697 100644 --- a/Canary/Canary/Formats/MediumRectangleAdViewController.swift +++ b/Canary/Canary/Formats/MediumRectangleAdViewController.swift @@ -37,6 +37,11 @@ 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 { + header.heightAnchor.constraint(equalToConstant: MOPUB_MEDIUM_RECT_SIZE.height).isActive = true + } } } diff --git a/Canary/Canary/Formats/NativeAdDataSource.swift b/Canary/Canary/Formats/NativeAdDataSource.swift index 11a693b0f..7681a09cc 100644 --- a/Canary/Canary/Formats/NativeAdDataSource.swift +++ b/Canary/Canary/Formats/NativeAdDataSource.swift @@ -114,6 +114,16 @@ class NativeAdDataSource: BaseNativeAdDataSource, AdDataSource { return nativeAdContainer } + /** + Queries if the data source has an ad loaded. + */ + private(set) var isAdLoaded: Bool = false + + /** + Queries if the data source currently requesting an ad. + */ + private(set) var isAdLoading: Bool = false + /** Retrieves the display status for the event. - Parameter event: Status event. @@ -198,6 +208,11 @@ class NativeAdDataSource: BaseNativeAdDataSource, AdDataSource { } private func loadAd() { + guard !isAdLoading else { + return + } + + isAdLoading = true clearStatus { [weak self] in self?.delegate?.adPresentationTableView.reloadData() } @@ -207,8 +222,12 @@ 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.isAdLoaded = false strongSelf.loadFailureReason = error?.localizedDescription strongSelf.setStatus(for: .didFailToLoad) { [weak self] in self?.delegate?.adPresentationTableView.reloadData() @@ -223,6 +242,7 @@ class NativeAdDataSource: BaseNativeAdDataSource, AdDataSource { strongSelf.addToAdContainer(view: nativeAdView) } + strongSelf.isAdLoaded = true strongSelf.setStatus(for: .didLoad) { [weak self] in self?.delegate?.adPresentationTableView.reloadData() } diff --git a/Canary/Canary/Formats/NativeAdView.swift b/Canary/Canary/Formats/NativeAdView.swift index 2c57db5ea..a15970738 100644 --- a/Canary/Canary/Formats/NativeAdView.swift +++ b/Canary/Canary/Formats/NativeAdView.swift @@ -51,6 +51,9 @@ 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 addSubview(view) diff --git a/Canary/Canary/Formats/RewardedAdDataSource.swift b/Canary/Canary/Formats/RewardedAdDataSource.swift index a679af030..86ddfa292 100644 --- a/Canary/Canary/Formats/RewardedAdDataSource.swift +++ b/Canary/Canary/Formats/RewardedAdDataSource.swift @@ -133,6 +133,18 @@ class RewardedAdDataSource: NSObject, AdDataSource { return nil } + /** + Queries if the data source has an ad loaded. + */ + var isAdLoaded: Bool { + return MPRewardedVideo.hasAdAvailable(forAdUnitID: adUnit.id) + } + + /** + Queries if the data source currently requesting an ad. + */ + private(set) var isAdLoading: Bool = false + /** Retrieves the display status for the event. - Parameter event: Status event. @@ -190,51 +202,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,6 +217,11 @@ class RewardedAdDataSource: NSObject, AdDataSource { // MARK: - Ad Loading private func loadAd() { + guard !isAdLoading else { + return + } + + isAdLoading = true clearStatus { [weak self] in self?.delegate?.adPresentationTableView.reloadData() } @@ -313,6 +293,7 @@ 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 @@ -323,6 +304,7 @@ extension RewardedAdDataSource: MPRewardedVideoDelegate { } func rewardedVideoAdDidFailToLoad(forAdUnitID adUnitID: String!, error: Error!) { + isAdLoading = false setStatus(for: .didFailToLoad) { [weak self] in if let strongSelf = self { strongSelf.loadFailureReason = error.localizedDescription diff --git a/Canary/Canary/Info.plist b/Canary/Canary/Info.plist index d4775f945..a832c817e 100644 --- a/Canary/Canary/Info.plist +++ b/Canary/Canary/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 5.5.0 + 5.6.0 CFBundleURLTypes @@ -28,7 +28,7 @@ CFBundleVersion - 5.5.0 + 5.6.0 LSRequiresIPhoneOS NSAppTransportSecurity @@ -69,5 +69,7 @@ NSCameraUsageDescription The MoPub Canary app uses the camera for the ease-of-use feature of reading MoPub ad QR codes. + ITSAppUsesNonExemptEncryption + diff --git a/Canary/Canary/LoggingLevel/LogingLevelMenuDataSource.swift b/Canary/Canary/LoggingLevel/LogingLevelMenuDataSource.swift index fa8aefcb7..fda27a940 100644 --- a/Canary/Canary/LoggingLevel/LogingLevelMenuDataSource.swift +++ b/Canary/Canary/LoggingLevel/LogingLevelMenuDataSource.swift @@ -14,11 +14,11 @@ fileprivate enum LoggingLevelMenuOptions: String { case info = "Informational" case none = "None" - var logLevel: MPLogLevel { + var logLevel: MPBLogLevel { switch self { - case .debug: return MPLogLevel.debug - case .info: return MPLogLevel.info - case .none: return MPLogLevel.none + case .debug: return MPBLogLevel.debug + case .info: return MPBLogLevel.info + case .none: return MPBLogLevel.none } } } @@ -51,7 +51,7 @@ extension LogingLevelMenuDataSource: MenuDisplayable { func cell(forItem index: Int, inTableView tableView: UITableView) -> UITableViewCell { let cell: BasicMenuTableViewCell = basicMenuCell(inTableView: tableView) let item: LoggingLevelMenuOptions = items[index] - let currentLogLevel: MPLogLevel = MPLogging.consoleLogLevel + let currentLogLevel: MPBLogLevel = MPLogging.consoleLogLevel cell.accessoryType = (currentLogLevel == item.logLevel ? .checkmark : .none) cell.title.text = item.rawValue @@ -71,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] + 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 7a44184b1..27eeb7d3b 100644 --- a/Canary/Canary/MainTabBarController.swift +++ b/Canary/Canary/MainTabBarController.swift @@ -27,6 +27,7 @@ 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) diff --git a/Canary/Canary/ManualEntryInterfaceViewController.swift b/Canary/Canary/ManualEntryInterfaceViewController.swift new file mode 100644 index 000000000..2f4d9a762 --- /dev/null +++ b/Canary/Canary/ManualEntryInterfaceViewController.swift @@ -0,0 +1,124 @@ +// +// 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 { + /** + The shared app delegate. This is used to access deep linking functionality contained in app delegate. + */ + let appDelegate = UIApplication.shared.delegate as? AppDelegate + + 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) + } + + fileprivate func dismissAndShowAd(shouldSave: Bool) { + guard let appDelegate = appDelegate 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. + navigationController?.dismiss(animated: true, completion: { + _ = appDelegate.openMoPubAdUnit(adUnit: adUnit, + onto: appDelegate.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 4c68baa9f..20bd335ba 100644 --- a/Canary/Canary/Menu/MenuDataSource.swift +++ b/Canary/Canary/Menu/MenuDataSource.swift @@ -28,6 +28,7 @@ class MenuDataSource { // MARK: - Initialization init() { + add(menu: AdapterVersionsMenuDataSource()) add(menu: LogingLevelMenuDataSource()) } @@ -46,6 +47,13 @@ class MenuDataSource { sources[menu.title] = menu } + /** + Updates all data sources if needed. + */ + func updateIfNeeded() { + sources.values.forEach({ $0.updateIfNeeded() }) + } + // MARK: - Accessors func cell(forIndexPath indexPath: IndexPath, inTableView tableView: UITableView) -> UITableViewCell { @@ -60,12 +68,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 e74e4f5c7..746b47a57 100644 --- a/Canary/Canary/Menu/MenuDisplayable.swift +++ b/Canary/Canary/Menu/MenuDisplayable.swift @@ -37,11 +37,17 @@ 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 indexPath: IndexPath, inTableView tableView: UITableView, presentFrom viewController: UIViewController) -> Bool + + /** + Updates the data source if needed. */ - func didSelect(itemAt index: Int, inTableView tableView: UITableView, presentFrom viewController: UIViewController) -> Swift.Void + func updateIfNeeded() -> Swift.Void // MARK: - Menu Cells @@ -59,7 +65,11 @@ extension MenuDisplayable { return false } - func didSelect(itemAt index: Int, inTableView tableView: UITableView, presentFrom viewController: UIViewController) -> Swift.Void { + func didSelect(itemAt indexPath: IndexPath, inTableView tableView: UITableView, presentFrom viewController: UIViewController) -> Bool { + return true + } + + func updateIfNeeded() -> Swift.Void { return } diff --git a/Canary/Canary/Menu/MenuViewController.swift b/Canary/Canary/Menu/MenuViewController.swift index d0232e8e7..7e2562ec4 100644 --- a/Canary/Canary/Menu/MenuViewController.swift +++ b/Canary/Canary/Menu/MenuViewController.swift @@ -43,6 +43,13 @@ class MenuViewController: UIViewController { func add(menu: MenuDisplayable) { dataSource.add(menu: menu) } + + /** + Updates the data source if needed. + */ + func updateIfNeeded() { + dataSource.updateIfNeeded() + } } extension MenuViewController: UITableViewDataSource { @@ -75,6 +82,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 { @@ -84,9 +96,11 @@ 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() + let presentingController = container.mainTabBarController { + let shouldCloseMenu: Bool = dataSource.didSelect(itemAtIndexPath: indexPath, inTableView: tableView, presentingFrom: presentingController) + if shouldCloseMenu { + container.closeMenu() + } } } } diff --git a/Canary/Canary/RoundedButton.swift b/Canary/Canary/RoundedButton.swift index b0f96cb79..4ddf5d867 100644 --- a/Canary/Canary/RoundedButton.swift +++ b/Canary/Canary/RoundedButton.swift @@ -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/Gemfile b/Gemfile new file mode 100644 index 000000000..e4b5be5c3 --- /dev/null +++ b/Gemfile @@ -0,0 +1,11 @@ +# Autogenerated by fastlane +# +# Ensure this file is checked in to source control! + +source "https://rubygems.org" + +gem 'fastlane' +gem 'cocoapods' + +plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile') +eval_gemfile(plugins_path) if File.exist?(plugins_path) diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 000000000..f9de1a61a --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,228 @@ +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.6.0) + public_suffix (>= 2.0.2, < 4.0) + apktools (0.7.2) + rubyzip (~> 1.2.1) + atomos (0.1.3) + aws-sdk (2.11.212) + aws-sdk-resources (= 2.11.212) + aws-sdk-core (2.11.212) + aws-sigv4 (~> 1.0) + jmespath (~> 1.0) + aws-sdk-resources (2.11.212) + aws-sdk-core (= 2.11.212) + aws-sigv4 (1.0.3) + 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) + digest-crc (0.4.1) + domain_name (0.5.20180417) + unf (>= 0.0.5, < 1.0.0) + dotenv (2.6.0) + emoji_regex (1.0.1) + escape (0.0.4) + excon (0.62.0) + faraday (0.15.4) + multipart-post (>= 1.2, < 3) + faraday-cookie_jar (0.0.6) + faraday (>= 0.7.4) + http-cookie (~> 1.0.0) + faraday_middleware (0.13.0) + faraday (>= 0.7.4, < 1.0) + fastimage (2.1.5) + fastlane (2.117.1) + CFPropertyList (>= 2.3, < 4.0.0) + addressable (>= 2.3, < 3.0.0) + babosa (>= 1.0.2, < 2.0.0) + bundler (>= 1.12.0, < 3.0.0) + colored + commander-fastlane (>= 4.4.6, < 5.0.0) + dotenv (>= 2.1.1, < 3.0.0) + emoji_regex (>= 0.1, < 2.0) + 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.24.0) + google-cloud-storage (>= 1.15.0, < 2.0.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.2, < 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.6.0, < 2.0.0) + xcpretty (~> 0.3.0) + xcpretty-travis-formatter (>= 0.0.3) + fastlane-plugin-aws_s3 (1.5.0) + apktools (~> 0.7) + aws-sdk (~> 2.3) + mime-types (~> 3.1) + fourflusher (2.0.1) + fuzzy_match (2.0.4) + gh_inspector (1.1.3) + google-api-client (0.23.9) + 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) + signet (~> 0.9) + google-cloud-core (1.2.7) + google-cloud-env (~> 1.0) + google-cloud-env (1.0.5) + faraday (~> 0.11) + google-cloud-storage (1.15.0) + digest-crc (~> 0.4) + google-api-client (~> 0.23) + google-cloud-core (~> 1.2) + googleauth (~> 0.6.2) + googleauth (0.6.7) + faraday (~> 0.12) + jwt (>= 1.4, < 3.0) + memoist (~> 0.16) + multi_json (~> 1.11) + os (>= 0.9, < 2.0) + 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) + jmespath (1.4.0) + json (2.1.0) + jwt (2.1.0) + memoist (0.16.0) + mime-types (3.2.2) + mime-types-data (~> 3.2015) + mime-types-data (3.2018.0812) + 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.6) + nap (1.1.0) + naturally (2.2.0) + netrc (0.11.0) + os (1.0.0) + plist (3.5.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.2) + security (0.1.3) + signet (0.11.0) + 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.6.0) + tty-screen (0.6.5) + tty-spinner (0.9.0) + tty-cursor (~> 0.6.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.1) + word_wrap (1.0.0) + xcodeproj (1.8.0) + CFPropertyList (>= 2.3.3, < 4.0) + atomos (~> 0.1.3) + claide (>= 1.0.2, < 2.0) + colored2 (~> 3.1) + nanaimo (~> 0.2.6) + xcpretty (0.3.0) + rouge (~> 2.0.7) + xcpretty-travis-formatter (1.0.0) + xcpretty (~> 0.2, >= 0.0.7) + +PLATFORMS + ruby + +DEPENDENCIES + cocoapods + fastlane + fastlane-plugin-aws_s3 + +BUNDLED WITH + 1.16.1 diff --git a/MoPubResources/Info.plist b/MoPubResources/Info.plist index e53aaba71..29af72262 100644 --- a/MoPubResources/Info.plist +++ b/MoPubResources/Info.plist @@ -13,7 +13,7 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 5.5.0 + 5.6.0 CFBundleSignature ???? CFBundleSupportedPlatforms @@ -21,7 +21,7 @@ iPhoneOS CFBundleVersion - 5.5.0 + 5.6.0 NSHumanReadableCopyright Copyright 2019 Twitter Inc. All rights reserved. NSPrincipalClass diff --git a/MoPubSDK.xcodeproj/project.pbxproj b/MoPubSDK.xcodeproj/project.pbxproj index 8d3a0a407..6b34961ed 100644 --- a/MoPubSDK.xcodeproj/project.pbxproj +++ b/MoPubSDK.xcodeproj/project.pbxproj @@ -212,6 +212,18 @@ 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 */; }; + 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 */; }; 2AA73B9C1FCF7EDB001FB787 /* MOPUBNativeVideoAdConfigValuesTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AA73B9B1FCF7EDB001FB787 /* MOPUBNativeVideoAdConfigValuesTests.m */; }; @@ -278,7 +290,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, ); }; }; - 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 */; }; @@ -637,6 +649,8 @@ BC11922A207D6D06005DF26E /* MPConsentManagerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = BC119229207D6D06005DF26E /* MPConsentManagerTests.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 */; }; @@ -823,7 +837,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 /* MPLogger.h in Headers */ = {isa = PBXBuildFile; fileRef = BCAD76A6214AE05B00A1B067 /* MPLogger.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 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 */; }; @@ -935,6 +949,16 @@ 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; }; + 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 = ""; }; @@ -1376,6 +1400,9 @@ 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 = ""; }; @@ -1429,7 +1456,7 @@ 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 = ""; }; @@ -1468,7 +1495,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 /* MPLogger.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPLogger.h; 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 = ""; }; @@ -1617,16 +1644,19 @@ B2FE8FAF206ABFF500593089 /* MPDictionaryAdditionTests.m */, B2FC80CC20993CF200D43EE7 /* MPGeolocationProviderTest.m */, BC7FF69C20D07C4E003B1842 /* MPHTTPNetworkSessionTests.m */, + BC1920862236F8FC004318D2 /* MPIdentityProviderTests.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 */, - BC8CDD64218A12DD006DE606 /* MPMoPubConfigurationTests.m */, 2A5C4DB71F6B25F20076C08C /* MPNativeAdConfigValuesTests.m */, BCC54C2B1ECFB81000A4FEF0 /* MPNativeAdRequestTests.m */, + 2A89F15D22371ECC00E03010 /* MPRateLimitConfigurationTests.m */, + 2A89F1552237136700E03010 /* MPRateLimitManagerTests.m */, BCCE7A2A20768922003027BA /* MPReachabilityTests.m */, 2AFF1BA61EC289E600495994 /* MPRealTimeTimerTests.m */, 2AF177321E9846A000A600BD /* MPRewardedVideoAdapterTests.m */, @@ -2035,6 +2065,10 @@ BC4370522087C5D6001B86D4 /* MPAdServerKeys.m */, BC926EF320D9753B004ED8F7 /* MPMemoryCache.h */, BC926EF420D9753B004ED8F7 /* MPMemoryCache.m */, + 2A89F1472236DF2200E03010 /* MPRateLimitConfiguration.h */, + 2A89F1482236DF2200E03010 /* MPRateLimitConfiguration.m */, + 2A89F14D2236DF3600E03010 /* MPRateLimitManager.h */, + 2A89F14E2236DF3600E03010 /* MPRateLimitManager.m */, AEF9D500171CA895005AAA5A /* MRAID */, AEF9D513171CA895005AAA5A /* Utility */, A776A5121B5DDE6200095706 /* VAST */, @@ -2349,6 +2383,8 @@ 2AB6301B1E9C2D0C00B0DDC7 /* MPConstants+Testing.m */, 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 */, @@ -2363,6 +2399,10 @@ BCAED2601DF628D400D45480 /* MPMoPubRewardedPlayableCustomEvent+Testing.m */, BCC54C2D1ECFB8A300A4FEF0 /* MPNativeAdRequest+Testing.h */, BCC54C2E1ECFB8A300A4FEF0 /* MPNativeAdRequest+Testing.m */, + 2A89F15A22371E6500E03010 /* MPRateLimitConfiguration+Testing.h */, + 2A89F15B22371E6500E03010 /* MPRateLimitConfiguration+Testing.m */, + 2A89F157223713A100E03010 /* MPRateLimitManager+Testing.h */, + 2A89F158223713A100E03010 /* MPRateLimitManager+Testing.m */, BCAC6F6C1E5D0730002B2656 /* MPRewardedVideo+Testing.h */, BCAC6F6D1E5D0730002B2656 /* MPRewardedVideo+Testing.m */, 2AF1773F1E984AD700A600BD /* MPRewardedVideoAdapter+Testing.h */, @@ -2454,10 +2494,10 @@ BC31026C21598E070007FA69 /* Internal */, BCA762302149B4B100D55A05 /* MPLogEvent.h */, BCA762312149B4B100D55A05 /* MPLogEvent.m */, - BCAD76A6214AE05B00A1B067 /* MPLogger.h */, + BCAD76A6214AE05B00A1B067 /* MPBLogger.h */, AEF9D523171CA895005AAA5A /* MPLogging.h */, AEF9D524171CA895005AAA5A /* MPLogging.m */, - BC79EC231F9810BF00FFC893 /* MPLogLevel.h */, + BC79EC231F9810BF00FFC893 /* MPBLogLevel.h */, ); path = Logging; sourceTree = ""; @@ -2472,6 +2512,7 @@ 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 */, @@ -2511,7 +2552,7 @@ 2AF031F12016B74400909F29 /* MPInterstitialCustomEvent.h in Headers */, 2AF031F22016B74400909F29 /* MPInterstitialCustomEventDelegate.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 */, @@ -2621,7 +2662,7 @@ 2AF032582016B78800909F29 /* MRBundleManager.h in Headers */, 2AF032592016B78800909F29 /* MRController.h in Headers */, 2AF0325A2016B78800909F29 /* MRVideoPlayerManager.h in Headers */, - BCAD76A7214AE1A600A1B067 /* MPLogger.h in Headers */, + BCAD76A7214AE1A600A1B067 /* MPBLogger.h in Headers */, BC437058208A8AC9001B86D4 /* MPBool.h in Headers */, 2AF0325B2016B78800909F29 /* MRCommand.h in Headers */, 2AF0325C2016B78800909F29 /* MRProperty.h in Headers */, @@ -2657,6 +2698,7 @@ 2AF032762016B78800909F29 /* MPVASTCompanionAd.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 */, @@ -2932,9 +2974,11 @@ 2AF177411E984AD700A600BD /* MPRewardedVideoAdapter+Testing.m in Sources */, BC11922A207D6D06005DF26E /* MPConsentManagerTests.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 */, @@ -2960,6 +3004,8 @@ 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 */, @@ -2986,6 +3032,7 @@ 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 */, BC0ADDED207FFBEA000ADEA4 /* MPConsentManager+Testing.m in Sources */, 2A80EAB81E675CCE00D7FDD9 /* MPViewabilityTracker+Testing.m in Sources */, @@ -3003,6 +3050,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 */, @@ -3062,6 +3110,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 */, @@ -3130,6 +3179,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 */, @@ -3258,6 +3308,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 */, @@ -3326,6 +3377,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 */, @@ -3444,6 +3496,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 */, @@ -3492,6 +3545,7 @@ 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 */, @@ -3555,7 +3609,7 @@ CLANG_WARN_SUSPICIOUS_MOVES = YES; CLANG_WARN_UNREACHABLE_CODE = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 5.5.0; + CURRENT_PROJECT_VERSION = 5.6.0; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 4S7XS533V3; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -3596,7 +3650,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 5.5.0; + CURRENT_PROJECT_VERSION = 5.6.0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 4S7XS533V3; ENABLE_NS_ASSERTIONS = NO; @@ -3638,12 +3692,12 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 5.5.0; + CURRENT_PROJECT_VERSION = 5.6.0; DEBUG_INFORMATION_FORMAT = dwarf; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 4S7XS533V3; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 5.5.0; + DYLIB_CURRENT_VERSION = 5.6.0; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_PREPROCESSOR_DEFINITIONS = ( @@ -3662,6 +3716,7 @@ 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) armv7s x86_64 i386"; VERSIONING_SYSTEM = "apple-generic"; @@ -3684,12 +3739,12 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 5.5.0; + CURRENT_PROJECT_VERSION = 5.6.0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 4S7XS533V3; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 5.5.0; + DYLIB_CURRENT_VERSION = 5.6.0; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_NS_ASSERTIONS = NO; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -3708,6 +3763,7 @@ 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) armv7s"; VERSIONING_SYSTEM = "apple-generic"; @@ -3722,7 +3778,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.5.0; + CURRENT_PROJECT_VERSION = 5.6.0; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_GENERATE_DEBUGGING_SYMBOLS = NO; GCC_PREPROCESSOR_DEFINITIONS = ( @@ -3756,7 +3812,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.5.0; + CURRENT_PROJECT_VERSION = 5.6.0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -3783,7 +3839,7 @@ AE515F9A171F1B110086B464 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - CURRENT_PROJECT_VERSION = 5.5.0; + CURRENT_PROJECT_VERSION = 5.6.0; DSTROOT = /tmp/MoPubSDK.dst; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -3803,7 +3859,7 @@ AE515F9B171F1B110086B464 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - CURRENT_PROJECT_VERSION = 5.5.0; + CURRENT_PROJECT_VERSION = 5.6.0; DSTROOT = /tmp/MoPubSDK.dst; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -3920,7 +3976,7 @@ BCA00B951EF47A91006FF762 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - CURRENT_PROJECT_VERSION = 5.5.0; + CURRENT_PROJECT_VERSION = 5.6.0; DSTROOT = /tmp/MoPubSDK.dst; GCC_PRECOMPILE_PREFIX_HEADER = NO; GCC_TREAT_WARNINGS_AS_ERRORS = YES; @@ -3937,7 +3993,7 @@ BCA00B961EF47A91006FF762 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - CURRENT_PROJECT_VERSION = 5.5.0; + CURRENT_PROJECT_VERSION = 5.6.0; DSTROOT = /tmp/MoPubSDK.dst; GCC_PRECOMPILE_PREFIX_HEADER = NO; GCC_TREAT_WARNINGS_AS_ERRORS = YES; diff --git a/MoPubSDK/Internal/Banners/MPBannerAdManager.m b/MoPubSDK/Internal/Banners/MPBannerAdManager.m index 8a3f1a040..7c426ee4f 100644 --- a/MoPubSDK/Internal/Banners/MPBannerAdManager.m +++ b/MoPubSDK/Internal/Banners/MPBannerAdManager.m @@ -138,6 +138,13 @@ - (void)pauseRefreshTimer } } +- (void)resumeRefreshTimer +{ + if ([self.refreshTimer isValid]) { + [self.refreshTimer resume]; + } +} + - (void)stopAutomaticallyRefreshingContents { self.automaticallyRefreshesContents = NO; @@ -215,17 +222,6 @@ - (BOOL)shouldScheduleTimerOnImpressionDisplay { - (void)fetchAdWithConfiguration:(MPAdConfiguration *)configuration { MPLogInfo(@"Banner ad view is fetching ad network type: %@", configuration.networkType); - if (configuration.adType == MPAdTypeUnknown) { - [self didFailToLoadAdapterWithError:[NSError errorWithCode:MOPUBErrorServerError]]; - return; - } - - if (configuration.adType == MPAdTypeInterstitial) { - MPLogInfo(@"Could not load ad: banner object received an interstitial ad unit ID."); - [self didFailToLoadAdapterWithError:[NSError errorWithCode:MOPUBErrorAdapterInvalid]]; - return; - } - if (configuration.adUnitWarmingUp) { MPLogInfo(kMPWarmingUpErrorLogFormatWithAdUnitID, self.delegate.adUnitId); [self didFailToLoadAdapterWithError:[NSError errorWithCode:MOPUBErrorAdUnitWarmingUp]]; @@ -282,6 +278,14 @@ - (void)didFailToLoadAdapterWithError:(NSError *)error [self scheduleRefreshTimer]; } +- (MPAdType)adTypeForAdServerCommunicator:(MPAdServerCommunicator *)adServerCommunicator { + return MPAdTypeInline; +} + +- (NSString *)adUnitIDForAdServerCommunicator:(MPAdServerCommunicator *)adServerCommunicator { + return [self.delegate adUnitId]; +} + #pragma mark - - (MPAdView *)banner @@ -430,6 +434,20 @@ - (void)userWillLeaveApplicationFromAdapter:(MPBaseBannerAdapter *)adapter } } +- (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]; +} + @end diff --git a/MoPubSDK/Internal/Banners/MPBannerCustomEventAdapter.m b/MoPubSDK/Internal/Banners/MPBannerCustomEventAdapter.m index 55ca892c8..125f99f21 100644 --- a/MoPubSDK/Internal/Banners/MPBannerCustomEventAdapter.m +++ b/MoPubSDK/Internal/Banners/MPBannerCustomEventAdapter.m @@ -189,6 +189,16 @@ - (void)trackClickOnce } } +- (void)bannerCustomEventWillExpandAd:(MPBannerCustomEvent *)event +{ + [self.delegate adWillExpandForAdapter:self]; +} + +- (void)bannerCustomEventDidCollapseAd:(MPBannerCustomEvent *)event +{ + [self.delegate adDidCollapseForAdapter:self]; +} + #pragma mark - MPAdImpressionTimerDelegate - (void)adViewWillLogImpression:(UIView *)adView diff --git a/MoPubSDK/Internal/Banners/MPBaseBannerAdapter.h b/MoPubSDK/Internal/Banners/MPBaseBannerAdapter.h index 2a350642c..359aea5d8 100644 --- a/MoPubSDK/Internal/Banners/MPBaseBannerAdapter.h +++ b/MoPubSDK/Internal/Banners/MPBaseBannerAdapter.h @@ -83,4 +83,10 @@ */ - (void)adapter:(MPBaseBannerAdapter *)adapter didTrackImpressionForAd:(UIView *)ad; +/** + * Fires when the banner ad is expanding/resizing and collapsing. + */ +- (void)adWillExpandForAdapter:(MPBaseBannerAdapter *)adapter; +- (void)adDidCollapseForAdapter:(MPBaseBannerAdapter *)adapter; + @end diff --git a/MoPubSDK/Internal/Common/MPAdConfiguration.h b/MoPubSDK/Internal/Common/MPAdConfiguration.h index d3b5b1a61..756618855 100644 --- a/MoPubSDK/Internal/Common/MPAdConfiguration.h +++ b/MoPubSDK/Internal/Common/MPAdConfiguration.h @@ -11,12 +11,10 @@ @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, @@ -116,7 +114,10 @@ extern NSString * const kBannerImpressionMinPixelMetadataKey; @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 cffdc8eea..4ed539204 100644 --- a/MoPubSDK/Internal/Common/MPAdConfiguration.m +++ b/MoPubSDK/Internal/Common/MPAdConfiguration.m @@ -107,7 +107,6 @@ @interface MPAdConfiguration () @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; @@ -121,13 +120,13 @@ - (Class)setUpCustomEventClassFromMetadata:(NSDictionary *)metadata; @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.adType = [self adTypeFromMetadata:metadata]; + self.adType = adType; self.adUnitWarmingUp = [metadata mp_boolForKey:kAdUnitWarmingUpMetadataKey]; self.networkType = [self networkTypeFromMetadata:metadata]; @@ -262,14 +261,14 @@ - (Class)setUpCustomEventClassFromMetadata:(NSDictionary *)metadata NSString *customEventClassName = [metadata objectForKey:kCustomEventClassNameMetadataKey]; NSMutableDictionary *convertedCustomEvents = [NSMutableDictionary dictionary]; - if (self.adType == MPAdTypeBanner) { + if (self.adType == MPAdTypeInline) { [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) { + } else if (self.adType == MPAdTypeFullscreen) { [convertedCustomEvents setObject:@"MPGoogleAdMobInterstitialCustomEvent" forKey:@"admob_full"]; [convertedCustomEvents setObject:@"MPMillennialInterstitialCustomEvent" forKey:@"millennial_full"]; [convertedCustomEvents setObject:@"MPHTMLInterstitialCustomEvent" forKey:@"html"]; @@ -381,22 +380,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]; diff --git a/MoPubSDK/Internal/Common/MPAdServerCommunicator.h b/MoPubSDK/Internal/Common/MPAdServerCommunicator.h index d38af7bdf..bfaba1ceb 100644 --- a/MoPubSDK/Internal/Common/MPAdServerCommunicator.h +++ b/MoPubSDK/Internal/Common/MPAdServerCommunicator.h @@ -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 a3c2ff133..8c989c0f8 100644 --- a/MoPubSDK/Internal/Common/MPAdServerCommunicator.m +++ b/MoPubSDK/Internal/Common/MPAdServerCommunicator.m @@ -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) @@ -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 @@ -144,6 +152,10 @@ - (void)sendAfterLoadUrlWithConfiguration:(MPAdConfiguration *)configuration } } +- (BOOL)isRateLimited { + return [[MPRateLimitManager sharedInstance] isRateLimitedForAdUnitId:[self.delegate adUnitIDForAdServerCommunicator:self]]; +} + - (void)failLoadForSDKInit { NSError *error = [NSError adLoadFailedBecauseSdkNotInitialized]; MPLogEvent([MPLogEvent error:error message:nil]); @@ -182,8 +194,7 @@ - (void)didFinishLoadingWithData:(NSData *)data { if (error) { NSError * parseError = [NSError adResponseFailedToParseWithError:error]; MPLogEvent([MPLogEvent error:parseError message:nil]); - self.loading = NO; - [self.delegate communicatorDidFailWithError:parseError]; + [self didFailWithError:parseError]; return; } @@ -198,8 +209,7 @@ - (void)didFinishLoadingWithData:(NSData *)data { if (responses == nil) { NSError * noResponsesError = [NSError adResponsesNotFound]; MPLogEvent([MPLogEvent error:noResponsesError message:nil]); - self.loading = NO; - [self.delegate communicatorDidFailWithError:noResponsesError]; + [self didFailWithError:noResponsesError]; return; } @@ -215,15 +225,21 @@ - (void)didFinishLoadingWithData:(NSData *)data { 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]; } @@ -265,7 +281,7 @@ - (NSDictionary *)handleAdResponseOverrides:(NSDictionary *)serverResponseJson { NSNumber * debugLoggingEnabled = json[kEnableDebugLogging]; if (debugLoggingEnabled != nil && [debugLoggingEnabled boolValue]) { MPLogInfo(@"Debug logging enabled"); - MPLogging.consoleLogLevel = MPLogLevelDebug; + MPLogging.consoleLogLevel = MPBLogLevelDebug; json[kEnableDebugLogging] = nil; } diff --git a/MoPubSDK/Internal/Common/MPAdServerURLBuilder.m b/MoPubSDK/Internal/Common/MPAdServerURLBuilder.m index 45fa52921..322b13885 100644 --- a/MoPubSDK/Internal/Common/MPAdServerURLBuilder.m +++ b/MoPubSDK/Internal/Common/MPAdServerURLBuilder.m @@ -21,6 +21,7 @@ #import "MPIdentityProvider.h" #import "MPLogging.h" #import "MPMediationManager.h" +#import "MPRateLimitManager.h" #import "MPReachabilityManager.h" #import "MPViewabilityTracker.h" #import "NSString+MPAdditions.h" @@ -203,6 +204,8 @@ + (MPURL *)URLWithAdUnitID:(NSString *)adUnitID queryParams[kUserDataKeywordsKey] = [self userDataKeywordsValue:userDataKeywords]; queryParams[kViewabilityStatusKey] = [self viewabilityStatusValue:viewability]; queryParams[kAdvancedBiddingKey] = [self advancedBiddingValue]; + queryParams[kBackoffMsKey] = [self backoffMillisecondsValueForAdUnitID:adUnitID]; + queryParams[kBackoffReasonKey] = [[MPRateLimitManager sharedInstance] lastRateLimitReasonForAdUnitId:adUnitID]; [queryParams addEntriesFromDictionary:[self locationInformationDictionary:location]]; return [self URLWithEndpointPath:MOPUB_API_PATH_AD_REQUEST postData:queryParams]; @@ -343,6 +346,11 @@ + (NSString *)advancedBiddingValue { 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; } diff --git a/MoPubSDK/Internal/Common/MPClosableView.m b/MoPubSDK/Internal/Common/MPClosableView.m index 757c32157..6c4802421 100644 --- a/MoPubSDK/Internal/Common/MPClosableView.m +++ b/MoPubSDK/Internal/Common/MPClosableView.m @@ -110,6 +110,7 @@ - (void)dealloc - (void)layoutSubviews { + [super layoutSubviews]; if (@available(iOS 11, *)) { self.closeButton.translatesAutoresizingMaskIntoConstraints = NO; diff --git a/MoPubSDK/Internal/HTML/MPAdWebViewAgent.m b/MoPubSDK/Internal/HTML/MPAdWebViewAgent.m index 3636b36af..3eee46e1c 100644 --- a/MoPubSDK/Internal/HTML/MPAdWebViewAgent.m +++ b/MoPubSDK/Internal/HTML/MPAdWebViewAgent.m @@ -132,7 +132,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; @@ -319,7 +319,7 @@ - (BOOL)shouldStartViewabilityDuringInitialization - (BOOL)isInterstitialAd { - return (self.configuration.adType == MPAdTypeInterstitial); + return (self.configuration.adType == MPAdTypeFullscreen); } - (void)initAdAlertManager diff --git a/MoPubSDK/Internal/HTML/MPContentBlocker.m b/MoPubSDK/Internal/HTML/MPContentBlocker.m index 707f03abb..0741685b4 100644 --- a/MoPubSDK/Internal/HTML/MPContentBlocker.m +++ b/MoPubSDK/Internal/HTML/MPContentBlocker.m @@ -7,6 +7,7 @@ // #import "MPContentBlocker.h" +#import "MPAPIEndpoints.h" @interface MPContentBlocker() @property (class, nonatomic, readonly) NSArray * blockedResources; @@ -20,12 +21,16 @@ @implementation MPContentBlocker Current list of blocked resources. */ + (NSArray *)blockedResources { - static NSArray * sBlockedResources = nil; + static NSSet * sBlockedResources = nil; + NSString * blockedURLString = [NSString stringWithFormat:@"http.?://%@/mraid.js", MPAPIEndpoints.baseHostname]; + if (sBlockedResources == nil) { - sBlockedResources = @[@"http.?://ads.mopub.com/mraid.js"]; + sBlockedResources = [NSSet setWithObject:blockedURLString]; + } else if (![sBlockedResources containsObject:blockedURLString]) { + sBlockedResources = [sBlockedResources setByAddingObject:blockedURLString]; } - return sBlockedResources; + return [sBlockedResources allObjects]; } /** @@ -44,7 +49,15 @@ + (NSDictionary *)blockPatternFromResource:(NSString *)resource { + (NSString *)blockedResourcesList { static NSString * sBlockedResourcesList = nil; - if (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) { diff --git a/MoPubSDK/Internal/HTML/MPWebView.m b/MoPubSDK/Internal/HTML/MPWebView.m index 18f88a66e..bc76249b1 100644 --- a/MoPubSDK/Internal/HTML/MPWebView.m +++ b/MoPubSDK/Internal/HTML/MPWebView.m @@ -27,7 +27,7 @@ @interface MPWebView () *wkWebViewLayoutConstraints; +@property (strong, nonatomic) NSArray *webViewLayoutConstraints; @property (nonatomic, assign) BOOL hasMovedToWindow; @@ -181,12 +181,19 @@ - (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 [self cleanUpOffscreenView]; } + // UIWebView doesn't need to be moved to the window per se, but the constraints + // binding it to the view need to be activated. + else if (self.uiWebView != nil && !self.hasMovedToWindow) { + self.uiWebView.frame = self.bounds; + [self constrainView:self.uiWebView shouldUseSafeArea:self.shouldConformToSafeArea]; + self.hasMovedToWindow = YES; + } } // Occasionally, we encounter an issue where, when MPWebView is initialized at a different frame size than when it's shown, @@ -237,35 +244,36 @@ - (void)setShouldConformToSafeArea:(BOOL)shouldConformToSafeArea { _shouldConformToSafeArea = shouldConformToSafeArea; if (self.hasMovedToWindow) { - [self constrainWebViewShouldUseSafeArea:shouldConformToSafeArea]; + UIView * webviewToConstrain = (self.uiWebView != nil ? self.uiWebView : self.wkWebView); + [self constrainView:webviewToConstrain shouldUseSafeArea:shouldConformToSafeArea]; } } -- (void)constrainWebViewShouldUseSafeArea:(BOOL)shouldUseSafeArea { +- (void)constrainView:(UIView *)view shouldUseSafeArea:(BOOL)shouldUseSafeArea { if (@available(iOS 11.0, *)) { - self.wkWebView.translatesAutoresizingMaskIntoConstraints = NO; + 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]; } } diff --git a/MoPubSDK/Internal/Interstitials/MPInterstitialAdManager.m b/MoPubSDK/Internal/Interstitials/MPInterstitialAdManager.m index b6b16930b..8f54993d1 100644 --- a/MoPubSDK/Internal/Interstitials/MPInterstitialAdManager.m +++ b/MoPubSDK/Internal/Interstitials/MPInterstitialAdManager.m @@ -167,13 +167,6 @@ - (void)fetchAdWithConfiguration:(MPAdConfiguration *)configuration return; } - if (configuration.adType != MPAdTypeInterstitial) { - MPLogInfo(@"Could not load ad: interstitial object received a non-interstitial ad unit ID."); - self.loading = NO; - [self.delegate manager:self didFailToLoadInterstitialWithError:[NSError errorWithCode:MOPUBErrorAdapterInvalid]]; - return; - } - [self setUpAdapterWithConfiguration:configuration]; } @@ -203,6 +196,14 @@ - (void)setUpAdapterWithConfiguration:(MPAdConfiguration *)configuration; [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 diff --git a/MoPubSDK/Internal/Interstitials/MPInterstitialAdManagerDelegate.h b/MoPubSDK/Internal/Interstitials/MPInterstitialAdManagerDelegate.h index a31815d52..d13091469 100644 --- a/MoPubSDK/Internal/Interstitials/MPInterstitialAdManagerDelegate.h +++ b/MoPubSDK/Internal/Interstitials/MPInterstitialAdManagerDelegate.h @@ -16,6 +16,7 @@ - (MPInterstitialAdController *)interstitialAdController; - (CLLocation *)location; +- (NSString *)adUnitId; - (id)interstitialDelegate; - (void)managerDidLoadInterstitial:(MPInterstitialAdManager *)manager; - (void)manager:(MPInterstitialAdManager *)manager diff --git a/MoPubSDK/Internal/MPAdServerKeys.h b/MoPubSDK/Internal/MPAdServerKeys.h index 98759fcfb..6cce7e97b 100644 --- a/MoPubSDK/Internal/MPAdServerKeys.h +++ b/MoPubSDK/Internal/MPAdServerKeys.h @@ -42,6 +42,8 @@ extern NSString * const kLocationLatitudeLongitudeKey; extern NSString * const kLocationHorizontalAccuracy; extern NSString * const kLocationIsFromSDK; extern NSString * const kLocationLastUpdatedMilliseconds; +extern NSString * const kBackoffMsKey; +extern NSString * const kBackoffReasonKey; #pragma mark - Ad Server Response Keys extern NSString * const kEnableDebugLogging; diff --git a/MoPubSDK/Internal/MPAdServerKeys.m b/MoPubSDK/Internal/MPAdServerKeys.m index d9baa4d7f..cb6d0c104 100644 --- a/MoPubSDK/Internal/MPAdServerKeys.m +++ b/MoPubSDK/Internal/MPAdServerKeys.m @@ -42,6 +42,8 @@ NSString * const kLocationHorizontalAccuracy = @"lla"; NSString * const kLocationIsFromSDK = @"llsdk"; NSString * const kLocationLastUpdatedMilliseconds = @"llf"; +NSString * const kBackoffMsKey = @"backoff_ms"; +NSString * const kBackoffReasonKey = @"backoff_reason"; #pragma mark - Ad Server Response Keys NSString * const kEnableDebugLogging = @"enable_debug_logging"; 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..c3565d4d8 --- /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 *)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..ebb94276c --- /dev/null +++ b/MoPubSDK/Internal/MPRateLimitManager.m @@ -0,0 +1,61 @@ +// +// 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, copy) 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 { + @synchronized (self) { + if (self.configurationDictionary[adUnitId] == nil) { + self.configurationDictionary[adUnitId] = [[MPRateLimitConfiguration alloc] init]; + } + + MPRateLimitConfiguration * config = self.configurationDictionary[adUnitId]; + [config setRateLimitTimerWithMilliseconds:milliseconds reason:reason]; + } +} + +- (BOOL)isRateLimitedForAdUnitId:(NSString *)adUnitId { + return self.configurationDictionary[adUnitId].isRateLimited; +} + +- (NSUInteger)lastRateLimitMillisecondsForAdUnitId:(NSString *)adUnitId { + return self.configurationDictionary[adUnitId].lastRateLimitMilliseconds; +} + +- (NSString *)lastRateLimitReasonForAdUnitId:(NSString *)adUnitId { + return self.configurationDictionary[adUnitId].lastRateLimitReason; +} + +@end diff --git a/MoPubSDK/Internal/MRAID/MPMRAIDBannerCustomEvent.m b/MoPubSDK/Internal/MRAID/MPMRAIDBannerCustomEvent.m index ec13c1d9b..4c9e09df3 100644 --- a/MoPubSDK/Internal/MRAID/MPMRAIDBannerCustomEvent.m +++ b/MoPubSDK/Internal/MRAID/MPMRAIDBannerCustomEvent.m @@ -103,4 +103,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/MRController.h b/MoPubSDK/Internal/MRAID/MRController.h index 7bcb07a76..5887f8b23 100644 --- a/MoPubSDK/Internal/MRAID/MRController.h +++ b/MoPubSDK/Internal/MRAID/MRController.h @@ -82,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 d053611e0..53c0fdebd 100644 --- a/MoPubSDK/Internal/MRAID/MRController.m +++ b/MoPubSDK/Internal/MRAID/MRController.m @@ -51,6 +51,7 @@ @interface MRController () )delegate { if (self = [super init]) { + _includeSafeAreaInsetsInCalculations = YES; _placementType = placementType; _currentState = MRAdViewStateDefault; _forceOrientationMask = MPInterstitialOrientationTypeToUIInterfaceOrientationMask(orientationType); _isAnimatingAdSize = NO; _didConfigureOrientationNotificationObservers = NO; _currentAdSize = CGSizeZero; + _isAppSuspended = NO; _mraidDefaultAdFrame = adViewFrame; @@ -124,6 +136,11 @@ - (instancetype)initWithAdViewFrame:(CGRect)adViewFrame _adAlertManagerTwoPart = [[MPCoreInstanceProvider sharedProvider] buildMPAdAlertManagerWithDelegate:self]; _delegate = delegate; + + _previousCurrentPosition = CGRectNull; + _previousDefaultPosition = CGRectNull; + _previousScreenSize = CGSizeZero; + _previousMaxSize = CGSizeZero; } return self; @@ -397,7 +414,7 @@ - (CGRect)adjustedFrameForFrame:(CGRect)frame allowOffscreen:(BOOL)allowOffscree return frame; } - CGRect applicationFrame = MPApplicationFrame(); + CGRect applicationFrame = MPApplicationFrame(self.includeSafeAreaInsetsInCalculations); CGFloat applicationWidth = CGRectGetWidth(applicationFrame); CGFloat applicationHeight = CGRectGetHeight(applicationFrame); CGFloat adFrameWidth = CGRectGetWidth(frame); @@ -436,7 +453,7 @@ - (CGRect)adjustedFrameForFrame:(CGRect)frame allowOffscreen:(BOOL)allowOffscree - (BOOL)isValidResizeFrame:(CGRect)frame allowOffscreen:(BOOL)allowOffscreen { BOOL valid = YES; - if (!allowOffscreen && !CGRectContainsRect(MPApplicationFrame(), frame)) { + if (!allowOffscreen && !CGRectContainsRect(MPApplicationFrame(self.includeSafeAreaInsetsInCalculations), frame)) { valid = NO; } else if (CGRectGetWidth(frame) < 50.0f || CGRectGetHeight(frame) < 50.0f) { valid = NO; @@ -445,14 +462,14 @@ - (BOOL)isValidResizeFrame:(CGRect)frame allowOffscreen:(BOOL)allowOffscreen return valid; } -- (BOOL)isValidResizeCloseButtonPlacementInFrame:(CGRect)newFrame +- (BOOL)isValidResizeCloseButtonPlacement:(MPClosableViewCloseButtonLocation)closeButtonLocation inFrame:(CGRect)newFrame { - CGRect closeButtonFrameForResize = MPClosableViewCustomCloseButtonFrame(newFrame.size, self.mraidAdView.closeButtonLocation); + CGRect closeButtonFrameForResize = MPClosableViewCustomCloseButtonFrame(newFrame.size, 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); + return CGRectContainsRect(MPApplicationFrame(self.includeSafeAreaInsetsInCalculations), closeButtonFrameInWindow); } - (MPClosableViewCloseButtonLocation)adCloseButtonLocationFromString:(NSString *)closeButtonLocationString @@ -597,6 +614,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]; + } }]; } @@ -606,15 +628,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]; + } }]; } @@ -655,7 +687,7 @@ - (UIViewController *)viewControllerForPresentingModalView - (void)nativeCommandWillPresentModalView { - [self adWillPresentModalView]; + [self adWillPresentModalViewByExpanding:NO]; } - (void)nativeCommandDidDismissModalView @@ -777,8 +809,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; } @@ -850,7 +884,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]; } @@ -919,14 +953,17 @@ - (void)bridge:(MRBridge *)bridge handleNativeCommandResizeWithParameters:(NSDic 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]; if (![self isValidResizeFrame:newFrame allowOffscreen:allowOffscreen]) { [self.mraidBridge fireErrorEventForAction:kMRAIDCommandResize withMessage:@"Could not display desired frame in compliance with MRAID 2.0 specifications."]; - } else if (![self isValidResizeCloseButtonPlacementInFrame:newFrame]) { + } else if (![self isValidResizeCloseButtonPlacement:closeButtonLocation inFrame:newFrame]) { [self.mraidBridge fireErrorEventForAction:kMRAIDCommandResize withMessage:@"Custom close event region is offscreen."]; } 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) { @@ -934,12 +971,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]; @@ -986,7 +1023,7 @@ - (void)closableView:(MPClosableView *)closableView didMoveToWindow:(UIWindow *) - (void)displayAgentWillPresentModal { - [self adWillPresentModalView]; + [self adWillPresentModalViewByExpanding:NO]; } - (void)displayAgentDidDismissModal @@ -1009,12 +1046,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]; } } @@ -1022,16 +1063,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; @@ -1059,22 +1102,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; - MPLogDebug(@"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; + } - MPLogDebug(@"Default Position: %@", NSStringFromCGRect(defaultFrame)); + // 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]; + + MPLogDebug(@"Default Position: %@", NSStringFromCGRect(defaultFrame)); + } } - (void)updateScreenSize @@ -1082,23 +1145,43 @@ - (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]; - MPLogDebug(@"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]; + // Fire to both ad views as it pertains to both views. + [self.mraidBridge fireSetMaxSize:maxSize]; + [self.mraidBridgeTwoPart fireSetMaxSize:maxSize]; - MPLogDebug(@"Max Size: %@", NSStringFromCGSize(maxSize)); + MPLogDebug(@"Max Size: %@", NSStringFromCGSize(maxSize)); + } } - (void)updateOrientation @@ -1210,12 +1293,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 @@ -1228,6 +1317,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]; } @@ -1235,6 +1330,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/Utility/MPError.h b/MoPubSDK/Internal/Utility/MPError.h index 9fcece425..6495df511 100644 --- a/MoPubSDK/Internal/Utility/MPError.h +++ b/MoPubSDK/Internal/Utility/MPError.h @@ -34,6 +34,7 @@ typedef enum { MOPUBErrorNoConsentDialogLoaded, MOPUBErrorAdapterFailedToLoadAd, MOPUBErrorFullScreenAdAlreadyOnScreen, + MOPUBErrorTooManyRequests, } MOPUBErrorCode; @interface NSError (MoPub) @@ -62,3 +63,7 @@ typedef enum { @interface NSError (Consent) + (instancetype)noConsentDialogLoaded; @end + +@interface NSError (RateLimit) ++ (instancetype)tooManyRequests; +@end diff --git a/MoPubSDK/Internal/Utility/MPError.m b/MoPubSDK/Internal/Utility/MPError.m index ce5625472..40e60241c 100644 --- a/MoPubSDK/Internal/Utility/MPError.m +++ b/MoPubSDK/Internal/Utility/MPError.m @@ -85,3 +85,9 @@ + (instancetype)noConsentDialogLoaded { } @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/MPGlobal.h b/MoPubSDK/Internal/Utility/MPGlobal.h index cd6e3930b..a1da19f09 100644 --- a/MoPubSDK/Internal/Utility/MPGlobal.h +++ b/MoPubSDK/Internal/Utility/MPGlobal.h @@ -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); diff --git a/MoPubSDK/Internal/Utility/MPGlobal.m b/MoPubSDK/Internal/Utility/MPGlobal.m index dd8ff2c0e..a41f7812d 100644 --- a/MoPubSDK/Internal/Utility/MPGlobal.m +++ b/MoPubSDK/Internal/Utility/MPGlobal.m @@ -40,10 +40,23 @@ CGFloat MPStatusBarHeight() { return (width < height) ? width : height; } -CGRect MPApplicationFrame() +CGRect MPApplicationFrame(BOOL includeSafeAreaInsets) { CGRect frame = MPScreenBounds(); + if (@available(iOS 11.0, *)) { + 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(); diff --git a/MoPubSDK/Internal/Utility/MPIdentityProvider.m b/MoPubSDK/Internal/Utility/MPIdentityProvider.m index 41b5ec2ba..df7580026 100644 --- a/MoPubSDK/Internal/Utility/MPIdentityProvider.m +++ b/MoPubSDK/Internal/Utility/MPIdentityProvider.m @@ -19,6 +19,7 @@ static BOOL gFrequencyCappingIdUsageEnabled = YES; @interface MPIdentityProvider () +@property (class, nonatomic, readonly) NSCalendar * iso8601Calendar; + (NSString *)mopubIdentifier:(BOOL)obfuscate; @@ -26,6 +27,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]; @@ -73,21 +85,25 @@ + (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); diff --git a/MoPubSDK/Logging/Internal/MPConsoleLogger.h b/MoPubSDK/Logging/Internal/MPConsoleLogger.h index f5c6f1a6a..461b921c1 100644 --- a/MoPubSDK/Logging/Internal/MPConsoleLogger.h +++ b/MoPubSDK/Logging/Internal/MPConsoleLogger.h @@ -7,16 +7,16 @@ // #import -#import "MPLogger.h" +#import "MPBLogger.h" /** Console logging destination routes all log messages to @c NSLog. */ -@interface MPConsoleLogger : NSObject +@interface MPConsoleLogger : NSObject /** - Log level. By default, this is set to @c MPLogLevelInfo. + Log level. By default, this is set to @c MPBLogLevelInfo. */ -@property (nonatomic, assign) MPLogLevel logLevel; +@property (nonatomic, assign) MPBLogLevel logLevel; @end diff --git a/MoPubSDK/Logging/Internal/MPConsoleLogger.m b/MoPubSDK/Logging/Internal/MPConsoleLogger.m index 02576d895..92d1d5dee 100644 --- a/MoPubSDK/Logging/Internal/MPConsoleLogger.m +++ b/MoPubSDK/Logging/Internal/MPConsoleLogger.m @@ -15,9 +15,9 @@ - (instancetype)init { // The console logging level is set to info by default in the event // that an error needs to be logged to the console prior to SDK // initialization. - _logLevel = MPLogLevelInfo; + _logLevel = MPBLogLevelInfo; } - + return self; } diff --git a/MoPubSDK/Logging/Internal/MPLogManager.h b/MoPubSDK/Logging/Internal/MPLogManager.h index 24a374762..a0f03bffc 100644 --- a/MoPubSDK/Logging/Internal/MPLogManager.h +++ b/MoPubSDK/Logging/Internal/MPLogManager.h @@ -8,9 +8,9 @@ #import #import "MPLogEvent.h" -#import "MPLogger.h" +#import "MPBLogger.h" #import "MPLogging.h" -#import "MPLogLevel.h" +#import "MPBLogLevel.h" NS_ASSUME_NONNULL_BEGIN @@ -23,7 +23,7 @@ NS_ASSUME_NONNULL_BEGIN /** Current log level of the console logger. */ -@property (nonatomic, assign) MPLogLevel consoleLogLevel; +@property (nonatomic, assign) MPBLogLevel consoleLogLevel; /** Retrieves the singleton instance of @c MPLogManager. @@ -34,13 +34,13 @@ NS_ASSUME_NONNULL_BEGIN Registers a logging destination. @param logger Logger to receive log events. */ -- (void)addLogger:(id)logger; +- (void)addLogger:(id)logger; /** Removes a logger from receiving log events. @param logger Logger to remove. */ -- (void)removeLogger:(id)logger; +- (void)removeLogger:(id)logger; /** Logs the message to all available logging destinations at the @@ -48,7 +48,7 @@ NS_ASSUME_NONNULL_BEGIN @param message Message to log. @param level Log level. */ -- (void)logMessage:(NSString *)message atLogLevel:(MPLogLevel)level; +- (void)logMessage:(NSString *)message atLogLevel:(MPBLogLevel)level; /** Logs the event generated from the calling class. The format of the log message diff --git a/MoPubSDK/Logging/Internal/MPLogManager.m b/MoPubSDK/Logging/Internal/MPLogManager.m index 5822a3bb3..9ce77cefe 100644 --- a/MoPubSDK/Logging/Internal/MPLogManager.m +++ b/MoPubSDK/Logging/Internal/MPLogManager.m @@ -28,7 +28,7 @@ @interface MPLogManager() /** Currently registered loggers. */ -@property (nonatomic, strong) NSMutableArray> * loggers; +@property (nonatomic, strong) NSMutableArray> * loggers; /** Serial dispatch queue to perform logging operations. @@ -47,7 +47,7 @@ + (MPLogManager *)sharedInstance { dispatch_once(&once, ^{ sharedManager = [[self alloc] init]; }); - + return sharedManager; } @@ -57,50 +57,50 @@ - (instancetype)init { _loggers = [NSMutableArray arrayWithObject:_consoleLogger]; _queue = dispatch_queue_create("com.mopub-ios-sdk.queue", DISPATCH_QUEUE_SERIAL); } - + return self; } #pragma mark - Computed Properties -- (MPLogLevel)consoleLogLevel { +- (MPBLogLevel)consoleLogLevel { return self.consoleLogger.logLevel; } -- (void)setConsoleLogLevel:(MPLogLevel)consoleLogLevel { +- (void)setConsoleLogLevel:(MPBLogLevel)consoleLogLevel { self.consoleLogger.logLevel = consoleLogLevel; } #pragma mark - Logger Management -- (void)addLogger:(id)logger { +- (void)addLogger:(id)logger { [self.loggers addObject:logger]; } -- (void)removeLogger:(id)logger { +- (void)removeLogger:(id)logger { [self.loggers removeObject:logger]; } #pragma mark - Logging -- (void)logMessage:(NSString *)message atLogLevel:(MPLogLevel)level { +- (void)logMessage:(NSString *)message atLogLevel:(MPBLogLevel)level { // Lazily retrieve the IDFA if (sIdentifier == nil) { sIdentifier = [[MPIdentityProvider identifier] copy]; } - + // Lazily retrieve the sanitized IDFA if (sObfuscatedIdentifier == nil) { sObfuscatedIdentifier = [[MPIdentityProvider obfuscatedIdentifier] copy]; } - + // Replace identifier with a obfuscated version when logging. NSString * logMessage = [message stringByReplacingOccurrencesOfString:sIdentifier withString:sObfuscatedIdentifier]; - + // Queue up the message for logging. __weak __typeof__(self) weakSelf = self; dispatch_async(self.queue, ^{ - [weakSelf.loggers enumerateObjectsUsingBlock:^(id logger, NSUInteger idx, BOOL *stop) { + [weakSelf.loggers enumerateObjectsUsingBlock:^(id logger, NSUInteger idx, BOOL *stop) { if (logger.logLevel <= level) { [logger logMessage:logMessage]; } diff --git a/MoPubSDK/Logging/MPLogLevel.h b/MoPubSDK/Logging/MPBLogLevel.h similarity index 65% rename from MoPubSDK/Logging/MPLogLevel.h rename to MoPubSDK/Logging/MPBLogLevel.h index 64acc1883..6f64bf84c 100644 --- a/MoPubSDK/Logging/MPLogLevel.h +++ b/MoPubSDK/Logging/MPBLogLevel.h @@ -1,5 +1,5 @@ // -// MPLogLevel.h +// MPBLogLevel.h // // Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement @@ -12,8 +12,8 @@ SDK logging level. @remark Lower values equate to more detailed logs. */ -typedef NS_ENUM(NSUInteger, MPLogLevel) { - MPLogLevelDebug = 20, - MPLogLevelInfo = 30, - MPLogLevelNone = 70 +typedef NS_ENUM(NSUInteger, MPBLogLevel) { + MPBLogLevelDebug = 20, + MPBLogLevelInfo = 30, + MPBLogLevelNone = 70 }; diff --git a/MoPubSDK/Logging/MPLogger.h b/MoPubSDK/Logging/MPBLogger.h similarity index 76% rename from MoPubSDK/Logging/MPLogger.h rename to MoPubSDK/Logging/MPBLogger.h index a4cdbff3b..ea91e7c15 100644 --- a/MoPubSDK/Logging/MPLogger.h +++ b/MoPubSDK/Logging/MPBLogger.h @@ -1,5 +1,5 @@ // -// MPLogger.h +// MPBLogger.h // // Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement @@ -7,17 +7,17 @@ // #import -#import "MPLogLevel.h" +#import "MPBLogLevel.h" /** Objects which are capable of consuming log messages. */ -@protocol MPLogger +@protocol MPBLogger /** Current logging level. */ -@property (nonatomic, readonly) MPLogLevel logLevel; +@property (nonatomic, readonly) MPBLogLevel logLevel; /** Message to be logged. diff --git a/MoPubSDK/Logging/MPLogEvent.h b/MoPubSDK/Logging/MPLogEvent.h index 1a0f21323..fcbe8865e 100644 --- a/MoPubSDK/Logging/MPLogEvent.h +++ b/MoPubSDK/Logging/MPLogEvent.h @@ -8,7 +8,7 @@ #import #import "MPConsentStatus.h" -#import "MPLogLevel.h" +#import "MPBLogLevel.h" @protocol MPAdapterConfiguration; @class MPRewardedVideoReward; @@ -28,7 +28,7 @@ NS_ASSUME_NONNULL_BEGIN /** Level at which the message should be logged. */ -@property (nonatomic, assign, readonly) MPLogLevel logLevel; +@property (nonatomic, assign, readonly) MPBLogLevel logLevel; /** Default initialization is disallowed. @@ -49,7 +49,7 @@ NS_ASSUME_NONNULL_BEGIN @param level Level at which the message should be logged @return Log event */ -- (instancetype)initWithMessage:(NSString *)message level:(MPLogLevel)level NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithMessage:(NSString *)message level:(MPBLogLevel)level NS_DESIGNATED_INITIALIZER; /** Initializes a generic error log event with optional message. The message and error @@ -67,7 +67,7 @@ NS_ASSUME_NONNULL_BEGIN @param level Level at which the message should be logged @return Log event */ -+ (instancetype)eventWithMessage:(NSString *)message level:(MPLogLevel)level; ++ (instancetype)eventWithMessage:(NSString *)message level:(MPBLogLevel)level; @end diff --git a/MoPubSDK/Logging/MPLogEvent.m b/MoPubSDK/Logging/MPLogEvent.m index 133868473..d7458a5f5 100644 --- a/MoPubSDK/Logging/MPLogEvent.m +++ b/MoPubSDK/Logging/MPLogEvent.m @@ -15,15 +15,15 @@ @implementation MPLogEvent - (instancetype)initWithMessage:(NSString *)message { - return [self initWithMessage:message level:MPLogLevelDebug]; + return [self initWithMessage:message level:MPBLogLevelDebug]; } -- (instancetype)initWithMessage:(NSString *)message level:(MPLogLevel)level { +- (instancetype)initWithMessage:(NSString *)message level:(MPBLogLevel)level { if (self = [super init]) { _message = message; _logLevel = level; } - + return self; } @@ -33,7 +33,7 @@ + (instancetype)error:(NSError *)error message:(NSString * _Nullable)message { return [[MPLogEvent alloc] initWithMessage:logMessage]; } -+ (instancetype)eventWithMessage:(NSString *)message level:(MPLogLevel)level { ++ (instancetype)eventWithMessage:(NSString *)message level:(MPBLogLevel)level { return [[MPLogEvent alloc] initWithMessage:message level:level]; } @@ -55,32 +55,32 @@ + (instancetype)adRequestReceivedResponse:(NSDictionary *)response { + (instancetype)adLoadAttempt { static NSString * const message = @"Attempting to load ad"; - return [[MPLogEvent alloc] initWithMessage:message level:MPLogLevelInfo]; + return [[MPLogEvent alloc] initWithMessage:message level:MPBLogLevelInfo]; } + (instancetype)adShowAttempt { static NSString * const message = @"Attempting to show ad"; - return [[MPLogEvent alloc] initWithMessage:message level:MPLogLevelInfo]; + return [[MPLogEvent alloc] initWithMessage:message level:MPBLogLevelInfo]; } + (instancetype)adShowSuccess { static NSString * const message = @"Ad shown"; - return [[MPLogEvent alloc] initWithMessage:message level:MPLogLevelInfo]; + return [[MPLogEvent alloc] initWithMessage:message level:MPBLogLevelInfo]; } + (instancetype)adShowFailedWithError:(NSError *)error { NSString * message = [NSString stringWithFormat:@"Ad failed to show: (%@) %@", @(error.code), error.localizedDescription]; - return [[MPLogEvent alloc] initWithMessage:message level:MPLogLevelInfo]; + return [[MPLogEvent alloc] initWithMessage:message level:MPBLogLevelInfo]; } + (instancetype)adDidLoad { static NSString * const message = @"Ad loaded"; - return [[MPLogEvent alloc] initWithMessage:message level:MPLogLevelInfo]; + return [[MPLogEvent alloc] initWithMessage:message level:MPBLogLevelInfo]; } + (instancetype)adFailedToLoadWithError:(NSError *)error { NSString * message = [NSString stringWithFormat:@"Ad failed to load: (%@) %@", @(error.code), error.localizedDescription]; - return [[MPLogEvent alloc] initWithMessage:message level:MPLogLevelInfo]; + return [[MPLogEvent alloc] initWithMessage:message level:MPBLogLevelInfo]; } + (instancetype)adExpiredWithTimeInterval:(NSTimeInterval)expirationInterval { @@ -226,9 +226,9 @@ + (instancetype)sdkInitializedWithNetworks:(NSArray> [networkVersions addObject:message]; }]; NSString * networksMessage = (networkVersions.count > 0 ? [networkVersions componentsJoinedByString:@"\n\t"] : @"No adapters initialized"); - + NSString * message = [NSString stringWithFormat:@"SDK initialized and ready to display ads.\n\tInitialized adapters:\n\t%@\n", networksMessage]; - return [[MPLogEvent alloc] initWithMessage:message level:MPLogLevelInfo]; + return [[MPLogEvent alloc] initWithMessage:message level:MPBLogLevelInfo]; } @end diff --git a/MoPubSDK/Logging/MPLogging.h b/MoPubSDK/Logging/MPLogging.h index a1eea97a1..26cbcad28 100644 --- a/MoPubSDK/Logging/MPLogging.h +++ b/MoPubSDK/Logging/MPLogging.h @@ -8,16 +8,16 @@ #import #import "MPLogEvent.h" -#import "MPLogger.h" -#import "MPLogLevel.h" +#import "MPBLogger.h" +#import "MPBLogLevel.h" NS_ASSUME_NONNULL_BEGIN extern NSString * const kMPClearErrorLogFormatWithAdUnitID; extern NSString * const kMPWarmingUpErrorLogFormatWithAdUnitID; -#define MPLogDebug(...) [MPLogging logEvent:[MPLogEvent eventWithMessage:[NSString stringWithFormat:__VA_ARGS__] level:MPLogLevelDebug] source:nil fromClass:self.class] -#define MPLogInfo(...) [MPLogging logEvent:[MPLogEvent eventWithMessage:[NSString stringWithFormat:__VA_ARGS__] level:MPLogLevelInfo] source:nil fromClass:self.class] +#define MPLogDebug(...) [MPLogging logEvent:[MPLogEvent eventWithMessage:[NSString stringWithFormat:__VA_ARGS__] level:MPBLogLevelDebug] source:nil fromClass:self.class] +#define MPLogInfo(...) [MPLogging logEvent:[MPLogEvent eventWithMessage:[NSString stringWithFormat:__VA_ARGS__] level:MPBLogLevelInfo] source:nil fromClass:self.class] // MPLogTrace, MPLogWarn, MPLogError, and MPLogFatal will be deprecated in // future SDK versions. Please use MPLogInfo or MPLogDebug @@ -37,21 +37,21 @@ extern NSString * const kMPWarmingUpErrorLogFormatWithAdUnitID; */ @interface MPLogging : NSObject /** - Current log level of the SDK console logger. The default value is @c MPLogLevelNone. + Current log level of the SDK console logger. The default value is @c MPBLogLevelNone. */ -@property (class, nonatomic, assign) MPLogLevel consoleLogLevel; +@property (class, nonatomic, assign) MPBLogLevel consoleLogLevel; /** Registers a logging destination. @param logger Logger to receive log events. */ -+ (void)addLogger:(id)logger; ++ (void)addLogger:(id)logger; /** Removes a logger from receiving log events. @param logger Logger to remove. */ -+ (void)removeLogger:(id)logger; ++ (void)removeLogger:(id)logger; /** Logs the event generated from the calling class. The format of the log message diff --git a/MoPubSDK/Logging/MPLogging.m b/MoPubSDK/Logging/MPLogging.m index 85e92868d..9be72ab11 100644 --- a/MoPubSDK/Logging/MPLogging.m +++ b/MoPubSDK/Logging/MPLogging.m @@ -16,21 +16,21 @@ @implementation MPLogging #pragma mark - Class Properties -+ (MPLogLevel)consoleLogLevel { ++ (MPBLogLevel)consoleLogLevel { return MPLogManager.sharedInstance.consoleLogLevel; } -+ (void)setConsoleLogLevel:(MPLogLevel)level { ++ (void)setConsoleLogLevel:(MPBLogLevel)level { MPLogManager.sharedInstance.consoleLogLevel = level; } #pragma mark - Class Methods -+ (void)addLogger:(id)logger { ++ (void)addLogger:(id)logger { [MPLogManager.sharedInstance addLogger:logger]; } -+ (void)removeLogger:(id)logger { ++ (void)removeLogger:(id)logger { [MPLogManager.sharedInstance removeLogger:logger]; } diff --git a/MoPubSDK/MPBannerCustomEventDelegate.h b/MoPubSDK/MPBannerCustomEventDelegate.h index 8518a52e0..9718ddc55 100644 --- a/MoPubSDK/MPBannerCustomEventDelegate.h +++ b/MoPubSDK/MPBannerCustomEventDelegate.h @@ -133,4 +133,20 @@ */ - (void)trackClick; +/** + * Call this method when the banner ad is expanding or resizing from its default size. + * + * @param event You should pass `self` to allow the MoPub SDK to associate this event with the correct + * instance of your custom event. + */ +- (void)bannerCustomEventWillExpandAd:(MPBannerCustomEvent *)event; + +/** + * Call this method when the banner ad is collapsing back to its default size. + * + * @param event You should pass `self` to allow the MoPub SDK to associate this event with the correct + * instance of your custom event. + */ +- (void)bannerCustomEventDidCollapseAd:(MPBannerCustomEvent *)event; + @end diff --git a/MoPubSDK/MPConstants.h b/MoPubSDK/MPConstants.h index 31464c17a..f685b4b6f 100644 --- a/MoPubSDK/MPConstants.h +++ b/MoPubSDK/MPConstants.h @@ -14,7 +14,7 @@ #define MP_SERVER_VERSION @"8" #define MP_REWARDED_API_VERSION @"1" #define MP_BUNDLE_IDENTIFIER @"com.mopub.mopub" -#define MP_SDK_VERSION @"5.5.0" +#define MP_SDK_VERSION @"5.6.0" // Sizing constants. extern CGSize const MOPUB_BANNER_SIZE; diff --git a/MoPubSDK/MPMoPubConfiguration.h b/MoPubSDK/MPMoPubConfiguration.h index 5275f40ff..17e6cb4ad 100644 --- a/MoPubSDK/MPMoPubConfiguration.h +++ b/MoPubSDK/MPMoPubConfiguration.h @@ -8,7 +8,7 @@ #import #import "MPAdapterConfiguration.h" -#import "MPLogLevel.h" +#import "MPBLogLevel.h" #import "MPMediationSettingsProtocol.h" #import "MPRewardedVideo.h" @@ -40,9 +40,9 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, strong, nullable) NSArray> * globalMediationSettings; /** - Optional logging level. By default, this value is set to @c MPLogLevelNone. + Optional logging level. By default, this value is set to @c MPBLogLevelNone. */ -@property (nonatomic, assign) MPLogLevel loggingLevel; +@property (nonatomic, assign) MPBLogLevel loggingLevel; /** Optional configuration settings for mediated networks during initialization. To add entries diff --git a/MoPubSDK/MPMoPubConfiguration.m b/MoPubSDK/MPMoPubConfiguration.m index 0c4238dd4..45eb387fe 100644 --- a/MoPubSDK/MPMoPubConfiguration.m +++ b/MoPubSDK/MPMoPubConfiguration.m @@ -18,7 +18,7 @@ - (instancetype)initWithAdUnitIdForAppInitialization:(NSString * _Nonnull)adUnit _adUnitIdForAppInitialization = adUnitId; _allowLegitimateInterest = NO; _globalMediationSettings = nil; - _loggingLevel = MPLogLevelNone; + _loggingLevel = MPBLogLevelNone; _mediatedNetworkConfigurations = nil; _moPubRequestOptions = nil; } diff --git a/MoPubSDK/MoPub.h b/MoPubSDK/MoPub.h index a263d7436..ff1ad1b5f 100644 --- a/MoPubSDK/MoPub.h +++ b/MoPubSDK/MoPub.h @@ -29,7 +29,7 @@ #import "MPInterstitialCustomEvent.h" #import "MPInterstitialCustomEventDelegate.h" #import "MPLogging.h" -#import "MPLogLevel.h" +#import "MPBLogLevel.h" #import "MPMediationSettingsProtocol.h" #import "MPMoPubConfiguration.h" #import "MPRealTimeTimer.h" @@ -130,9 +130,9 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, assign) BOOL forceWKWebView; /** - * SDK log level. The default value is `MPLogLevelNone`. + * SDK log level. The default value is `MPBLogLevelNone`. */ -@property (nonatomic, assign) MPLogLevel logLevel __attribute((deprecated("Use the MPMoPubConfiguration.loggingLevel instead."))); +@property (nonatomic, assign) MPBLogLevel logLevel __attribute((deprecated("Use the MPMoPubConfiguration.loggingLevel instead."))); /** * Initializes the MoPub SDK asynchronously on a background thread. @@ -182,6 +182,18 @@ NS_ASSUME_NONNULL_BEGIN @interface MoPub (Mediation) +/** + * Retrieves the adapter configuration for the specified class. + * @param className The classname of the adapter configuration instance to retrieve. + * @return The adapter configuration if available; otherwise @c nil. + */ +- (id _Nullable)adapterConfigurationNamed:(NSString *)className; + +/** + Retrieves the available adapter configuration class names. + */ +- (NSArray * _Nullable)availableAdapterClassNames; + /** * Clears all currently cached mediated networks. */ diff --git a/MoPubSDK/MoPub.m b/MoPubSDK/MoPub.m index ccc1d99e4..3c2d545fc 100644 --- a/MoPubSDK/MoPub.m +++ b/MoPubSDK/MoPub.m @@ -77,12 +77,12 @@ - (BOOL)forceWKWebView return [MPWebView isForceWKWebView]; } -- (void)setLogLevel:(MPLogLevel)level +- (void)setLogLevel:(MPBLogLevel)level { MPLogging.consoleLogLevel = level; } -- (MPLogLevel)logLevel +- (MPBLogLevel)logLevel { return MPLogging.consoleLogLevel; } @@ -210,6 +210,18 @@ @implementation MoPub (Mediation) return adapters.firstObject; } +- (NSArray * _Nullable)availableAdapterClassNames { + NSMutableArray * adapterClassNames = [NSMutableArray arrayWithCapacity:MPMediationManager.sharedManager.adapters.count]; + [MPMediationManager.sharedManager.adapters.allValues enumerateObjectsUsingBlock:^(id _Nonnull adapter, NSUInteger idx, BOOL * _Nonnull stop) { + NSString * className = NSStringFromClass(adapter.class); + if (className != nil) { + [adapterClassNames addObject:className]; + } + }]; + + return adapterClassNames; +} + - (void)clearCachedNetworks { return [MPMediationManager.sharedManager clearCache]; } diff --git a/MoPubSDK/NativeAds/MPNativeAdRequest.m b/MoPubSDK/NativeAds/MPNativeAdRequest.m index a8d9a65fa..003615266 100644 --- a/MoPubSDK/NativeAds/MPNativeAdRequest.m +++ b/MoPubSDK/NativeAds/MPNativeAdRequest.m @@ -307,6 +307,14 @@ - (void)communicatorDidFailWithError:(NSError *)error [self completeAdRequestWithAdObject:nil error:MPNativeAdNSErrorForNetworkConnectionError()]; } +- (MPAdType)adTypeForAdServerCommunicator:(MPAdServerCommunicator *)adServerCommunicator { + return MPAdTypeInline; +} + +- (NSString *)adUnitIDForAdServerCommunicator:(MPAdServerCommunicator *)adServerCommunicator { + return self.adUnitIdentifier; +} + #pragma mark - - (void)nativeCustomEvent:(MPNativeCustomEvent *)event didLoadAd:(MPNativeAd *)adObject diff --git a/MoPubSDK/Resources/MPCountdownTimer.html b/MoPubSDK/Resources/MPCountdownTimer.html index 70a496a26..766d27449 100644 --- a/MoPubSDK/Resources/MPCountdownTimer.html +++ b/MoPubSDK/Resources/MPCountdownTimer.html @@ -1,6 +1,7 @@ + diff --git a/MoPubSDK/RewardedVideo/Internal/MPMoPubRewardedPlayableCustomEvent.m b/MoPubSDK/RewardedVideo/Internal/MPMoPubRewardedPlayableCustomEvent.m index 8013d0a58..4126c65a7 100644 --- a/MoPubSDK/RewardedVideo/Internal/MPMoPubRewardedPlayableCustomEvent.m +++ b/MoPubSDK/RewardedVideo/Internal/MPMoPubRewardedPlayableCustomEvent.m @@ -121,6 +121,16 @@ - (void)presentRewardedVideoFromViewController:(UIViewController *)viewControlle self.timerView = [[MPCountdownTimerView alloc] initWithFrame:viewController.view.bounds duration:self.countdownDuration]; [self.interstitial.view addSubview:self.timerView]; + if (@available(iOS 9.0, *)) { + NSArray * constraints = @[[self.timerView.leftAnchor constraintEqualToAnchor:self.interstitial.view.leftAnchor], + [self.timerView.rightAnchor constraintEqualToAnchor:self.interstitial.view.rightAnchor], + [self.timerView.topAnchor constraintEqualToAnchor:self.interstitial.view.topAnchor], + [self.timerView.bottomAnchor constraintEqualToAnchor:self.interstitial.view.bottomAnchor] + ]; + [NSLayoutConstraint activateConstraints:constraints]; + self.timerView.translatesAutoresizingMaskIntoConstraints = NO; + } + [self.timerView startWithTimerCompletion:^(BOOL hasElapsed) { __typeof__(self) strongSelf = weakSelf; if (strongSelf != nil) { diff --git a/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdManager.m b/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdManager.m index 274312206..06b8fa7d9 100644 --- a/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdManager.m +++ b/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdManager.m @@ -242,6 +242,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 diff --git a/MoPubSDKFramework/Info.plist b/MoPubSDKFramework/Info.plist index 7e5d3ad53..2b0f8dcf7 100644 --- a/MoPubSDKFramework/Info.plist +++ b/MoPubSDKFramework/Info.plist @@ -15,9 +15,9 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 5.5.0 + 5.6.0 CFBundleVersion - 5.5.0 + 5.6.0 NSPrincipalClass diff --git a/MoPubSDKTests/Info.plist b/MoPubSDKTests/Info.plist index 8a43305e3..7c7e8e14b 100644 --- a/MoPubSDKTests/Info.plist +++ b/MoPubSDKTests/Info.plist @@ -15,8 +15,8 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 5.5.0 + 5.6.0 CFBundleVersion - 5.5.0 + 5.6.0 diff --git a/MoPubSDKTests/MOPUBExperimentProviderTests.m b/MoPubSDKTests/MOPUBExperimentProviderTests.m index 0611fd76f..34ce45e92 100644 --- a/MoPubSDKTests/MOPUBExperimentProviderTests.m +++ b/MoPubSDKTests/MOPUBExperimentProviderTests.m @@ -21,7 +21,7 @@ @implementation MOPUBExperimentProviderTests - (void)testClickthroughExperimentDefault { [MOPUBExperimentProvider setDisplayAgentOverriddenByClientFlag:NO]; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:nil data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:nil data:nil adType:MPAdTypeFullscreen]; XCTAssertEqual(config.clickthroughExperimentBrowserAgent, MOPUBDisplayAgentTypeInApp); XCTAssertEqual([MOPUBExperimentProvider displayAgentType], MOPUBDisplayAgentTypeInApp); } @@ -29,7 +29,7 @@ - (void)testClickthroughExperimentDefault { - (void)testClickthroughExperimentInApp { [MOPUBExperimentProvider setDisplayAgentOverriddenByClientFlag:NO]; NSDictionary * headers = @{ kClickthroughExperimentBrowserAgent: @"0"}; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil adType:MPAdTypeFullscreen]; XCTAssertEqual(config.clickthroughExperimentBrowserAgent, MOPUBDisplayAgentTypeInApp); XCTAssertEqual([MOPUBExperimentProvider displayAgentType], MOPUBDisplayAgentTypeInApp); } @@ -37,7 +37,7 @@ - (void)testClickthroughExperimentInApp { - (void)testClickthroughExperimentNativeBrowser { [MOPUBExperimentProvider setDisplayAgentOverriddenByClientFlag:NO]; NSDictionary * headers = @{ kClickthroughExperimentBrowserAgent: @"1"}; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil adType:MPAdTypeFullscreen]; XCTAssertEqual(config.clickthroughExperimentBrowserAgent, MOPUBDisplayAgentTypeNativeSafari); XCTAssertEqual([MOPUBExperimentProvider displayAgentType], MOPUBDisplayAgentTypeNativeSafari); } @@ -45,7 +45,7 @@ - (void)testClickthroughExperimentNativeBrowser { - (void)testClickthroughExperimentSafariViewController { [MOPUBExperimentProvider setDisplayAgentOverriddenByClientFlag:NO]; NSDictionary * headers = @{ kClickthroughExperimentBrowserAgent: @"2"}; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil adType:MPAdTypeFullscreen]; XCTAssertEqual(config.clickthroughExperimentBrowserAgent, MOPUBDisplayAgentTypeSafariViewController); XCTAssertEqual([MOPUBExperimentProvider displayAgentType], MOPUBDisplayAgentTypeSafariViewController); } @@ -54,7 +54,7 @@ - (void)testClickthroughClientOverride { [[MoPub sharedInstance] setClickthroughDisplayAgentType:MOPUBDisplayAgentTypeInApp]; NSDictionary * headers = @{ kClickthroughExperimentBrowserAgent: @"2"}; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil adType:MPAdTypeFullscreen]; XCTAssertEqual(config.clickthroughExperimentBrowserAgent, MOPUBDisplayAgentTypeSafariViewController); XCTAssertEqual([MOPUBExperimentProvider displayAgentType], MOPUBDisplayAgentTypeInApp); diff --git a/MoPubSDKTests/MPAdConfigurationFactory.m b/MoPubSDKTests/MPAdConfigurationFactory.m index 3e28c4f7e..c31e5809a 100644 --- a/MoPubSDKTests/MPAdConfigurationFactory.m +++ b/MoPubSDKTests/MPAdConfigurationFactory.m @@ -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 181d564d0..c566687fa 100644 --- a/MoPubSDKTests/MPAdConfigurationTests.m +++ b/MoPubSDKTests/MPAdConfigurationTests.m @@ -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); @@ -172,28 +172,28 @@ - (void)testNaiveVideoTrackers { #pragma mark - Clickthrough experiments test - (void)testClickthroughExperimentDefault { - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:nil data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:nil data:nil adType:MPAdTypeFullscreen]; XCTAssertEqual(config.clickthroughExperimentBrowserAgent, MOPUBDisplayAgentTypeInApp); XCTAssertEqual([MOPUBExperimentProvider displayAgentType], MOPUBDisplayAgentTypeInApp); } - (void)testClickthroughExperimentInApp { NSDictionary * headers = @{ kClickthroughExperimentBrowserAgent: @"0"}; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil adType:MPAdTypeFullscreen]; XCTAssertEqual(config.clickthroughExperimentBrowserAgent, MOPUBDisplayAgentTypeInApp); XCTAssertEqual([MOPUBExperimentProvider displayAgentType], MOPUBDisplayAgentTypeInApp); } - (void)testClickthroughExperimentNativeBrowser { NSDictionary * headers = @{ kClickthroughExperimentBrowserAgent: @"1"}; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil adType:MPAdTypeFullscreen]; XCTAssertEqual(config.clickthroughExperimentBrowserAgent, MOPUBDisplayAgentTypeNativeSafari); XCTAssertEqual([MOPUBExperimentProvider displayAgentType], MOPUBDisplayAgentTypeNativeSafari); } - (void)testClickthroughExperimentSafariViewController { NSDictionary * headers = @{ kClickthroughExperimentBrowserAgent: @"2"}; - MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil]; + MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil adType:MPAdTypeFullscreen]; XCTAssertEqual(config.clickthroughExperimentBrowserAgent, MOPUBDisplayAgentTypeSafariViewController); XCTAssertEqual([MOPUBExperimentProvider displayAgentType], MOPUBDisplayAgentTypeSafariViewController); } @@ -208,7 +208,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 +224,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 +240,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 +255,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 +271,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 +287,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 +299,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 +343,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 +370,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 +380,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 +391,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 +401,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 +410,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 +423,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 +440,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 +459,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 +473,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 +492,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 +506,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 +520,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 +530,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 +543,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 +556,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 +569,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 +582,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 +595,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 +609,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 +622,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 +635,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 +648,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 +661,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 +675,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 +689,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 +704,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 +720,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 +736,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 +752,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 +767,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 +783,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 +799,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 +815,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 +831,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 +847,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 +863,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 +879,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 +896,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 +915,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 +934,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 +953,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/MPAdServerCommunicator+Testing.h b/MoPubSDKTests/MPAdServerCommunicator+Testing.h index 03bb036c3..c4dfcc3ca 100644 --- a/MoPubSDKTests/MPAdServerCommunicator+Testing.h +++ b/MoPubSDKTests/MPAdServerCommunicator+Testing.h @@ -8,10 +8,14 @@ #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; - (NSArray *)getFlattenJsonResponses:(NSDictionary *)json keys:(NSArray *)keys; diff --git a/MoPubSDKTests/MPAdServerCommunicator+Testing.m b/MoPubSDKTests/MPAdServerCommunicator+Testing.m index 93cf2ed4e..6679de7cd 100644 --- a/MoPubSDKTests/MPAdServerCommunicator+Testing.m +++ b/MoPubSDKTests/MPAdServerCommunicator+Testing.m @@ -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 71899d012..a3992897f 100644 --- a/MoPubSDKTests/MPAdServerCommunicatorTests.m +++ b/MoPubSDKTests/MPAdServerCommunicatorTests.m @@ -13,14 +13,19 @@ #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; @@ -343,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); @@ -381,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); @@ -421,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); @@ -497,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); @@ -536,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); @@ -574,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); @@ -610,4 +621,283 @@ - (void)testConsentInvalidateConsentDoesNothingWhenMalformed { XCTAssert(MPConsentManager.sharedManager.currentStatus == MPConsentStatusConsented); } +#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/MPAdServerURLBuilderTests.m b/MoPubSDKTests/MPAdServerURLBuilderTests.m index 3627b2f6f..144dd2cac 100644 --- a/MoPubSDKTests/MPAdServerURLBuilderTests.m +++ b/MoPubSDKTests/MPAdServerURLBuilderTests.m @@ -19,6 +19,7 @@ #import "NSString+MPConsentStatus.h" #import "NSString+MPAdditions.h" #import "NSURLComponents+Testing.h" +#import "MPRateLimitManager.h" static NSString * const kTestAdUnitId = @""; static NSString * const kTestKeywords = @""; @@ -215,4 +216,45 @@ - (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" + keywords:nil + userDataKeywords:nil + location: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" + keywords:nil + userDataKeywords:nil + location: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" + keywords:nil + userDataKeywords:nil + location:nil]; + + NSNumber * value = [url numberForPOSTDataKey:kBackoffMsKey]; + XCTAssertEqual([value integerValue], 10); + XCTAssertNil([url stringForPOSTDataKey:kBackoffReasonKey]); +} + @end diff --git a/MoPubSDKTests/MPAdserverCommunicatorDelegateHandler.h b/MoPubSDKTests/MPAdserverCommunicatorDelegateHandler.h index 618d43474..d25bf9803 100644 --- a/MoPubSDKTests/MPAdserverCommunicatorDelegateHandler.h +++ b/MoPubSDKTests/MPAdserverCommunicatorDelegateHandler.h @@ -13,5 +13,7 @@ @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 2979e8b62..b545bd067 100644 --- a/MoPubSDKTests/MPAdserverCommunicatorDelegateHandler.m +++ b/MoPubSDKTests/MPAdserverCommunicatorDelegateHandler.m @@ -12,5 +12,7 @@ @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/MPBannerAdapterDelegateHandler.h b/MoPubSDKTests/MPBannerAdapterDelegateHandler.h index c992a3a02..68699ca4a 100644 --- a/MoPubSDKTests/MPBannerAdapterDelegateHandler.h +++ b/MoPubSDKTests/MPBannerAdapterDelegateHandler.h @@ -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 9f212685c..fba761b23 100644 --- a/MoPubSDKTests/MPBannerAdapterDelegateHandler.m +++ b/MoPubSDKTests/MPBannerAdapterDelegateHandler.m @@ -34,4 +34,12 @@ - (void)adapter:(MPBaseBannerAdapter *)adapter didTrackImpressionForAd:(UIView * 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/MPBannerCustomEventAdapterTests.m b/MoPubSDKTests/MPBannerCustomEventAdapterTests.m index 30cc0dabf..c6bfb1841 100644 --- a/MoPubSDKTests/MPBannerCustomEventAdapterTests.m +++ b/MoPubSDKTests/MPBannerCustomEventAdapterTests.m @@ -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/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/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..6b71713b4 --- /dev/null +++ b/MoPubSDKTests/MPRateLimitManagerTests.m @@ -0,0 +1,186 @@ +// +// 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]); +} + +@end diff --git a/MoPubSDKTests/MPRewardedVideoAdManagerTests.m b/MoPubSDKTests/MPRewardedVideoAdManagerTests.m index 11bf35790..4258b066e 100644 --- a/MoPubSDKTests/MPRewardedVideoAdManagerTests.m +++ b/MoPubSDKTests/MPRewardedVideoAdManagerTests.m @@ -37,7 +37,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 +70,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 +105,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 +140,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 +182,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 +224,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."]; diff --git a/MoPubSDKTests/MPRewardedVideoTests.m b/MoPubSDKTests/MPRewardedVideoTests.m index 6877ed34b..41df5183a 100644 --- a/MoPubSDKTests/MPRewardedVideoTests.m +++ b/MoPubSDKTests/MPRewardedVideoTests.m @@ -142,7 +142,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."]; @@ -183,7 +183,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."]; @@ -226,7 +226,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."]; @@ -281,7 +281,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."]; @@ -320,7 +320,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."]; @@ -365,7 +365,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."]; @@ -415,7 +415,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."]; @@ -458,7 +458,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."]; @@ -508,7 +508,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."]; @@ -545,7 +545,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."]; @@ -581,7 +581,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."]; @@ -612,7 +612,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."]; @@ -646,7 +646,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."]; @@ -682,7 +682,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."]; @@ -729,7 +729,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."]; @@ -765,7 +765,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."]; diff --git a/MoPubSDKTests/MoPubTests.m b/MoPubSDKTests/MoPubTests.m index 2baa8fc8c..fe89c1605 100644 --- a/MoPubSDKTests/MoPubTests.m +++ b/MoPubSDKTests/MoPubTests.m @@ -32,7 +32,7 @@ - (void)setUp { [MPMediationManager.sharedManager clearCache]; [MoPub sharedInstance].forceWKWebView = NO; - MPLogging.consoleLogLevel = MPLogLevelInfo; + MPLogging.consoleLogLevel = MPBLogLevelInfo; } #pragma mark - Initialization @@ -205,7 +205,7 @@ - (void)testNoForceWKWebView { 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]; MRController * controller = [[MRController alloc] initWithAdViewFrame:CGRectZero supportedOrientations:MPInterstitialOrientationTypeAll adPlacementType:MRAdViewPlacementTypeInterstitial delegate:nil]; [controller loadAdWithConfiguration:config]; @@ -224,7 +224,7 @@ - (void)testForceWKWebView { 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]; MRController * controller = [[MRController alloc] initWithAdViewFrame:CGRectZero supportedOrientations:MPInterstitialOrientationTypeAll adPlacementType:MRAdViewPlacementTypeInterstitial delegate:nil]; [controller loadAdWithConfiguration:config]; @@ -236,9 +236,9 @@ - (void)testForceWKWebView { #pragma mark - Logging - (void)testSetLogLevel { - MPLogging.consoleLogLevel = MPLogLevelDebug; + MPLogging.consoleLogLevel = MPBLogLevelDebug; - XCTAssertTrue(MPLogging.consoleLogLevel == MPLogLevelDebug); + XCTAssertTrue(MPLogging.consoleLogLevel == MPBLogLevelDebug); } @end diff --git a/MoPubSampleApp/AppDelegate.m b/MoPubSampleApp/AppDelegate.m index c5499fa87..884641eec 100644 --- a/MoPubSampleApp/AppDelegate.m +++ b/MoPubSampleApp/AppDelegate.m @@ -42,7 +42,7 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( MPMoPubConfiguration * sdkConfig = [[MPMoPubConfiguration alloc] initWithAdUnitIdForAppInitialization: @"0ac59b0996d947309c33f59d6676399f"]; sdkConfig.globalMediationSettings = @[]; - sdkConfig.loggingLevel = MPLogLevelInfo; + sdkConfig.loggingLevel = MPBLogLevelInfo; [[MoPub sharedInstance] initializeSdkWithConfiguration:sdkConfig completion:^{ NSLog(@"SDK initialization complete"); }]; diff --git a/MoPubSampleApp/Controllers/MPRewardedVideoAdDetailViewController.m b/MoPubSampleApp/Controllers/MPRewardedVideoAdDetailViewController.m index cd00a3ea3..d135bb2d7 100644 --- a/MoPubSampleApp/Controllers/MPRewardedVideoAdDetailViewController.m +++ b/MoPubSampleApp/Controllers/MPRewardedVideoAdDetailViewController.m @@ -103,7 +103,7 @@ - (BOOL)textFieldShouldReturn:(UITextField *)textField return YES; } -#pragma mark - +#pragma mark - - (void)rewardedVideoAdDidLoadForAdUnitID:(NSString *)adUnitID { diff --git a/MoPubSampleApp/MPSampleAppLogReader.m b/MoPubSampleApp/MPSampleAppLogReader.m index 2569578f2..4ade75948 100644 --- a/MoPubSampleApp/MPSampleAppLogReader.m +++ b/MoPubSampleApp/MPSampleAppLogReader.m @@ -9,7 +9,7 @@ #import "MPSampleAppLogReader.h" #import "MPLogging.h" -@interface MPSampleAppLogReader () +@interface MPSampleAppLogReader () @property (nonatomic, strong) UIAlertView *warmingUpAlertView; @@ -39,11 +39,11 @@ - (void)beginReadingLogMessages [MPLogging addLogger:self]; } -#pragma mark - +#pragma mark - -- (MPLogLevel)logLevel +- (MPBLogLevel)logLevel { - return MPLogLevelDebug; + return MPBLogLevelDebug; } - (void)logMessage:(NSString *)message diff --git a/README.md b/README.md index 706d737be..afbddd2d0 100644 --- a/README.md +++ b/README.md @@ -23,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.5.0 +The current version of the SDK is 5.6.0 ### Installation with CocoaPods @@ -56,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.6.0/mopub-framework-5.6.0.zip)** Includes everything you need to serve HTML, MRAID, and Native MoPub advertisements. Third party ad networks are not included. @@ -66,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.6.0/mopub-base-5.6.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.6.0/mopub-nonnative-5.6.0.zip)** Includes everything you need to serve HTML and MRAID advertisements. Third party ad networks and Native MoPub advertisements are not included. @@ -83,14 +83,13 @@ 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** - - 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 + - Added `+` button to the Canary sample app allowing manual entry of custom ad units - **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 + - 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 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. diff --git a/mopub-ios-sdk.podspec b/mopub-ios-sdk.podspec index d47cf1b4c..4febe276f 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.5.0' + spec.version = '5.6.0' spec.license = { :type => 'New BSD', :file => 'LICENSE' } spec.homepage = 'https://github.com/mopub/mopub-ios-sdk' spec.authors = { 'MoPub' => 'support@mopub.com' } @@ -14,7 +14,7 @@ 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.5.0' } + spec.source = { :git => 'https://github.com/mopub/mopub-ios-sdk.git', :tag => '5.6.0' } spec.requires_arc = true spec.ios.deployment_target = '8.0' spec.frameworks = [ From 42e6f7a644e718899e592ba80700fee6cd7fa5a4 Mon Sep 17 00:00:00 2001 From: Caleb Lee Date: Fri, 17 May 2019 16:12:13 -0700 Subject: [PATCH 07/12] 5.7.0 --- CHANGELOG.md | 14 + Canary/Canary.xcodeproj/project.pbxproj | 589 +++++++++++++++--- .../xcschemes/Canary Screenshots.xcscheme | 6 +- ...e => Canary-AppStore Application.xcscheme} | 8 +- ...e => Canary-Internal Application.xcscheme} | 21 +- .../xcschemes/Canary-Unit Tests.xcscheme | 86 +++ .../AdapterVersionsMenuDataSource.swift | 140 +++-- Canary/Canary/AppDelegate.swift | 5 + .../Base/AdUnitTableViewController.swift | 18 +- .../FilterableAdUnitTableViewController.swift | 9 +- .../Canary/Cells/AdActionsTableViewCell.swift | 7 +- Canary/Canary/Cells/AdUnitTableViewCell.swift | 7 +- .../Canary/Cells/BasicMenuTableViewCell.swift | 2 +- .../CollapsibleAdapterInfoTableViewCell.swift | 2 +- Canary/Canary/Cells/StatusTableViewCell.swift | 7 +- .../Cells/TextAndToggleTableViewCell.swift | 42 ++ .../Cells/TextAndToggleTableViewCell.xib | 51 ++ .../Canary/Cells/TextEntryTableViewCell.swift | 7 +- Canary/Canary/Configuration/Default.xcconfig | 12 + Canary/Canary/{ => Configuration}/Info.plist | 6 +- Canary/Canary/Extensions/Array+Sort.swift | 55 ++ .../Extensions/MPBool+Description.swift | 3 +- .../MPConsentStatus+Description.swift | 3 +- ...iveAdRendererConfiguration+Utilities.swift | 28 + .../Canary/Extensions/NSObject+Utility.swift | 18 + .../Extensions/Notification+Token.swift | 42 ++ .../Extensions/UITableView+Utility.swift | 31 + .../Extensions/UserDefaults+Subscript.swift | 66 ++ Canary/Canary/Formats/AdDataSource.swift | 85 ++- .../Formats/AdTableViewController.swift | 29 +- .../Canary/Formats/BannerAdDataSource.swift | 81 +-- .../Formats/BaseNativeAdDataSource.swift | 65 +- .../Formats/InterstitialAdDataSource.swift | 101 +-- .../NativeAdCollectionDataSource.swift | 2 +- .../NativeAdCollectionViewController.swift | 2 +- .../Canary/Formats/NativeAdDataSource.swift | 77 +-- .../Formats/NativeAdTableViewController.swift | 4 +- .../Canary/Formats/RewardedAdDataSource.swift | 114 +--- .../LogingLevelMenuDataSource.swift | 2 +- Canary/Canary/Menu/MenuDataSource.swift | 6 +- Canary/Canary/Menu/MenuDisplayable.swift | 28 +- Canary/Canary/Menu/MenuViewController.swift | 6 +- .../Renderer/NativeAdRendererManager.swift | 184 ++++++ .../NativeAdRendererMenuDataSource.swift | 99 +++ .../OrderPreferenceViewController.swift | 141 +++++ .../Canary/SavedAds/SavedAdsDataSource.swift | 15 +- Canary/Canary/SavedAds/SavedAdsManager.swift | 112 ++-- .../SavedAds/SavedAdsViewController.swift | 27 +- Canary/Canary/TableViewCellRegisterable.swift | 24 +- Canary/Canary/Utility/TypedNotification.swift | 86 +++ .../Extensions/ArraySortExtensionTests.swift | 147 +++++ Canary/CanaryUnitTests/Info.plist | 22 + .../NativeAdRendererManagerTests.swift | 87 +++ .../NativeAdRendererManagerTests.swift.plist | Bin 0 -> 42 bytes .../SavedAds/SavedAdsManagerTests.swift | 97 +++ .../SavedAds/SavedAdsManagerTests.swift.plist | Bin 0 -> 42 bytes Canary/Podfile | 84 ++- Gemfile.lock | 84 +-- MoPubResources/Info.plist | 4 +- MoPubSDK.xcodeproj/project.pbxproj | 145 ++++- .../xcshareddata/xcschemes/MoPubSDK.xcscheme | 20 + .../xcschemes/MoPubSDKTests.xcscheme | 1 - MoPubSDK/Internal/Banners/MPBannerAdManager.m | 14 +- .../Banners/MPBannerAdManagerDelegate.h | 3 +- .../Banners/MPBannerCustomEventAdapter.m | 10 +- .../Internal/Banners/MPBaseBannerAdapter.h | 2 +- .../AdAlerts/MPAdAlertGestureRecognizer.m | 7 - .../Common/AdAlerts/MPAdAlertManager.m | 4 +- .../Internal/Common/MPAdBrowserController.m | 5 +- MoPubSDK/Internal/Common/MPAdConfiguration.h | 3 + MoPubSDK/Internal/Common/MPAdConfiguration.m | 17 +- .../Common/MPAdDestinationDisplayAgent.m | 41 +- MoPubSDK/Internal/Common/MPClosableView.h | 2 + MoPubSDK/Internal/Common/MPClosableView.m | 41 +- .../Internal/Common/MPCountdownTimerView.h | 45 +- .../Internal/Common/MPCountdownTimerView.m | 243 +++++--- .../Internal/Common/MPProgressOverlayView.m | 3 - MoPubSDK/Internal/HTML/MPAdWebViewAgent.m | 16 +- .../Internal/HTML/MPHTMLBannerCustomEvent.m | 2 - .../HTML/MPHTMLInterstitialCustomEvent.m | 2 - .../HTML/MPHTMLInterstitialViewController.m | 4 - .../Interstitials/MPBaseInterstitialAdapter.h | 1 + .../Interstitials/MPBaseInterstitialAdapter.m | 5 +- .../Interstitials/MPInterstitialAdManager.m | 11 +- .../MPInterstitialAdManagerDelegate.h | 2 + .../MPInterstitialCustomEventAdapter.m | 4 - .../MPInterstitialViewController.m | 7 - MoPubSDK/Internal/MPAdServerKeys.h | 17 + MoPubSDK/Internal/MPAdServerKeys.m | 147 +++-- .../Internal/MPConsentDialogViewController.m | 3 +- MoPubSDK/Internal/MPConsentManager.m | 56 +- MoPubSDK/Internal/MPCoreInstanceProvider.m | 4 - MoPubSDK/Internal/MPRateLimitManager.m | 2 +- .../MRAID/MPMRAIDInterstitialCustomEvent.m | 2 - MoPubSDK/Internal/MRAID/MRController.m | 133 ++-- MoPubSDK/Internal/MRAID/MRProperty.m | 10 - .../Internal/MRAID/MRVideoPlayerManager.m | 2 - .../Utility/Categories/MoPub+Utility.h | 38 ++ .../Utility/Categories/MoPub+Utility.m | 27 + MoPubSDK/Internal/Utility/MPError.h | 2 + MoPubSDK/Internal/Utility/MPError.m | 4 + MoPubSDK/Internal/Utility/MPInternalUtils.h | 26 - MoPubSDK/Internal/Utility/MPInternalUtils.m | 33 - MoPubSDK/Internal/Utility/MPTimer.h | 24 +- MoPubSDK/Internal/Utility/MPTimer.m | 122 ++-- MoPubSDK/MPAdView.h | 91 +-- MoPubSDK/MPAdView.m | 18 +- MoPubSDK/MPAdViewDelegate.h | 103 +++ MoPubSDK/MPConstants.h | 2 +- MoPubSDK/MPImpressionData.h | 45 ++ MoPubSDK/MPImpressionData.m | 203 ++++++ MoPubSDK/MPInterstitialAdController.h | 115 +--- MoPubSDK/MPInterstitialAdController.m | 6 + MoPubSDK/MPInterstitialAdControllerDelegate.h | 119 ++++ MoPubSDK/MPInterstitialCustomEvent.m | 2 - MoPubSDK/MPMoPubAd.h | 45 ++ MoPubSDK/MPMoPubAdPlacer.h | 48 ++ MoPubSDK/MoPub.h | 8 + .../Internal/MPMoPubNativeAdAdapter.m | 1 + .../Internal/MPMoPubNativeCustomEvent.m | 5 +- .../NativeAds/Internal/MPNativeAd+Internal.h | 1 + .../NativeAds/Internal/MPNativeAd+Internal.m | 1 + .../Internal/MPStreamAdPlacerDelegate.h | 40 ++ MoPubSDK/NativeAds/MPCollectionViewAdPlacer.h | 31 +- MoPubSDK/NativeAds/MPCollectionViewAdPlacer.m | 8 + .../MPCollectionViewAdPlacerDelegate.h | 38 ++ MoPubSDK/NativeAds/MPNativeAd.h | 5 +- MoPubSDK/NativeAds/MPNativeAd.m | 6 +- MoPubSDK/NativeAds/MPNativeAdDelegate.h | 4 +- MoPubSDK/NativeAds/MPNativeAdRequest.m | 1 + MoPubSDK/NativeAds/MPStaticNativeAdRenderer.h | 3 + MoPubSDK/NativeAds/MPStaticNativeAdRenderer.m | 8 +- MoPubSDK/NativeAds/MPStreamAdPlacer.h | 35 +- MoPubSDK/NativeAds/MPStreamAdPlacer.m | 8 + MoPubSDK/NativeAds/MPTableViewAdPlacer.h | 31 +- MoPubSDK/NativeAds/MPTableViewAdPlacer.m | 8 + .../NativeAds/MPTableViewAdPlacerDelegate.h | 38 ++ .../Internal/MOPUBNativeVideoAdAdapter.m | 1 + .../Internal/MOPUBNativeVideoCustomEvent.m | 5 +- MoPubSDK/Resources/MPAdapters.plist | 2 +- MoPubSDK/Resources/MPCountdownTimer.html | 36 -- MoPubSDK/Resources/MRAID.bundle/mraid.js | 2 +- .../MPMoPubRewardedPlayableCustomEvent.m | 33 +- .../Internal/MPRewardedVideoAdManager.h | 2 + .../Internal/MPRewardedVideoAdManager.m | 4 + .../Internal/MPRewardedVideoAdapter.h | 1 + .../Internal/MPRewardedVideoAdapter.m | 1 + MoPubSDK/RewardedVideo/MPRewardedVideo.h | 9 + MoPubSDK/RewardedVideo/MPRewardedVideo.m | 8 + .../RewardedVideo/MPRewardedVideoReward.m | 8 + MoPubSDKFramework/Info.plist | 4 +- MoPubSDKTests/Info.plist | 4 +- .../MPBannerAdManagerDelegateHandler.h | 6 +- .../MPBannerAdManagerDelegateHandler.m | 8 +- MoPubSDKTests/MPBannerAdManagerTests.m | 93 ++- .../MPBannerCustomEventAdapter+Testing.h | 1 + MoPubSDKTests/MPCountdownTimerView+Testing.h | 22 + MoPubSDKTests/MPCountdownTimerViewTests.m | 261 ++++---- MoPubSDKTests/MPImpressionDataTests.m | 169 +++++ .../MPInterstitialAdManagerDelegateHandler.h | 3 + .../MPInterstitialAdManagerDelegateHandler.m | 4 + MoPubSDKTests/MPInterstitialAdManagerTests.m | 89 ++- .../MPInterstitialAdapterDelegateHandler.h | 1 + .../MPInterstitialAdapterDelegateHandler.m | 1 + ...MoPubRewardedPlayableCustomEvent+Testing.m | 3 +- MoPubSDKTests/MPNativeAd+Testing.h | 15 + MoPubSDKTests/MPNativeAd+Testing.m | 13 + MoPubSDKTests/MPNativeAdDelegateHandler.h | 24 + MoPubSDKTests/MPNativeAdDelegateHandler.m | 35 ++ MoPubSDKTests/MPNativeAdRequestTests.m | 87 +++ MoPubSDKTests/MPRewardedVideoAdManagerTests.m | 77 +++ .../MPRewardedVideoAdapterDelegateHandler.h | 1 + .../MPRewardedVideoAdapterDelegateHandler.m | 1 + .../MPRewardedVideoDelegateHandler.h | 2 + .../MPRewardedVideoDelegateHandler.m | 9 + MoPubSDKTests/MPTimer+Testing.h | 22 + MoPubSDKTests/MPTimer+Testing.m | 24 + MoPubSDKTests/MPTimerTests.m | 270 ++++++++ MoPubSDKTests/MRController+Testing.h | 12 + MoPubSDKTests/MRControllerTests.m | 229 +++++++ README.md | 24 +- Scripts/mraid_build.sh | 2 +- mopub-ios-sdk.podspec | 6 +- 183 files changed, 5305 insertions(+), 1818 deletions(-) rename Canary/Canary.xcodeproj/xcshareddata/xcschemes/{Canary.xcscheme => Canary-AppStore Application.xcscheme} (94%) rename Canary/Canary.xcodeproj/xcshareddata/xcschemes/{Canary (Internal).xcscheme => Canary-Internal Application.xcscheme} (80%) create mode 100644 Canary/Canary.xcodeproj/xcshareddata/xcschemes/Canary-Unit Tests.xcscheme create mode 100644 Canary/Canary/Cells/TextAndToggleTableViewCell.swift create mode 100644 Canary/Canary/Cells/TextAndToggleTableViewCell.xib create mode 100644 Canary/Canary/Configuration/Default.xcconfig rename Canary/Canary/{ => Configuration}/Info.plist (93%) create mode 100644 Canary/Canary/Extensions/Array+Sort.swift create mode 100644 Canary/Canary/Extensions/MPNativeAdRendererConfiguration+Utilities.swift create mode 100644 Canary/Canary/Extensions/NSObject+Utility.swift create mode 100644 Canary/Canary/Extensions/Notification+Token.swift create mode 100644 Canary/Canary/Extensions/UITableView+Utility.swift create mode 100644 Canary/Canary/Extensions/UserDefaults+Subscript.swift create mode 100644 Canary/Canary/Renderer/NativeAdRendererManager.swift create mode 100644 Canary/Canary/Renderer/NativeAdRendererMenuDataSource.swift create mode 100644 Canary/Canary/Renderer/OrderPreferenceViewController.swift create mode 100644 Canary/Canary/Utility/TypedNotification.swift create mode 100644 Canary/CanaryUnitTests/Extensions/ArraySortExtensionTests.swift create mode 100644 Canary/CanaryUnitTests/Info.plist create mode 100644 Canary/CanaryUnitTests/Renderer/NativeAdRendererManagerTests.swift create mode 100644 Canary/CanaryUnitTests/Renderer/NativeAdRendererManagerTests.swift.plist create mode 100644 Canary/CanaryUnitTests/SavedAds/SavedAdsManagerTests.swift create mode 100644 Canary/CanaryUnitTests/SavedAds/SavedAdsManagerTests.swift.plist create mode 100644 MoPubSDK/Internal/Utility/Categories/MoPub+Utility.h create mode 100644 MoPubSDK/Internal/Utility/Categories/MoPub+Utility.m delete mode 100644 MoPubSDK/Internal/Utility/MPInternalUtils.h delete mode 100644 MoPubSDK/Internal/Utility/MPInternalUtils.m create mode 100644 MoPubSDK/MPAdViewDelegate.h create mode 100644 MoPubSDK/MPImpressionData.h create mode 100644 MoPubSDK/MPImpressionData.m create mode 100644 MoPubSDK/MPInterstitialAdControllerDelegate.h create mode 100644 MoPubSDK/MPMoPubAd.h create mode 100644 MoPubSDK/MPMoPubAdPlacer.h create mode 100644 MoPubSDK/NativeAds/Internal/MPStreamAdPlacerDelegate.h create mode 100644 MoPubSDK/NativeAds/MPCollectionViewAdPlacerDelegate.h create mode 100644 MoPubSDK/NativeAds/MPTableViewAdPlacerDelegate.h delete mode 100644 MoPubSDK/Resources/MPCountdownTimer.html create mode 100644 MoPubSDKTests/MPCountdownTimerView+Testing.h create mode 100644 MoPubSDKTests/MPImpressionDataTests.m create mode 100644 MoPubSDKTests/MPNativeAd+Testing.h create mode 100644 MoPubSDKTests/MPNativeAd+Testing.m create mode 100644 MoPubSDKTests/MPNativeAdDelegateHandler.h create mode 100644 MoPubSDKTests/MPNativeAdDelegateHandler.m create mode 100644 MoPubSDKTests/MPTimer+Testing.h create mode 100644 MoPubSDKTests/MPTimer+Testing.m create mode 100644 MoPubSDKTests/MPTimerTests.m create mode 100644 MoPubSDKTests/MRControllerTests.m diff --git a/CHANGELOG.md b/CHANGELOG.md index 4305e2e7b..a47c09a4f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,17 @@ +## 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 diff --git a/Canary/Canary.xcodeproj/project.pbxproj b/Canary/Canary.xcodeproj/project.pbxproj index 66a751752..d43ba718b 100644 --- a/Canary/Canary.xcodeproj/project.pbxproj +++ b/Canary/Canary.xcodeproj/project.pbxproj @@ -15,7 +15,8 @@ 2A35FAAB21B5DA6200DC8805 /* QRCodeCameraInterfaceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A35FAAA21B5DA6200DC8805 /* QRCodeCameraInterfaceViewController.swift */; }; 2A35FAAC21B5DA6200DC8805 /* QRCodeCameraInterfaceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A35FAAA21B5DA6200DC8805 /* QRCodeCameraInterfaceViewController.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 */; }; + 3FFB21881C2230B8120188EF /* Pods_AppStore_Application.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DB1088039C5F170AE338BACD /* Pods_AppStore_Application.framework */; }; + 40FDA37129281ACEDA16C5D1 /* Pods_Internal_Application.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 04062B8FCC938E7869BA852B /* Pods_Internal_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 */; }; @@ -196,7 +197,34 @@ 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 */; }; - D1EA998EABF442DAD42BE461 /* Pods_Canary.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E125D60FEE1A91995D3FDF82 /* Pods_Canary.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 */; }; + 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 */; }; + F5527B01889E33C5A2B6CB9E /* Pods_CanaryUnitTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B73A1E273FE9E424C016B22E /* Pods_CanaryUnitTests.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -207,12 +235,19 @@ remoteGlobalIDString = BC33E0321FBB627B0060ECBE; remoteInfo = Canary; }; - CE237F23216BF2C800A8134A /* PBXContainerItemProxy */ = { + EC2C1A6B22710A8E00B1B594 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BC33E02B1FBB627B0060ECBE /* Project object */; proxyType = 1; - remoteGlobalIDString = BC33E0321FBB627B0060ECBE; - remoteInfo = Canary; + remoteGlobalIDString = BC33E04A1FBB63260060ECBE; + remoteInfo = "Internal Application"; + }; + EC469C1522613063006CABAA /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BC33E02B1FBB627B0060ECBE /* Project object */; + proxyType = 1; + remoteGlobalIDString = BC33E04A1FBB63260060ECBE; + remoteInfo = "Internal Application"; }; /* End PBXContainerItemProxy section */ @@ -230,17 +265,21 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 04062B8FCC938E7869BA852B /* Pods_Internal_Application.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Internal_Application.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 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 = ""; }; 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 = ""; }; + 792ED9133D866365E93CDB2E /* Pods-AppStore Application.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AppStore Application.release.xcconfig"; path = "Pods/Target Support Files/Pods-AppStore Application/Pods-AppStore Application.release.xcconfig"; sourceTree = ""; }; + 7B24E5575F339F1A17104381 /* Pods-Internal Application.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Internal Application.release.xcconfig"; path = "Pods/Target Support Files/Pods-Internal Application/Pods-Internal Application.release.xcconfig"; sourceTree = ""; }; + 7D892EB99170C15369014895 /* Pods-AppStore Application.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AppStore Application.debug.xcconfig"; path = "Pods/Target Support Files/Pods-AppStore Application/Pods-AppStore Application.debug.xcconfig"; sourceTree = ""; }; + 84ADB8A0A63B79D852517109 /* Pods-CanaryUnitTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CanaryUnitTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-CanaryUnitTests/Pods-CanaryUnitTests.debug.xcconfig"; sourceTree = ""; }; + A55602BC6D63A07F0C2F32F9 /* Pods-CanaryUnitTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CanaryUnitTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-CanaryUnitTests/Pods-CanaryUnitTests.release.xcconfig"; sourceTree = ""; }; 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 = ""; }; + B73A1E273FE9E424C016B22E /* Pods_CanaryUnitTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_CanaryUnitTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 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; }; @@ -304,6 +343,7 @@ 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 = ""; }; @@ -333,7 +373,6 @@ 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 = ""; }; 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 = ""; }; @@ -354,8 +393,25 @@ 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 = ""; }; - 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; }; + DB1088039C5F170AE338BACD /* Pods_AppStore_Application.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_AppStore_Application.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 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 = ""; }; + 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 = ""; }; + F80CB2C182236FA7D7126F3A /* Pods-Internal Application.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Internal Application.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Internal Application/Pods-Internal Application.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -371,7 +427,7 @@ buildActionMask = 2147483647; files = ( 2A35FAA921B5DA1C00DC8805 /* AVFoundation.framework in Frameworks */, - D1EA998EABF442DAD42BE461 /* Pods_Canary.framework in Frameworks */, + 3FFB21881C2230B8120188EF /* Pods_AppStore_Application.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -379,7 +435,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 3C669C6604F60567A169BAC2 /* Pods_Canary__Internal_.framework in Frameworks */, + 40FDA37129281ACEDA16C5D1 /* Pods_Internal_Application.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -390,6 +446,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + EC469C0D22613063006CABAA /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + F5527B01889E33C5A2B6CB9E /* Pods_CanaryUnitTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -401,17 +465,6 @@ path = APIEndpoint; sourceTree = ""; }; - B0C7C0257843E1CF62319AB8 /* Pods */ = { - 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 */, - ); - name = Pods; - sourceTree = ""; - }; B275DB2B200FD5F500F053F8 /* SavedAds */ = { isa = PBXGroup; children = ( @@ -469,9 +522,10 @@ isa = PBXGroup; children = ( BC33E0351FBB627B0060ECBE /* Canary */, + EC469C1122613063006CABAA /* CanaryUnitTests */, BC33E0341FBB627B0060ECBE /* Products */, BD565F79D171D27FE1BAD7EB /* Frameworks */, - B0C7C0257843E1CF62319AB8 /* Pods */, + DF7CF2094E2195D55BC35732 /* Pods */, ); sourceTree = ""; }; @@ -482,6 +536,7 @@ BC33E0571FBB63260060ECBE /* Canary.app */, CE237F1E216BF2C800A8134A /* CanaryUITests.xctest */, BC014490220B6E2F0090F497 /* CanaryScreenshots.xctest */, + EC469C1022613063006CABAA /* CanaryUnitTests.xctest */, ); name = Products; sourceTree = ""; @@ -489,17 +544,17 @@ BC33E0351FBB627B0060ECBE /* Canary */ = { isa = PBXGroup; children = ( + BC7892E8220B9C22007A6218 /* AccessibilityIdentifier.swift */, BC4FE3052208C57A00B5D240 /* AdapterVersions */, BC33E0361FBB627B0060ECBE /* AppDelegate.swift */, - BC7892E8220B9C22007A6218 /* AccessibilityIdentifier.swift */, BC00C5B7208FCF46008E0AEB /* AppDelegate+Consent.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 */, @@ -509,11 +564,13 @@ BCB63FE420AB3D8100C22C7F /* Menu */, BC525EE82135BA4E007B1761 /* PreferredWidthLabel.swift */, 2A35FAAA21B5DA6200DC8805 /* QRCodeCameraInterfaceViewController.swift */, + ECF2181F227900C7008BD940 /* Renderer */, BCA042EE211E049D001B1AF5 /* RoundedButton.swift */, BCFE5B3D1FE44F6D00D760E9 /* Samples */, B275DB2B200FD5F500F053F8 /* SavedAds */, BC04945920F91C9E00CFD9C2 /* StoryboardInstantiable.swift */, BCB4DF33211CD970005BD171 /* TableViewCellRegisterable.swift */, + EC469C06226006FC006CABAA /* Utility */, ); path = Canary; sourceTree = ""; @@ -552,6 +609,15 @@ path = ReleaseTesting; sourceTree = ""; }; + BC8D8EC5222F238F003BE094 /* Configuration */ = { + isa = PBXGroup; + children = ( + BC8D8EC4222F234B003BE094 /* Default.xcconfig */, + BC33E0441FBB627B0060ECBE /* Info.plist */, + ); + path = Configuration; + sourceTree = ""; + }; BCB63FE420AB3D8100C22C7F /* Menu */ = { isa = PBXGroup; children = ( @@ -587,6 +653,8 @@ BC4FE3092208E90800B5D240 /* CollapsibleAdapterInfoTableViewCell.xib */, BCB4DF2D211CC265005BD171 /* StatusTableViewCell.swift */, BCB4DF30211CC27D005BD171 /* StatusTableViewCell.xib */, + EC325257225273AD00D955C3 /* TextAndToggleTableViewCell.swift */, + EC325258225273AD00D955C3 /* TextAndToggleTableViewCell.xib */, BC2CEFB5211CF12C00EAA99D /* TextEntryTableViewCell.swift */, BC2CEFB8211CF14600EAA99D /* TextEntryTableViewCell.xib */, BC3B0C972007DBAA002D28B1 /* TweetCollectionViewCell.swift */, @@ -638,12 +706,18 @@ 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 */, + EC32524C224ECF8E00D955C3 /* UserDefaults+Subscript.swift */, ); path = Extensions; sourceTree = ""; @@ -653,8 +727,9 @@ children = ( 2A35FAA821B5DA1C00DC8805 /* AVFoundation.framework */, BCE738601FBB707500093CCD /* CoalMine.framework */, - E125D60FEE1A91995D3FDF82 /* Pods_Canary.framework */, - DD29E9B2187F9B97C60CAB75 /* Pods_Canary__Internal_.framework */, + DB1088039C5F170AE338BACD /* Pods_AppStore_Application.framework */, + 04062B8FCC938E7869BA852B /* Pods_Internal_Application.framework */, + B73A1E273FE9E424C016B22E /* Pods_CanaryUnitTests.framework */, ); name = Frameworks; sourceTree = ""; @@ -733,6 +808,72 @@ path = Models; sourceTree = ""; }; + DF7CF2094E2195D55BC35732 /* Pods */ = { + isa = PBXGroup; + children = ( + 7D892EB99170C15369014895 /* Pods-AppStore Application.debug.xcconfig */, + 792ED9133D866365E93CDB2E /* Pods-AppStore Application.release.xcconfig */, + F80CB2C182236FA7D7126F3A /* Pods-Internal Application.debug.xcconfig */, + 7B24E5575F339F1A17104381 /* Pods-Internal Application.release.xcconfig */, + 84ADB8A0A63B79D852517109 /* Pods-CanaryUnitTests.debug.xcconfig */, + A55602BC6D63A07F0C2F32F9 /* Pods-CanaryUnitTests.release.xcconfig */, + ); + name = Pods; + 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 */ @@ -754,43 +895,43 @@ productReference = BC014490220B6E2F0090F497 /* CanaryScreenshots.xctest */; productType = "com.apple.product-type.bundle.ui-testing"; }; - BC33E0321FBB627B0060ECBE /* Canary */ = { + BC33E0321FBB627B0060ECBE /* AppStore Application */ = { isa = PBXNativeTarget; - buildConfigurationList = BC33E0471FBB627B0060ECBE /* Build configuration list for PBXNativeTarget "Canary" */; + buildConfigurationList = BC33E0471FBB627B0060ECBE /* Build configuration list for PBXNativeTarget "AppStore Application" */; buildPhases = ( - E7266716C35EA4759CA33871 /* [CP] Check Pods Manifest.lock */, + D51BD0DE37345D03D205482E /* [CP] Check Pods Manifest.lock */, BC33E02F1FBB627B0060ECBE /* Sources */, BC33E0301FBB627B0060ECBE /* Frameworks */, BC33E0311FBB627B0060ECBE /* Resources */, - D9B5F3C7C1B93E72AACE00E0 /* [CP] Embed Pods Frameworks */, - 89659F9C7E702CF4A26D6D9E /* [CP] Copy Pods Resources */, + 13E326CCA598A76497758F8C /* [CP] Embed Pods Frameworks */, + 2D493E5599C29683E4828EA3 /* [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 */, + 371F83FF04D20DE5CD91ADC2 /* [CP] Check Pods Manifest.lock */, BC33E04B1FBB63260060ECBE /* Sources */, BC33E04F1FBB63260060ECBE /* Frameworks */, BC33E0501FBB63260060ECBE /* Resources */, BC4A99D5200EBCDD00E3EB07 /* Embed Frameworks */, - 0DBB44327E78A1A0DBA77326 /* [CP] Embed Pods Frameworks */, - 9EC2E1AA95D72F2B86EC33AE /* [CP] Copy Pods Resources */, + 981882FB734329AFDEA3F443 /* [CP] Embed Pods Frameworks */, + 0FFF2E27255AD016DA6C1AEC /* [CP] Copy Pods Resources */, ); buildRules = ( ); dependencies = ( ); - name = "Canary (Internal)"; + name = "Internal Application"; productName = Canary; productReference = BC33E0571FBB63260060ECBE /* Canary.app */; productType = "com.apple.product-type.application"; @@ -806,13 +947,32 @@ buildRules = ( ); dependencies = ( - CE237F24216BF2C800A8134A /* PBXTargetDependency */, + 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 = ( + E872B166018E00107914C51A /* [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 */ @@ -834,13 +994,19 @@ ProvisioningStyle = Automatic; }; BC33E04A1FBB63260060ECBE = { - LastSwiftMigration = 1000; + LastSwiftMigration = 1020; ProvisioningStyle = Automatic; }; CE237F1D216BF2C800A8134A = { CreatedOnToolsVersion = 10.0; + LastSwiftMigration = 1020; ProvisioningStyle = Automatic; - TestTargetID = BC33E0321FBB627B0060ECBE; + TestTargetID = BC33E04A1FBB63260060ECBE; + }; + EC469C0F22613063006CABAA = { + CreatedOnToolsVersion = 10.2; + ProvisioningStyle = Automatic; + TestTargetID = BC33E04A1FBB63260060ECBE; }; }; }; @@ -857,10 +1023,11 @@ projectDirPath = ""; projectRoot = ""; targets = ( - BC33E0321FBB627B0060ECBE /* Canary */, - BC33E04A1FBB63260060ECBE /* Canary (Internal) */, - CE237F1D216BF2C800A8134A /* CanaryUITests */, + BC33E0321FBB627B0060ECBE /* AppStore Application */, + BC33E04A1FBB63260060ECBE /* Internal Application */, BC01448F220B6E2F0090F497 /* CanaryScreenshots */, + CE237F1D216BF2C800A8134A /* CanaryUITests */, + EC469C0F22613063006CABAA /* CanaryUnitTests */, ); }; /* End PBXProject section */ @@ -878,6 +1045,7 @@ 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 */, @@ -908,6 +1076,7 @@ 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 */, BC4CE2A3213733EB00CA0220 /* NativeAdCollectionViewController.xib in Resources */, BC3B0C9C2007DBC8002D28B1 /* TweetCollectionViewCell.xib in Resources */, @@ -929,121 +1098,258 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + EC469C0E22613063006CABAA /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 0DBB44327E78A1A0DBA77326 /* [CP] Embed Pods Frameworks */ = { + 0FFF2E27255AD016DA6C1AEC /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Internal Application/Pods-Internal Application-resources.sh", + "${PODS_ROOT}/TapjoySDK/TapjoySDK_iOS_v12.2.1/Libraries/Tapjoy.embeddedframework/Resources/TapjoyResources.bundle", + "${PODS_ROOT}/TapjoySDK/TapjoySDK_iOS_v12.2.1/Libraries/Tapjoy.embeddedframework/Tapjoy.framework/Versions/A/Resources/TapjoyResources.bundle", + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + ); + outputPaths = ( + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/TapjoyResources.bundle", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Internal Application/Pods-Internal Application-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + 13E326CCA598A76497758F8C /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); + inputFileListPaths = ( + ); inputPaths = ( - "${SRCROOT}/Pods/Target Support Files/Pods-Canary (Internal)/Pods-Canary (Internal)-frameworks.sh", + "${PODS_ROOT}/Target Support Files/Pods-AppStore Application/Pods-AppStore Application-frameworks.sh", "${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-StandardEdition/VerizonAdsCore.framework", + "${PODS_ROOT}/Verizon-Ads-StandardEdition/VerizonAdsInlinePlacement.framework", + "${PODS_ROOT}/Verizon-Ads-StandardEdition/VerizonAdsInlineWebAdapter.framework", + "${PODS_ROOT}/Verizon-Ads-StandardEdition/VerizonAdsInterstitialPlacement.framework", + "${PODS_ROOT}/Verizon-Ads-StandardEdition/VerizonAdsInterstitialVASTAdapter.framework", + "${PODS_ROOT}/Verizon-Ads-StandardEdition/VerizonAdsInterstitialWebAdapter.framework", + "${PODS_ROOT}/Verizon-Ads-StandardEdition/VerizonAdsNativeOpenRTBNativeAdapter.framework", + "${PODS_ROOT}/Verizon-Ads-StandardEdition/VerizonAdsNativePlacement.framework", + "${PODS_ROOT}/Verizon-Ads-StandardEdition/VerizonAdsOMSDK.framework", + "${PODS_ROOT}/Verizon-Ads-StandardEdition/VerizonAdsOpenRTBNativeController.framework", + "${PODS_ROOT}/Verizon-Ads-StandardEdition/VerizonAdsStandardEdition.framework", + "${PODS_ROOT}/Verizon-Ads-StandardEdition/VerizonAdsSupport.framework", + "${PODS_ROOT}/Verizon-Ads-StandardEdition/VerizonAdsVASTController.framework", + "${PODS_ROOT}/Verizon-Ads-StandardEdition/VerizonAdsVerizonSSPConfigProvider.framework", + "${PODS_ROOT}/Verizon-Ads-StandardEdition/VerizonAdsVerizonSSPReporter.framework", + "${PODS_ROOT}/Verizon-Ads-StandardEdition/VerizonAdsVerizonSSPWaterfallProvider.framework", + "${PODS_ROOT}/Verizon-Ads-StandardEdition/VerizonAdsVideoPlayer.framework", + "${PODS_ROOT}/Verizon-Ads-StandardEdition/VerizonAdsWebController.framework", + "${PODS_ROOT}/Verizon-Ads-StandardEdition/VerizonAdsWebView.framework", "${BUILT_PRODUCTS_DIR}/mopub-ios-sdk/MoPub.framework", + "${BUILT_PRODUCTS_DIR}/nanopb/nanopb.framework", ); name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + ); outputPaths = ( "${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}/VerizonAdsNativeOpenRTBNativeAdapter.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/VerizonAdsNativePlacement.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/VerizonAdsOMSDK.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/VerizonAdsOpenRTBNativeController.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/VerizonAdsStandardEdition.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/VerizonAdsSupport.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/VerizonAdsVASTController.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 (Internal)/Pods-Canary (Internal)-frameworks.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-AppStore Application/Pods-AppStore Application-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - 89659F9C7E702CF4A26D6D9E /* [CP] Copy Pods Resources */ = { + 2D493E5599C29683E4828EA3 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); + inputFileListPaths = ( + ); inputPaths = ( - "${SRCROOT}/Pods/Target Support Files/Pods-Canary/Pods-Canary-resources.sh", - "${PODS_ROOT}/TapjoySDK/TapjoySDK_iOS_v12.2.0/Libraries/Tapjoy.embeddedframework/Resources/TapjoyResources.bundle", - "${PODS_ROOT}/TapjoySDK/TapjoySDK_iOS_v12.2.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}/TapjoySDK/TapjoySDK_iOS_v12.2.1/Libraries/Tapjoy.embeddedframework/Resources/TapjoyResources.bundle", + "${PODS_ROOT}/TapjoySDK/TapjoySDK_iOS_v12.2.1/Libraries/Tapjoy.embeddedframework/Tapjoy.framework/Versions/A/Resources/TapjoyResources.bundle", ); name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + ); outputPaths = ( "${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-AppStore Application/Pods-AppStore Application-resources.sh\"\n"; showEnvVarsInLog = 0; }; - 9EC2E1AA95D72F2B86EC33AE /* [CP] Copy Pods Resources */ = { + 371F83FF04D20DE5CD91ADC2 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); + inputFileListPaths = ( + ); inputPaths = ( - "${SRCROOT}/Pods/Target Support Files/Pods-Canary (Internal)/Pods-Canary (Internal)-resources.sh", - "${PODS_ROOT}/TapjoySDK/TapjoySDK_iOS_v12.2.0/Libraries/Tapjoy.embeddedframework/Resources/TapjoyResources.bundle", - "${PODS_ROOT}/TapjoySDK/TapjoySDK_iOS_v12.2.0/Libraries/Tapjoy.embeddedframework/Tapjoy.framework/Versions/A/Resources/TapjoyResources.bundle", + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( ); - name = "[CP] Copy Pods Resources"; outputPaths = ( - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/TapjoyResources.bundle", + "$(DERIVED_FILE_DIR)/Pods-Internal Application-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Canary (Internal)/Pods-Canary (Internal)-resources.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; }; - D9B5F3C7C1B93E72AACE00E0 /* [CP] Embed Pods Frameworks */ = { + 981882FB734329AFDEA3F443 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); + inputFileListPaths = ( + ); inputPaths = ( - "${SRCROOT}/Pods/Target Support Files/Pods-Canary/Pods-Canary-frameworks.sh", + "${PODS_ROOT}/Target Support Files/Pods-Internal Application/Pods-Internal Application-frameworks.sh", "${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-StandardEdition/VerizonAdsCore.framework", + "${PODS_ROOT}/Verizon-Ads-StandardEdition/VerizonAdsInlinePlacement.framework", + "${PODS_ROOT}/Verizon-Ads-StandardEdition/VerizonAdsInlineWebAdapter.framework", + "${PODS_ROOT}/Verizon-Ads-StandardEdition/VerizonAdsInterstitialPlacement.framework", + "${PODS_ROOT}/Verizon-Ads-StandardEdition/VerizonAdsInterstitialVASTAdapter.framework", + "${PODS_ROOT}/Verizon-Ads-StandardEdition/VerizonAdsInterstitialWebAdapter.framework", + "${PODS_ROOT}/Verizon-Ads-StandardEdition/VerizonAdsNativeOpenRTBNativeAdapter.framework", + "${PODS_ROOT}/Verizon-Ads-StandardEdition/VerizonAdsNativePlacement.framework", + "${PODS_ROOT}/Verizon-Ads-StandardEdition/VerizonAdsOMSDK.framework", + "${PODS_ROOT}/Verizon-Ads-StandardEdition/VerizonAdsOpenRTBNativeController.framework", + "${PODS_ROOT}/Verizon-Ads-StandardEdition/VerizonAdsStandardEdition.framework", + "${PODS_ROOT}/Verizon-Ads-StandardEdition/VerizonAdsSupport.framework", + "${PODS_ROOT}/Verizon-Ads-StandardEdition/VerizonAdsVASTController.framework", + "${PODS_ROOT}/Verizon-Ads-StandardEdition/VerizonAdsVerizonSSPConfigProvider.framework", + "${PODS_ROOT}/Verizon-Ads-StandardEdition/VerizonAdsVerizonSSPReporter.framework", + "${PODS_ROOT}/Verizon-Ads-StandardEdition/VerizonAdsVerizonSSPWaterfallProvider.framework", + "${PODS_ROOT}/Verizon-Ads-StandardEdition/VerizonAdsVideoPlayer.framework", + "${PODS_ROOT}/Verizon-Ads-StandardEdition/VerizonAdsWebController.framework", + "${PODS_ROOT}/Verizon-Ads-StandardEdition/VerizonAdsWebView.framework", "${BUILT_PRODUCTS_DIR}/mopub-ios-sdk/MoPub.framework", + "${BUILT_PRODUCTS_DIR}/nanopb/nanopb.framework", ); name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + ); outputPaths = ( "${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}/VerizonAdsNativeOpenRTBNativeAdapter.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/VerizonAdsNativePlacement.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/VerizonAdsOMSDK.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/VerizonAdsOpenRTBNativeController.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/VerizonAdsStandardEdition.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/VerizonAdsSupport.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/VerizonAdsVASTController.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 */ = { + D51BD0DE37345D03D205482E /* [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-AppStore 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; }; - ECBDCB3F2D0131BAABBBAD09 /* [CP] Check Pods Manifest.lock */ = { + E872B166018E00107914C51A /* [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 (Internal)-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-CanaryUnitTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -1070,12 +1376,14 @@ 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 */, @@ -1086,6 +1394,7 @@ 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 */, @@ -1095,25 +1404,31 @@ 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 */, BC525ED4212F2D71007B1761 /* InterstitialAdDataSource.swift in Sources */, BC33E0371FBB627B0060ECBE /* AppDelegate.swift in Sources */, BCA042EF211E049D001B1AF5 /* RoundedButton.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 */, 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 */, @@ -1124,10 +1439,12 @@ 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 */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1137,6 +1454,7 @@ 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 */, @@ -1154,6 +1472,7 @@ 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 */, @@ -1161,7 +1480,10 @@ BCA042EA211E004D001B1AF5 /* AdActionsTableViewCell.swift in Sources */, BC00C5B9208FCF46008E0AEB /* AppDelegate+Consent.swift in Sources */, BC3B0C962007DAA5002D28B1 /* NativeAdCollectionViewController.swift in Sources */, + EC325253224ED56F00D955C3 /* UserDefaults+Subscript.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 */, @@ -1170,12 +1492,14 @@ 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 */, @@ -1183,19 +1507,23 @@ BC3B0C992007DBAA002D28B1 /* TweetCollectionViewCell.swift in Sources */, BC59F25E20F524E40051DA00 /* AdUnitDataSource.swift in Sources */, BC04945E20F9266A00CFD9C2 /* Internal.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 */, 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 */, + 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 */, @@ -1233,18 +1561,33 @@ ); 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 /* Canary */; + target = BC33E0321FBB627B0060ECBE /* AppStore Application */; targetProxy = BC014495220B6E2F0090F497 /* PBXContainerItemProxy */; }; - CE237F24216BF2C800A8134A /* PBXTargetDependency */ = { + EC2C1A6C22710A8E00B1B594 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = BC33E04A1FBB63260060ECBE /* Internal Application */; + targetProxy = EC2C1A6B22710A8E00B1B594 /* PBXContainerItemProxy */; + }; + EC469C1622613063006CABAA /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = BC33E0321FBB627B0060ECBE /* Canary */; - targetProxy = CE237F23216BF2C800A8134A /* PBXContainerItemProxy */; + target = BC33E04A1FBB63260060ECBE /* Internal Application */; + targetProxy = EC469C1522613063006CABAA /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ @@ -1307,6 +1650,7 @@ }; BC33E0451FBB627B0060ECBE /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = BC8D8EC4222F234B003BE094 /* Default.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; @@ -1366,6 +1710,7 @@ }; BC33E0461FBB627B0060ECBE /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = BC8D8EC4222F234B003BE094 /* Default.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; @@ -1418,16 +1763,15 @@ }; BC33E0481FBB627B0060ECBE /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = C8048ED6DE0E405574BB074C /* Pods-Canary.debug.xcconfig */; + baseConfigurationReference = 7D892EB99170C15369014895 /* Pods-AppStore Application.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 5.6.0; + CURRENT_PROJECT_VERSION = 5.7.0; DEVELOPMENT_TEAM = 4S7XS533V3; - INFOPLIST_FILE = Canary/Info.plist; + INFOPLIST_FILE = Canary/Configuration/Info.plist; 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"; @@ -1436,16 +1780,15 @@ }; BC33E0491FBB627B0060ECBE /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 55F769B196AF032A147C6387 /* Pods-Canary.release.xcconfig */; + baseConfigurationReference = 792ED9133D866365E93CDB2E /* Pods-AppStore Application.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 5.6.0; + CURRENT_PROJECT_VERSION = 5.7.0; DEVELOPMENT_TEAM = 4S7XS533V3; - INFOPLIST_FILE = Canary/Info.plist; + INFOPLIST_FILE = Canary/Configuration/Info.plist; 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"; @@ -1454,18 +1797,17 @@ }; BC33E0551FBB63260060ECBE /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 3FA62C94B44B098C968278EE /* Pods-Canary (Internal).debug.xcconfig */; + baseConfigurationReference = F80CB2C182236FA7D7126F3A /* Pods-Internal Application.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon.Internal; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 5.6.0; + CURRENT_PROJECT_VERSION = 5.7.0; DEVELOPMENT_TEAM = 4S7XS533V3; INFOPLIST_FILE = "Canary/Internal/Internal-Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; OTHER_SWIFT_FLAGS = "-DINTERNAL"; PRODUCT_BUNDLE_IDENTIFIER = com.mopub.Canary; - PRODUCT_NAME = Canary; - SWIFT_VERSION = 4.2; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; }; @@ -1473,18 +1815,17 @@ }; BC33E0561FBB63260060ECBE /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = BBA3359CC86A7CE1AF32934B /* Pods-Canary (Internal).release.xcconfig */; + baseConfigurationReference = 7B24E5575F339F1A17104381 /* Pods-Internal Application.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon.Internal; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 5.6.0; + CURRENT_PROJECT_VERSION = 5.7.0; DEVELOPMENT_TEAM = 4S7XS533V3; INFOPLIST_FILE = "Canary/Internal/Internal-Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; OTHER_SWIFT_FLAGS = "-DINTERNAL"; PRODUCT_BUNDLE_IDENTIFIER = com.mopub.Canary; - PRODUCT_NAME = Canary; - SWIFT_VERSION = 4.2; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; }; @@ -1495,6 +1836,7 @@ 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"; @@ -1502,9 +1844,9 @@ MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.twitter.CanaryUITests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 4.2; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; - TEST_TARGET_NAME = Canary; + TEST_TARGET_NAME = "Internal Application"; }; name = Debug; }; @@ -1513,15 +1855,59 @@ 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 = 4.2; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; - TEST_TARGET_NAME = Canary; + TEST_TARGET_NAME = "Internal Application"; + }; + name = Release; + }; + EC469C1722613063006CABAA /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 84ADB8A0A63B79D852517109 /* 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 = A55602BC6D63A07F0C2F32F9 /* 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; }; @@ -1546,7 +1932,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 */, @@ -1555,7 +1941,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 */, @@ -1573,6 +1959,15 @@ 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 index 22e5e53db..c7404c28d 100644 --- a/Canary/Canary.xcodeproj/xcshareddata/xcschemes/Canary Screenshots.xcscheme +++ b/Canary/Canary.xcodeproj/xcshareddata/xcschemes/Canary Screenshots.xcscheme @@ -28,7 +28,7 @@ BuildableIdentifier = "primary" BlueprintIdentifier = "BC33E0321FBB627B0060ECBE" BuildableName = "Canary.app" - BlueprintName = "Canary" + BlueprintName = "AppStore Application" ReferencedContainer = "container:Canary.xcodeproj"> @@ -51,7 +51,7 @@ BuildableIdentifier = "primary" BlueprintIdentifier = "BC33E0321FBB627B0060ECBE" BuildableName = "Canary.app" - BlueprintName = "Canary" + BlueprintName = "AppStore Application" ReferencedContainer = "container:Canary.xcodeproj"> @@ -69,7 +69,7 @@ BuildableIdentifier = "primary" BlueprintIdentifier = "BC33E0321FBB627B0060ECBE" BuildableName = "Canary.app" - BlueprintName = "Canary" + BlueprintName = "AppStore Application" ReferencedContainer = "container:Canary.xcodeproj"> diff --git a/Canary/Canary.xcodeproj/xcshareddata/xcschemes/Canary.xcscheme b/Canary/Canary.xcodeproj/xcshareddata/xcschemes/Canary-AppStore Application.xcscheme similarity index 94% rename from Canary/Canary.xcodeproj/xcshareddata/xcschemes/Canary.xcscheme rename to Canary/Canary.xcodeproj/xcshareddata/xcschemes/Canary-AppStore Application.xcscheme index 739e4256a..44b7424db 100644 --- a/Canary/Canary.xcodeproj/xcshareddata/xcschemes/Canary.xcscheme +++ b/Canary/Canary.xcodeproj/xcshareddata/xcschemes/Canary-AppStore Application.xcscheme @@ -16,7 +16,7 @@ BuildableIdentifier = "primary" BlueprintIdentifier = "BC33E0321FBB627B0060ECBE" BuildableName = "Canary.app" - BlueprintName = "Canary" + BlueprintName = "AppStore Application" ReferencedContainer = "container:Canary.xcodeproj"> @@ -44,7 +44,7 @@ BuildableIdentifier = "primary" BlueprintIdentifier = "BC33E0321FBB627B0060ECBE" BuildableName = "Canary.app" - BlueprintName = "Canary" + BlueprintName = "AppStore Application" ReferencedContainer = "container:Canary.xcodeproj"> @@ -67,7 +67,7 @@ BuildableIdentifier = "primary" BlueprintIdentifier = "BC33E0321FBB627B0060ECBE" BuildableName = "Canary.app" - BlueprintName = "Canary" + BlueprintName = "AppStore Application" ReferencedContainer = "container:Canary.xcodeproj"> @@ -86,7 +86,7 @@ BuildableIdentifier = "primary" BlueprintIdentifier = "BC33E0321FBB627B0060ECBE" BuildableName = "Canary.app" - BlueprintName = "Canary" + BlueprintName = "AppStore Application" ReferencedContainer = "container:Canary.xcodeproj"> diff --git a/Canary/Canary.xcodeproj/xcshareddata/xcschemes/Canary (Internal).xcscheme b/Canary/Canary.xcodeproj/xcshareddata/xcschemes/Canary-Internal Application.xcscheme similarity index 80% rename from Canary/Canary.xcodeproj/xcshareddata/xcschemes/Canary (Internal).xcscheme rename to Canary/Canary.xcodeproj/xcshareddata/xcschemes/Canary-Internal Application.xcscheme index d00ff75cd..f998966a0 100644 --- a/Canary/Canary.xcodeproj/xcshareddata/xcschemes/Canary (Internal).xcscheme +++ b/Canary/Canary.xcodeproj/xcshareddata/xcschemes/Canary-Internal Application.xcscheme @@ -16,7 +16,7 @@ BuildableIdentifier = "primary" BlueprintIdentifier = "BC33E04A1FBB63260060ECBE" BuildableName = "Canary.app" - BlueprintName = "Canary (Internal)" + BlueprintName = "Internal Application" ReferencedContainer = "container:Canary.xcodeproj"> @@ -26,15 +26,28 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + codeCoverageEnabled = "YES" shouldUseLaunchSchemeArgsEnv = "YES"> + + + + @@ -57,7 +70,7 @@ BuildableIdentifier = "primary" BlueprintIdentifier = "BC33E04A1FBB63260060ECBE" BuildableName = "Canary.app" - BlueprintName = "Canary (Internal)" + BlueprintName = "Internal Application" ReferencedContainer = "container:Canary.xcodeproj"> @@ -76,7 +89,7 @@ BuildableIdentifier = "primary" BlueprintIdentifier = "BC33E04A1FBB63260060ECBE" BuildableName = "Canary.app" - BlueprintName = "Canary (Internal)" + BlueprintName = "Internal Application" ReferencedContainer = "container:Canary.xcodeproj"> 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..fa0a7d5ce --- /dev/null +++ b/Canary/Canary.xcodeproj/xcshareddata/xcschemes/Canary-Unit Tests.xcscheme @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Canary/Canary/AdapterVersions/AdapterVersionsMenuDataSource.swift b/Canary/Canary/AdapterVersions/AdapterVersionsMenuDataSource.swift index cbeaab6b4..f01c75365 100644 --- a/Canary/Canary/AdapterVersions/AdapterVersionsMenuDataSource.swift +++ b/Canary/Canary/AdapterVersions/AdapterVersionsMenuDataSource.swift @@ -12,34 +12,32 @@ 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 + } + /** - Alphabetically sorted adapter names. + This `Item` array represents the cells in the table view. */ - private var adapterNames: [String] = [] + private var items: [Item] = [] /** Set of adapter names that are currently in an expanded state. */ private var expandedAdapters: Set = Set() - - // MARK: - Adapter Cell Retrieval - - /** - Registers and retrieves a reusable instance of a `CollapsibleAdapterInfoTableViewCell`. - - Parameter tableView: Table View rendering the cell. - - Returns: A reusable `CollapsibleAdapterInfoTableViewCell` instance. - */ - func adapterMenuCell(inTableView tableView: UITableView) -> CollapsibleAdapterInfoTableViewCell { - let adapterCellReuseIdentifier: String = "CollapsibleAdapterInfoTableViewCell" - - var cell = tableView.dequeueReusableCell(withIdentifier: adapterCellReuseIdentifier) as? CollapsibleAdapterInfoTableViewCell - if cell == nil { - tableView.register(UINib(nibName: adapterCellReuseIdentifier, bundle: nil), forCellReuseIdentifier: adapterCellReuseIdentifier) - cell = tableView.dequeueReusableCell(withIdentifier: adapterCellReuseIdentifier) as? CollapsibleAdapterInfoTableViewCell - } - - return cell! - } } extension AdapterVersionsMenuDataSource: MenuDisplayable { @@ -47,8 +45,7 @@ extension AdapterVersionsMenuDataSource: MenuDisplayable { Number of menu items available */ var count: Int { - // There will always be at least one item. - return max(adapterNames.count, 1) + return items.count } /** @@ -65,24 +62,32 @@ extension AdapterVersionsMenuDataSource: MenuDisplayable { - Returns: A configured `UITableViewCell` */ func cell(forItem index: Int, inTableView tableView: UITableView) -> UITableViewCell { - let cell: CollapsibleAdapterInfoTableViewCell = adapterMenuCell(inTableView: tableView) - - // There are no adapters initialized. - guard adapterNames.count > 0 else { + switch items[index] { + case .noAdapters: + let cell = tableView.dequeueCellFromNib(cellType: CollapsibleAdapterInfoTableViewCell.self) cell.titleLabel.text = "No adapters initialized" return cell - } - - // There exist some initialized adapters - let name: String = adapterNames[index] - guard let adapter: MPAdapterConfiguration = MoPub.sharedInstance().adapterConfigurationNamed(name) else { - cell.update(title: name) + + 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 } - - let isCollapsed: Bool = !expandedAdapters.contains(name) - cell.update(adapterName: name , info: adapter, isCollapsed: isCollapsed) - return cell } /** @@ -93,7 +98,12 @@ extension AdapterVersionsMenuDataSource: MenuDisplayable { */ func canSelect(itemAt index: Int, inTableView tableView: UITableView) -> Bool { // Selection is only valid if there are adapters present. - return (adapterNames.count > 0) + switch items[index] { + case .noAdapters, .clearCachedNetowrksToggle: + return false + case .adapter: + return true + } } /** @@ -104,31 +114,47 @@ extension AdapterVersionsMenuDataSource: MenuDisplayable { - Returns: `true` if the menu should collapse when selected; `false` otherwise. */ func didSelect(itemAt indexPath: IndexPath, inTableView tableView: UITableView, presentFrom viewController: UIViewController) -> Bool { - // Verify that there are adapters present - guard adapterNames.count > 0 && indexPath.row < adapterNames.count else { + 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 } - - // Toggle the expanded state for the adapter - let name: String = adapterNames[indexPath.row] - 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 } /** Updates the data source if needed. + - Returns: `true` update happened; `false` otherwise. */ - func updateIfNeeded() -> Swift.Void { - adapterNames = MoPub.sharedInstance().availableAdapterClassNames()?.sorted() ?? [] + 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.swift b/Canary/Canary/AppDelegate.swift index 6ec9bb2d7..81c381c3c 100644 --- a/Canary/Canary/AppDelegate.swift +++ b/Canary/Canary/AppDelegate.swift @@ -32,6 +32,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // MARK: - UIApplicationDelegate func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + if UserDefaults.standard.shouldClearCachedNetworks { + MoPub.sharedInstance().clearCachedNetworks() // do this before initializing the MoPub SDK + print("\(#function) cached networks are cleared") + } + // Extract the UI elements for easier manipulation later. // Calls to `loadViewIfNeeded()` are needed to load any children view controllers // before `viewDidLoad()` occurs. diff --git a/Canary/Canary/Base/AdUnitTableViewController.swift b/Canary/Canary/Base/AdUnitTableViewController.swift index 88f73f1a6..13caa00a5 100644 --- a/Canary/Canary/Base/AdUnitTableViewController.swift +++ b/Canary/Canary/Base/AdUnitTableViewController.swift @@ -94,6 +94,15 @@ class AdUnitTableViewController: UIViewController { 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) } } @@ -110,11 +119,11 @@ 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() @@ -126,15 +135,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? { diff --git a/Canary/Canary/Base/FilterableAdUnitTableViewController.swift b/Canary/Canary/Base/FilterableAdUnitTableViewController.swift index cb6e79ab5..3bc1f04df 100644 --- a/Canary/Canary/Base/FilterableAdUnitTableViewController.swift +++ b/Canary/Canary/Base/FilterableAdUnitTableViewController.swift @@ -46,13 +46,8 @@ class FilterableAdUnitTableViewController: AdUnitTableViewController { searchController.searchBar.placeholder = "Filter Ads" searchController.searchBar.searchBarStyle = .minimal - if #available(iOS 11.0, *) { - navigationItem.searchController = searchController - } - else { - searchController.hidesNavigationBarDuringPresentation = false - navigationItem.titleView = searchController.searchBar - } + 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 diff --git a/Canary/Canary/Cells/AdActionsTableViewCell.swift b/Canary/Canary/Cells/AdActionsTableViewCell.swift index 9eeae940c..b3b133dc8 100644 --- a/Canary/Canary/Cells/AdActionsTableViewCell.swift +++ b/Canary/Canary/Cells/AdActionsTableViewCell.swift @@ -8,7 +8,7 @@ import UIKit -class AdActionsTableViewCell: UITableViewCell { +final class AdActionsTableViewCell: UITableViewCell, TableViewCellRegisterable { // MARK: - IBOutlets @IBOutlet weak var stackView: UIStackView! @IBOutlet weak var showAdButton: RoundedButton! @@ -53,8 +53,3 @@ class AdActionsTableViewCell: UITableViewCell { showAdButton.isEnabled = showButtonEnabled } } - -extension AdActionsTableViewCell: TableViewCellRegisterable { - // MARK: - TableViewCellRegisterable - static private(set) var reuseId: String = "AdActionsTableViewCell" -} diff --git a/Canary/Canary/Cells/AdUnitTableViewCell.swift b/Canary/Canary/Cells/AdUnitTableViewCell.swift index c08e78f0f..cbf85637e 100644 --- a/Canary/Canary/Cells/AdUnitTableViewCell.swift +++ b/Canary/Canary/Cells/AdUnitTableViewCell.swift @@ -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/BasicMenuTableViewCell.swift b/Canary/Canary/Cells/BasicMenuTableViewCell.swift index c7edd2161..afe1fbf14 100644 --- a/Canary/Canary/Cells/BasicMenuTableViewCell.swift +++ b/Canary/Canary/Cells/BasicMenuTableViewCell.swift @@ -8,6 +8,6 @@ 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 index a1add4f3c..17b982180 100644 --- a/Canary/Canary/Cells/CollapsibleAdapterInfoTableViewCell.swift +++ b/Canary/Canary/Cells/CollapsibleAdapterInfoTableViewCell.swift @@ -9,7 +9,7 @@ import MoPub import UIKit -class CollapsibleAdapterInfoTableViewCell: UITableViewCell { +final class CollapsibleAdapterInfoTableViewCell: UITableViewCell, TableViewCellRegisterable { // MARK: - Constants struct Constants { // The padding and spacing constant for the stack view elements diff --git a/Canary/Canary/Cells/StatusTableViewCell.swift b/Canary/Canary/Cells/StatusTableViewCell.swift index b3aa24feb..ab852aac5 100644 --- a/Canary/Canary/Cells/StatusTableViewCell.swift +++ b/Canary/Canary/Cells/StatusTableViewCell.swift @@ -8,7 +8,7 @@ import UIKit -class StatusTableViewCell: UITableViewCell { +final class StatusTableViewCell: UITableViewCell, TableViewCellRegisterable { // MARK: - IBOutlets @IBOutlet weak var stackView: UIStackView! @IBOutlet weak var nameLabel: UILabel! @@ -38,8 +38,3 @@ class StatusTableViewCell: UITableViewCell { 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 1d9629a61..0698fd90d 100644 --- a/Canary/Canary/Cells/TextEntryTableViewCell.swift +++ b/Canary/Canary/Cells/TextEntryTableViewCell.swift @@ -8,7 +8,7 @@ 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/Configuration/Default.xcconfig b/Canary/Canary/Configuration/Default.xcconfig new file mode 100644 index 000000000..a84f6faad --- /dev/null +++ b/Canary/Canary/Configuration/Default.xcconfig @@ -0,0 +1,12 @@ +// +// 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 + +PRODUCT_NAME = Canary diff --git a/Canary/Canary/Info.plist b/Canary/Canary/Configuration/Info.plist similarity index 93% rename from Canary/Canary/Info.plist rename to Canary/Canary/Configuration/Info.plist index a832c817e..e7a70eb3b 100644 --- a/Canary/Canary/Info.plist +++ b/Canary/Canary/Configuration/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 5.6.0 + 5.7.0 CFBundleURLTypes @@ -28,7 +28,7 @@ CFBundleVersion - 5.6.0 + 5.7.0 LSRequiresIPhoneOS NSAppTransportSecurity @@ -71,5 +71,7 @@ The MoPub Canary app uses the camera for the ease-of-use feature of reading MoPub ad QR codes. ITSAppUsesNonExemptEncryption + GADApplicationIdentifier + ca-app-pub-3940256099942544~1458002511 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/MPBool+Description.swift b/Canary/Canary/Extensions/MPBool+Description.swift index 4e8137da2..ba0159515 100644 --- a/Canary/Canary/Extensions/MPBool+Description.swift +++ b/Canary/Canary/Extensions/MPBool+Description.swift @@ -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 b8d6988cc..4fab7fe3b 100644 --- a/Canary/Canary/Extensions/MPConsentStatus+Description.swift +++ b/Canary/Canary/Extensions/MPConsentStatus+Description.swift @@ -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/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/UserDefaults+Subscript.swift b/Canary/Canary/Extensions/UserDefaults+Subscript.swift new file mode 100644 index 000000000..2a8a7503d --- /dev/null +++ b/Canary/Canary/Extensions/UserDefaults+Subscript.swift @@ -0,0 +1,66 @@ +// +// 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 + } + } +} diff --git a/Canary/Canary/Formats/AdDataSource.swift b/Canary/Canary/Formats/AdDataSource.swift index 1ef0504ae..2967ad64e 100644 --- a/Canary/Canary/Formats/AdDataSource.swift +++ b/Canary/Canary/Formats/AdDataSource.swift @@ -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. */ @@ -115,6 +121,17 @@ protocol AdDataSource { */ 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 } + /** Retrieves the display status for the event. - Parameter event: Status event. @@ -126,9 +143,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. @@ -136,3 +154,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/AdTableViewController.swift b/Canary/Canary/Formats/AdTableViewController.swift index 53c18cf9a..b3620f8fd 100644 --- a/Canary/Canary/Formats/AdTableViewController.swift +++ b/Canary/Canary/Formats/AdTableViewController.swift @@ -174,10 +174,7 @@ 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() - } - + let cell = tableView.dequeueCellFromNib(cellType: AdUnitTableViewCell.self) cell.accessibilityIdentifier = dataSource.adUnit.id cell.accessoryType = .none cell.adUnitId.text = dataSource.adUnit.id @@ -190,10 +187,7 @@ 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 } @@ -206,10 +200,7 @@ 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() - } - + 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 } @@ -222,10 +213,7 @@ 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 } @@ -240,10 +228,7 @@ 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) let isAdLoading = dataSource.isAdLoading let loadHandler = dataSource.actionHandlers[.load] let showHandler = dataSource.actionHandlers[.show] @@ -259,9 +244,7 @@ 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] diff --git a/Canary/Canary/Formats/BannerAdDataSource.swift b/Canary/Canary/Formats/BannerAdDataSource.swift index e212be537..36dc12fd6 100644 --- a/Canary/Canary/Formats/BannerAdDataSource.swift +++ b/Canary/Canary/Formats/BannerAdDataSource.swift @@ -27,27 +27,29 @@ class BannerAdDataSource: 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 `MPAdViewDelegate` */ - private lazy var title: [AdEvent: String] = { + lazy var title: [AdEvent: String] = { var titleStrings: [AdEvent: String] = [:] titleStrings[.didLoad] = "adViewDidLoadAd(_:)" - titleStrings[.didFailToLoad] = "adViewDidFailToLoadAd(_:)" + 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 /** @@ -71,13 +73,6 @@ class BannerAdDataSource: 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. */ @@ -101,7 +96,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] }() /** @@ -126,38 +121,6 @@ class BannerAdDataSource: NSObject, AdDataSource { */ private(set) var isAdLoading: Bool = false - /** - 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 = (event == .didFailToLoad ? loadFailureReason : nil) - 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 complete: Completion closure. - */ - func setStatus(for event: AdEvent, complete:(() -> Swift.Void)) { - eventTriggered[event] = true - complete() - } - - /** - Clears the highlighted state for all status events. - - Parameter complete: Completion closure. - */ - func clearStatus(complete:(() -> Swift.Void)) { - loadFailureReason = nil - eventTriggered = [:] - complete() - } - // MARK: - Ad Loading private func loadAd() { @@ -189,22 +152,15 @@ extension BannerAdDataSource: MPAdViewDelegate { isAdLoading = false isAdLoaded = true setStatus(for: .didLoad) { [weak self] in - if let strongSelf = self { - strongSelf.loadFailureReason = nil - strongSelf.delegate?.adPresentationTableView.reloadData() - } + self?.delegate?.adPresentationTableView.reloadData() } } - func adViewDidFail(toLoadAd view: MPAdView!) { + func adView(_ view: MPAdView!, didFailToLoadAdWithError error: Error!) { isAdLoading = false isAdLoaded = false - 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() - } + setStatus(for: .didFailToLoad, message: "\(error!.localizedDescription)") { [weak self] in + self?.delegate?.adPresentationTableView.reloadData() } } @@ -225,4 +181,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/BaseNativeAdDataSource.swift b/Canary/Canary/Formats/BaseNativeAdDataSource.swift index 934794981..67a6070d1 100644 --- a/Canary/Canary/Formats/BaseNativeAdDataSource.swift +++ b/Canary/Canary/Formats/BaseNativeAdDataSource.swift @@ -6,78 +6,17 @@ // 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 556bd5b69..4cf793c63 100644 --- a/Canary/Canary/Formats/InterstitialAdDataSource.swift +++ b/Canary/Canary/Formats/InterstitialAdDataSource.swift @@ -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] }() /** @@ -129,38 +117,6 @@ class InterstitialAdDataSource: NSObject, AdDataSource { */ private(set) var isAdLoading: Bool = false - /** - 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 = (event == .didFailToLoad ? loadFailureReason : nil) - 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 complete: Completion closure. - */ - func setStatus(for event: AdEvent, complete:(() -> Swift.Void)) { - eventTriggered[event] = true - complete() - } - - /** - Clears the highlighted state for all status events. - - Parameter complete: Completion closure. - */ - func clearStatus(complete:(() -> Swift.Void)) { - loadFailureReason = nil - eventTriggered = [:] - complete() - } - // MARK: - Ad Loading private func loadAd() { @@ -196,21 +152,15 @@ extension InterstitialAdDataSource: 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!) { isAdLoading = false - 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() - } + // 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() } } @@ -249,4 +199,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/NativeAdCollectionDataSource.swift b/Canary/Canary/Formats/NativeAdCollectionDataSource.swift index 4f754e0fb..431e2c9de 100644 --- a/Canary/Canary/Formats/NativeAdCollectionDataSource.swift +++ b/Canary/Canary/Formats/NativeAdCollectionDataSource.swift @@ -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 d10e10afc..3e6985485 100644 --- a/Canary/Canary/Formats/NativeAdCollectionViewController.swift +++ b/Canary/Canary/Formats/NativeAdCollectionViewController.swift @@ -118,7 +118,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) } } diff --git a/Canary/Canary/Formats/NativeAdDataSource.swift b/Canary/Canary/Formats/NativeAdDataSource.swift index 7681a09cc..9d122414b 100644 --- a/Canary/Canary/Formats/NativeAdDataSource.swift +++ b/Canary/Canary/Formats/NativeAdDataSource.swift @@ -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] }() /** @@ -124,38 +119,6 @@ class NativeAdDataSource: BaseNativeAdDataSource, AdDataSource { */ private(set) var isAdLoading: Bool = false - /** - 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 = (event == .didFailToLoad ? loadFailureReason : nil) - 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 complete: Completion closure. - */ - func setStatus(for event: AdEvent, complete:(() -> Swift.Void)) { - eventTriggered[event] = true - complete() - } - - /** - Clears the highlighted state for all status events. - - Parameter complete: Completion closure. - */ - func clearStatus(complete:(() -> Swift.Void)) { - loadFailureReason = nil - eventTriggered = [:] - complete() - } - // MARK: - Ad Loading /** @@ -228,8 +191,7 @@ class NativeAdDataSource: BaseNativeAdDataSource, AdDataSource { // Error loading the native ad guard error == nil else { strongSelf.isAdLoaded = false - strongSelf.loadFailureReason = error?.localizedDescription - strongSelf.setStatus(for: .didFailToLoad) { [weak self] in + strongSelf.setStatus(for: .didFailToLoad, message: error?.localizedDescription) { [weak self] in self?.delegate?.adPresentationTableView.reloadData() } return @@ -275,4 +237,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/NativeAdTableViewController.swift b/Canary/Canary/Formats/NativeAdTableViewController.swift index 40fbb8a71..673740112 100644 --- a/Canary/Canary/Formats/NativeAdTableViewController.swift +++ b/Canary/Canary/Formats/NativeAdTableViewController.swift @@ -104,9 +104,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/RewardedAdDataSource.swift b/Canary/Canary/Formats/RewardedAdDataSource.swift index 86ddfa292..fd92dec33 100644 --- a/Canary/Canary/Formats/RewardedAdDataSource.swift +++ b/Canary/Canary/Formats/RewardedAdDataSource.swift @@ -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] }() /** @@ -145,49 +130,6 @@ class RewardedAdDataSource: NSObject, AdDataSource { */ private(set) var isAdLoading: Bool = false - /** - 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) { - 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) - } - - /** - 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. - */ - func setStatus(for event: AdEvent, complete:(() -> Swift.Void)) { - eventTriggered[event] = true - complete() - } - - /** - Clears the highlighted state for all status events. - - Parameter complete: Completion closure. - */ - func clearStatus(complete:(() -> Swift.Void)) { - loadFailureReason = nil - playFailureReason = nil - eventTriggered = [:] - complete() - } - // MARK: - Reward Selection /** @@ -228,7 +170,6 @@ class RewardedAdDataSource: NSObject, AdDataSource { // 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) @@ -295,30 +236,20 @@ extension RewardedAdDataSource: 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!) { isAdLoading = false - setStatus(for: .didFailToLoad) { [weak self] in - if let strongSelf = self { - strongSelf.loadFailureReason = error.localizedDescription - strongSelf.delegate?.adPresentationTableView.reloadData() - } + 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() } } @@ -365,11 +296,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/LoggingLevel/LogingLevelMenuDataSource.swift b/Canary/Canary/LoggingLevel/LogingLevelMenuDataSource.swift index fda27a940..49da208ff 100644 --- a/Canary/Canary/LoggingLevel/LogingLevelMenuDataSource.swift +++ b/Canary/Canary/LoggingLevel/LogingLevelMenuDataSource.swift @@ -49,7 +49,7 @@ 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: MPBLogLevel = MPLogging.consoleLogLevel diff --git a/Canary/Canary/Menu/MenuDataSource.swift b/Canary/Canary/Menu/MenuDataSource.swift index 20bd335ba..d9cfb5fcd 100644 --- a/Canary/Canary/Menu/MenuDataSource.swift +++ b/Canary/Canary/Menu/MenuDataSource.swift @@ -30,6 +30,7 @@ class MenuDataSource { init() { add(menu: AdapterVersionsMenuDataSource()) add(menu: LogingLevelMenuDataSource()) + add(menu: NativeAdRendererMenuDataSource()) } // MARK: - Data Source @@ -49,9 +50,10 @@ class MenuDataSource { /** Updates all data sources if needed. + - Returns: `true` update happened; `false` otherwise. */ - func updateIfNeeded() { - sources.values.forEach({ $0.updateIfNeeded() }) + func updateIfNeeded() -> Bool { + return sources.values.reduce(false) { $0 || $1.updateIfNeeded() } } // MARK: - Accessors diff --git a/Canary/Canary/Menu/MenuDisplayable.swift b/Canary/Canary/Menu/MenuDisplayable.swift index 746b47a57..21191a0be 100644 --- a/Canary/Canary/Menu/MenuDisplayable.swift +++ b/Canary/Canary/Menu/MenuDisplayable.swift @@ -46,17 +46,9 @@ protocol MenuDisplayable { /** Updates the data source if needed. + - Returns: `true` update happened; `false` otherwise. */ - func updateIfNeeded() -> Swift.Void - - // MARK: - Menu Cells - - /** - Provides a reusable basic menu cell that can be further customized. - - Parameter tableView: `UITableView` to retrieve the cell from - - Returns: A `BasicMenuTableViewCell` - */ - func basicMenuCell(inTableView tableView: UITableView) -> BasicMenuTableViewCell + func updateIfNeeded() -> Bool } extension MenuDisplayable { @@ -69,19 +61,7 @@ extension MenuDisplayable { return true } - func updateIfNeeded() -> Swift.Void { - return - } - - 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 7e2562ec4..580a5cd02 100644 --- a/Canary/Canary/Menu/MenuViewController.swift +++ b/Canary/Canary/Menu/MenuViewController.swift @@ -48,7 +48,11 @@ class MenuViewController: UIViewController { Updates the data source if needed. */ func updateIfNeeded() { - dataSource.updateIfNeeded() + if dataSource.updateIfNeeded() { + DispatchQueue.main.async { + self.tableView.reloadData() + } + } } } diff --git a/Canary/Canary/Renderer/NativeAdRendererManager.swift b/Canary/Canary/Renderer/NativeAdRendererManager.swift new file mode 100644 index 000000000..8bcb734c5 --- /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({ + 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)) + }()) + configs.append(MOPUBNativeVideoAdRenderer.rendererConfiguration(with: mopubVideoRendererSettings)) + configs.append(contentsOf: networkRendererConfigurations) + + 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/SavedAds/SavedAdsDataSource.swift b/Canary/Canary/SavedAds/SavedAdsDataSource.swift index 3ad3df742..bef1cb6f3 100644 --- a/Canary/Canary/SavedAds/SavedAdsDataSource.swift +++ b/Canary/Canary/SavedAds/SavedAdsDataSource.swift @@ -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 64f3261c5..ed033fbf4 100644 --- a/Canary/Canary/SavedAds/SavedAdsManager.swift +++ b/Canary/Canary/SavedAds/SavedAdsManager.swift @@ -8,69 +8,81 @@ 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 4d629c2f0..fd35822be 100644 --- a/Canary/Canary/SavedAds/SavedAdsViewController.swift +++ b/Canary/Canary/SavedAds/SavedAdsViewController.swift @@ -8,25 +8,20 @@ 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/TableViewCellRegisterable.swift b/Canary/Canary/TableViewCellRegisterable.swift index 6a9b4a29d..0d60423e9 100644 --- a/Canary/Canary/TableViewCellRegisterable.swift +++ b/Canary/Canary/TableViewCellRegisterable.swift @@ -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..10e4d593e --- /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 + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 5.7.0 + CFBundleVersion + 5.7.0 + + diff --git a/Canary/CanaryUnitTests/Renderer/NativeAdRendererManagerTests.swift b/Canary/CanaryUnitTests/Renderer/NativeAdRendererManagerTests.swift new file mode 100644 index 000000000..fa7b28540 --- /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 = ["MPStaticNativeAdRenderer", + "MOPUBNativeVideoAdRenderer", + "MPGoogleAdMobNativeRenderer", + "FacebookNativeAdRenderer", + "FlurryNativeVideoAdRenderer"] + + 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 0000000000000000000000000000000000000000..3967e063f94f2b9de2fdbeb4d90be9963443c793 GIT binary patch literal 42 dcmYc)$jK}&F)+Bm!2kw~j1ZauMnky_oB)p~1JeKi literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..3967e063f94f2b9de2fdbeb4d90be9963443c793 GIT binary patch literal 42 dcmYc)$jK}&F)+Bm!2kw~j1ZauMnky_oB)p~1JeKi literal 0 HcmV?d00001 diff --git a/Canary/Podfile b/Canary/Podfile index 02830f6f1..f35a744f7 100644 --- a/Canary/Podfile +++ b/Canary/Podfile @@ -5,26 +5,78 @@ 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-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-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.lock b/Gemfile.lock index f9de1a61a..4662fedf2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -2,7 +2,7 @@ GEM remote: https://rubygems.org/ specs: CFPropertyList (3.0.0) - activesupport (4.2.10) + activesupport (4.2.11.1) i18n (~> 0.7) minitest (~> 5.1) thread_safe (~> 0.3, >= 0.3.4) @@ -12,46 +12,48 @@ GEM apktools (0.7.2) rubyzip (~> 1.2.1) atomos (0.1.3) - aws-sdk (2.11.212) - aws-sdk-resources (= 2.11.212) - aws-sdk-core (2.11.212) + aws-eventstream (1.0.3) + aws-sdk (2.11.274) + aws-sdk-resources (= 2.11.274) + aws-sdk-core (2.11.274) aws-sigv4 (~> 1.0) jmespath (~> 1.0) - aws-sdk-resources (2.11.212) - aws-sdk-core (= 2.11.212) - aws-sigv4 (1.0.3) + aws-sdk-resources (2.11.274) + aws-sdk-core (= 2.11.274) + aws-sigv4 (1.1.0) + aws-eventstream (~> 1.0, >= 1.0.2) babosa (1.0.2) claide (1.0.2) - cocoapods (1.5.3) + cocoapods (1.6.1) activesupport (>= 4.0.2, < 5) claide (>= 1.0.2, < 2.0) - cocoapods-core (= 1.5.3) + cocoapods-core (= 1.6.1) cocoapods-deintegrate (>= 1.0.2, < 2.0) - cocoapods-downloader (>= 1.2.0, < 2.0) + cocoapods-downloader (>= 1.2.2, < 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-trunk (>= 1.3.1, < 2.0) cocoapods-try (>= 1.1.0, < 2.0) colored2 (~> 3.1) escape (~> 0.0.4) - fourflusher (~> 2.0.1) + fourflusher (>= 2.2.0, < 3.0) gh_inspector (~> 1.0) - molinillo (~> 0.6.5) + molinillo (~> 0.6.6) nap (~> 1.0) - ruby-macho (~> 1.1) - xcodeproj (>= 1.5.7, < 2.0) - cocoapods-core (1.5.3) + ruby-macho (~> 1.4) + xcodeproj (>= 1.8.1, < 2.0) + cocoapods-core (1.6.1) activesupport (>= 4.0.2, < 6) fuzzy_match (~> 2.0.4) nap (~> 1.0) - cocoapods-deintegrate (1.0.2) - cocoapods-downloader (1.2.0) + cocoapods-deintegrate (1.0.4) + cocoapods-downloader (1.2.2) cocoapods-plugins (1.0.0) nap cocoapods-search (1.0.0) - cocoapods-stats (1.0.0) - cocoapods-trunk (1.3.0) + cocoapods-stats (1.1.0) + cocoapods-trunk (1.3.1) nap (>= 0.8, < 2.0) netrc (~> 0.11) cocoapods-try (1.1.0) @@ -59,25 +61,25 @@ GEM colored2 (3.1.2) commander-fastlane (4.4.6) highline (~> 1.7.2) - concurrent-ruby (1.0.5) + concurrent-ruby (1.1.5) declarative (0.0.10) declarative-option (0.1.0) digest-crc (0.4.1) domain_name (0.5.20180417) unf (>= 0.0.5, < 1.0.0) - dotenv (2.6.0) + dotenv (2.7.2) emoji_regex (1.0.1) escape (0.0.4) - excon (0.62.0) + excon (0.64.0) faraday (0.15.4) multipart-post (>= 1.2, < 3) faraday-cookie_jar (0.0.6) faraday (>= 0.7.4) http-cookie (~> 1.0.0) - faraday_middleware (0.13.0) + faraday_middleware (0.13.1) faraday (>= 0.7.4, < 1.0) fastimage (2.1.5) - fastlane (2.117.1) + fastlane (2.123.0) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.3, < 3.0.0) babosa (>= 1.0.2, < 2.0.0) @@ -106,19 +108,19 @@ GEM 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-notifier (>= 2.0.0, < 3.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.6.0, < 2.0.0) + xcodeproj (>= 1.8.1, < 2.0.0) xcpretty (~> 0.3.0) xcpretty-travis-formatter (>= 0.0.3) fastlane-plugin-aws_s3 (1.5.0) apktools (~> 0.7) aws-sdk (~> 2.3) mime-types (~> 3.1) - fourflusher (2.0.1) + fourflusher (2.2.0) fuzzy_match (2.0.4) gh_inspector (1.1.3) google-api-client (0.23.9) @@ -129,15 +131,15 @@ GEM representable (~> 3.0) retriable (>= 2.0, < 4.0) signet (~> 0.9) - google-cloud-core (1.2.7) + google-cloud-core (1.3.0) google-cloud-env (~> 1.0) google-cloud-env (1.0.5) faraday (~> 0.11) - google-cloud-storage (1.15.0) + google-cloud-storage (1.16.0) digest-crc (~> 0.4) google-api-client (~> 0.23) google-cloud-core (~> 1.2) - googleauth (~> 0.6.2) + googleauth (>= 0.6.2, < 0.10.0) googleauth (0.6.7) faraday (~> 0.12) jwt (>= 1.4, < 3.0) @@ -157,10 +159,10 @@ GEM memoist (0.16.0) mime-types (3.2.2) mime-types-data (~> 3.2015) - mime-types-data (3.2018.0812) + mime-types-data (3.2019.0331) mini_magick (4.5.1) minitest (5.10.3) - molinillo (0.6.5) + molinillo (0.6.6) multi_json (1.13.1) multi_xml (0.6.0) multipart-post (2.0.0) @@ -168,7 +170,7 @@ GEM nap (1.1.0) naturally (2.2.0) netrc (0.11.0) - os (1.0.0) + os (1.0.1) plist (3.5.0) public_suffix (2.0.5) representable (3.0.4) @@ -177,7 +179,7 @@ GEM uber (< 0.2.0) retriable (3.1.2) rouge (2.0.7) - ruby-macho (1.1.0) + ruby-macho (1.4.0) rubyzip (1.2.2) security (0.1.3) signet (0.11.0) @@ -189,11 +191,11 @@ GEM CFPropertyList naturally slack-notifier (2.3.2) - terminal-notifier (1.8.0) + terminal-notifier (2.0.0) terminal-table (1.8.0) unicode-display_width (~> 1.1, >= 1.1.1) thread_safe (0.3.6) - tty-cursor (0.6.0) + tty-cursor (0.6.1) tty-screen (0.6.5) tty-spinner (0.9.0) tty-cursor (~> 0.6.0) @@ -202,10 +204,10 @@ GEM uber (0.1.0) unf (0.1.4) unf_ext - unf_ext (0.0.7.5) - unicode-display_width (1.4.1) + unf_ext (0.0.7.6) + unicode-display_width (1.6.0) word_wrap (1.0.0) - xcodeproj (1.8.0) + xcodeproj (1.9.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) @@ -225,4 +227,4 @@ DEPENDENCIES fastlane-plugin-aws_s3 BUNDLED WITH - 1.16.1 + 2.0.1 diff --git a/MoPubResources/Info.plist b/MoPubResources/Info.plist index 29af72262..9b66e8a2a 100644 --- a/MoPubResources/Info.plist +++ b/MoPubResources/Info.plist @@ -13,7 +13,7 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 5.6.0 + 5.7.0 CFBundleSignature ???? CFBundleSupportedPlatforms @@ -21,7 +21,7 @@ iPhoneOS CFBundleVersion - 5.6.0 + 5.7.0 NSHumanReadableCopyright Copyright 2019 Twitter Inc. All rights reserved. NSPrincipalClass diff --git a/MoPubSDK.xcodeproj/project.pbxproj b/MoPubSDK.xcodeproj/project.pbxproj index 6b34961ed..8a7e0037d 100644 --- a/MoPubSDK.xcodeproj/project.pbxproj +++ b/MoPubSDK.xcodeproj/project.pbxproj @@ -137,7 +137,6 @@ 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 */; }; @@ -181,6 +180,7 @@ 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 */; }; @@ -204,7 +204,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 */; }; @@ -226,6 +230,9 @@ 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 */; }; @@ -407,7 +414,6 @@ 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 */; }; @@ -434,10 +440,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 */; }; @@ -679,7 +689,6 @@ 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 */; }; - BC4C9EEC1E2991EF006021CB /* MPCountdownTimer.html in Resources */ = {isa = PBXBuildFile; fileRef = BCF0FA8A1DC9512900ADFE4F /* MPCountdownTimer.html */; }; 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 */; }; @@ -738,7 +747,6 @@ 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 */; }; - BCA00AF11EF47A91006FF762 /* MPInternalUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 4678267819903BF300DB1B61 /* MPInternalUtils.m */; }; 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 */; }; @@ -872,7 +880,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, ); }; }; @@ -881,6 +888,13 @@ 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 */; }; + 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 */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -933,10 +947,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 = ""; }; @@ -962,6 +982,11 @@ 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 = ""; }; @@ -985,14 +1010,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; }; @@ -1526,7 +1552,6 @@ 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 = ""; }; @@ -1535,6 +1560,13 @@ 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 = ""; }; + 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 = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -1623,6 +1655,7 @@ BCAED2621DF62CED00D45480 /* Categories */, BCAED27E1DF73E2A00D45480 /* Delegate Handlers */, B23C3B501E353F950003D79E /* Fixtures */, + ECD106E92280FE3700398CA5 /* Internal */, 2AAA8DFD1D95C77B006962E8 /* Info.plist */, BCAED2661DF62E4200D45480 /* Mocks */, B2D54B661ED20C95004E3C7B /* MOPUBExperimentProviderTests.m */, @@ -1645,6 +1678,7 @@ B2FC80CC20993CF200D43EE7 /* MPGeolocationProviderTest.m */, BC7FF69C20D07C4E003B1842 /* MPHTTPNetworkSessionTests.m */, BC1920862236F8FC004318D2 /* MPIdentityProviderTests.m */, + 2AA2E2FD225FE0AB00478D5C /* MPImpressionDataTests.m */, BCC54C231ECFACE200A4FEF0 /* MPInterstitialAdControllerTests.m */, BC0BE6A620D4349200DB0D2C /* MPInterstitialAdManagerTests.m */, 2A922D391E9C4CF900F83923 /* MPInterstitialCustomEventAdapterTests.m */, @@ -1663,6 +1697,7 @@ BC246A6B1E5672AA00CEFA33 /* MPRewardedVideoAdManagerTests.m */, BC031AE3211CF93B00E4715B /* MPRewardedVideoRewardTests.m */, BCAC6F5D1E5CF6F5002B2656 /* MPRewardedVideoTests.m */, + ECA6FF16226F8DD7007626A5 /* MPTimerTests.m */, BCC79EC3204DC28B00F7ABE6 /* MPURLRequestTests.m */, B2D54B681ED20FA1004E3C7B /* MPURLResolverTests.m */, BC08C7821E36AD7C00444F16 /* MPVASTLinearAdTests.m */, @@ -1838,6 +1873,7 @@ A7A9AC0D1974578E00C26A7E /* MPClientAdPositioning.m */, A7A1CDD61974783D0082A6FA /* MPCollectionViewAdPlacer.h */, A7A1CDD71974783D0082A6FA /* MPCollectionViewAdPlacer.m */, + 2A73E33C226E46B7001FEE03 /* MPCollectionViewAdPlacerDelegate.h */, A77FBEE618C533B400531E8A /* MPNativeAd.h */, A77FBEE718C533B400531E8A /* MPNativeAd.m */, 4AF54B40194A21340093F714 /* MPNativeAdAdapter.h */, @@ -1858,6 +1894,7 @@ A72F90EC19B7CA0400A5601B /* MPServerAdPositioning.m */, A7A1CDD11974782E0082A6FA /* MPTableViewAdPlacer.h */, A7A1CDD21974782E0082A6FA /* MPTableViewAdPlacer.m */, + 2A73E33E226E5851001FEE03 /* MPTableViewAdPlacerDelegate.h */, ); path = NativeAds; sourceTree = ""; @@ -1908,6 +1945,7 @@ A7A1CDE61974904A0082A6FA /* MPStreamAdPlacementData.m */, A7A1CDC219745F0E0082A6FA /* MPStreamAdPlacer.h */, A7A1CDC319745F0E0082A6FA /* MPStreamAdPlacer.m */, + 2A73E33A226E45F4001FEE03 /* MPStreamAdPlacerDelegate.h */, 573A82EE1B8E488400ED4067 /* MPTableViewAdPlacerCell.h */, 573A82EF1B8E488400ED4067 /* MPTableViewAdPlacerCell.m */, A77FBEDF18C533B400531E8A /* MPTableViewCellImpressionTracker.h */, @@ -2003,6 +2041,7 @@ BC4A4D13217F99ED008A7410 /* MPAdapterConfiguration.h */, AEF9D530171CA895005AAA5A /* MPAdView.h */, AEF9D531171CA895005AAA5A /* MPAdView.m */, + 2A73E336226E43D3001FEE03 /* MPAdViewDelegate.h */, AEF9D532171CA895005AAA5A /* MPBannerCustomEvent.h */, AEF9D533171CA895005AAA5A /* MPBannerCustomEvent.m */, AEF9D534171CA895005AAA5A /* MPBannerCustomEventDelegate.h */, @@ -2017,12 +2056,17 @@ BC119219207BDD45005DF26E /* MPConsentStatus.h */, AEF9D535171CA895005AAA5A /* MPConstants.h */, 57B86B5619C78F5D00AD50EE /* MPConstants.m */, + 2AFEE721225BC38400DD82C8 /* MPImpressionData.h */, + 2AFEE722225BC38400DD82C8 /* MPImpressionData.m */, AEF9D536171CA895005AAA5A /* MPInterstitialAdController.h */, AEF9D537171CA895005AAA5A /* MPInterstitialAdController.m */, + 2A73E338226E44F8001FEE03 /* MPInterstitialAdControllerDelegate.h */, AEF9D538171CA895005AAA5A /* MPInterstitialCustomEvent.h */, AEF9D539171CA895005AAA5A /* MPInterstitialCustomEvent.m */, AEF9D53A171CA895005AAA5A /* MPInterstitialCustomEventDelegate.h */, BC27343D203F51C900920507 /* MPMediationSettingsProtocol.h */, + 2AFEE720225BC22300DD82C8 /* MPMoPubAd.h */, + 2A60998E225E8E020095890A /* MPMoPubAdPlacer.h */, 2AD9F9E01F8547DF00E0A5F0 /* MPMoPubConfiguration.h */, 2AD9F9D51F8547DE00E0A5F0 /* MPMoPubConfiguration.m */, A77FBED818C533B400531E8A /* NativeAds */, @@ -2233,8 +2277,6 @@ AEF9D52C171CA895005AAA5A /* MPTimer.m */, 4614B0A4195B6B2200B39F8A /* MPUserInteractionGestureRecognizer.h */, 4614B0A3195B6B2200B39F8A /* MPUserInteractionGestureRecognizer.m */, - 4678267719903BF300DB1B61 /* MPInternalUtils.h */, - 4678267819903BF300DB1B61 /* MPInternalUtils.m */, A7759F041A1EB19500087E00 /* MPGeolocationProvider.h */, A7759F051A1EB19500087E00 /* MPGeolocationProvider.m */, ); @@ -2244,6 +2286,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 */, @@ -2298,7 +2342,6 @@ 57401A421B7AAEF0000EEA64 /* MPDAAIcon.png */, 57401A431B7AAEF0000EEA64 /* MPDAAIcon@2x.png */, 57401A441B7AAEF0000EEA64 /* MPDAAIcon@3x.png */, - BCF0FA8A1DC9512900ADFE4F /* MPCountdownTimer.html */, BC9EF9DE216811B3005BEA65 /* MPAdapters.plist */, ); path = Resources; @@ -2381,6 +2424,7 @@ 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 */, @@ -2399,6 +2443,8 @@ 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 */, @@ -2409,6 +2455,8 @@ 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 */, @@ -2478,6 +2526,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 */, @@ -2502,6 +2552,22 @@ path = Logging; sourceTree = ""; }; + ECD106E92280FE3700398CA5 /* Internal */ = { + isa = PBXGroup; + children = ( + ECD106EA2280FE4400398CA5 /* MRAID */, + ); + name = Internal; + sourceTree = ""; + }; + ECD106EA2280FE4400398CA5 /* MRAID */ = { + isa = PBXGroup; + children = ( + ECD106E52280FE2600398CA5 /* MRControllerTests.m */, + ); + name = MRAID; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -2509,6 +2575,7 @@ 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 */, @@ -2571,6 +2638,7 @@ 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 */, @@ -2632,6 +2700,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 */, @@ -2692,9 +2761,9 @@ 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 */, 2AF032772016B78800909F29 /* MPVASTCreative.h in Headers */, 2AF032782016B78800909F29 /* MPVASTDurationOffset.h in Headers */, @@ -2713,12 +2782,17 @@ 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 */, 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; }; @@ -2843,6 +2917,7 @@ developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( + English, en, ); mainGroup = AEF1F31F16EF9AD700273462; @@ -2869,7 +2944,6 @@ 2AA73B9F1FCF8ACC001FB787 /* MPDAAIcon.png in Resources */, B23C3B5B1E35709D0003D79E /* linear-tracking-no-event.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 */, @@ -2900,7 +2974,6 @@ 2A711FED202267B6007A2412 /* MPDAAIcon.png in Resources */, 2A711FEE202267B6007A2412 /* MPDAAIcon@2x.png in Resources */, 2A711FEF202267B6007A2412 /* MPDAAIcon@3x.png in Resources */, - 2A711FF0202267B6007A2412 /* MPCountdownTimer.html in Resources */, BC9EF9E0216811B3005BEA65 /* MPAdapters.plist in Resources */, 2A711FDC20226737007A2412 /* MPAdBrowserController.xib in Resources */, ); @@ -2927,7 +3000,6 @@ 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 */, @@ -2973,6 +3045,7 @@ files = ( 2AF177411E984AD700A600BD /* MPRewardedVideoAdapter+Testing.m in Sources */, BC11922A207D6D06005DF26E /* MPConsentManagerTests.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 */, @@ -3001,6 +3074,7 @@ 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 */, @@ -3010,12 +3084,15 @@ 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 */, 2A5C4DB81F6B25F20076C08C /* MPNativeAdConfigValuesTests.m in Sources */, 2AE45D3E1E9D8FFA00ED5DD2 /* MPInterstitialCustomEventAdapter+Testing.m in Sources */, BC7F0F1C1ECF9D6400BB087E /* MPAdView+Testing.m in Sources */, @@ -3026,6 +3103,7 @@ B2D54B671ED20C95004E3C7B /* MOPUBExperimentProviderTests.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 */, @@ -3100,6 +3178,7 @@ 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 */, 2A27022320214502004A72E6 /* MPRewardedVideoError.m in Sources */, @@ -3162,6 +3241,7 @@ 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 */, @@ -3231,7 +3311,6 @@ 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 */, @@ -3277,7 +3356,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 */, @@ -3298,6 +3376,7 @@ 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 */, @@ -3361,6 +3440,7 @@ 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 */, @@ -3467,7 +3547,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - BCA00AF11EF47A91006FF762 /* MPInternalUtils.m in Sources */, BCA00AF31EF47A91006FF762 /* MPBannerAdManager.m in Sources */, BCA00AF41EF47A91006FF762 /* MPBannerCustomEventAdapter.m in Sources */, BCA00AF61EF47A91006FF762 /* MRConstants.m in Sources */, @@ -3540,6 +3619,7 @@ 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 */, @@ -3580,6 +3660,7 @@ BCA00B8E1EF47A91006FF762 /* MRVideoPlayerManager.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 */, ); @@ -3609,7 +3690,7 @@ CLANG_WARN_SUSPICIOUS_MOVES = YES; CLANG_WARN_UNREACHABLE_CODE = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 5.6.0; + CURRENT_PROJECT_VERSION = 5.7.0; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 4S7XS533V3; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -3650,7 +3731,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 5.6.0; + CURRENT_PROJECT_VERSION = 5.7.0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 4S7XS533V3; ENABLE_NS_ASSERTIONS = NO; @@ -3692,12 +3773,12 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 5.6.0; + CURRENT_PROJECT_VERSION = 5.7.0; DEBUG_INFORMATION_FORMAT = dwarf; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 4S7XS533V3; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 5.6.0; + DYLIB_CURRENT_VERSION = 5.7.0; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_PREPROCESSOR_DEFINITIONS = ( @@ -3739,12 +3820,12 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 5.6.0; + CURRENT_PROJECT_VERSION = 5.7.0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 4S7XS533V3; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 5.6.0; + DYLIB_CURRENT_VERSION = 5.7.0; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_NS_ASSERTIONS = NO; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -3778,7 +3859,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.6.0; + CURRENT_PROJECT_VERSION = 5.7.0; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_GENERATE_DEBUGGING_SYMBOLS = NO; GCC_PREPROCESSOR_DEFINITIONS = ( @@ -3812,7 +3893,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.6.0; + CURRENT_PROJECT_VERSION = 5.7.0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -3839,7 +3920,7 @@ AE515F9A171F1B110086B464 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - CURRENT_PROJECT_VERSION = 5.6.0; + CURRENT_PROJECT_VERSION = 5.7.0; DSTROOT = /tmp/MoPubSDK.dst; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -3859,7 +3940,7 @@ AE515F9B171F1B110086B464 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - CURRENT_PROJECT_VERSION = 5.6.0; + CURRENT_PROJECT_VERSION = 5.7.0; DSTROOT = /tmp/MoPubSDK.dst; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -3976,7 +4057,7 @@ BCA00B951EF47A91006FF762 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - CURRENT_PROJECT_VERSION = 5.6.0; + CURRENT_PROJECT_VERSION = 5.7.0; DSTROOT = /tmp/MoPubSDK.dst; GCC_PRECOMPILE_PREFIX_HEADER = NO; GCC_TREAT_WARNINGS_AS_ERRORS = YES; @@ -3993,7 +4074,7 @@ BCA00B961EF47A91006FF762 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - CURRENT_PROJECT_VERSION = 5.6.0; + CURRENT_PROJECT_VERSION = 5.7.0; DSTROOT = /tmp/MoPubSDK.dst; GCC_PRECOMPILE_PREFIX_HEADER = NO; GCC_TREAT_WARNINGS_AS_ERRORS = YES; diff --git a/MoPubSDK.xcodeproj/xcshareddata/xcschemes/MoPubSDK.xcscheme b/MoPubSDK.xcodeproj/xcshareddata/xcschemes/MoPubSDK.xcscheme index 6663f6500..f1a199ee9 100644 --- a/MoPubSDK.xcodeproj/xcshareddata/xcschemes/MoPubSDK.xcscheme +++ b/MoPubSDK.xcodeproj/xcshareddata/xcschemes/MoPubSDK.xcscheme @@ -26,9 +26,29 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + codeCoverageEnabled = "YES" shouldUseLaunchSchemeArgsEnv = "YES"> + + + + + + + + diff --git a/MoPubSDK.xcodeproj/xcshareddata/xcschemes/MoPubSDKTests.xcscheme b/MoPubSDK.xcodeproj/xcshareddata/xcschemes/MoPubSDKTests.xcscheme index a06fb70da..2eb6e1fa0 100644 --- a/MoPubSDK.xcodeproj/xcshareddata/xcschemes/MoPubSDKTests.xcscheme +++ b/MoPubSDK.xcodeproj/xcshareddata/xcschemes/MoPubSDKTests.xcscheme @@ -27,7 +27,6 @@ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" codeCoverageEnabled = "YES" - onlyGenerateCoverageForSpecifiedTargets = "YES" shouldUseLaunchSchemeArgsEnv = "YES"> )delegate { self = [super init]; @@ -274,7 +266,7 @@ - (void)communicatorDidFailWithError:(NSError *)error - (void)didFailToLoadAdapterWithError:(NSError *)error { - [self.delegate managerDidFailToLoadAd]; + [self.delegate managerDidFailToLoadAdWithError:error]; [self scheduleRefreshTimer]; } @@ -397,10 +389,12 @@ - (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 diff --git a/MoPubSDK/Internal/Banners/MPBannerAdManagerDelegate.h b/MoPubSDK/Internal/Banners/MPBannerAdManagerDelegate.h index 8a98c4d2b..ca4a19ea8 100644 --- a/MoPubSDK/Internal/Banners/MPBannerAdManagerDelegate.h +++ b/MoPubSDK/Internal/Banners/MPBannerAdManagerDelegate.h @@ -23,9 +23,10 @@ - (void)invalidateContentView; - (void)managerDidLoadAd:(UIView *)ad; -- (void)managerDidFailToLoadAd; +- (void)managerDidFailToLoadAdWithError:(NSError *)error; - (void)userActionWillBegin; - (void)userActionDidFinish; - (void)userWillLeaveApplication; +- (void)impressionDidFireWithImpressionData:(MPImpressionData *)impressionData; @end diff --git a/MoPubSDK/Internal/Banners/MPBannerCustomEventAdapter.m b/MoPubSDK/Internal/Banners/MPBannerCustomEventAdapter.m index 125f99f21..d94015b85 100644 --- a/MoPubSDK/Internal/Banners/MPBannerCustomEventAdapter.m +++ b/MoPubSDK/Internal/Banners/MPBannerCustomEventAdapter.m @@ -199,6 +199,13 @@ - (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 @@ -209,9 +216,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 359aea5d8..bfc0cd165 100644 --- a/MoPubSDK/Internal/Banners/MPBaseBannerAdapter.h +++ b/MoPubSDK/Internal/Banners/MPBaseBannerAdapter.h @@ -81,7 +81,7 @@ /** * 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. diff --git a/MoPubSDK/Internal/Common/AdAlerts/MPAdAlertGestureRecognizer.m b/MoPubSDK/Internal/Common/AdAlerts/MPAdAlertGestureRecognizer.m index b96acddb6..38de5851a 100644 --- a/MoPubSDK/Internal/Common/AdAlerts/MPAdAlertGestureRecognizer.m +++ b/MoPubSDK/Internal/Common/AdAlerts/MPAdAlertGestureRecognizer.m @@ -28,13 +28,6 @@ @interface MPAdAlertGestureRecognizer () @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.m b/MoPubSDK/Internal/Common/AdAlerts/MPAdAlertManager.m index 83d82d9d9..0cbda33d0 100644 --- a/MoPubSDK/Internal/Common/AdAlerts/MPAdAlertManager.m +++ b/MoPubSDK/Internal/Common/AdAlerts/MPAdAlertManager.m @@ -31,14 +31,12 @@ @interface MPAdAlertManager () #import "MPGlobal.h" +#import "MPImpressionData.h" @class MPRewardedVideoReward; @@ -51,6 +52,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; @@ -108,6 +110,7 @@ extern NSString * const kBannerImpressionMinPixelMetadataKey; @property (nonatomic, assign) NSTimeInterval rewardedPlayableDuration; @property (nonatomic, assign) BOOL rewardedPlayableShouldRewardOnClick; @property (nonatomic, copy) NSString *advancedBidPayload; +@property (nonatomic, strong) MPImpressionData * impressionData; // viewable impression tracking experiment @property (nonatomic) NSTimeInterval impressionMinVisibleTimeInSec; diff --git a/MoPubSDK/Internal/Common/MPAdConfiguration.m b/MoPubSDK/Internal/Common/MPAdConfiguration.m index 4ed539204..38edceab4 100644 --- a/MoPubSDK/Internal/Common/MPAdConfiguration.m +++ b/MoPubSDK/Internal/Common/MPAdConfiguration.m @@ -48,6 +48,7 @@ NSString * const kDspCreativeIdKey = @"x-dspcreativeid"; NSString * const kPrecacheRequiredKey = @"x-precacherequired"; NSString * const kIsVastVideoPlayerKey = @"x-vastvideoplayer"; +NSString * const kImpressionDataMetadataKey = @"impdata"; NSString * const kInterstitialAdTypeMetadataKey = @"x-fulladtype"; NSString * const kOrientationTypeMetadataKey = @"x-orientation"; @@ -113,6 +114,7 @@ - (NSDictionary *)dictionaryFromMetadata:(NSDictionary *)metadata forKey:(NSStri - (NSURL *)URLFromMetadata:(NSDictionary *)metadata forKey:(NSString *)key; - (NSArray *)URLsFromMetadata:(NSDictionary *)metadata forKey:(NSString *)key; - (Class)setUpCustomEventClassFromMetadata:(NSDictionary *)metadata; +- (MPImpressionData *)impressionDataFromMetadata:(NSDictionary *)metadata; @end @@ -186,6 +188,8 @@ - (instancetype)initWithMetadata:(NSDictionary *)metadata data:(NSData *)data ad self.impressionMinVisibleTimeInSec = [self timeIntervalFromMsmetadata:metadata forKey:kBannerImpressionVisableMsMetadataKey]; self.impressionMinVisiblePixels = [[self adAmountFromMetadata:metadata key:kBannerImpressionMinPixelMetadataKey] floatValue]; + self.impressionData = [self impressionDataFromMetadata:metadata]; + // Organize impression tracking URLs NSArray * URLs = [self URLsFromMetadata:metadata forKey:kImpressionTrackersMetadataKey]; // Check to see if the array actually contains URLs @@ -263,14 +267,12 @@ - (Class)setUpCustomEventClassFromMetadata:(NSDictionary *)metadata NSMutableDictionary *convertedCustomEvents = [NSMutableDictionary dictionary]; if (self.adType == MPAdTypeInline) { [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 == MPAdTypeFullscreen) { [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"]; @@ -609,4 +611,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.m b/MoPubSDK/Internal/Common/MPAdDestinationDisplayAgent.m index 02e2fe12b..4bfa58d6e 100644 --- a/MoPubSDK/Internal/Common/MPAdDestinationDisplayAgent.m +++ b/MoPubSDK/Internal/Common/MPAdDestinationDisplayAgent.m @@ -14,6 +14,7 @@ #import "MPCoreInstanceProvider.h" #import "MPAnalyticsTracker.h" #import "MOPUBExperimentProvider.h" +#import "MoPub+Utility.h" #import static NSString * const kDisplayAgentErrorDomain = @"com.mopub.displayagent"; @@ -46,10 +47,6 @@ - (void)dismissAllModalContent; @implementation MPAdDestinationDisplayAgent -@synthesize delegate = _delegate; -@synthesize resolver = _resolver; -@synthesize isLoadingDestination = _isLoadingDestination; - + (MPAdDestinationDisplayAgent *)agentWithDelegate:(id)delegate { MPAdDestinationDisplayAgent *agent = [[MPAdDestinationDisplayAgent alloc] init]; @@ -164,17 +161,18 @@ - (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; @@ -253,11 +251,12 @@ - (void)openURLInApplication:(NSURL *)URL if ([URL mp_hasTelephoneScheme] || [URL mp_hasTelephonePromptScheme]) { [self interceptTelephoneURL:URL]; } else { - BOOL didOpenSuccessfully = [[UIApplication sharedApplication] openURL:URL]; - if (didOpenSuccessfully) { - [self.delegate displayAgentWillLeaveApplication]; - } - [self completeDestinationLoading]; + [MoPub openURL:URL options:@{} completion:^(BOOL didOpenURLSuccessfully) { + if (didOpenURLSuccessfully) { + [self.delegate displayAgentWillLeaveApplication]; + } + [self completeDestinationLoading]; + }]; } } @@ -282,7 +281,7 @@ - (void)interceptTelephoneURL:(NSURL *)URL if (strongSelf) { if (confirmed) { [strongSelf.delegate displayAgentWillLeaveApplication]; - [[UIApplication sharedApplication] openURL:targetTelephoneURL]; + [MoPub openURL:targetTelephoneURL]; } [strongSelf completeDestinationLoading]; } diff --git a/MoPubSDK/Internal/Common/MPClosableView.h b/MoPubSDK/Internal/Common/MPClosableView.h index 3908211ba..e440c7700 100644 --- a/MoPubSDK/Internal/Common/MPClosableView.h +++ b/MoPubSDK/Internal/Common/MPClosableView.h @@ -8,6 +8,8 @@ #import +extern const CGSize kCloseRegionSize; + @class MPWebView; enum { diff --git a/MoPubSDK/Internal/Common/MPClosableView.m b/MoPubSDK/Internal/Common/MPClosableView.m index 6c4802421..e68d95323 100644 --- a/MoPubSDK/Internal/Common/MPClosableView.m +++ b/MoPubSDK/Internal/Common/MPClosableView.m @@ -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; } @@ -115,8 +124,8 @@ - (void)layoutSubviews 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) { diff --git a/MoPubSDK/Internal/Common/MPCountdownTimerView.h b/MoPubSDK/Internal/Common/MPCountdownTimerView.h index 6b06e924c..68cd6e831 100644 --- a/MoPubSDK/Internal/Common/MPCountdownTimerView.h +++ b/MoPubSDK/Internal/Common/MPCountdownTimerView.h @@ -8,52 +8,39 @@ #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 452454353..a4d7023b2 100644 --- a/MoPubSDK/Internal/Common/MPCountdownTimerView.m +++ b/MoPubSDK/Internal/Common/MPCountdownTimerView.m @@ -9,50 +9,89 @@ #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; + if (@available(iOS 8.2, *)) { + label.font = [UIFont systemFontOfSize:12 weight:UIFontWeightBold]; + } else { + label.font = [UIFont systemFontOfSize:12]; + } + 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 +104,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 +158,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 +183,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) { - MPLogInfo(@"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/MPProgressOverlayView.m b/MoPubSDK/Internal/Common/MPProgressOverlayView.m index 69f601e97..f93530b8b 100644 --- a/MoPubSDK/Internal/Common/MPProgressOverlayView.m +++ b/MoPubSDK/Internal/Common/MPProgressOverlayView.m @@ -37,9 +37,6 @@ - (void)setTransformForAllSubviews:(CGAffineTransform)transform; @implementation MPProgressOverlayView -@synthesize delegate = _delegate; -@synthesize closeButton = _closeButton; - - (id)initWithDelegate:(id)delegate { self = [self initWithFrame:MPKeyWindow().bounds]; diff --git a/MoPubSDK/Internal/HTML/MPAdWebViewAgent.m b/MoPubSDK/Internal/HTML/MPAdWebViewAgent.m index 3eee46e1c..d8f707cd9 100644 --- a/MoPubSDK/Internal/HTML/MPAdWebViewAgent.m +++ b/MoPubSDK/Internal/HTML/MPAdWebViewAgent.m @@ -18,7 +18,6 @@ #import "MPUserInteractionGestureRecognizer.h" #import "NSJSONSerialization+MPAdditions.h" #import "NSURL+MPAdditions.h" -#import "MPInternalUtils.h" #import "MPAPIEndPoints.h" #import "MoPub.h" #import "MPViewabilityTracker.h" @@ -40,6 +39,7 @@ @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; @@ -49,15 +49,6 @@ - (void)interceptURL:(NSURL *)URL; @implementation MPAdWebViewAgent -@synthesize configuration = _configuration; -@synthesize delegate = _delegate; -@synthesize destinationDisplayAgent = _destinationDisplayAgent; -@synthesize shouldHandleRequests = _shouldHandleRequests; -@synthesize view = _view; -@synthesize adAlertManager = _adAlertManager; -@synthesize userInteractedWithWebView = _userInteractedWithWebView; -@synthesize userInteractionRecognizer = _userInteractionRecognizer; - - (id)initWithAdWebViewFrame:(CGRect)frame delegate:(id)delegate; { self = [super init]; @@ -67,6 +58,7 @@ - (id)initWithAdWebViewFrame:(CGRect)frame delegate:(id)delegate { self = [super init]; @@ -105,6 +101,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.m b/MoPubSDK/Internal/Interstitials/MPInterstitialAdManager.m index 8f54993d1..decdb55b2 100644 --- a/MoPubSDK/Internal/Interstitials/MPInterstitialAdManager.m +++ b/MoPubSDK/Internal/Interstitials/MPInterstitialAdManager.m @@ -42,13 +42,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]; @@ -295,4 +288,8 @@ - (void)interstitialWillLeaveApplicationForAdapter:(MPBaseInterstitialAdapter *) 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 d13091469..cc4cd925f 100644 --- a/MoPubSDK/Internal/Interstitials/MPInterstitialAdManagerDelegate.h +++ b/MoPubSDK/Internal/Interstitials/MPInterstitialAdManagerDelegate.h @@ -10,6 +10,7 @@ @class MPInterstitialAdManager; @class MPInterstitialAdController; +@class MPImpressionData; @class CLLocation; @protocol MPInterstitialAdManagerDelegate @@ -26,6 +27,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.m b/MoPubSDK/Internal/Interstitials/MPInterstitialCustomEventAdapter.m index bed6009c3..b391aa8c9 100644 --- a/MoPubSDK/Internal/Interstitials/MPInterstitialCustomEventAdapter.m +++ b/MoPubSDK/Internal/Interstitials/MPInterstitialCustomEventAdapter.m @@ -31,10 +31,6 @@ @interface MPInterstitialCustomEventAdapter () @end @implementation MPInterstitialCustomEventAdapter -@synthesize hasTrackedImpression = _hasTrackedImpression; -@synthesize hasTrackedClick = _hasTrackedClick; - -@synthesize interstitialCustomEvent = _interstitialCustomEvent; - (void)dealloc { diff --git a/MoPubSDK/Internal/Interstitials/MPInterstitialViewController.m b/MoPubSDK/Internal/Interstitials/MPInterstitialViewController.m index ffe23eda0..6a5c950a3 100644 --- a/MoPubSDK/Internal/Interstitials/MPInterstitialViewController.m +++ b/MoPubSDK/Internal/Interstitials/MPInterstitialViewController.m @@ -33,13 +33,6 @@ - (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]; diff --git a/MoPubSDK/Internal/MPAdServerKeys.h b/MoPubSDK/Internal/MPAdServerKeys.h index 6cce7e97b..1d46af416 100644 --- a/MoPubSDK/Internal/MPAdServerKeys.h +++ b/MoPubSDK/Internal/MPAdServerKeys.h @@ -100,3 +100,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 cb6d0c104..8edabbb90 100644 --- a/MoPubSDK/Internal/MPAdServerKeys.m +++ b/MoPubSDK/Internal/MPAdServerKeys.m @@ -9,94 +9,111 @@ #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 kBundleKey = @"bundle"; +NSString * const kDoNotTrackIdKey = @"dnt"; +NSString * const kSDKVersionKey = @"nv"; #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 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 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"; #pragma mark - Ad Server Response Keys -NSString * const kEnableDebugLogging = @"enable_debug_logging"; +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/MPConsentDialogViewController.m b/MoPubSDK/Internal/MPConsentDialogViewController.m index 188c1cbb4..c192ab22b 100644 --- a/MoPubSDK/Internal/MPConsentDialogViewController.m +++ b/MoPubSDK/Internal/MPConsentDialogViewController.m @@ -10,6 +10,7 @@ #import "MPConsentDialogViewController.h" #import "MPGlobal.h" #import "MPWebView.h" +#import "MoPub+Utility.h" typedef void(^MPConsentDialogViewControllerCompletion)(BOOL success, NSError *error); @@ -204,7 +205,7 @@ - (BOOL)webView:(MPWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *) // 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.m b/MoPubSDK/Internal/MPConsentManager.m index a674823b7..403815028 100644 --- a/MoPubSDK/Internal/MPConsentManager.m +++ b/MoPubSDK/Internal/MPConsentManager.m @@ -349,6 +349,21 @@ - (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 @@ -396,6 +411,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(); @@ -527,30 +551,29 @@ - (void)synchronizeConsentWithCompletion:(void (^ _Nonnull)(NSError * error))com // ad server. strongSelf.isForcedGDPRAppliesTransition = NO; - // Schedule the next timer. - strongSelf.nextUpdateTimer = [strongSelf newNextUpdateTimer]; - // 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]; + // Complete with error. MPLogEvent([MPLogEvent consentSyncFailedWithError:deserializationError]); completion(deserializationError); - return; } - - // Attempt to parse and update the consent state - if (![strongSelf updateConsentStateWithParameters:json]) { + else if (![strongSelf 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); + } - // Complete successfully. - MPLogEvent([MPLogEvent consentSyncCompletedWithMessage:nil]); - completion(nil); + // `updateConsentStateWithParameters` might update `syncFrequency`, which is referenced in + // `newNextUpdateTimer`, so, call `updateConsentStateWithParameters` before `newNextUpdateTimer` + strongSelf.nextUpdateTimer = [strongSelf newNextUpdateTimer]; } errorHandler:^(NSError * _Nonnull error) { __typeof__(self) strongSelf = weakSelf; @@ -570,7 +593,14 @@ - (void)synchronizeConsentWithCompletion:(void (^ _Nonnull)(NSError * error))com */ - (MPTimer * _Nonnull)newNextUpdateTimer { MPTimer * timer = [MPTimer timerWithTimeInterval:self.syncFrequency target:self selector:@selector(onNextUpdateFiredWithTimer) repeats:YES]; - [timer scheduleNow]; + dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + // 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 since + // the same `dispatch_once` is called twice for `MPConsentManager.sharedManager` in the same + // call stack. To avoid this crash, call `MPTimer.scheduleNow` asynchronusly for now. + [timer scheduleNow]; + }); return timer; } diff --git a/MoPubSDK/Internal/MPCoreInstanceProvider.m b/MoPubSDK/Internal/MPCoreInstanceProvider.m index 0ea7a7ea5..71e30a125 100644 --- a/MoPubSDK/Internal/MPCoreInstanceProvider.m +++ b/MoPubSDK/Internal/MPCoreInstanceProvider.m @@ -47,10 +47,6 @@ @interface MPCoreInstanceProvider () @implementation MPCoreInstanceProvider -@synthesize singletons = _singletons; -@synthesize carrierInfo = _carrierInfo; -@synthesize twitterDeepLinkStatus = _twitterDeepLinkStatus; - static MPCoreInstanceProvider *sharedProvider = nil; + (instancetype)sharedProvider diff --git a/MoPubSDK/Internal/MPRateLimitManager.m b/MoPubSDK/Internal/MPRateLimitManager.m index ebb94276c..fffbab62b 100644 --- a/MoPubSDK/Internal/MPRateLimitManager.m +++ b/MoPubSDK/Internal/MPRateLimitManager.m @@ -12,7 +12,7 @@ @interface MPRateLimitManager () // Ad Unit IDs are used as keys; @c MPRateLimitConfiguration objects are used as values -@property (nonatomic, copy) NSMutableDictionary * configurationDictionary; +@property (nonatomic, strong) NSMutableDictionary * configurationDictionary; @end diff --git a/MoPubSDK/Internal/MRAID/MPMRAIDInterstitialCustomEvent.m b/MoPubSDK/Internal/MRAID/MPMRAIDInterstitialCustomEvent.m index 32c524915..830b0fadc 100644 --- a/MoPubSDK/Internal/MRAID/MPMRAIDInterstitialCustomEvent.m +++ b/MoPubSDK/Internal/MRAID/MPMRAIDInterstitialCustomEvent.m @@ -19,8 +19,6 @@ @interface MPMRAIDInterstitialCustomEvent () @implementation MPMRAIDInterstitialCustomEvent -@synthesize interstitial = _interstitial; - - (void)requestInterstitialWithCustomEventInfo:(NSDictionary *)info { MPAdConfiguration * configuration = self.delegate.configuration; diff --git a/MoPubSDK/Internal/MRAID/MRController.m b/MoPubSDK/Internal/MRAID/MRController.m index 53c0fdebd..b0610f8a5 100644 --- a/MoPubSDK/Internal/MRAID/MRController.m +++ b/MoPubSDK/Internal/MRAID/MRController.m @@ -408,68 +408,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(self.includeSafeAreaInsetsInCalculations); - 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(self.includeSafeAreaInsetsInCalculations), 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)isValidResizeCloseButtonPlacement:(MPClosableViewCloseButtonLocation)closeButtonLocation inFrame:(CGRect)newFrame -{ - CGRect closeButtonFrameForResize = MPClosableViewCustomCloseButtonFrame(newFrame.size, 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(self.includeSafeAreaInsetsInCalculations), 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 @@ -950,15 +964,24 @@ - (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]; - 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 isValidResizeCloseButtonPlacement:closeButtonLocation inFrame: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; diff --git a/MoPubSDK/Internal/MRAID/MRProperty.m b/MoPubSDK/Internal/MRAID/MRProperty.m index e134955b7..dd4297dc1 100644 --- a/MoPubSDK/Internal/MRAID/MRProperty.m +++ b/MoPubSDK/Internal/MRAID/MRProperty.m @@ -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; @@ -178,8 +170,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.m b/MoPubSDK/Internal/MRAID/MRVideoPlayerManager.m index 2be69cac0..86a13a292 100644 --- a/MoPubSDK/Internal/MRAID/MRVideoPlayerManager.m +++ b/MoPubSDK/Internal/MRAID/MRVideoPlayerManager.m @@ -12,8 +12,6 @@ @implementation MRVideoPlayerManager -@synthesize delegate = _delegate; - - (id)initWithDelegate:(id)delegate { self = [super init]; diff --git a/MoPubSDK/Internal/Utility/Categories/MoPub+Utility.h b/MoPubSDK/Internal/Utility/Categories/MoPub+Utility.h new file mode 100644 index 000000000..b355a0621 --- /dev/null +++ b/MoPubSDK/Internal/Utility/Categories/MoPub+Utility.h @@ -0,0 +1,38 @@ +// +// 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; + +@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..e31974053 --- /dev/null +++ b/MoPubSDK/Internal/Utility/Categories/MoPub+Utility.m @@ -0,0 +1,27 @@ +// +// 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]); + } +} + +@end diff --git a/MoPubSDK/Internal/Utility/MPError.h b/MoPubSDK/Internal/Utility/MPError.h index 6495df511..620adf240 100644 --- a/MoPubSDK/Internal/Utility/MPError.h +++ b/MoPubSDK/Internal/Utility/MPError.h @@ -31,6 +31,7 @@ typedef enum { MOPUBErrorInvalidCustomEventClass, MOPUBErrorJSONSerializationFailed, MOPUBErrorUnableToParseAdResponse, + MOPUBErrorConsentDialogAlreadyShowing, MOPUBErrorNoConsentDialogLoaded, MOPUBErrorAdapterFailedToLoadAd, MOPUBErrorFullScreenAdAlreadyOnScreen, @@ -61,6 +62,7 @@ typedef enum { @end @interface NSError (Consent) ++ (instancetype)consentDialogAlreadyShowing; + (instancetype)noConsentDialogLoaded; @end diff --git a/MoPubSDK/Internal/Utility/MPError.m b/MoPubSDK/Internal/Utility/MPError.m index 40e60241c..40bdd79ac 100644 --- a/MoPubSDK/Internal/Utility/MPError.m +++ b/MoPubSDK/Internal/Utility/MPError.m @@ -80,6 +80,10 @@ + (instancetype)fullscreenAdAlreadyOnScreen { @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."]; } diff --git a/MoPubSDK/Internal/Utility/MPInternalUtils.h b/MoPubSDK/Internal/Utility/MPInternalUtils.h deleted file mode 100644 index 31fffd1d9..000000000 --- a/MoPubSDK/Internal/Utility/MPInternalUtils.h +++ /dev/null @@ -1,26 +0,0 @@ -// -// MPInternalUtils.h -// -// Copyright 2018-2019 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 083bccfc0..000000000 --- a/MoPubSDK/Internal/Utility/MPInternalUtils.m +++ /dev/null @@ -1,33 +0,0 @@ -// -// MPInternalUtils.m -// -// Copyright 2018-2019 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/MPTimer.h b/MoPubSDK/Internal/Utility/MPTimer.h index b0b2efb16..2106f3e5d 100644 --- a/MoPubSDK/Internal/Utility/MPTimer.h +++ b/MoPubSDK/Internal/Utility/MPTimer.h @@ -8,13 +8,24 @@ #import -/* - * MPTimer wraps an NSTimer and adds pause/resume functionality. +NS_ASSUME_NONNULL_BEGIN + +/** + * @c MPTimer wraps an @c NSTimer and adds pause/resume functionality. */ @interface MPTimer : NSObject +/** + * The default run loop mode is @c NSDefaultRunLoopMode. If a new mode is assigned, it will be effective + * for the subsequent @c isScheduled or @c resume calls. + */ @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 @@ -23,9 +34,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 9251299cf..84a83eb46 100644 --- a/MoPubSDK/Internal/Utility/MPTimer.m +++ b/MoPubSDK/Internal/Utility/MPTimer.m @@ -6,18 +6,15 @@ // 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 isCountdownActive; @property (nonatomic, weak) id target; @property (nonatomic, assign) SEL selector; @@ -26,13 +23,6 @@ @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 @@ -42,10 +32,11 @@ + (MPTimer *)timerWithTimeInterval:(NSTimeInterval)seconds timer.target = target; timer.selector = aSelector; timer.timer = [NSTimer timerWithTimeInterval:seconds - target:timer - selector:@selector(timerDidFire) - userInfo:nil - repeats:repeats]; + target:timer + selector:@selector(timerDidFire) + userInfo:nil + repeats:repeats]; + timer.isCountdownActive = NO; timer.timeInterval = seconds; timer.runLoopMode = NSDefaultRunLoopMode; return timer; @@ -56,11 +47,25 @@ - (void)dealloc [self.timer invalidate]; } +/** + This is the designated run loop that the timer should attach to. + */ +- (NSRunLoop *)runloop +{ + return [NSRunLoop mainRunLoop]; // use the main run loop to make sure the timer stays alive +} + - (void)timerDidFire { - SUPPRESS_PERFORM_SELECTOR_LEAK_WARNING( - [self.target performSelector:self.selector withObject:nil] - ); + 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 @@ -74,6 +79,7 @@ - (void)invalidate self.selector = nil; [self.timer invalidate]; self.timer = nil; + self.isCountdownActive = NO; } - (BOOL)isScheduled @@ -81,7 +87,7 @@ - (BOOL)isScheduled if (!self.timer) { return NO; } - CFRunLoopRef runLoopRef = [[NSRunLoop currentRunLoop] getCFRunLoop]; + CFRunLoopRef runLoopRef = [self.runloop getCFRunLoop]; CFArrayRef arrayRef = CFRunLoopCopyAllModes(runLoopRef); CFIndex count = CFArrayGetCount(arrayRef); @@ -97,38 +103,51 @@ - (BOOL)isScheduled return NO; } -- (BOOL)scheduleNow +- (void)scheduleNow { if (![self.timer isValid]) { MPLogDebug(@"Could not schedule invalidated MPTimer (%p).", self); - return NO; + return; } - [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:self.runLoopMode]; - return YES; + if (self.isCountdownActive) { + MPLogDebug(@"Tried to schedule an MPTimer (%p) that is already ticking.",self); + return; + } + + NSDate *newFireDate = [NSDate dateWithTimeInterval:self.timeInterval sinceDate:[NSDate date]]; + [self.timer setFireDate:newFireDate]; + + if ([self isScheduled]) { + MPLogDebug(@"MPTimer is already scheduled (%p).", self); + } else { + MPLogDebug(@"Start MPTimer (%p), should fire in %.1f seconds.", self, self.timeInterval); + [self.runloop addTimer:self.timer forMode:self.runLoopMode]; + } + + self.isCountdownActive = YES; } -- (BOOL)pause +- (void)pause { - NSTimeInterval secondsLeft; - if (self.isPaused) { + if (!self.isCountdownActive) { MPLogDebug(@"No-op: tried to pause an MPTimer (%p) that was already paused.", self); - return NO; + return; } if (![self.timer isValid]) { MPLogDebug(@"Cannot pause invalidated MPTimer (%p).", self); - return NO; + return; } if (![self isScheduled]) { MPLogDebug(@"No-op: tried to pause an MPTimer (%p) that was never scheduled.", self); - return NO; + return; } - NSDate *fireDate = [self.timer fireDate]; - self.pauseDate = [NSDate date]; - secondsLeft = [fireDate timeIntervalSinceDate:self.pauseDate]; + // `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 { @@ -137,41 +156,12 @@ - (BOOL)pause // Pause the timer by setting its fire date far into the future. [self.timer setFireDate:[NSDate distantFuture]]; - self.isPaused = YES; - - return YES; -} - -- (BOOL)resume -{ - if (![self.timer isValid]) { - MPLogDebug(@"Cannot resume invalidated MPTimer (%p).", self); - return NO; - } - - if (!self.isPaused) { - MPLogDebug(@"No-op: tried to resume an MPTimer (%p) that was never paused.", self); - return NO; - } - - MPLogDebug(@"Resumed MPTimer (%p), should fire in %.1f seconds.", self, self.timeInterval); - - // Resume the timer. - NSDate *newFireDate = [NSDate dateWithTimeInterval:self.timeInterval sinceDate:[NSDate date]]; - [self.timer setFireDate:newFireDate]; - - if (![self isScheduled]) { - [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:self.runLoopMode]; - } - - self.isPaused = NO; - return YES; + self.isCountdownActive = NO; } -- (NSTimeInterval)initialTimeInterval +- (void)resume { - return self.timeInterval; + [self scheduleNow]; } @end - diff --git a/MoPubSDK/MPAdView.h b/MoPubSDK/MPAdView.h index c95a9391a..8232bcab5 100644 --- a/MoPubSDK/MPAdView.h +++ b/MoPubSDK/MPAdView.h @@ -9,6 +9,7 @@ #import #import #import "MPConstants.h" +#import "MPAdViewDelegate.h" typedef enum { @@ -17,13 +18,11 @@ typedef enum MPNativeAdOrientationLandscape } MPNativeAdOrientation; -@protocol MPAdViewDelegate; - /** * The MPAdView class provides a view that can display banner advertisements. */ -@interface MPAdView : UIView +@interface MPAdView : UIView /** @name Initializing a Banner Ad */ @@ -212,89 +211,3 @@ typedef enum - (void)startAutomaticallyRefreshingContents; @end - -//////////////////////////////////////////////////////////////////////////////////////////////////// - -#pragma mark - - -/** - * The delegate of an `MPAdView` object must adopt the `MPAdViewDelegate` protocol. It must - * implement `viewControllerForPresentingModalView` to provide a root view controller from which - * the ad view should present modal content. - * - * Optional methods of this protocol allow the delegate to be notified of banner success or - * failure, as well as other lifecycle events. - */ - -@protocol MPAdViewDelegate - -@required - -/** @name Managing Modal Content Presentation */ - -/** - * Asks the delegate for a view controller to use for presenting modal content, such as the in-app - * browser that can appear when an ad is tapped. - * - * @return A view controller that should be used for presenting modal content. - */ -- (UIViewController *)viewControllerForPresentingModalView; - -@optional - -/** @name Detecting When a Banner Ad is Loaded */ - -/** - * Sent when an ad view successfully loads an ad. - * - * Your implementation of this method should insert the ad view into the view hierarchy, if you - * have not already done so. - * - * @param view The ad view sending the message. - */ -- (void)adViewDidLoadAd:(MPAdView *)view; - -/** - * Sent when an ad view fails to load an ad. - * - * To avoid displaying blank ads, you should hide the ad view in response to this message. - * - * @param view The ad view sending the message. - */ -- (void)adViewDidFailToLoadAd:(MPAdView *)view; - -/** @name Detecting When a User Interacts With the Ad View */ - -/** - * Sent when an ad view is about to present modal content. - * - * This method is called when the user taps on the ad view. Your implementation of this method - * should pause any application activity that requires user interaction. - * - * @param view The ad view sending the message. - * @see `didDismissModalViewForAd:` - */ -- (void)willPresentModalViewForAd:(MPAdView *)view; - -/** - * Sent when an ad view has dismissed its modal content, returning control to your application. - * - * Your implementation of this method should resume any application activity that was paused - * in response to `willPresentModalViewForAd:`. - * - * @param view The ad view sending the message. - * @see `willPresentModalViewForAd:` - */ -- (void)didDismissModalViewForAd:(MPAdView *)view; - -/** - * Sent when a user is about to leave your application as a result of tapping - * on an ad. - * - * Your application will be moved to the background shortly after this method is called. - * - * @param view The ad view sending the message. - */ -- (void)willLeaveApplicationFromAd:(MPAdView *)view; - -@end diff --git a/MoPubSDK/MPAdView.m b/MoPubSDK/MPAdView.m index 5264ab312..515015a0e 100644 --- a/MoPubSDK/MPAdView.m +++ b/MoPubSDK/MPAdView.m @@ -150,14 +150,22 @@ - (void)invalidateContentView [self setAdContentView:nil]; } -- (void)managerDidFailToLoadAd +- (void)managerDidFailToLoadAdWithError:(NSError *)error { if ([self.delegate respondsToSelector:@selector(adViewDidFailToLoadAd:)]) { // make sure we are not released synchronously as objects owned by us // may do additional work after this callback [[MPCoreInstanceProvider sharedProvider] keepObjectAliveForCurrentRunLoopIteration:self]; - +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" [self.delegate adViewDidFailToLoadAd:self]; +#pragma GCC diagnostic pop + } + if ([self.delegate respondsToSelector:@selector(adView:didFailToLoadAdWithError:)]) { + // make sure we are not released synchronously as objects owned by us + // may do additional work after this callback + [[MPCoreInstanceProvider sharedProvider] keepObjectAliveForCurrentRunLoopIteration:self]; + [self.delegate adView:self didFailToLoadAdWithError:error]; } } @@ -190,4 +198,10 @@ - (void)userWillLeaveApplication } } +- (void)impressionDidFireWithImpressionData:(MPImpressionData *)impressionData { + if ([self.delegate respondsToSelector:@selector(mopubAd:didTrackImpressionWithImpressionData:)]) { + [self.delegate mopubAd:self didTrackImpressionWithImpressionData:impressionData]; + } +} + @end diff --git a/MoPubSDK/MPAdViewDelegate.h b/MoPubSDK/MPAdViewDelegate.h new file mode 100644 index 000000000..31ef8ac50 --- /dev/null +++ b/MoPubSDK/MPAdViewDelegate.h @@ -0,0 +1,103 @@ +// +// MPAdViewDelegate.h +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import "MPMoPubAd.h" + +@class MPAdView; + +/** + * The delegate of an `MPAdView` object must adopt the `MPAdViewDelegate` protocol. It must + * implement `viewControllerForPresentingModalView` to provide a root view controller from which + * the ad view should present modal content. + * + * Optional methods of this protocol allow the delegate to be notified of banner success or + * failure, as well as other lifecycle events. + */ + +@protocol MPAdViewDelegate + +@required + +/** @name Managing Modal Content Presentation */ + +/** + * Asks the delegate for a view controller to use for presenting modal content, such as the in-app + * browser that can appear when an ad is tapped. + * + * @return A view controller that should be used for presenting modal content. + */ +- (UIViewController *)viewControllerForPresentingModalView; + +@optional + +/** @name Detecting When a Banner Ad is Loaded */ + +/** + * Sent when an ad view successfully loads an ad. + * + * Your implementation of this method should insert the ad view into the view hierarchy, if you + * have not already done so. + * + * @param view The ad view sending the message. + */ +- (void)adViewDidLoadAd:(MPAdView *)view; + +/** + * Sent when an ad view fails to load an ad. + * + * To avoid displaying blank ads, you should hide the ad view in response to this message. + * + * @param view The ad view sending the message. + */ +- (void)adViewDidFailToLoadAd:(MPAdView *)view __attribute__((deprecated("Deprecated; please use adView:didFailToLoadAdWithError: instead."))); + +/** + * Sent when an ad view fails to load an ad. + * + * To avoid displaying blank ads, you should hide the ad view in response to this message. + * + * @param view The ad view sending the message. + * @param error The error + */ +- (void)adView:(MPAdView *)view didFailToLoadAdWithError:(NSError *)error; + +/** @name Detecting When a User Interacts With the Ad View */ + +/** + * Sent when an ad view is about to present modal content. + * + * This method is called when the user taps on the ad view. Your implementation of this method + * should pause any application activity that requires user interaction. + * + * @param view The ad view sending the message. + * @see `didDismissModalViewForAd:` + */ +- (void)willPresentModalViewForAd:(MPAdView *)view; + +/** + * Sent when an ad view has dismissed its modal content, returning control to your application. + * + * Your implementation of this method should resume any application activity that was paused + * in response to `willPresentModalViewForAd:`. + * + * @param view The ad view sending the message. + * @see `willPresentModalViewForAd:` + */ +- (void)didDismissModalViewForAd:(MPAdView *)view; + +/** + * Sent when a user is about to leave your application as a result of tapping + * on an ad. + * + * Your application will be moved to the background shortly after this method is called. + * + * @param view The ad view sending the message. + */ +- (void)willLeaveApplicationFromAd:(MPAdView *)view; + +@end diff --git a/MoPubSDK/MPConstants.h b/MoPubSDK/MPConstants.h index f685b4b6f..16e9e4cd1 100644 --- a/MoPubSDK/MPConstants.h +++ b/MoPubSDK/MPConstants.h @@ -14,7 +14,7 @@ #define MP_SERVER_VERSION @"8" #define MP_REWARDED_API_VERSION @"1" #define MP_BUNDLE_IDENTIFIER @"com.mopub.mopub" -#define MP_SDK_VERSION @"5.6.0" +#define MP_SDK_VERSION @"5.7.0" // Sizing constants. extern CGSize const MOPUB_BANNER_SIZE; diff --git a/MoPubSDK/MPImpressionData.h b/MoPubSDK/MPImpressionData.h new file mode 100644 index 000000000..6e163df64 --- /dev/null +++ b/MoPubSDK/MPImpressionData.h @@ -0,0 +1,45 @@ +// +// MPImpressionData.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 + +typedef NS_ENUM(NSInteger, MPImpressionDataPrecision) { + MPImpressionDataPrecisionUnknown = 0, + MPImpressionDataPrecisionExact, + MPImpressionDataPrecisionEstimated, + MPImpressionDataPrecisionPublisherDefined, + MPImpressionDataPrecisionUndisclosed, +}; + +@interface MPImpressionData : NSObject + +- (instancetype)initWithDictionary:(NSDictionary *)impressionDataDictionary NS_DESIGNATED_INITIALIZER; +- (instancetype)init NS_UNAVAILABLE; + +@property (nonatomic, copy, readonly) NSNumber * _Nullable publisherRevenue; +@property (nonatomic, copy, readonly) NSString * _Nullable impressionID; +@property (nonatomic, copy, readonly) NSString * _Nullable adUnitID; +@property (nonatomic, copy, readonly) NSString * _Nullable adUnitName; +@property (nonatomic, copy, readonly) NSString * _Nullable adUnitFormat; +@property (nonatomic, copy, readonly) NSString * _Nullable currency; +@property (nonatomic, copy, readonly) NSString * _Nullable adGroupID; +@property (nonatomic, copy, readonly) NSString * _Nullable adGroupName; +@property (nonatomic, copy, readonly) NSString * _Nullable adGroupType; +@property (nonatomic, copy, readonly) NSNumber * _Nullable adGroupPriority; +@property (nonatomic, copy, readonly) NSString * _Nullable country; +@property (nonatomic, assign, readonly) MPImpressionDataPrecision precision; +@property (nonatomic, copy, readonly) NSString * _Nullable networkName; +@property (nonatomic, copy, readonly) NSString * _Nullable networkPlacementID; + +@property (nonatomic, copy, readonly) NSData * _Nullable jsonRepresentation; + +@end + +NS_ASSUME_NONNULL_END diff --git a/MoPubSDK/MPImpressionData.m b/MoPubSDK/MPImpressionData.m new file mode 100644 index 000000000..611f69b64 --- /dev/null +++ b/MoPubSDK/MPImpressionData.m @@ -0,0 +1,203 @@ +// +// MPImpressionData.m +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import "MPImpressionData.h" +#import "MPAdServerKeys.h" + +static NSString * const kPrecisionOptionPublisherDefinedKey = @"publisher_defined"; +static NSString * const kPrecisionOptionExactKey = @"exact"; +static NSString * const kPrecisionOptionEstimatedKey = @"estimated"; +static NSString * const kPrecisionOptionUndisclosedKey = @"undisclosed"; + +@interface MPImpressionData () + +@property (nonatomic, assign, readwrite) MPImpressionDataPrecision precision; +@property (nonatomic, copy, readwrite) NSData * jsonRepresentation; + +@property (nonatomic, copy) NSDictionary * impressionDataDictionary; +@property (nonatomic, assign) BOOL isPrecisionSet; + +@end + +@implementation MPImpressionData + +- (instancetype)initWithDictionary:(NSDictionary *)impressionDataDictionary { + if (self = [super init]) { + _impressionDataDictionary = impressionDataDictionary; + } + + return self; +} + +/* + @c impressionDataDictionary must be stored for the purposes of assembling the JSON + (in case ad server includes fields that this model class doesn't know about), so since + dictionary reads are O(1) and the property values are already stored in the dictionary, + just override the property getters and return values from the dictionary, rather than + storing duplicates of the values in memory. + */ + +- (NSString *)impressionID { + return [self nullableImpressionDataObjectForKey:kImpressionDataImpressionIDKey]; +} + +- (NSString *)adUnitID { + return [self nullableImpressionDataObjectForKey:kImpressionDataAdUnitIDKey]; +} + +- (NSString *)adUnitName { + return [self nullableImpressionDataObjectForKey:kImpressionDataAdUnitNameKey]; +} + +- (NSString *)adUnitFormat { + return [self nullableImpressionDataObjectForKey:kImpressionDataAdUnitFormatKey]; +} + +- (NSString *)adGroupID { + return [self nullableImpressionDataObjectForKey:kImpressionDataAdGroupIDKey]; +} + +- (NSString *)adGroupName { + return [self nullableImpressionDataObjectForKey:kImpressionDataAdGroupNameKey]; +} + +- (NSString *)adGroupType { + return [self nullableImpressionDataObjectForKey:kImpressionDataAdGroupTypeKey]; +} + +- (NSNumber *)adGroupPriority { + return [self nullableImpressionDataObjectForKey:kImpressionDataAdGroupPriorityKey]; +} + +- (NSString *)currency { + return [self nullableImpressionDataObjectForKey:kImpressionDataCurrencyKey]; +} + +- (NSString *)country { + return [self nullableImpressionDataObjectForKey:kImpressionDataCountryKey]; +} + +- (NSString *)networkName { + return [self nullableImpressionDataObjectForKey:kImpressionDataNetworkNameKey]; +} + +- (NSString *)networkPlacementID { + return [self nullableImpressionDataObjectForKey:kImpressionDataNetworkPlacementIDKey]; +} + +- (NSNumber *)publisherRevenue { + return [self nullableImpressionDataObjectForKey:kImpressionDataPublisherRevenueKey]; +} + +- (MPImpressionDataPrecision)precision { + // Return the precision value if it was already set + if (self.isPrecisionSet) { + return _precision; + } + + // If not, set it + self.isPrecisionSet = YES; + + NSString * precisionString = [self nullableImpressionDataObjectForKey:kImpressionDataPrecisionKey]; + + // If the precision string is nil, the precision is unknown, and no other checks are required. + if (precisionString == nil) { + self.precision = MPImpressionDataPrecisionUnknown; + return _precision; + } + + // If the precision string is equal to some known string, the precision is known. Otherwise, it's unknown. + if ([precisionString isEqualToString:kPrecisionOptionExactKey]) { + self.precision = MPImpressionDataPrecisionExact; + } else if ([precisionString isEqualToString:kPrecisionOptionEstimatedKey]) { + self.precision = MPImpressionDataPrecisionEstimated; + } else if ([precisionString isEqualToString:kPrecisionOptionPublisherDefinedKey]) { + self.precision = MPImpressionDataPrecisionPublisherDefined; + } else if ([precisionString isEqualToString:kPrecisionOptionUndisclosedKey]) { + self.precision = MPImpressionDataPrecisionUndisclosed; + } else { + self.precision = MPImpressionDataPrecisionUnknown; + } + + return _precision; +} + +// This method gets the object for the @c key given from @c impressionDataDictionary. If the object is of type @c NSNull, this method returns @c nil. +// This way, getters will return @c nil for null JSON values. This method does not mutate the dictionary to remove @c NSNull objects. +- (id)nullableImpressionDataObjectForKey:(id)key { + id object = self.impressionDataDictionary[key]; + + if ([object isKindOfClass:[NSNull class]]) { + return nil; + } + + return object; +} + +- (NSData *)jsonRepresentation { + // If the JSON representation was already computed, do not do it again. + if (_jsonRepresentation != nil) { + return _jsonRepresentation; + } + + // If this is the first time accessing the JSON representation, compute it. + NSError * error = nil; + NSData * jsonData = [NSJSONSerialization dataWithJSONObject:self.impressionDataDictionary + options:0 + error:&error]; + if (error == nil) { + self.jsonRepresentation = jsonData; + } + + return _jsonRepresentation; +} + +- (NSString *)description { + NSString * precisionString; + switch (self.precision) { + case MPImpressionDataPrecisionExact: + precisionString = kPrecisionOptionExactKey; + break; + case MPImpressionDataPrecisionEstimated: + precisionString = kPrecisionOptionEstimatedKey; + break; + case MPImpressionDataPrecisionPublisherDefined: + precisionString = kPrecisionOptionPublisherDefinedKey; + break; + case MPImpressionDataPrecisionUndisclosed: + precisionString = kPrecisionOptionUndisclosedKey; + break; + default: + precisionString = nil; + break; + } + + NSDictionary * jsonRepresentationDeserialized = [NSJSONSerialization JSONObjectWithData:self.jsonRepresentation + options:0 + error:nil]; + + return [NSString stringWithFormat:@"Impression Data %@:\n\nImpression ID: %@\nPublisher Revenue: %@\nCurrency: %@\nAd Unit ID: %@\nAd Unit Name: %@\nAd Unit Format: %@\nAd Group ID: %@\nAd Group Name: %@\nAd Group Type: %@\nAd Group Priority: %@\nPrecision: %@\nCountry: %@\nNetwork Name: %@\nNetwork Placement ID: %@\n\nJSON Representation:\n%@", + [super description], + self.impressionID, + self.publisherRevenue, + self.currency, + self.adUnitID, + self.adUnitName, + self.adUnitFormat, + self.adGroupID, + self.adGroupName, + self.adGroupType, + self.adGroupPriority, + precisionString, + self.country, + self.networkName, + self.networkPlacementID, + jsonRepresentationDeserialized]; +} + +@end diff --git a/MoPubSDK/MPInterstitialAdController.h b/MoPubSDK/MPInterstitialAdController.h index 9cb9f068f..8833c3e36 100644 --- a/MoPubSDK/MPInterstitialAdController.h +++ b/MoPubSDK/MPInterstitialAdController.h @@ -8,15 +8,14 @@ #import #import - -@protocol MPInterstitialAdControllerDelegate; +#import "MPInterstitialAdControllerDelegate.h" /** * The `MPInterstitialAdController` class provides a full-screen advertisement that can be * displayed during natural transition points in your application. */ -@interface MPInterstitialAdController : NSObject +@interface MPInterstitialAdController : NSObject /** @name Obtaining an Interstitial Ad */ @@ -151,113 +150,3 @@ + (NSMutableArray *)sharedInterstitialAdControllers DEPRECATED_MSG_ATTRIBUTE("This functionality will be removed in a future SDK release."); @end - -#pragma mark - - -/** - * The delegate of an `MPInterstitialAdController` object must adopt the - * `MPInterstitialAdControllerDelegate` protocol. - * - * The optional methods of this protocol allow the delegate to be notified of interstitial state - * changes, such as when an ad has loaded, when an ad has been presented or dismissed from the - * screen, and when an ad has expired. - */ - -@protocol MPInterstitialAdControllerDelegate - -@optional - -/** @name Detecting When an Interstitial Ad is Loaded */ - -/** - * Sent when an interstitial ad object successfully loads an ad. - * - * @param interstitial The interstitial ad object sending the message. - */ -- (void)interstitialDidLoadAd:(MPInterstitialAdController *)interstitial; - -/** - * Sent when an interstitial ad object fails to load an ad. - * - * @param interstitial The interstitial ad object sending the message. - */ -- (void)interstitialDidFailToLoadAd:(MPInterstitialAdController *)interstitial; - -/** - * Sent when an interstitial ad object fails to load an ad. - * - * @param interstitial The interstitial ad object sending the message. - * @param error The error that occurred during the load. - */ -- (void)interstitialDidFailToLoadAd:(MPInterstitialAdController *)interstitial - withError:(NSError *)error; - -/** @name Detecting When an Interstitial Ad is Presented */ - -/** - * Sent immediately before an interstitial ad object is presented on the screen. - * - * Your implementation of this method should pause any application activity that requires user - * interaction. - * - * @param interstitial The interstitial ad object sending the message. - */ -- (void)interstitialWillAppear:(MPInterstitialAdController *)interstitial; - -/** - * Sent after an interstitial ad object has been presented on the screen. - * - * @param interstitial The interstitial ad object sending the message. - */ -- (void)interstitialDidAppear:(MPInterstitialAdController *)interstitial; - -/** @name Detecting When an Interstitial Ad is Dismissed */ - -/** - * Sent immediately before an interstitial ad object will be dismissed from the screen. - * - * @param interstitial The interstitial ad object sending the message. - */ -- (void)interstitialWillDisappear:(MPInterstitialAdController *)interstitial; - -/** - * Sent after an interstitial ad object has been dismissed from the screen, returning control - * to your application. - * - * Your implementation of this method should resume any application activity that was paused - * prior to the interstitial being presented on-screen. - * - * @param interstitial The interstitial ad object sending the message. - */ -- (void)interstitialDidDisappear:(MPInterstitialAdController *)interstitial; - -/** @name Detecting When an Interstitial Ad Expires */ - -/** - * Sent when a loaded interstitial ad is no longer eligible to be displayed. - * - * Interstitial ads from certain networks may expire their content at any time, - * even if the content is currently on-screen. This method notifies you when the currently- - * loaded interstitial has expired and is no longer eligible for display. - * - * If the ad was on-screen when it expired, you can expect that the ad will already have been - * dismissed by the time this message is sent. - * - * Your implementation may include a call to `loadAd` to fetch a new ad, if desired. - * - * @param interstitial The interstitial ad object sending the message. - */ -- (void)interstitialDidExpire:(MPInterstitialAdController *)interstitial; - -/** - * Sent when the user taps the interstitial ad and the ad is about to perform its target action. - * - * This action may include displaying a modal or leaving your application. Certain ad networks - * may not expose a "tapped" callback so you should not rely on this callback to perform - * critical tasks. - * - * @param interstitial The interstitial ad object sending the message. - */ -- (void)interstitialDidReceiveTapEvent:(MPInterstitialAdController *)interstitial; - -@end diff --git a/MoPubSDK/MPInterstitialAdController.m b/MoPubSDK/MPInterstitialAdController.m index cf700bcce..94060f57a 100644 --- a/MoPubSDK/MPInterstitialAdController.m +++ b/MoPubSDK/MPInterstitialAdController.m @@ -174,6 +174,12 @@ - (void)managerDidReceiveTapEventFromInterstitial:(MPInterstitialAdManager *)man } } +- (void)interstitialAdManager:(MPInterstitialAdManager *)manager didReceiveImpressionEventWithImpressionData:(MPImpressionData *)impressionData { + if ([self.delegate respondsToSelector:@selector(mopubAd:didTrackImpressionWithImpressionData:)]) { + [self.delegate mopubAd:self didTrackImpressionWithImpressionData:impressionData]; + } +} + + (NSMutableArray *)sharedInterstitialAdControllers { return [NSMutableArray arrayWithArray:[[self class] sharedInterstitials].allValues]; diff --git a/MoPubSDK/MPInterstitialAdControllerDelegate.h b/MoPubSDK/MPInterstitialAdControllerDelegate.h new file mode 100644 index 000000000..e89bbc41e --- /dev/null +++ b/MoPubSDK/MPInterstitialAdControllerDelegate.h @@ -0,0 +1,119 @@ +// +// MPInterstitialAdControllerDelegate.h +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import "MPMoPubAd.h" + +@class MPInterstitialAdController; + +/** + * The delegate of an `MPInterstitialAdController` object must adopt the + * `MPInterstitialAdControllerDelegate` protocol. + * + * The optional methods of this protocol allow the delegate to be notified of interstitial state + * changes, such as when an ad has loaded, when an ad has been presented or dismissed from the + * screen, and when an ad has expired. + */ + +@protocol MPInterstitialAdControllerDelegate + +@optional + +/** @name Detecting When an Interstitial Ad is Loaded */ + +/** + * Sent when an interstitial ad object successfully loads an ad. + * + * @param interstitial The interstitial ad object sending the message. + */ +- (void)interstitialDidLoadAd:(MPInterstitialAdController *)interstitial; + +/** + * Sent when an interstitial ad object fails to load an ad. + * + * @param interstitial The interstitial ad object sending the message. + */ +- (void)interstitialDidFailToLoadAd:(MPInterstitialAdController *)interstitial; + +/** + * Sent when an interstitial ad object fails to load an ad. + * + * @param interstitial The interstitial ad object sending the message. + * @param error The error that occurred during the load. + */ +- (void)interstitialDidFailToLoadAd:(MPInterstitialAdController *)interstitial + withError:(NSError *)error; + +/** @name Detecting When an Interstitial Ad is Presented */ + +/** + * Sent immediately before an interstitial ad object is presented on the screen. + * + * Your implementation of this method should pause any application activity that requires user + * interaction. + * + * @param interstitial The interstitial ad object sending the message. + */ +- (void)interstitialWillAppear:(MPInterstitialAdController *)interstitial; + +/** + * Sent after an interstitial ad object has been presented on the screen. + * + * @param interstitial The interstitial ad object sending the message. + */ +- (void)interstitialDidAppear:(MPInterstitialAdController *)interstitial; + +/** @name Detecting When an Interstitial Ad is Dismissed */ + +/** + * Sent immediately before an interstitial ad object will be dismissed from the screen. + * + * @param interstitial The interstitial ad object sending the message. + */ +- (void)interstitialWillDisappear:(MPInterstitialAdController *)interstitial; + +/** + * Sent after an interstitial ad object has been dismissed from the screen, returning control + * to your application. + * + * Your implementation of this method should resume any application activity that was paused + * prior to the interstitial being presented on-screen. + * + * @param interstitial The interstitial ad object sending the message. + */ +- (void)interstitialDidDisappear:(MPInterstitialAdController *)interstitial; + +/** @name Detecting When an Interstitial Ad Expires */ + +/** + * Sent when a loaded interstitial ad is no longer eligible to be displayed. + * + * Interstitial ads from certain networks may expire their content at any time, + * even if the content is currently on-screen. This method notifies you when the currently- + * loaded interstitial has expired and is no longer eligible for display. + * + * If the ad was on-screen when it expired, you can expect that the ad will already have been + * dismissed by the time this message is sent. + * + * Your implementation may include a call to `loadAd` to fetch a new ad, if desired. + * + * @param interstitial The interstitial ad object sending the message. + */ +- (void)interstitialDidExpire:(MPInterstitialAdController *)interstitial; + +/** + * Sent when the user taps the interstitial ad and the ad is about to perform its target action. + * + * This action may include displaying a modal or leaving your application. Certain ad networks + * may not expose a "tapped" callback so you should not rely on this callback to perform + * critical tasks. + * + * @param interstitial The interstitial ad object sending the message. + */ +- (void)interstitialDidReceiveTapEvent:(MPInterstitialAdController *)interstitial; + +@end diff --git a/MoPubSDK/MPInterstitialCustomEvent.m b/MoPubSDK/MPInterstitialCustomEvent.m index b01692e8f..cd7f07d20 100644 --- a/MoPubSDK/MPInterstitialCustomEvent.m +++ b/MoPubSDK/MPInterstitialCustomEvent.m @@ -10,8 +10,6 @@ @implementation MPInterstitialCustomEvent -@synthesize delegate; - - (void)requestInterstitialWithCustomEventInfo:(NSDictionary *)info { // The default implementation of this method does nothing. Subclasses must override this method diff --git a/MoPubSDK/MPMoPubAd.h b/MoPubSDK/MPMoPubAd.h new file mode 100644 index 000000000..c2fdc328e --- /dev/null +++ b/MoPubSDK/MPMoPubAd.h @@ -0,0 +1,45 @@ +// +// MPMoPubAd.h +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import +#import "MPImpressionData.h" + +NS_ASSUME_NONNULL_BEGIN + +@protocol MPMoPubAdDelegate; + +/** + This protocol defines functionality that is shared between all MoPub ads. + */ +@protocol MPMoPubAd + +@required +/** + All MoPub ads have a delegate to call back when certain events occur. + */ +@property (nonatomic, weak, nullable) id delegate; + +@end + +/** + This protocol defines callback events shared between all MoPub ads. + */ +@protocol MPMoPubAdDelegate + +@optional +/** + Called when an impression is fired on the @c MPMoPubAd instance. Includes information about the impression if applicable. + + @param ad The @c MPMoPubAd instance that fired the impression + @param impressionData Information about the impression, or @c nil if the server didn't return any information. + */ +- (void)mopubAd:(id)ad didTrackImpressionWithImpressionData:(MPImpressionData * _Nullable)impressionData; + +@end + +NS_ASSUME_NONNULL_END diff --git a/MoPubSDK/MPMoPubAdPlacer.h b/MoPubSDK/MPMoPubAdPlacer.h new file mode 100644 index 000000000..cf4dd2e80 --- /dev/null +++ b/MoPubSDK/MPMoPubAdPlacer.h @@ -0,0 +1,48 @@ +// +// MPMoPubAdPlacer.h +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import +#import "MPMoPubAd.h" +#import "MPImpressionData.h" + +NS_ASSUME_NONNULL_BEGIN + +@protocol MPMoPubAdPlacerDelegate; + +/** + This protocol defines functionality that is shared between all MoPub ad placers. + */ +@protocol MPMoPubAdPlacer + +@required +/** + All MoPub ad placers have a delegate to call back when certain events occur. + */ +@property (nonatomic, weak, nullable) id delegate; + +@end + +/** + This protocol defines callback events shared between all MoPub ad placers. + */ +@protocol MPMoPubAdPlacerDelegate + +@optional +/** + Called when an impression is fired on the @c MPMoPubAdPlacer instance. Includes + information about the impression if applicable. + + @param adPlacer The @c MPMoPubAdPlacer instance that fired the impression + @param ad The @c MPMoPubAd instance that fired the impression + @param impressionData Information about the impression, or @c nil if the server didn't return any information. + */ +- (void)mopubAdPlacer:(id)adPlacer didTrackImpressionForAd:(id)ad withImpressionData:(MPImpressionData * _Nullable)impressionData; + +@end + +NS_ASSUME_NONNULL_END diff --git a/MoPubSDK/MoPub.h b/MoPubSDK/MoPub.h index ff1ad1b5f..862000d84 100644 --- a/MoPubSDK/MoPub.h +++ b/MoPubSDK/MoPub.h @@ -14,6 +14,7 @@ #import "MPAdImpressionTimer.h" #import "MPAdTargeting.h" #import "MPAdView.h" +#import "MPAdViewDelegate.h" #import "MPBannerCustomEvent.h" #import "MPBannerCustomEventDelegate.h" #import "MPBaseAdapterConfiguration.h" @@ -25,12 +26,16 @@ #import "MPError.h" #import "MPGlobal.h" #import "MPIdentityProvider.h" +#import "MPImpressionData.h" #import "MPInterstitialAdController.h" +#import "MPInterstitialAdControllerDelegate.h" #import "MPInterstitialCustomEvent.h" #import "MPInterstitialCustomEventDelegate.h" #import "MPLogging.h" #import "MPBLogLevel.h" #import "MPMediationSettingsProtocol.h" +#import "MPMoPubAd.h" +#import "MPMoPubAdPlacer.h" #import "MPMoPubConfiguration.h" #import "MPRealTimeTimer.h" #import "MPRewardedVideo.h" @@ -53,7 +58,9 @@ #import "MPNativeView.h" #import "MPNativeAdUtils.h" #import "MPCollectionViewAdPlacer.h" +#import "MPCollectionViewAdPlacerDelegate.h" #import "MPTableViewAdPlacer.h" +#import "MPTableViewAdPlacerDelegate.h" #import "MPClientAdPositioning.h" #import "MPServerAdPositioning.h" #import "MPNativeAdDelegate.h" @@ -67,6 +74,7 @@ #import "MOPUBNativeVideoAdRenderer.h" #import "MPNativeAdRenderingImageLoader.h" #import "MPStreamAdPlacer.h" +#import "MPStreamAdPlacerDelegate.h" #endif // Import these frameworks for module support. diff --git a/MoPubSDK/NativeAds/Internal/MPMoPubNativeAdAdapter.m b/MoPubSDK/NativeAds/Internal/MPMoPubNativeAdAdapter.m index 1e358b911..ded9c3b53 100644 --- a/MoPubSDK/NativeAds/Internal/MPMoPubNativeAdAdapter.m +++ b/MoPubSDK/NativeAds/Internal/MPMoPubNativeAdAdapter.m @@ -28,6 +28,7 @@ @interface MPMoPubNativeAdAdapter () renderer; +@property (nonatomic, readwrite, strong) MPAdConfiguration *configuration; @property (nonatomic, readonly) NSMutableSet *clickTrackerURLs; @property (nonatomic, readonly) NSMutableSet *impressionTrackerURLs; @property (nonatomic, readonly, strong) id adAdapter; diff --git a/MoPubSDK/NativeAds/Internal/MPNativeAd+Internal.m b/MoPubSDK/NativeAds/Internal/MPNativeAd+Internal.m index 01af32c18..ab4be0a57 100644 --- a/MoPubSDK/NativeAds/Internal/MPNativeAd+Internal.m +++ b/MoPubSDK/NativeAds/Internal/MPNativeAd+Internal.m @@ -17,6 +17,7 @@ @implementation MPNativeAd (Internal) @dynamic clickTrackerURLs; @dynamic creationDate; @dynamic renderer; +@dynamic configuration; @dynamic associatedView; @dynamic adAdapter; diff --git a/MoPubSDK/NativeAds/Internal/MPStreamAdPlacerDelegate.h b/MoPubSDK/NativeAds/Internal/MPStreamAdPlacerDelegate.h new file mode 100644 index 000000000..7a766b5a6 --- /dev/null +++ b/MoPubSDK/NativeAds/Internal/MPStreamAdPlacerDelegate.h @@ -0,0 +1,40 @@ +// +// MPStreamAdPlacerDelegate.h +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import "MPMoPubAdPlacer.h" + +@class MPStreamAdPlacer; + +@protocol MPStreamAdPlacerDelegate + +@optional +- (void)adPlacer:(MPStreamAdPlacer *)adPlacer didLoadAdAtIndexPath:(NSIndexPath *)indexPath; +- (void)adPlacer:(MPStreamAdPlacer *)adPlacer didRemoveAdsAtIndexPaths:(NSArray *)indexPaths; + +/* + * This method is called when a native ad, placed by the stream ad placer, will present a modal view controller. + * + * @param placer The stream ad placer that contains the ad displaying the modal. + */ +- (void)nativeAdWillPresentModalForStreamAdPlacer:(MPStreamAdPlacer *)adPlacer; + +/* + * This method is called when a native ad, placed by the stream ad placer, did dismiss its modal view controller. + * + * @param placer The stream ad placer that contains the ad that dismissed the modal. + */ +- (void)nativeAdDidDismissModalForStreamAdPlacer:(MPStreamAdPlacer *)adPlacer; + +/* + * This method is called when a native ad, placed by the stream ad placer, will cause the app to background due to user interaction with the ad. + * + * @param placer The stream ad placer that contains the ad causing the app to background. + */ +- (void)nativeAdWillLeaveApplicationFromStreamAdPlacer:(MPStreamAdPlacer *)adPlacer; + +@end diff --git a/MoPubSDK/NativeAds/MPCollectionViewAdPlacer.h b/MoPubSDK/NativeAds/MPCollectionViewAdPlacer.h index 6f35b67bd..b234a43db 100644 --- a/MoPubSDK/NativeAds/MPCollectionViewAdPlacer.h +++ b/MoPubSDK/NativeAds/MPCollectionViewAdPlacer.h @@ -10,9 +10,9 @@ #import #import "MPClientAdPositioning.h" #import "MPServerAdPositioning.h" +#import "MPCollectionViewAdPlacerDelegate.h" @class MPNativeAdRequestTargeting; -@protocol MPCollectionViewAdPlacerDelegate; //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -25,7 +25,7 @@ * content cells. */ -@interface MPCollectionViewAdPlacer : NSObject +@interface MPCollectionViewAdPlacer : NSObject @property (nonatomic, weak) id delegate; @@ -359,30 +359,3 @@ - (NSArray *)mp_visibleCells; @end - -@protocol MPCollectionViewAdPlacerDelegate - -@optional - -/* - * This method is called when a native ad, placed by the collection view ad placer, will present a modal view controller. - * - * @param placer The collection view ad placer that contains the ad displaying the modal. - */ --(void)nativeAdWillPresentModalForCollectionViewAdPlacer:(MPCollectionViewAdPlacer *)placer; - -/* - * This method is called when a native ad, placed by the collection view ad placer, did dismiss its modal view controller. - * - * @param placer The collection view ad placer that contains the ad that dismissed the modal. - */ --(void)nativeAdDidDismissModalForCollectionViewAdPlacer:(MPCollectionViewAdPlacer *)placer; - -/* - * This method is called when a native ad, placed by the collection view ad placer, will cause the app to background due to user interaction with the ad. - * - * @param placer The collection view ad placer that contains the ad causing the app to background. - */ --(void)nativeAdWillLeaveApplicationFromCollectionViewAdPlacer:(MPCollectionViewAdPlacer *)placer; - -@end diff --git a/MoPubSDK/NativeAds/MPCollectionViewAdPlacer.m b/MoPubSDK/NativeAds/MPCollectionViewAdPlacer.m index cb32072b5..93cb1de27 100644 --- a/MoPubSDK/NativeAds/MPCollectionViewAdPlacer.m +++ b/MoPubSDK/NativeAds/MPCollectionViewAdPlacer.m @@ -148,6 +148,14 @@ - (void)nativeAdWillLeaveApplicationFromStreamAdPlacer:(MPStreamAdPlacer *)adPla } } +- (void)mopubAdPlacer:(id)adPlacer didTrackImpressionForAd:(id)ad withImpressionData:(MPImpressionData *)impressionData { + if ([self.delegate respondsToSelector:@selector(mopubAdPlacer:didTrackImpressionForAd:withImpressionData:)]) { + [self.delegate mopubAdPlacer:self + didTrackImpressionForAd:ad + withImpressionData:impressionData]; + } +} + #pragma mark - - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView diff --git a/MoPubSDK/NativeAds/MPCollectionViewAdPlacerDelegate.h b/MoPubSDK/NativeAds/MPCollectionViewAdPlacerDelegate.h new file mode 100644 index 000000000..303c08646 --- /dev/null +++ b/MoPubSDK/NativeAds/MPCollectionViewAdPlacerDelegate.h @@ -0,0 +1,38 @@ +// +// MPCollectionViewAdPlacerDelegate.h +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import "MPMoPubAdPlacer.h" + +@class MPCollectionViewAdPlacer; + +@protocol MPCollectionViewAdPlacerDelegate + +@optional + +/* + * This method is called when a native ad, placed by the collection view ad placer, will present a modal view controller. + * + * @param placer The collection view ad placer that contains the ad displaying the modal. + */ +- (void)nativeAdWillPresentModalForCollectionViewAdPlacer:(MPCollectionViewAdPlacer *)placer; + +/* + * This method is called when a native ad, placed by the collection view ad placer, did dismiss its modal view controller. + * + * @param placer The collection view ad placer that contains the ad that dismissed the modal. + */ +- (void)nativeAdDidDismissModalForCollectionViewAdPlacer:(MPCollectionViewAdPlacer *)placer; + +/* + * This method is called when a native ad, placed by the collection view ad placer, will cause the app to background due to user interaction with the ad. + * + * @param placer The collection view ad placer that contains the ad causing the app to background. + */ +- (void)nativeAdWillLeaveApplicationFromCollectionViewAdPlacer:(MPCollectionViewAdPlacer *)placer; + +@end diff --git a/MoPubSDK/NativeAds/MPNativeAd.h b/MoPubSDK/NativeAds/MPNativeAd.h index 1286e5dfb..db106abe6 100644 --- a/MoPubSDK/NativeAds/MPNativeAd.h +++ b/MoPubSDK/NativeAds/MPNativeAd.h @@ -8,9 +8,10 @@ #import #import +#import "MPMoPubAd.h" +#import "MPNativeAdDelegate.h" @protocol MPNativeAdAdapter; -@protocol MPNativeAdDelegate; @protocol MPNativeAdRenderer; @class MPAdConfiguration; @@ -20,7 +21,7 @@ * convenience methods for URL navigation and metrics-gathering. */ -@interface MPNativeAd : NSObject +@interface MPNativeAd : NSObject /** @name Ad Resources */ diff --git a/MoPubSDK/NativeAds/MPNativeAd.m b/MoPubSDK/NativeAds/MPNativeAd.m index 5ef6c8185..4c00d4642 100644 --- a/MoPubSDK/NativeAds/MPNativeAd.m +++ b/MoPubSDK/NativeAds/MPNativeAd.m @@ -20,7 +20,6 @@ #import "MPNativeAdConstants.h" #import "MPTimer.h" #import "MPNativeAdRenderer.h" -#import "MPNativeAdDelegate.h" #import "MPNativeView.h" #import "MPHTTPNetworkSession.h" #import "MPURLRequest.h" @@ -30,6 +29,7 @@ @interface MPNativeAd () @property (nonatomic, readwrite, strong) id renderer; +@property (nonatomic, readwrite, strong) MPAdConfiguration *configuration; @property (nonatomic, strong) NSDate *creationDate; @@ -125,6 +125,10 @@ - (void)trackImpression MPLogDebug(@"Tracking an impression for %@.", self.adIdentifier); self.hasTrackedImpression = YES; [self trackMetricsForURLs:self.impressionTrackerURLs]; + + if ([self.delegate respondsToSelector:@selector(mopubAd:didTrackImpressionWithImpressionData:)]) { + [self.delegate mopubAd:self didTrackImpressionWithImpressionData:self.configuration.impressionData]; + } } - (void)trackClick diff --git a/MoPubSDK/NativeAds/MPNativeAdDelegate.h b/MoPubSDK/NativeAds/MPNativeAdDelegate.h index cbe45e1d2..c220fb3a1 100644 --- a/MoPubSDK/NativeAds/MPNativeAdDelegate.h +++ b/MoPubSDK/NativeAds/MPNativeAdDelegate.h @@ -6,13 +6,15 @@ // http://www.mopub.com/legal/sdk-license-agreement/ // +#import "MPMoPubAd.h" + /** * The delegate of an `MPNativeAd` object must adopt the `MPNativeAdDelegate` protocol. It must * implement `viewControllerForPresentingModalView` to provide a root view controller from which * the ad view should present modal content. */ @class MPNativeAd; -@protocol MPNativeAdDelegate +@protocol MPNativeAdDelegate @optional diff --git a/MoPubSDK/NativeAds/MPNativeAdRequest.m b/MoPubSDK/NativeAds/MPNativeAdRequest.m index 003615266..40e06027e 100644 --- a/MoPubSDK/NativeAds/MPNativeAdRequest.m +++ b/MoPubSDK/NativeAds/MPNativeAdRequest.m @@ -242,6 +242,7 @@ - (void)completeAdRequestWithAdObject:(MPNativeAd *)adObject error:(NSError *)er self.remainingConfigurations = nil; adObject.renderer = self.customEventRenderer; + adObject.configuration = self.adConfiguration; if ([(id)adObject.adAdapter respondsToSelector:@selector(setAdConfiguration:)]) { [(id)adObject.adAdapter performSelector:@selector(setAdConfiguration:) withObject:self.adConfiguration]; diff --git a/MoPubSDK/NativeAds/MPStaticNativeAdRenderer.h b/MoPubSDK/NativeAds/MPStaticNativeAdRenderer.h index 04d3756c0..dedcaa76f 100644 --- a/MoPubSDK/NativeAds/MPStaticNativeAdRenderer.h +++ b/MoPubSDK/NativeAds/MPStaticNativeAdRenderer.h @@ -18,4 +18,7 @@ + (MPNativeAdRendererConfiguration *)rendererConfigurationWithRendererSettings:(id)rendererSettings; ++ (MPNativeAdRendererConfiguration *)rendererConfigurationWithRendererSettings:(id)rendererSettings + additionalSupportedCustomEvents:(NSArray *)additionalSupportedCustomEvents; + @end diff --git a/MoPubSDK/NativeAds/MPStaticNativeAdRenderer.m b/MoPubSDK/NativeAds/MPStaticNativeAdRenderer.m index 9f56a5f5c..7fad5ab37 100644 --- a/MoPubSDK/NativeAds/MPStaticNativeAdRenderer.m +++ b/MoPubSDK/NativeAds/MPStaticNativeAdRenderer.m @@ -46,11 +46,17 @@ @interface MPStaticNativeAdRenderer () @implementation MPStaticNativeAdRenderer + (MPNativeAdRendererConfiguration *)rendererConfigurationWithRendererSettings:(id)rendererSettings +{ + return [MPStaticNativeAdRenderer rendererConfigurationWithRendererSettings:rendererSettings additionalSupportedCustomEvents:@[]]; +} + ++ (MPNativeAdRendererConfiguration *)rendererConfigurationWithRendererSettings:(id)rendererSettings + additionalSupportedCustomEvents:(NSArray *)additionalSupportedCustomEvents { MPNativeAdRendererConfiguration *config = [[MPNativeAdRendererConfiguration alloc] init]; config.rendererClass = [self class]; config.rendererSettings = rendererSettings; - config.supportedCustomEvents = @[@"MPMoPubNativeCustomEvent", @"FacebookNativeCustomEvent", @"MillennialNativeCustomEvent"]; + config.supportedCustomEvents = [@[@"MPMoPubNativeCustomEvent"] arrayByAddingObjectsFromArray:additionalSupportedCustomEvents]; return config; } diff --git a/MoPubSDK/NativeAds/MPStreamAdPlacer.h b/MoPubSDK/NativeAds/MPStreamAdPlacer.h index 84a103c57..e89ed2562 100644 --- a/MoPubSDK/NativeAds/MPStreamAdPlacer.h +++ b/MoPubSDK/NativeAds/MPStreamAdPlacer.h @@ -8,9 +8,9 @@ #import #import +#import "MPStreamAdPlacerDelegate.h" #import "MPClientAdPositioning.h" -@protocol MPStreamAdPlacerDelegate; @protocol MPNativeAdRendering; @class MPNativeAdRequestTargeting; @class MPNativeAd; @@ -47,7 +47,7 @@ * Use -renderAdAtIndexPath:inView: to populate a view with the contents of an ad. */ -@interface MPStreamAdPlacer : NSObject +@interface MPStreamAdPlacer : NSObject /** * An array of `NSIndexPath` objects representing the positions of items that are currently visible @@ -240,34 +240,3 @@ - (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection; @end - -//////////////////////////////////////////////////////////////////////////////////////////////////// - -@protocol MPStreamAdPlacerDelegate - -@optional -- (void)adPlacer:(MPStreamAdPlacer *)adPlacer didLoadAdAtIndexPath:(NSIndexPath *)indexPath; -- (void)adPlacer:(MPStreamAdPlacer *)adPlacer didRemoveAdsAtIndexPaths:(NSArray *)indexPaths; - -/* - * This method is called when a native ad, placed by the stream ad placer, will present a modal view controller. - * - * @param placer The stream ad placer that contains the ad displaying the modal. - */ -- (void)nativeAdWillPresentModalForStreamAdPlacer:(MPStreamAdPlacer *)adPlacer; - -/* - * This method is called when a native ad, placed by the stream ad placer, did dismiss its modal view controller. - * - * @param placer The stream ad placer that contains the ad that dismissed the modal. - */ -- (void)nativeAdDidDismissModalForStreamAdPlacer:(MPStreamAdPlacer *)adPlacer; - -/* - * This method is called when a native ad, placed by the stream ad placer, will cause the app to background due to user interaction with the ad. - * - * @param placer The stream ad placer that contains the ad causing the app to background. - */ -- (void)nativeAdWillLeaveApplicationFromStreamAdPlacer:(MPStreamAdPlacer *)adPlacer; - -@end diff --git a/MoPubSDK/NativeAds/MPStreamAdPlacer.m b/MoPubSDK/NativeAds/MPStreamAdPlacer.m index 49f149556..4e1cb1055 100644 --- a/MoPubSDK/NativeAds/MPStreamAdPlacer.m +++ b/MoPubSDK/NativeAds/MPStreamAdPlacer.m @@ -553,6 +553,14 @@ - (void)willLeaveApplicationFromNativeAd:(MPNativeAd *)nativeAd } } +- (void)mopubAd:(id)ad didTrackImpressionWithImpressionData:(MPImpressionData *)impressionData { + if ([self.delegate respondsToSelector:@selector(mopubAdPlacer:didTrackImpressionForAd:withImpressionData:)]) { + [self.delegate mopubAdPlacer:self + didTrackImpressionForAd:ad + withImpressionData:impressionData]; + } +} + #pragma mark - Internal - (CGSize)sizeForAd:(MPNativeAd *)ad withMaximumWidth:(CGFloat)maxWidth andIndexPath:(NSIndexPath *)indexPath diff --git a/MoPubSDK/NativeAds/MPTableViewAdPlacer.h b/MoPubSDK/NativeAds/MPTableViewAdPlacer.h index a9b975bb4..b81b9eca3 100644 --- a/MoPubSDK/NativeAds/MPTableViewAdPlacer.h +++ b/MoPubSDK/NativeAds/MPTableViewAdPlacer.h @@ -10,9 +10,9 @@ #import #import "MPClientAdPositioning.h" #import "MPServerAdPositioning.h" +#import "MPTableViewAdPlacerDelegate.h" @class MPNativeAdRequestTargeting; -@protocol MPTableViewAdPlacerDelegate; //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -25,7 +25,7 @@ * content cells. */ -@interface MPTableViewAdPlacer : NSObject +@interface MPTableViewAdPlacer : NSObject @property (nonatomic, weak) id delegate; @@ -403,30 +403,3 @@ - (NSArray *)mp_visibleCells; @end - -@protocol MPTableViewAdPlacerDelegate - -@optional - -/* - * This method is called when a native ad, placed by the table view ad placer, will present a modal view controller. - * - * @param placer The table view ad placer that contains the ad displaying the modal. - */ --(void)nativeAdWillPresentModalForTableViewAdPlacer:(MPTableViewAdPlacer *)placer; - -/* - * This method is called when a native ad, placed by the table view ad placer, did dismiss its modal view controller. - * - * @param placer The table view ad placer that contains the ad that dismissed the modal. - */ --(void)nativeAdDidDismissModalForTableViewAdPlacer:(MPTableViewAdPlacer *)placer; - -/* - * This method is called when a native ad, placed by the table view ad placer, will cause the app to background due to user interaction with the ad. - * - * @param placer The table view ad placer that contains the ad causing the app to background. - */ --(void)nativeAdWillLeaveApplicationFromTableViewAdPlacer:(MPTableViewAdPlacer *)placer; - -@end diff --git a/MoPubSDK/NativeAds/MPTableViewAdPlacer.m b/MoPubSDK/NativeAds/MPTableViewAdPlacer.m index 9eba91e7d..ab557bf9e 100644 --- a/MoPubSDK/NativeAds/MPTableViewAdPlacer.m +++ b/MoPubSDK/NativeAds/MPTableViewAdPlacer.m @@ -146,6 +146,14 @@ - (void)nativeAdWillLeaveApplicationFromStreamAdPlacer:(MPStreamAdPlacer *)adPla } } +- (void)mopubAdPlacer:(id)adPlacer didTrackImpressionForAd:(id)ad withImpressionData:(MPImpressionData *)impressionData { + if ([self.delegate respondsToSelector:@selector(mopubAdPlacer:didTrackImpressionForAd:withImpressionData:)]) { + [self.delegate mopubAdPlacer:self + didTrackImpressionForAd:ad + withImpressionData:impressionData]; + } +} + #pragma mark - // Default is 1 if not implemented diff --git a/MoPubSDK/NativeAds/MPTableViewAdPlacerDelegate.h b/MoPubSDK/NativeAds/MPTableViewAdPlacerDelegate.h new file mode 100644 index 000000000..57fca3c48 --- /dev/null +++ b/MoPubSDK/NativeAds/MPTableViewAdPlacerDelegate.h @@ -0,0 +1,38 @@ +// +// MPTableViewAdPlacerDelegate.h +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import "MPMoPubAdPlacer.h" + +@class MPTableViewAdPlacer; + +@protocol MPTableViewAdPlacerDelegate + +@optional + +/* + * This method is called when a native ad, placed by the table view ad placer, will present a modal view controller. + * + * @param placer The table view ad placer that contains the ad displaying the modal. + */ +- (void)nativeAdWillPresentModalForTableViewAdPlacer:(MPTableViewAdPlacer *)placer; + +/* + * This method is called when a native ad, placed by the table view ad placer, did dismiss its modal view controller. + * + * @param placer The table view ad placer that contains the ad that dismissed the modal. + */ +- (void)nativeAdDidDismissModalForTableViewAdPlacer:(MPTableViewAdPlacer *)placer; + +/* + * This method is called when a native ad, placed by the table view ad placer, will cause the app to background due to user interaction with the ad. + * + * @param placer The table view ad placer that contains the ad causing the app to background. + */ +- (void)nativeAdWillLeaveApplicationFromTableViewAdPlacer:(MPTableViewAdPlacer *)placer; + +@end diff --git a/MoPubSDK/NativeVideo/Internal/MOPUBNativeVideoAdAdapter.m b/MoPubSDK/NativeVideo/Internal/MOPUBNativeVideoAdAdapter.m index f6e8fe634..deb82792f 100644 --- a/MoPubSDK/NativeVideo/Internal/MOPUBNativeVideoAdAdapter.m +++ b/MoPubSDK/NativeVideo/Internal/MOPUBNativeVideoAdAdapter.m @@ -25,6 +25,7 @@ @interface MOPUBNativeVideoAdAdapter() FlurryAdapterConfiguration GoogleAdMobAdapterConfiguration IronSourceAdapterConfiguration - MillennialAdapterConfiguration TapjoyAdapterConfiguration UnityAdsAdapterConfiguration + VerizonAdapterConfiguration VungleAdapterConfiguration diff --git a/MoPubSDK/Resources/MPCountdownTimer.html b/MoPubSDK/Resources/MPCountdownTimer.html deleted file mode 100644 index 766d27449..000000000 --- a/MoPubSDK/Resources/MPCountdownTimer.html +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - - - -

-
- -
-
- - 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.m b/MoPubSDK/RewardedVideo/Internal/MPMoPubRewardedPlayableCustomEvent.m index 4126c65a7..8829c9b0f 100644 --- a/MoPubSDK/RewardedVideo/Internal/MPMoPubRewardedPlayableCustomEvent.m +++ b/MoPubSDK/RewardedVideo/Internal/MPMoPubRewardedPlayableCustomEvent.m @@ -118,26 +118,33 @@ - (void)presentRewardedVideoFromViewController:(UIViewController *)viewControlle } // Add the countdown timer to the interstitial and start the timer. - self.timerView = [[MPCountdownTimerView alloc] initWithFrame:viewController.view.bounds duration:self.countdownDuration]; + self.timerView = [[MPCountdownTimerView alloc] initWithDuration:self.countdownDuration timerCompletion:^(BOOL hasElapsed) { + __typeof__(self) strongSelf = weakSelf; + if (strongSelf != nil) { + [strongSelf rewardUserWithConfiguration:strongSelf.configuration timerHasElapsed:hasElapsed]; + [strongSelf showCloseButton]; + } + }]; [self.interstitial.view addSubview:self.timerView]; if (@available(iOS 9.0, *)) { - NSArray * constraints = @[[self.timerView.leftAnchor constraintEqualToAnchor:self.interstitial.view.leftAnchor], - [self.timerView.rightAnchor constraintEqualToAnchor:self.interstitial.view.rightAnchor], - [self.timerView.topAnchor constraintEqualToAnchor:self.interstitial.view.topAnchor], - [self.timerView.bottomAnchor constraintEqualToAnchor:self.interstitial.view.bottomAnchor] - ]; + NSArray * constraints; + if (@available(iOS 11.0, *)) { // consider safe area + constraints = @[[self.timerView.topAnchor constraintEqualToAnchor:self.interstitial.view.safeAreaLayoutGuide.topAnchor], + [self.timerView.rightAnchor constraintEqualToAnchor:self.interstitial.view.safeAreaLayoutGuide.rightAnchor]]; + } else { + constraints = @[[self.timerView.topAnchor constraintEqualToAnchor:self.interstitial.view.topAnchor], + [self.timerView.rightAnchor constraintEqualToAnchor:self.interstitial.view.rightAnchor]]; + } [NSLayoutConstraint activateConstraints:constraints]; self.timerView.translatesAutoresizingMaskIntoConstraints = NO; + } else { // use auto resizing since auto layout is not available + self.timerView.frame = CGRectMake(self.interstitial.view.bounds.size.width - self.timerView.intrinsicContentSize.width, 0, + self.timerView.intrinsicContentSize.width , self.timerView.intrinsicContentSize.height); + self.timerView.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin; } - [self.timerView startWithTimerCompletion:^(BOOL hasElapsed) { - __typeof__(self) strongSelf = weakSelf; - if (strongSelf != nil) { - [strongSelf rewardUserWithConfiguration:strongSelf.configuration timerHasElapsed:hasElapsed]; - [strongSelf showCloseButton]; - } - }]; + [self.timerView start]; [self.interstitial presentInterstitialFromViewController:viewController complete:^(NSError * error) { if (error != nil) { diff --git a/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdManager.h b/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdManager.h index 4237ec7af..ffb9c71a8 100644 --- a/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdManager.h +++ b/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdManager.h @@ -8,6 +8,7 @@ #import #import "MPAdTargeting.h" +#import "MPImpressionData.h" @class MPRewardedVideoReward; @protocol MPRewardedVideoAdManagerDelegate; @@ -88,6 +89,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 06b8fa7d9..6c24891b7 100644 --- a/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdManager.m +++ b/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdManager.m @@ -360,6 +360,10 @@ - (void)rewardedVideoDidReceiveTapEventForAdapter:(MPRewardedVideoAdapter *)adap [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); diff --git a/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdapter.h b/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdapter.h index fca18776b..bef501823 100644 --- a/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdapter.h +++ b/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdapter.h @@ -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 6fca4f5dc..94cfca45f 100644 --- a/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdapter.m +++ b/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdapter.m @@ -167,6 +167,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/MPRewardedVideo.h b/MoPubSDK/RewardedVideo/MPRewardedVideo.h index 5d085861c..4f484e4a6 100644 --- a/MoPubSDK/RewardedVideo/MPRewardedVideo.h +++ b/MoPubSDK/RewardedVideo/MPRewardedVideo.h @@ -8,6 +8,7 @@ #import #import +#import "MPImpressionData.h" @class MPRewardedVideoReward; @class CLLocation; @@ -255,4 +256,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 47ae9157a..51ed7f9b8 100644 --- a/MoPubSDK/RewardedVideo/MPRewardedVideo.m +++ b/MoPubSDK/RewardedVideo/MPRewardedVideo.m @@ -281,6 +281,14 @@ - (void)rewardedVideoDidReceiveTapEventForAdManager:(MPRewardedVideoAdManager *) } } +- (void)rewardedVideoAdManager:(MPRewardedVideoAdManager *)manager didReceiveImpressionEventWithImpressionData:(MPImpressionData *)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]; diff --git a/MoPubSDK/RewardedVideo/MPRewardedVideoReward.m b/MoPubSDK/RewardedVideo/MPRewardedVideoReward.m index 6c2027cc7..fbb600f38 100644 --- a/MoPubSDK/RewardedVideo/MPRewardedVideoReward.m +++ b/MoPubSDK/RewardedVideo/MPRewardedVideoReward.m @@ -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/MoPubSDKFramework/Info.plist b/MoPubSDKFramework/Info.plist index 2b0f8dcf7..72bcf273c 100644 --- a/MoPubSDKFramework/Info.plist +++ b/MoPubSDKFramework/Info.plist @@ -15,9 +15,9 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 5.6.0 + 5.7.0 CFBundleVersion - 5.6.0 + 5.7.0 NSPrincipalClass diff --git a/MoPubSDKTests/Info.plist b/MoPubSDKTests/Info.plist index 7c7e8e14b..2dfb84971 100644 --- a/MoPubSDKTests/Info.plist +++ b/MoPubSDKTests/Info.plist @@ -15,8 +15,8 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 5.6.0 + 5.7.0 CFBundleVersion - 5.6.0 + 5.7.0 diff --git a/MoPubSDKTests/MPBannerAdManagerDelegateHandler.h b/MoPubSDKTests/MPBannerAdManagerDelegateHandler.h index 085470f95..6603a82c6 100644 --- a/MoPubSDKTests/MPBannerAdManagerDelegateHandler.h +++ b/MoPubSDKTests/MPBannerAdManagerDelegateHandler.h @@ -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 768c776e1..209c82f0c 100644 --- a/MoPubSDKTests/MPBannerAdManagerDelegateHandler.m +++ b/MoPubSDKTests/MPBannerAdManagerDelegateHandler.m @@ -20,8 +20,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 +36,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 f58c49438..b11ad3412 100644 --- a/MoPubSDKTests/MPBannerAdManagerTests.m +++ b/MoPubSDKTests/MPBannerAdManagerTests.m @@ -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]; }; @@ -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] init]; + [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] init]; + [manager loadAdWithTargeting:targeting]; + + [self waitForExpectationsWithTimeout:kDefaultTimeout handler:^(NSError * _Nullable error) { + if (error != nil) { + XCTFail(@"Timed out"); + } + }]; +} + @end diff --git a/MoPubSDKTests/MPBannerCustomEventAdapter+Testing.h b/MoPubSDKTests/MPBannerCustomEventAdapter+Testing.h index 8c51a801e..9ad36f72c 100644 --- a/MoPubSDKTests/MPBannerCustomEventAdapter+Testing.h +++ b/MoPubSDKTests/MPBannerCustomEventAdapter+Testing.h @@ -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/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 f1eadd177..cd84dc6a4 100644 --- a/MoPubSDKTests/MPCountdownTimerViewTests.m +++ b/MoPubSDKTests/MPCountdownTimerViewTests.m @@ -7,10 +7,14 @@ // #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/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/MPInterstitialAdManagerDelegateHandler.h b/MoPubSDKTests/MPInterstitialAdManagerDelegateHandler.h index 130ceeb72..0fdb0715f 100644 --- a/MoPubSDKTests/MPInterstitialAdManagerDelegateHandler.h +++ b/MoPubSDKTests/MPInterstitialAdManagerDelegateHandler.h @@ -9,8 +9,10 @@ #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 @@ -27,5 +29,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 2623b66b8..7f4b6b787 100644 --- a/MoPubSDKTests/MPInterstitialAdManagerDelegateHandler.m +++ b/MoPubSDKTests/MPInterstitialAdManagerDelegateHandler.m @@ -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 9e08d9cd7..152eca1b7 100644 --- a/MoPubSDKTests/MPInterstitialAdManagerTests.m +++ b/MoPubSDKTests/MPInterstitialAdManagerTests.m @@ -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"]; @@ -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] init]; + [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] init]; + [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 fc142737f..f10334a52 100644 --- a/MoPubSDKTests/MPInterstitialAdapterDelegateHandler.h +++ b/MoPubSDKTests/MPInterstitialAdapterDelegateHandler.h @@ -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 f32147cc9..46082f08b 100644 --- a/MoPubSDKTests/MPInterstitialAdapterDelegateHandler.m +++ b/MoPubSDKTests/MPInterstitialAdapterDelegateHandler.m @@ -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/MPMoPubRewardedPlayableCustomEvent+Testing.m b/MoPubSDKTests/MPMoPubRewardedPlayableCustomEvent+Testing.m index fd8ba6d12..86939cf03 100644 --- a/MoPubSDKTests/MPMoPubRewardedPlayableCustomEvent+Testing.m +++ b/MoPubSDKTests/MPMoPubRewardedPlayableCustomEvent+Testing.m @@ -6,6 +6,7 @@ // 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/MPNativeAd+Testing.h b/MoPubSDKTests/MPNativeAd+Testing.h new file mode 100644 index 000000000..b92583e49 --- /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.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..7c72b8fa6 --- /dev/null +++ b/MoPubSDKTests/MPNativeAd+Testing.m @@ -0,0 +1,13 @@ +// +// 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" + +@implementation MPNativeAd (Testing) + +@end 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/MPNativeAdRequestTests.m b/MoPubSDKTests/MPNativeAdRequestTests.m index 787d2de66..402bcecbd 100644 --- a/MoPubSDKTests/MPNativeAdRequestTests.m +++ b/MoPubSDKTests/MPNativeAdRequestTests.m @@ -20,6 +20,9 @@ #import "MPNativeAdRendererConfiguration.h" #import "MPStaticNativeAdRendererSettings.h" #import "NSURLComponents+Testing.h" +#import "MPNativeAdDelegateHandler.h" +#import "MPNativeAd+Testing.h" +#import "MPAdServerKeys.h" static const NSTimeInterval kTestTimeout = 2; // seconds @@ -379,4 +382,88 @@ - (void)testLocalExtrasInCustomEvent { XCTAssertTrue(customEvent.isLocalExtrasAvailableAtRequest); } +#pragma mark - Impression Level Revenue Data + +- (void)testImpressionDelegateFiresWithoutILRD { + XCTestExpectation * expectation = [self expectationWithDescription:@"Wait for impression"]; + NSString * testAdUnitId = @"FAKE_AD_UNIT_ID"; + + // Make delegate handler + MPNativeAdDelegateHandler * handler = [[MPNativeAdDelegateHandler alloc] init]; + handler.didTrackImpression = ^(MPNativeAd * ad, MPImpressionData * impressionData) { + [expectation fulfill]; + + XCTAssertNil(impressionData); + }; + + // 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"); + } + + response.delegate = handler; + [response trackImpression]; + }]; + + [self waitForExpectationsWithTimeout:kTestTimeout handler:^(NSError * _Nullable error) { + if (error != nil) { + XCTFail(@"Timed out"); + } + }]; +} + +- (void)testImpressionDelegateFiresWithILRD { + XCTestExpectation * expectation = [self expectationWithDescription:@"Wait for impression"]; + NSString * testAdUnitId = @"FAKE_AD_UNIT_ID"; + + // Make delegate handler + MPNativeAdDelegateHandler * handler = [[MPNativeAdDelegateHandler alloc] init]; + handler.didTrackImpression = ^(MPNativeAd * ad, MPImpressionData * impressionData) { + [expectation fulfill]; + + XCTAssertNotNil(impressionData); + XCTAssert([impressionData.adUnitID isEqualToString:testAdUnitId]); + }; + + // 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"); + } + + response.delegate = handler; + [response trackImpression]; + }]; + + [self waitForExpectationsWithTimeout:kTestTimeout handler:^(NSError * _Nullable error) { + if (error != nil) { + XCTFail(@"Timed out"); + } + }]; +} + @end diff --git a/MoPubSDKTests/MPRewardedVideoAdManagerTests.m b/MoPubSDKTests/MPRewardedVideoAdManagerTests.m index 4258b066e..168231364 100644 --- a/MoPubSDKTests/MPRewardedVideoAdManagerTests.m +++ b/MoPubSDKTests/MPRewardedVideoAdManagerTests.m @@ -548,4 +548,81 @@ - (void)testLocalExtrasInCustomEvent { XCTAssertTrue(customEvent.isLocalExtrasAvailableAtRequest); } +#pragma mark - Impression Level Revenue Data + +- (void)testImpressionDelegateFiresWithoutILRD { + XCTestExpectation * expectation = [self expectationWithDescription:@"Wait for impression delegate"]; + + MPRewardedVideoDelegateHandler * handler = [MPRewardedVideoDelegateHandler new]; + handler.didReceiveImpression = ^(MPImpressionData * impressionData) { + [expectation fulfill]; + + XCTAssertNil(impressionData); + }; + + // Generate the ad configurations + MPAdConfiguration * rewardedVideoThatShouldLoad = [MPAdConfigurationFactory defaultRewardedVideoConfigurationWithCustomEventClassName:@"MPMockRewardedVideoCustomEvent"]; + NSArray * configurations = @[rewardedVideoThatShouldLoad]; + + MPRewardedVideoAdManager * 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]; + }; + + MPAdTargeting * targeting = [[MPAdTargeting alloc] init]; + [manager loadRewardedVideoAdWithCustomerId:@"CUSTOMER_ID" targeting:targeting]; + + [self waitForExpectationsWithTimeout:kTestTimeout handler:^(NSError * _Nullable error) { + if (error != nil) { + XCTFail(@"Timed out"); + } + }]; +} + +- (void)testImpressionDelegateFiresWithILRD { + XCTestExpectation * expectation = [self expectationWithDescription:@"Wait for impression delegate"]; + NSString * testAdUnitID = @"TEST_ADUNIT_ID"; + + MPRewardedVideoDelegateHandler * handler = [MPRewardedVideoDelegateHandler new]; + handler.didReceiveImpression = ^(MPImpressionData * impressionData) { + [expectation fulfill]; + + XCTAssertNotNil(impressionData); + XCTAssert([impressionData.adUnitID isEqualToString:testAdUnitID]); + }; + + // Generate the ad configurations + MPAdConfiguration * rewardedVideoThatShouldLoad = [MPAdConfigurationFactory defaultRewardedVideoConfigurationWithCustomEventClassName:@"MPMockRewardedVideoCustomEvent"]; + rewardedVideoThatShouldLoad.impressionData = [[MPImpressionData alloc] initWithDictionary:@{ + kImpressionDataAdUnitIDKey: testAdUnitID + }]; + NSArray * configurations = @[rewardedVideoThatShouldLoad]; + + MPRewardedVideoAdManager * 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]; + }; + + MPAdTargeting * targeting = [[MPAdTargeting alloc] init]; + [manager loadRewardedVideoAdWithCustomerId:@"CUSTOMER_ID" targeting:targeting]; + + [self waitForExpectationsWithTimeout:kTestTimeout handler:^(NSError * _Nullable error) { + if (error != nil) { + XCTFail(@"Timed out"); + } + }]; +} + @end diff --git a/MoPubSDKTests/MPRewardedVideoAdapterDelegateHandler.h b/MoPubSDKTests/MPRewardedVideoAdapterDelegateHandler.h index b5532384e..58fbaa62e 100644 --- a/MoPubSDKTests/MPRewardedVideoAdapterDelegateHandler.h +++ b/MoPubSDKTests/MPRewardedVideoAdapterDelegateHandler.h @@ -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 856170aa2..598120f65 100644 --- a/MoPubSDKTests/MPRewardedVideoAdapterDelegateHandler.m +++ b/MoPubSDKTests/MPRewardedVideoAdapterDelegateHandler.m @@ -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/MPRewardedVideoDelegateHandler.h b/MoPubSDKTests/MPRewardedVideoDelegateHandler.h index b72224769..3b56c9174 100644 --- a/MoPubSDKTests/MPRewardedVideoDelegateHandler.h +++ b/MoPubSDKTests/MPRewardedVideoDelegateHandler.h @@ -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 6e140e8a4..2543e1439 100644 --- a/MoPubSDKTests/MPRewardedVideoDelegateHandler.m +++ b/MoPubSDKTests/MPRewardedVideoDelegateHandler.m @@ -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/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..fd6cd8278 --- /dev/null +++ b/MoPubSDKTests/MPTimerTests.m @@ -0,0 +1,270 @@ +// +// 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. +static const NSTimeInterval kWaitTimeTolerance = 2; + +/** + * 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); + XCTAssertFalse(timer.isScheduled); + XCTAssertTrue(timer.isValid); + [timer invalidate]; + XCTAssertFalse(timer.isCountdownActive); + XCTAssertFalse(timer.isScheduled); + 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); + XCTAssertFalse(timer.isScheduled); + XCTAssertTrue(timer.isValid); + [timer scheduleNow]; + XCTAssertTrue(timer.isCountdownActive); + XCTAssertTrue(timer.isScheduled); + 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.isScheduled); + XCTAssertTrue(timer.isValid); + [timer invalidate]; + XCTAssertFalse(timer.isCountdownActive); + XCTAssertFalse(timer.isScheduled); + 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); + XCTAssertFalse(timer.isScheduled); + XCTAssertTrue(timer.isValid); + [timer scheduleNow]; + XCTAssertTrue(timer.isCountdownActive); + XCTAssertTrue(timer.isScheduled); + 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.isScheduled); + XCTAssertTrue(timer.isValid); + [timer pause]; + XCTAssertFalse(timer.isCountdownActive); + XCTAssertTrue(timer.isScheduled); + XCTAssertTrue(timer.isValid); + [timer invalidate]; + XCTAssertFalse(timer.isCountdownActive); + XCTAssertFalse(timer.isScheduled); + 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); + XCTAssertFalse(timer.isScheduled); + XCTAssertTrue(timer.isValid); + [timer pause]; + XCTAssertFalse(timer.isCountdownActive); + XCTAssertFalse(timer.isScheduled); + XCTAssertTrue(timer.isValid); + [timer scheduleNow]; + XCTAssertTrue(timer.isCountdownActive); + XCTAssertTrue(timer.isScheduled); + 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.isScheduled); + XCTAssertTrue(timer.isValid); + [timer pause]; + XCTAssertFalse(timer.isCountdownActive); + XCTAssertTrue(timer.isScheduled); + XCTAssertTrue(timer.isValid); + [timer resume]; + XCTAssertTrue(timer.isCountdownActive); + XCTAssertTrue(timer.isScheduled); + XCTAssertTrue(timer.isValid); + [timer invalidate]; + XCTAssertFalse(timer.isCountdownActive); + XCTAssertFalse(timer.isScheduled); + XCTAssertFalse(timer.isValid); + [timer pause]; + XCTAssertFalse(timer.isCountdownActive); + XCTAssertFalse(timer.isScheduled); + XCTAssertFalse(timer.isValid); + [timer resume]; + XCTAssertFalse(timer.isCountdownActive); + XCTAssertFalse(timer.isScheduled); + 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); + XCTAssertFalse(timer.isScheduled); + XCTAssertTrue(timer.isValid); + [timer scheduleNow]; + XCTAssertTrue(timer.isCountdownActive); + XCTAssertTrue(timer.isScheduled); + 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.isScheduled); + XCTAssertTrue(timer.isValid); + [timer invalidate]; + XCTAssertFalse(timer.isCountdownActive); + XCTAssertFalse(timer.isScheduled); + 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); + XCTAssertFalse(timer.isScheduled); + XCTAssertTrue(timer.isValid); + [timer scheduleNow]; + XCTAssertTrue(timer.isCountdownActive); + XCTAssertTrue(timer.isScheduled); + XCTAssertTrue(timer.isValid); + [timer scheduleNow]; + XCTAssertTrue(timer.isCountdownActive); + XCTAssertTrue(timer.isScheduled); + 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.isScheduled); + XCTAssertTrue(timer.isValid); + [timer invalidate]; + XCTAssertFalse(timer.isCountdownActive); + XCTAssertFalse(timer.isScheduled); + XCTAssertFalse(timer.isValid); + [timer scheduleNow]; + XCTAssertFalse(timer.isCountdownActive); + XCTAssertFalse(timer.isScheduled); + XCTAssertFalse(timer.isValid); + }); + [self waitForExpectationsWithTimeout:kTimerRepeatIntervalInSeconds * kWaitTimeTolerance handler:^(NSError * _Nullable error) { + XCTAssertEqual([(NSNumber *)self.testNameVsFiringCount[timer.associatedTitle] intValue], 1); + XCTAssertNil(error); + }]; +} + +@end diff --git a/MoPubSDKTests/MRController+Testing.h b/MoPubSDKTests/MRController+Testing.h index 53f452a74..bb8c81761 100644 --- a/MoPubSDKTests/MRController+Testing.h +++ b/MoPubSDKTests/MRController+Testing.h @@ -6,9 +6,21 @@ // 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/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/README.md b/README.md index afbddd2d0..f90700929 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ 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). @@ -23,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.6.0 +The current version of the SDK is 5.7.0 ### Installation with CocoaPods @@ -56,7 +56,7 @@ $ pod install MoPub provides a prepackaged archive of the dynamic framework: -- **[MoPub SDK Framework.zip](https://github.com/mopub/mopub-ios-sdk/releases/download/5.6.0/mopub-framework-5.6.0.zip)** +- **[MoPub SDK Framework.zip](https://github.com/mopub/mopub-ios-sdk/releases/download/5.7.0/mopub-framework-5.7.0.zip)** Includes everything you need to serve HTML, MRAID, and Native MoPub advertisements. Third party ad networks are not included. @@ -66,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](https://github.com/mopub/mopub-ios-sdk/releases/download/5.6.0/mopub-base-5.6.0.zip)** +- **[MoPub Base SDK.zip](https://github.com/mopub/mopub-ios-sdk/releases/download/5.7.0/mopub-base-5.7.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](https://github.com/mopub/mopub-ios-sdk/releases/download/5.6.0/mopub-nonnative-5.6.0.zip)** +- **[MoPub Base SDK Excluding Native.zip](https://github.com/mopub/mopub-ios-sdk/releases/download/5.7.0/mopub-nonnative-5.7.0.zip)** Includes everything you need to serve HTML and MRAID advertisements. Third party ad networks and Native MoPub advertisements are not included. @@ -83,13 +83,17 @@ 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** - - Added `+` button to the Canary sample app allowing manual entry of custom ad units + - 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** - - 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 + - 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 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. 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 4febe276f..2fb4b607f 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.6.0' + spec.version = '5.7.0' spec.license = { :type => 'New BSD', :file => 'LICENSE' } spec.homepage = 'https://github.com/mopub/mopub-ios-sdk' spec.authors = { 'MoPub' => 'support@mopub.com' } @@ -14,7 +14,7 @@ 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.6.0' } + spec.source = { :git => 'https://github.com/mopub/mopub-ios-sdk.git', :tag => '5.7.0' } spec.requires_arc = true spec.ios.deployment_target = '8.0' spec.frameworks = [ @@ -45,7 +45,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}', 'MoPubSDK/**/MPAdapters.plist'] + core.resources = ['MoPubSDK/**/*.{png,bundle,xib,nib}', 'MoPubSDK/**/MPAdapters.plist'] core.exclude_files = ['MoPubSDK/Viewability/Moat', 'MoPubSDK/Viewability/Avid'] end From 12dc151568785e8cab5468d76d02491562ef6cf6 Mon Sep 17 00:00:00 2001 From: Kelly Dun Date: Thu, 30 May 2019 13:03:10 -0700 Subject: [PATCH 08/12] 5.7.1 --- CHANGELOG.md | 7 + Canary/Canary.xcodeproj/project.pbxproj | 112 +++++---- Canary/Canary/Configuration/Info.plist | 10 +- Canary/Canary/MainTabBarController.swift | 11 +- .../Renderer/NativeAdRendererManager.swift | 8 +- Canary/CanaryUnitTests/Info.plist | 4 +- .../NativeAdRendererManagerTests.swift | 6 +- Gemfile.lock | 230 ------------------ MoPubResources/Info.plist | 4 +- MoPubSDK.xcodeproj/project.pbxproj | 36 ++- .../MPInterstitialViewController.m | 11 - MoPubSDK/Internal/MPConsentManager.m | 22 +- .../MRAID/MPMRAIDInterstitialViewController.m | 5 - .../MRAID/MRExpandModalViewController.m | 5 - .../Utility/Categories/MoPub+Utility.h | 22 ++ .../Utility/Categories/MoPub+Utility.m | 27 ++ MoPubSDK/Internal/Utility/MPGlobal.h | 13 - MoPubSDK/Internal/Utility/MPTimer.h | 2 +- MoPubSDK/Internal/Utility/MPTimer.m | 146 +++++------ MoPubSDK/MPAdView.m | 8 +- MoPubSDK/MPConstants.h | 2 +- MoPubSDK/MPImpressionTrackedNotification.h | 29 +++ MoPubSDK/MPImpressionTrackedNotification.m | 18 ++ MoPubSDK/MPInterstitialAdController.m | 10 +- MoPubSDK/MoPub.h | 1 + .../NativeAds/Internal/MPNativeAd+Internal.h | 1 + .../NativeAds/Internal/MPNativeAd+Internal.m | 1 + MoPubSDK/NativeAds/MPNativeAd.m | 9 +- MoPubSDK/NativeAds/MPNativeAdRequest.m | 1 + .../Internal/MOPUBPlayerViewController.h | 2 +- MoPubSDK/RewardedVideo/MPRewardedVideo.m | 6 + MoPubSDKFramework/Info.plist | 4 +- MoPubSDKTests/Info.plist | 4 +- MoPubSDKTests/MPAdView+Testing.h | 1 + MoPubSDKTests/MPAdViewTests.m | 71 +++++- .../MPInterstitialAdController+Testing.h | 1 + .../MPInterstitialAdControllerTests.m | 72 +++++- MoPubSDKTests/MPNativeAd+Testing.h | 2 +- MoPubSDKTests/MPNativeAdRequestTests.m | 47 +++- MoPubSDKTests/MPRewardedVideo+Testing.h | 2 + MoPubSDKTests/MPRewardedVideoAdManagerTests.m | 59 ++++- MoPubSDKTests/MPTimerTests.m | 87 +++++++ README.md | 19 +- mopub-ios-sdk.podspec | 4 +- 44 files changed, 673 insertions(+), 469 deletions(-) delete mode 100644 Gemfile.lock create mode 100644 MoPubSDK/MPImpressionTrackedNotification.h create mode 100644 MoPubSDK/MPImpressionTrackedNotification.m diff --git a/CHANGELOG.md b/CHANGELOG.md index a47c09a4f..d79d82220 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## 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 diff --git a/Canary/Canary.xcodeproj/project.pbxproj b/Canary/Canary.xcodeproj/project.pbxproj index d43ba718b..ad6a13cb1 100644 --- a/Canary/Canary.xcodeproj/project.pbxproj +++ b/Canary/Canary.xcodeproj/project.pbxproj @@ -14,6 +14,8 @@ 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 */; }; 3FFB21881C2230B8120188EF /* Pods_AppStore_Application.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DB1088039C5F170AE338BACD /* Pods_AppStore_Application.framework */; }; 40FDA37129281ACEDA16C5D1 /* Pods_Internal_Application.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 04062B8FCC938E7869BA852B /* Pods_Internal_Application.framework */; }; @@ -270,6 +272,8 @@ 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 = ""; }; 792ED9133D866365E93CDB2E /* Pods-AppStore Application.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AppStore Application.release.xcconfig"; path = "Pods/Target Support Files/Pods-AppStore Application/Pods-AppStore Application.release.xcconfig"; sourceTree = ""; }; 7B24E5575F339F1A17104381 /* Pods-Internal Application.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Internal Application.release.xcconfig"; path = "Pods/Target Support Files/Pods-Internal Application/Pods-Internal Application.release.xcconfig"; sourceTree = ""; }; @@ -457,6 +461,15 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 2A49E1FE229876310049B61B /* Testing */ = { + isa = PBXGroup; + children = ( + 2A49E1FF2298766C0049B61B /* TestingMenuDataSource.swift */, + 2A49E20122987B530049B61B /* SafeAreaTestViewController.swift */, + ); + path = Testing; + sourceTree = ""; + }; 2A4D35E0211D06ED00BE9377 /* APIEndpoint */ = { isa = PBXGroup; children = ( @@ -593,6 +606,7 @@ BC04944620F91BF400CFD9C2 /* Internal.storyboard */, BC04945C20F9266A00CFD9C2 /* Internal.swift */, BC713BAF21700BC6003655B2 /* ReleaseTesting */, + 2A49E1FE229876310049B61B /* Testing */, BC04944920F91BFC00CFD9C2 /* NetworkAds */, BCE7078B20B373A500DA4BCB /* Privacy */, ); @@ -1143,25 +1157,26 @@ "${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-StandardEdition/VerizonAdsCore.framework", - "${PODS_ROOT}/Verizon-Ads-StandardEdition/VerizonAdsInlinePlacement.framework", - "${PODS_ROOT}/Verizon-Ads-StandardEdition/VerizonAdsInlineWebAdapter.framework", - "${PODS_ROOT}/Verizon-Ads-StandardEdition/VerizonAdsInterstitialPlacement.framework", - "${PODS_ROOT}/Verizon-Ads-StandardEdition/VerizonAdsInterstitialVASTAdapter.framework", - "${PODS_ROOT}/Verizon-Ads-StandardEdition/VerizonAdsInterstitialWebAdapter.framework", - "${PODS_ROOT}/Verizon-Ads-StandardEdition/VerizonAdsNativeOpenRTBNativeAdapter.framework", - "${PODS_ROOT}/Verizon-Ads-StandardEdition/VerizonAdsNativePlacement.framework", - "${PODS_ROOT}/Verizon-Ads-StandardEdition/VerizonAdsOMSDK.framework", - "${PODS_ROOT}/Verizon-Ads-StandardEdition/VerizonAdsOpenRTBNativeController.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-StandardEdition/VerizonAdsSupport.framework", - "${PODS_ROOT}/Verizon-Ads-StandardEdition/VerizonAdsVASTController.framework", - "${PODS_ROOT}/Verizon-Ads-StandardEdition/VerizonAdsVerizonSSPConfigProvider.framework", - "${PODS_ROOT}/Verizon-Ads-StandardEdition/VerizonAdsVerizonSSPReporter.framework", - "${PODS_ROOT}/Verizon-Ads-StandardEdition/VerizonAdsVerizonSSPWaterfallProvider.framework", - "${PODS_ROOT}/Verizon-Ads-StandardEdition/VerizonAdsVideoPlayer.framework", - "${PODS_ROOT}/Verizon-Ads-StandardEdition/VerizonAdsWebController.framework", - "${PODS_ROOT}/Verizon-Ads-StandardEdition/VerizonAdsWebView.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", ); @@ -1178,13 +1193,14 @@ "${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}/VerizonAdsNativeOpenRTBNativeAdapter.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}/VerizonAdsOpenRTBNativeController.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", @@ -1256,25 +1272,26 @@ "${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-StandardEdition/VerizonAdsCore.framework", - "${PODS_ROOT}/Verizon-Ads-StandardEdition/VerizonAdsInlinePlacement.framework", - "${PODS_ROOT}/Verizon-Ads-StandardEdition/VerizonAdsInlineWebAdapter.framework", - "${PODS_ROOT}/Verizon-Ads-StandardEdition/VerizonAdsInterstitialPlacement.framework", - "${PODS_ROOT}/Verizon-Ads-StandardEdition/VerizonAdsInterstitialVASTAdapter.framework", - "${PODS_ROOT}/Verizon-Ads-StandardEdition/VerizonAdsInterstitialWebAdapter.framework", - "${PODS_ROOT}/Verizon-Ads-StandardEdition/VerizonAdsNativeOpenRTBNativeAdapter.framework", - "${PODS_ROOT}/Verizon-Ads-StandardEdition/VerizonAdsNativePlacement.framework", - "${PODS_ROOT}/Verizon-Ads-StandardEdition/VerizonAdsOMSDK.framework", - "${PODS_ROOT}/Verizon-Ads-StandardEdition/VerizonAdsOpenRTBNativeController.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-StandardEdition/VerizonAdsSupport.framework", - "${PODS_ROOT}/Verizon-Ads-StandardEdition/VerizonAdsVASTController.framework", - "${PODS_ROOT}/Verizon-Ads-StandardEdition/VerizonAdsVerizonSSPConfigProvider.framework", - "${PODS_ROOT}/Verizon-Ads-StandardEdition/VerizonAdsVerizonSSPReporter.framework", - "${PODS_ROOT}/Verizon-Ads-StandardEdition/VerizonAdsVerizonSSPWaterfallProvider.framework", - "${PODS_ROOT}/Verizon-Ads-StandardEdition/VerizonAdsVideoPlayer.framework", - "${PODS_ROOT}/Verizon-Ads-StandardEdition/VerizonAdsWebController.framework", - "${PODS_ROOT}/Verizon-Ads-StandardEdition/VerizonAdsWebView.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", ); @@ -1291,13 +1308,14 @@ "${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}/VerizonAdsNativeOpenRTBNativeAdapter.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}/VerizonAdsOpenRTBNativeController.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", @@ -1481,6 +1499,7 @@ 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 */, @@ -1514,6 +1533,7 @@ 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 */, @@ -1626,7 +1646,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; - TEST_TARGET_NAME = Canary; + TEST_TARGET_NAME = "AppStore Application"; }; name = Debug; }; @@ -1644,7 +1664,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; - TEST_TARGET_NAME = Canary; + TEST_TARGET_NAME = "AppStore Application"; }; name = Release; }; @@ -1767,7 +1787,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 5.7.0; + CURRENT_PROJECT_VERSION = 5.7.1; DEVELOPMENT_TEAM = 4S7XS533V3; INFOPLIST_FILE = Canary/Configuration/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; @@ -1784,7 +1804,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 5.7.0; + CURRENT_PROJECT_VERSION = 5.7.1; DEVELOPMENT_TEAM = 4S7XS533V3; INFOPLIST_FILE = Canary/Configuration/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; @@ -1801,7 +1821,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon.Internal; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 5.7.0; + CURRENT_PROJECT_VERSION = 5.7.1; DEVELOPMENT_TEAM = 4S7XS533V3; INFOPLIST_FILE = "Canary/Internal/Internal-Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; @@ -1819,7 +1839,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon.Internal; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 5.7.0; + CURRENT_PROJECT_VERSION = 5.7.1; DEVELOPMENT_TEAM = 4S7XS533V3; INFOPLIST_FILE = "Canary/Internal/Internal-Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; diff --git a/Canary/Canary/Configuration/Info.plist b/Canary/Canary/Configuration/Info.plist index e7a70eb3b..581143d75 100644 --- a/Canary/Canary/Configuration/Info.plist +++ b/Canary/Canary/Configuration/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 5.7.0 + 5.7.1 CFBundleURLTypes @@ -28,7 +28,7 @@ CFBundleVersion - 5.7.0 + 5.7.1 LSRequiresIPhoneOS NSAppTransportSecurity @@ -69,6 +69,12 @@ NSCameraUsageDescription The MoPub Canary app uses the camera for the ease-of-use feature of reading MoPub ad QR codes. + NSCalendarsUsageDescription + Calendar access requested on behalf of Verizon Ads Network. + NSPhotoLibraryUsageDescription + Photo access requested on behalf of Verizon Ads Network. + NSBluetoothPeripheralUsageDescription + Bluetooth access requested on behalf of Verizon Ads Network. ITSAppUsesNonExemptEncryption GADApplicationIdentifier diff --git a/Canary/Canary/MainTabBarController.swift b/Canary/Canary/MainTabBarController.swift index 27eeb7d3b..508c655a3 100644 --- a/Canary/Canary/MainTabBarController.swift +++ b/Canary/Canary/MainTabBarController.swift @@ -19,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 @@ -33,12 +33,19 @@ class MainTabBarController: UITabBarController { 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) ]) } diff --git a/Canary/Canary/Renderer/NativeAdRendererManager.swift b/Canary/Canary/Renderer/NativeAdRendererManager.swift index 8bcb734c5..92a0a27ce 100644 --- a/Canary/Canary/Renderer/NativeAdRendererManager.swift +++ b/Canary/Canary/Renderer/NativeAdRendererManager.swift @@ -74,16 +74,16 @@ private extension NativeAdRendererManager { var rendererConfigurations: [MPNativeAdRendererConfiguration] { let networkRendererConfigurations = self.networkRendererConfigurations // cache computed var result var configs = [MPNativeAdRendererConfiguration]() - configs.append({ + 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)) - }()) - configs.append(MOPUBNativeVideoAdRenderer.rendererConfiguration(with: mopubVideoRendererSettings)) - configs.append(contentsOf: networkRendererConfigurations) + }()) return configs } diff --git a/Canary/CanaryUnitTests/Info.plist b/Canary/CanaryUnitTests/Info.plist index 10e4d593e..76824464a 100644 --- a/Canary/CanaryUnitTests/Info.plist +++ b/Canary/CanaryUnitTests/Info.plist @@ -15,8 +15,8 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 5.7.0 + 5.7.1 CFBundleVersion - 5.7.0 + 5.7.1 diff --git a/Canary/CanaryUnitTests/Renderer/NativeAdRendererManagerTests.swift b/Canary/CanaryUnitTests/Renderer/NativeAdRendererManagerTests.swift index fa7b28540..5ff7cccef 100644 --- a/Canary/CanaryUnitTests/Renderer/NativeAdRendererManagerTests.swift +++ b/Canary/CanaryUnitTests/Renderer/NativeAdRendererManagerTests.swift @@ -13,11 +13,11 @@ final class NativeAdRendererManagerTests: XCTestCase { private var userDefaults: UserDefaults! private var nativeAdRendererManager: NativeAdRendererManager! - private let defaultRenderers = ["MPStaticNativeAdRenderer", - "MOPUBNativeVideoAdRenderer", + private let defaultRenderers = ["MOPUBNativeVideoAdRenderer", "MPGoogleAdMobNativeRenderer", "FacebookNativeAdRenderer", - "FlurryNativeVideoAdRenderer"] + "FlurryNativeVideoAdRenderer", + "MPStaticNativeAdRenderer"] override func setUp() { userDefaults = UserDefaults(suiteName: #file) diff --git a/Gemfile.lock b/Gemfile.lock deleted file mode 100644 index 4662fedf2..000000000 --- a/Gemfile.lock +++ /dev/null @@ -1,230 +0,0 @@ -GEM - remote: https://rubygems.org/ - specs: - CFPropertyList (3.0.0) - activesupport (4.2.11.1) - i18n (~> 0.7) - minitest (~> 5.1) - thread_safe (~> 0.3, >= 0.3.4) - tzinfo (~> 1.1) - addressable (2.6.0) - public_suffix (>= 2.0.2, < 4.0) - apktools (0.7.2) - rubyzip (~> 1.2.1) - atomos (0.1.3) - aws-eventstream (1.0.3) - aws-sdk (2.11.274) - aws-sdk-resources (= 2.11.274) - aws-sdk-core (2.11.274) - aws-sigv4 (~> 1.0) - jmespath (~> 1.0) - aws-sdk-resources (2.11.274) - aws-sdk-core (= 2.11.274) - aws-sigv4 (1.1.0) - aws-eventstream (~> 1.0, >= 1.0.2) - babosa (1.0.2) - claide (1.0.2) - cocoapods (1.6.1) - activesupport (>= 4.0.2, < 5) - claide (>= 1.0.2, < 2.0) - cocoapods-core (= 1.6.1) - cocoapods-deintegrate (>= 1.0.2, < 2.0) - cocoapods-downloader (>= 1.2.2, < 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.1, < 2.0) - cocoapods-try (>= 1.1.0, < 2.0) - colored2 (~> 3.1) - escape (~> 0.0.4) - fourflusher (>= 2.2.0, < 3.0) - gh_inspector (~> 1.0) - molinillo (~> 0.6.6) - nap (~> 1.0) - ruby-macho (~> 1.4) - xcodeproj (>= 1.8.1, < 2.0) - cocoapods-core (1.6.1) - activesupport (>= 4.0.2, < 6) - fuzzy_match (~> 2.0.4) - nap (~> 1.0) - cocoapods-deintegrate (1.0.4) - cocoapods-downloader (1.2.2) - cocoapods-plugins (1.0.0) - nap - cocoapods-search (1.0.0) - cocoapods-stats (1.1.0) - cocoapods-trunk (1.3.1) - 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.1.5) - declarative (0.0.10) - declarative-option (0.1.0) - digest-crc (0.4.1) - domain_name (0.5.20180417) - unf (>= 0.0.5, < 1.0.0) - dotenv (2.7.2) - emoji_regex (1.0.1) - escape (0.0.4) - excon (0.64.0) - faraday (0.15.4) - multipart-post (>= 1.2, < 3) - faraday-cookie_jar (0.0.6) - faraday (>= 0.7.4) - http-cookie (~> 1.0.0) - faraday_middleware (0.13.1) - faraday (>= 0.7.4, < 1.0) - fastimage (2.1.5) - fastlane (2.123.0) - CFPropertyList (>= 2.3, < 4.0.0) - addressable (>= 2.3, < 3.0.0) - babosa (>= 1.0.2, < 2.0.0) - bundler (>= 1.12.0, < 3.0.0) - colored - commander-fastlane (>= 4.4.6, < 5.0.0) - dotenv (>= 2.1.1, < 3.0.0) - emoji_regex (>= 0.1, < 2.0) - 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.24.0) - google-cloud-storage (>= 1.15.0, < 2.0.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.2, < 2.0.0) - security (= 0.1.3) - simctl (~> 1.6.3) - slack-notifier (>= 2.0.0, < 3.0.0) - terminal-notifier (>= 2.0.0, < 3.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.8.1, < 2.0.0) - xcpretty (~> 0.3.0) - xcpretty-travis-formatter (>= 0.0.3) - fastlane-plugin-aws_s3 (1.5.0) - apktools (~> 0.7) - aws-sdk (~> 2.3) - mime-types (~> 3.1) - fourflusher (2.2.0) - fuzzy_match (2.0.4) - gh_inspector (1.1.3) - google-api-client (0.23.9) - 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) - signet (~> 0.9) - google-cloud-core (1.3.0) - google-cloud-env (~> 1.0) - google-cloud-env (1.0.5) - faraday (~> 0.11) - google-cloud-storage (1.16.0) - digest-crc (~> 0.4) - google-api-client (~> 0.23) - google-cloud-core (~> 1.2) - googleauth (>= 0.6.2, < 0.10.0) - googleauth (0.6.7) - faraday (~> 0.12) - jwt (>= 1.4, < 3.0) - memoist (~> 0.16) - multi_json (~> 1.11) - os (>= 0.9, < 2.0) - 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) - jmespath (1.4.0) - json (2.1.0) - jwt (2.1.0) - memoist (0.16.0) - mime-types (3.2.2) - mime-types-data (~> 3.2015) - mime-types-data (3.2019.0331) - mini_magick (4.5.1) - minitest (5.10.3) - molinillo (0.6.6) - multi_json (1.13.1) - multi_xml (0.6.0) - multipart-post (2.0.0) - nanaimo (0.2.6) - nap (1.1.0) - naturally (2.2.0) - netrc (0.11.0) - os (1.0.1) - plist (3.5.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.4.0) - rubyzip (1.2.2) - security (0.1.3) - signet (0.11.0) - 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 (2.0.0) - terminal-table (1.8.0) - unicode-display_width (~> 1.1, >= 1.1.1) - thread_safe (0.3.6) - tty-cursor (0.6.1) - tty-screen (0.6.5) - tty-spinner (0.9.0) - tty-cursor (~> 0.6.0) - tzinfo (1.2.5) - thread_safe (~> 0.1) - uber (0.1.0) - unf (0.1.4) - unf_ext - unf_ext (0.0.7.6) - unicode-display_width (1.6.0) - word_wrap (1.0.0) - xcodeproj (1.9.0) - CFPropertyList (>= 2.3.3, < 4.0) - atomos (~> 0.1.3) - claide (>= 1.0.2, < 2.0) - colored2 (~> 3.1) - nanaimo (~> 0.2.6) - xcpretty (0.3.0) - rouge (~> 2.0.7) - xcpretty-travis-formatter (1.0.0) - xcpretty (~> 0.2, >= 0.0.7) - -PLATFORMS - ruby - -DEPENDENCIES - cocoapods - fastlane - fastlane-plugin-aws_s3 - -BUNDLED WITH - 2.0.1 diff --git a/MoPubResources/Info.plist b/MoPubResources/Info.plist index 9b66e8a2a..e848852bb 100644 --- a/MoPubResources/Info.plist +++ b/MoPubResources/Info.plist @@ -13,7 +13,7 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 5.7.0 + 5.7.1 CFBundleSignature ???? CFBundleSupportedPlatforms @@ -21,7 +21,7 @@ iPhoneOS CFBundleVersion - 5.7.0 + 5.7.1 NSHumanReadableCopyright Copyright 2019 Twitter Inc. All rights reserved. NSPrincipalClass diff --git a/MoPubSDK.xcodeproj/project.pbxproj b/MoPubSDK.xcodeproj/project.pbxproj index 8a7e0037d..a5ed40d82 100644 --- a/MoPubSDK.xcodeproj/project.pbxproj +++ b/MoPubSDK.xcodeproj/project.pbxproj @@ -165,6 +165,10 @@ 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 */; }; 2A4A5D441F86E2340082FC4C /* MPAdServerCommunicator+Testing.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A4A5D431F86E2340082FC4C /* MPAdServerCommunicator+Testing.m */; }; @@ -931,6 +935,8 @@ /* 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 = ""; }; @@ -2058,6 +2064,8 @@ 57B86B5619C78F5D00AD50EE /* MPConstants.m */, 2AFEE721225BC38400DD82C8 /* MPImpressionData.h */, 2AFEE722225BC38400DD82C8 /* MPImpressionData.m */, + 2A48DF39229601C1003763C2 /* MPImpressionTrackedNotification.h */, + 2A48DF3A229601C1003763C2 /* MPImpressionTrackedNotification.m */, AEF9D536171CA895005AAA5A /* MPInterstitialAdController.h */, AEF9D537171CA895005AAA5A /* MPInterstitialAdController.m */, 2A73E338226E44F8001FEE03 /* MPInterstitialAdControllerDelegate.h */, @@ -2787,6 +2795,7 @@ 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 */, @@ -3226,6 +3235,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 */, @@ -3425,6 +3435,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 */, @@ -3613,6 +3624,7 @@ BCA00B491EF47A91006FF762 /* NSJSONSerialization+MPAdditions.m in Sources */, BCA00B4A1EF47A91006FF762 /* MPActivityViewControllerHelper+TweetShare.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 */, @@ -3690,7 +3702,7 @@ CLANG_WARN_SUSPICIOUS_MOVES = YES; CLANG_WARN_UNREACHABLE_CODE = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 5.7.0; + CURRENT_PROJECT_VERSION = 5.7.1; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 4S7XS533V3; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -3731,7 +3743,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 5.7.0; + CURRENT_PROJECT_VERSION = 5.7.1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 4S7XS533V3; ENABLE_NS_ASSERTIONS = NO; @@ -3773,12 +3785,12 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 5.7.0; + CURRENT_PROJECT_VERSION = 5.7.1; DEBUG_INFORMATION_FORMAT = dwarf; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 4S7XS533V3; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 5.7.0; + DYLIB_CURRENT_VERSION = 5.7.1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_PREPROCESSOR_DEFINITIONS = ( @@ -3820,12 +3832,12 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 5.7.0; + CURRENT_PROJECT_VERSION = 5.7.1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 4S7XS533V3; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 5.7.0; + DYLIB_CURRENT_VERSION = 5.7.1; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_NS_ASSERTIONS = NO; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -3859,7 +3871,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.7.0; + CURRENT_PROJECT_VERSION = 5.7.1; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_GENERATE_DEBUGGING_SYMBOLS = NO; GCC_PREPROCESSOR_DEFINITIONS = ( @@ -3893,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.7.0; + CURRENT_PROJECT_VERSION = 5.7.1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -3920,7 +3932,7 @@ AE515F9A171F1B110086B464 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - CURRENT_PROJECT_VERSION = 5.7.0; + CURRENT_PROJECT_VERSION = 5.7.1; DSTROOT = /tmp/MoPubSDK.dst; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -3940,7 +3952,7 @@ AE515F9B171F1B110086B464 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - CURRENT_PROJECT_VERSION = 5.7.0; + CURRENT_PROJECT_VERSION = 5.7.1; DSTROOT = /tmp/MoPubSDK.dst; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -4057,7 +4069,7 @@ BCA00B951EF47A91006FF762 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - CURRENT_PROJECT_VERSION = 5.7.0; + CURRENT_PROJECT_VERSION = 5.7.1; DSTROOT = /tmp/MoPubSDK.dst; GCC_PRECOMPILE_PREFIX_HEADER = NO; GCC_TREAT_WARNINGS_AS_ERRORS = YES; @@ -4074,7 +4086,7 @@ BCA00B961EF47A91006FF762 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - CURRENT_PROJECT_VERSION = 5.7.0; + CURRENT_PROJECT_VERSION = 5.7.1; DSTROOT = /tmp/MoPubSDK.dst; GCC_PRECOMPILE_PREFIX_HEADER = NO; GCC_TREAT_WARNINGS_AS_ERRORS = YES; diff --git a/MoPubSDK/Internal/Interstitials/MPInterstitialViewController.m b/MoPubSDK/Internal/Interstitials/MPInterstitialViewController.m index 6a5c950a3..12109ee01 100644 --- a/MoPubSDK/Internal/Interstitials/MPInterstitialViewController.m +++ b/MoPubSDK/Internal/Interstitials/MPInterstitialViewController.m @@ -200,18 +200,7 @@ - (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()]; diff --git a/MoPubSDK/Internal/MPConsentManager.m b/MoPubSDK/Internal/MPConsentManager.m index 403815028..ad6b6937c 100644 --- a/MoPubSDK/Internal/MPConsentManager.m +++ b/MoPubSDK/Internal/MPConsentManager.m @@ -137,9 +137,16 @@ - (instancetype)init { _consentDialogViewController = nil; _syncFrequency = kDefaultRefreshInterval; - // Initializing the timer must be done last since it depends on the - // value of _syncFrequency - _nextUpdateTimer = [self newNextUpdateTimer]; + // Initializing the timer must be done last since it depends on the value of _syncFrequency + __weak __typeof__(self) weakSelf = self; + dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + // 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 since + // the same `dispatch_once` is called twice for `MPConsentManager.sharedManager` in the + // same call stack. To avoid this crash, call `newNextUpdateTimer` asynchronusly for now. + weakSelf.nextUpdateTimer = [weakSelf newNextUpdateTimer]; + }); } return self; @@ -593,14 +600,7 @@ - (void)synchronizeConsentWithCompletion:(void (^ _Nonnull)(NSError * error))com */ - (MPTimer * _Nonnull)newNextUpdateTimer { MPTimer * timer = [MPTimer timerWithTimeInterval:self.syncFrequency target:self selector:@selector(onNextUpdateFiredWithTimer) repeats:YES]; - dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - // 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 since - // the same `dispatch_once` is called twice for `MPConsentManager.sharedManager` in the same - // call stack. To avoid this crash, call `MPTimer.scheduleNow` asynchronusly for now. - [timer scheduleNow]; - }); + [timer scheduleNow]; return timer; } diff --git a/MoPubSDK/Internal/MRAID/MPMRAIDInterstitialViewController.m b/MoPubSDK/Internal/MRAID/MPMRAIDInterstitialViewController.m index 067a65918..a4de081fd 100644 --- a/MoPubSDK/Internal/MRAID/MPMRAIDInterstitialViewController.m +++ b/MoPubSDK/Internal/MRAID/MPMRAIDInterstitialViewController.m @@ -178,12 +178,7 @@ - (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 : [super supportedInterfaceOrientations]; } diff --git a/MoPubSDK/Internal/MRAID/MRExpandModalViewController.m b/MoPubSDK/Internal/MRAID/MRExpandModalViewController.m index b0717b6b1..5716a4ef4 100644 --- a/MoPubSDK/Internal/MRAID/MRExpandModalViewController.m +++ b/MoPubSDK/Internal/MRAID/MRExpandModalViewController.m @@ -78,12 +78,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/Utility/Categories/MoPub+Utility.h b/MoPubSDK/Internal/Utility/Categories/MoPub+Utility.h index b355a0621..d0c372a95 100644 --- a/MoPubSDK/Internal/Utility/Categories/MoPub+Utility.h +++ b/MoPubSDK/Internal/Utility/Categories/MoPub+Utility.h @@ -33,6 +33,28 @@ NS_ASSUME_NONNULL_BEGIN 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 index e31974053..06f1a85f1 100644 --- a/MoPubSDK/Internal/Utility/Categories/MoPub+Utility.m +++ b/MoPubSDK/Internal/Utility/Categories/MoPub+Utility.m @@ -24,4 +24,31 @@ + (void)openURL:(NSURL*)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/MPGlobal.h b/MoPubSDK/Internal/Utility/MPGlobal.h index a1da19f09..ef97c28ca 100644 --- a/MoPubSDK/Internal/Utility/MPGlobal.h +++ b/MoPubSDK/Internal/Utility/MPGlobal.h @@ -32,19 +32,6 @@ 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 diff --git a/MoPubSDK/Internal/Utility/MPTimer.h b/MoPubSDK/Internal/Utility/MPTimer.h index 2106f3e5d..3dfb58db0 100644 --- a/MoPubSDK/Internal/Utility/MPTimer.h +++ b/MoPubSDK/Internal/Utility/MPTimer.h @@ -11,7 +11,7 @@ NS_ASSUME_NONNULL_BEGIN /** - * @c MPTimer wraps an @c NSTimer and adds pause/resume functionality. + * @c MPTimer is a thread safe @c NSTimer wrapper, with pause / resume functionality. */ @interface MPTimer : NSObject diff --git a/MoPubSDK/Internal/Utility/MPTimer.m b/MoPubSDK/Internal/Utility/MPTimer.m index 84a83eb46..ee7596044 100644 --- a/MoPubSDK/Internal/Utility/MPTimer.m +++ b/MoPubSDK/Internal/Utility/MPTimer.m @@ -57,15 +57,17 @@ - (NSRunLoop *)runloop - (void)timerDidFire { - if (self.selector == nil) { - MPLogDebug(@"%s `selector` is unexpectedly nil. Return early to avoid crash.", __FUNCTION__); - return; - } + @synchronized (self) { + 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); + // 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 @@ -75,88 +77,96 @@ - (BOOL)isValid - (void)invalidate { - self.target = nil; - self.selector = nil; - [self.timer invalidate]; - self.timer = nil; - self.isCountdownActive = NO; + @synchronized (self) { + self.target = nil; + self.selector = nil; + [self.timer invalidate]; + self.timer = nil; + self.isCountdownActive = NO; + } } - (BOOL)isScheduled { - if (!self.timer) { - return NO; - } - CFRunLoopRef runLoopRef = [self.runloop 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; + @synchronized (self) { + if (!self.timer) { + return NO; + } + CFRunLoopRef runLoopRef = [self.runloop 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; + CFRelease(arrayRef); + return NO; + } } - (void)scheduleNow { - if (![self.timer isValid]) { - MPLogDebug(@"Could not schedule invalidated MPTimer (%p).", self); - return; - } + @synchronized (self) { + if (![self.timer isValid]) { + MPLogDebug(@"Could not schedule invalidated MPTimer (%p).", self); + return; + } - if (self.isCountdownActive) { - MPLogDebug(@"Tried to schedule an MPTimer (%p) that is already ticking.",self); - return; - } + if (self.isCountdownActive) { + MPLogDebug(@"Tried to schedule an MPTimer (%p) that is already ticking.",self); + return; + } - NSDate *newFireDate = [NSDate dateWithTimeInterval:self.timeInterval sinceDate:[NSDate date]]; - [self.timer setFireDate:newFireDate]; + NSDate *newFireDate = [NSDate dateWithTimeInterval:self.timeInterval sinceDate:[NSDate date]]; + [self.timer setFireDate:newFireDate]; - if ([self isScheduled]) { - MPLogDebug(@"MPTimer is already scheduled (%p).", self); - } else { - MPLogDebug(@"Start MPTimer (%p), should fire in %.1f seconds.", self, self.timeInterval); - [self.runloop addTimer:self.timer forMode:self.runLoopMode]; - } + if ([self isScheduled]) { + MPLogDebug(@"MPTimer is already scheduled (%p).", self); + } else { + MPLogDebug(@"Start MPTimer (%p), should fire in %.1f seconds.", self, self.timeInterval); + [self.runloop addTimer:self.timer forMode:self.runLoopMode]; + } - self.isCountdownActive = YES; + self.isCountdownActive = YES; + } } - (void)pause { - if (!self.isCountdownActive) { - MPLogDebug(@"No-op: tried to pause an MPTimer (%p) that was already paused.", self); - return; - } + @synchronized (self) { + if (!self.isCountdownActive) { + MPLogDebug(@"No-op: tried to pause an MPTimer (%p) that was already paused.", self); + return; + } - if (![self.timer isValid]) { - MPLogDebug(@"Cannot pause invalidated MPTimer (%p).", self); - return; - } + if (![self.timer isValid]) { + MPLogDebug(@"Cannot pause invalidated MPTimer (%p).", self); + return; + } - if (![self isScheduled]) { - MPLogDebug(@"No-op: tried to pause an MPTimer (%p) that was never scheduled.", self); - return; - } + if (![self isScheduled]) { + MPLogDebug(@"No-op: tried to pause an MPTimer (%p) that was never scheduled.", self); + return; + } - // `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); - } + // `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); + } - // Pause the timer by setting its fire date far into the future. - [self.timer setFireDate:[NSDate distantFuture]]; - self.isCountdownActive = NO; + // Pause the timer by setting its fire date far into the future. + [self.timer setFireDate:[NSDate distantFuture]]; + self.isCountdownActive = NO; + } } - (void)resume diff --git a/MoPubSDK/MPAdView.m b/MoPubSDK/MPAdView.m index 515015a0e..b9925b148 100644 --- a/MoPubSDK/MPAdView.m +++ b/MoPubSDK/MPAdView.m @@ -7,11 +7,13 @@ // #import "MPAdView.h" +#import "MoPub+Utility.h" #import "MPAdTargeting.h" #import "MPBannerAdManager.h" #import "MPBannerAdManagerDelegate.h" #import "MPClosableView.h" #import "MPCoreInstanceProvider.h" +#import "MPImpressionTrackedNotification.h" #import "MPLogging.h" @interface MPAdView () @@ -199,9 +201,9 @@ - (void)userWillLeaveApplication } - (void)impressionDidFireWithImpressionData:(MPImpressionData *)impressionData { - if ([self.delegate respondsToSelector:@selector(mopubAd:didTrackImpressionWithImpressionData:)]) { - [self.delegate mopubAd:self didTrackImpressionWithImpressionData:impressionData]; - } + [MoPub sendImpressionDelegateAndNotificationFromAd:self + adUnitID:self.adUnitId + impressionData:impressionData]; } @end diff --git a/MoPubSDK/MPConstants.h b/MoPubSDK/MPConstants.h index 16e9e4cd1..8bc36c473 100644 --- a/MoPubSDK/MPConstants.h +++ b/MoPubSDK/MPConstants.h @@ -14,7 +14,7 @@ #define MP_SERVER_VERSION @"8" #define MP_REWARDED_API_VERSION @"1" #define MP_BUNDLE_IDENTIFIER @"com.mopub.mopub" -#define MP_SDK_VERSION @"5.7.0" +#define MP_SDK_VERSION @"5.7.1" // Sizing constants. extern CGSize const MOPUB_BANNER_SIZE; diff --git a/MoPubSDK/MPImpressionTrackedNotification.h b/MoPubSDK/MPImpressionTrackedNotification.h new file mode 100644 index 000000000..539ae4e3b --- /dev/null +++ b/MoPubSDK/MPImpressionTrackedNotification.h @@ -0,0 +1,29 @@ +// +// MPImpressionTrackedNotification.h +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import + +/** + Notification fired when an impression is tracked. The adunit ID will always be + included in the @c NSNotification.userData dictionary. If the server returned + impression data, the @c MPImpressionData object will be included in the + @c NSNotification.userData dictionary. The sender can be determined by + querying @c NSNotification.object. + */ +extern NSString * const kMPImpressionTrackedNotification; + +/** + The @c MPImpressionData object for the given impression, or @c nil if the server did not send + impression data with this impression. + */ +extern NSString * const kMPImpressionTrackedInfoImpressionDataKey; + +/** + The adunit ID associated with the impression. + */ +extern NSString * const kMPImpressionTrackedInfoAdUnitIDKey; diff --git a/MoPubSDK/MPImpressionTrackedNotification.m b/MoPubSDK/MPImpressionTrackedNotification.m new file mode 100644 index 000000000..a6c9a657e --- /dev/null +++ b/MoPubSDK/MPImpressionTrackedNotification.m @@ -0,0 +1,18 @@ +// +// MPImpressionTrackedNotification.m +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import "MPImpressionTrackedNotification.h" + +#pragma mark - NSNotification Name + +NSString * const kMPImpressionTrackedNotification = @"com.mopub.impression-notification.impression-received"; + +#pragma mark - NSNotification userInfo Keys + +NSString * const kMPImpressionTrackedInfoImpressionDataKey = @"com.mopub.impression-notification.userinfo.impression-data"; +NSString * const kMPImpressionTrackedInfoAdUnitIDKey = @"com.mopub.impression-notification.userinfo.adunit-id"; diff --git a/MoPubSDK/MPInterstitialAdController.m b/MoPubSDK/MPInterstitialAdController.m index 94060f57a..c12238622 100644 --- a/MoPubSDK/MPInterstitialAdController.m +++ b/MoPubSDK/MPInterstitialAdController.m @@ -7,10 +7,12 @@ // #import "MPInterstitialAdController.h" +#import "MoPub+Utility.h" #import "MPAdTargeting.h" -#import "MPLogging.h" +#import "MPImpressionTrackedNotification.h" #import "MPInterstitialAdManager.h" #import "MPInterstitialAdManagerDelegate.h" +#import "MPLogging.h" @interface MPInterstitialAdController () @@ -175,9 +177,9 @@ - (void)managerDidReceiveTapEventFromInterstitial:(MPInterstitialAdManager *)man } - (void)interstitialAdManager:(MPInterstitialAdManager *)manager didReceiveImpressionEventWithImpressionData:(MPImpressionData *)impressionData { - if ([self.delegate respondsToSelector:@selector(mopubAd:didTrackImpressionWithImpressionData:)]) { - [self.delegate mopubAd:self didTrackImpressionWithImpressionData:impressionData]; - } + [MoPub sendImpressionDelegateAndNotificationFromAd:self + adUnitID:self.adUnitId + impressionData:impressionData]; } + (NSMutableArray *)sharedInterstitialAdControllers diff --git a/MoPubSDK/MoPub.h b/MoPubSDK/MoPub.h index 862000d84..0c20bf9b9 100644 --- a/MoPubSDK/MoPub.h +++ b/MoPubSDK/MoPub.h @@ -27,6 +27,7 @@ #import "MPGlobal.h" #import "MPIdentityProvider.h" #import "MPImpressionData.h" +#import "MPImpressionTrackedNotification.h" #import "MPInterstitialAdController.h" #import "MPInterstitialAdControllerDelegate.h" #import "MPInterstitialCustomEvent.h" diff --git a/MoPubSDK/NativeAds/Internal/MPNativeAd+Internal.h b/MoPubSDK/NativeAds/Internal/MPNativeAd+Internal.h index 5f9010761..3de1423fc 100644 --- a/MoPubSDK/NativeAds/Internal/MPNativeAd+Internal.h +++ b/MoPubSDK/NativeAds/Internal/MPNativeAd+Internal.h @@ -16,6 +16,7 @@ @property (nonatomic) MPNativeView *associatedView; @property (nonatomic, readwrite, strong) id renderer; @property (nonatomic, readwrite, strong) MPAdConfiguration *configuration; +@property (nonatomic, readwrite, strong) NSString *adUnitID; @property (nonatomic, readonly) NSMutableSet *clickTrackerURLs; @property (nonatomic, readonly) NSMutableSet *impressionTrackerURLs; @property (nonatomic, readonly, strong) id adAdapter; diff --git a/MoPubSDK/NativeAds/Internal/MPNativeAd+Internal.m b/MoPubSDK/NativeAds/Internal/MPNativeAd+Internal.m index ab4be0a57..75816e6aa 100644 --- a/MoPubSDK/NativeAds/Internal/MPNativeAd+Internal.m +++ b/MoPubSDK/NativeAds/Internal/MPNativeAd+Internal.m @@ -16,6 +16,7 @@ @implementation MPNativeAd (Internal) @dynamic impressionTrackerURLs; @dynamic clickTrackerURLs; @dynamic creationDate; +@dynamic adUnitID; @dynamic renderer; @dynamic configuration; @dynamic associatedView; diff --git a/MoPubSDK/NativeAds/MPNativeAd.m b/MoPubSDK/NativeAds/MPNativeAd.m index 4c00d4642..dc34a1929 100644 --- a/MoPubSDK/NativeAds/MPNativeAd.m +++ b/MoPubSDK/NativeAds/MPNativeAd.m @@ -7,6 +7,7 @@ // #import "MPNativeAd+Internal.h" +#import "MoPub+Utility.h" #import "MPAdConfiguration.h" #import "MPCoreInstanceProvider.h" #import "MPNativeAdError.h" @@ -23,6 +24,7 @@ #import "MPNativeView.h" #import "MPHTTPNetworkSession.h" #import "MPURLRequest.h" +#import "MPImpressionTrackedNotification.h" //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -30,6 +32,7 @@ @interface MPNativeAd () @property (nonatomic, readwrite, strong) id renderer; @property (nonatomic, readwrite, strong) MPAdConfiguration *configuration; +@property (nonatomic, readwrite, strong) NSString *adUnitID; @property (nonatomic, strong) NSDate *creationDate; @@ -126,9 +129,9 @@ - (void)trackImpression self.hasTrackedImpression = YES; [self trackMetricsForURLs:self.impressionTrackerURLs]; - if ([self.delegate respondsToSelector:@selector(mopubAd:didTrackImpressionWithImpressionData:)]) { - [self.delegate mopubAd:self didTrackImpressionWithImpressionData:self.configuration.impressionData]; - } + [MoPub sendImpressionDelegateAndNotificationFromAd:self + adUnitID:self.adUnitID + impressionData:self.configuration.impressionData]; } - (void)trackClick diff --git a/MoPubSDK/NativeAds/MPNativeAdRequest.m b/MoPubSDK/NativeAds/MPNativeAdRequest.m index 40e06027e..cffb4842a 100644 --- a/MoPubSDK/NativeAds/MPNativeAdRequest.m +++ b/MoPubSDK/NativeAds/MPNativeAdRequest.m @@ -243,6 +243,7 @@ - (void)completeAdRequestWithAdObject:(MPNativeAd *)adObject error:(NSError *)er adObject.renderer = self.customEventRenderer; adObject.configuration = self.adConfiguration; + adObject.adUnitID = self.adUnitIdentifier; if ([(id)adObject.adAdapter respondsToSelector:@selector(setAdConfiguration:)]) { [(id)adObject.adAdapter performSelector:@selector(setAdConfiguration:) withObject:self.adConfiguration]; diff --git a/MoPubSDK/NativeVideo/Internal/MOPUBPlayerViewController.h b/MoPubSDK/NativeVideo/Internal/MOPUBPlayerViewController.h index 7ee73dbd9..77e6eac18 100644 --- a/MoPubSDK/NativeVideo/Internal/MOPUBPlayerViewController.h +++ b/MoPubSDK/NativeVideo/Internal/MOPUBPlayerViewController.h @@ -53,7 +53,7 @@ @property (nonatomic) BOOL isReadyToPlay; @property (nonatomic) BOOL disposed; -#pragma - Call to action click tracking url +#pragma mark - Call to action click tracking url @property (nonatomic) NSURL *defaultActionURL; @property (nonatomic, weak) id delegate; diff --git a/MoPubSDK/RewardedVideo/MPRewardedVideo.m b/MoPubSDK/RewardedVideo/MPRewardedVideo.m index 51ed7f9b8..9eca25a99 100644 --- a/MoPubSDK/RewardedVideo/MPRewardedVideo.m +++ b/MoPubSDK/RewardedVideo/MPRewardedVideo.m @@ -8,11 +8,13 @@ #import "MPRewardedVideo.h" #import "MPAdTargeting.h" +#import "MPImpressionTrackedNotification.h" #import "MPLogging.h" #import "MPRewardedVideoAdManager.h" #import "MPRewardedVideoError.h" #import "MPRewardedVideoConnection.h" #import "MPRewardedVideoCustomEvent.h" +#import "MoPub+Utility.h" static MPRewardedVideo *gSharedInstance = nil; @@ -283,6 +285,10 @@ - (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]; diff --git a/MoPubSDKFramework/Info.plist b/MoPubSDKFramework/Info.plist index 72bcf273c..35699cd1d 100644 --- a/MoPubSDKFramework/Info.plist +++ b/MoPubSDKFramework/Info.plist @@ -15,9 +15,9 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 5.7.0 + 5.7.1 CFBundleVersion - 5.7.0 + 5.7.1 NSPrincipalClass diff --git a/MoPubSDKTests/Info.plist b/MoPubSDKTests/Info.plist index 2dfb84971..59ab86668 100644 --- a/MoPubSDKTests/Info.plist +++ b/MoPubSDKTests/Info.plist @@ -15,8 +15,8 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 5.7.0 + 5.7.1 CFBundleVersion - 5.7.0 + 5.7.1 diff --git a/MoPubSDKTests/MPAdView+Testing.h b/MoPubSDKTests/MPAdView+Testing.h index b91fdf381..f7931da09 100644 --- a/MoPubSDKTests/MPAdView+Testing.h +++ b/MoPubSDKTests/MPAdView+Testing.h @@ -11,4 +11,5 @@ @interface MPAdView (Testing) @property (nonatomic, strong) MPBannerAdManager *adManager; +- (void)impressionDidFireWithImpressionData:(MPImpressionData *)impressionData; @end diff --git a/MoPubSDKTests/MPAdViewTests.m b/MoPubSDKTests/MPAdViewTests.m index e1be2cc3a..921d88ccf 100644 --- a/MoPubSDKTests/MPAdViewTests.m +++ b/MoPubSDKTests/MPAdViewTests.m @@ -15,6 +15,9 @@ #import "MPMockAdServerCommunicator.h" #import "MPURL.h" #import "NSURLComponents+Testing.h" +#import "MPImpressionTrackedNotification.h" + +static NSTimeInterval const kTestTimeout = 0.5; @interface MPAdViewTests : XCTestCase @property (nonatomic, strong) MPAdView * adView; @@ -34,11 +37,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 { @@ -56,5 +54,68 @@ - (void)testViewabilityQueryParameter { XCTAssertTrue([viewabilityValue isEqualToString:@"1"]); } +#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/MPInterstitialAdController+Testing.h b/MoPubSDKTests/MPInterstitialAdController+Testing.h index 25bc976a8..141a19ae3 100644 --- a/MoPubSDKTests/MPInterstitialAdController+Testing.h +++ b/MoPubSDKTests/MPInterstitialAdController+Testing.h @@ -11,4 +11,5 @@ @interface MPInterstitialAdController (Testing) @property (nonatomic, strong) MPInterstitialAdManager * manager; +- (void)interstitialAdManager:(MPInterstitialAdManager *)manager didReceiveImpressionEventWithImpressionData:(MPImpressionData *)impressionData; @end diff --git a/MoPubSDKTests/MPInterstitialAdControllerTests.m b/MoPubSDKTests/MPInterstitialAdControllerTests.m index a30171ff0..3d4592a1c 100644 --- a/MoPubSDKTests/MPInterstitialAdControllerTests.m +++ b/MoPubSDKTests/MPInterstitialAdControllerTests.m @@ -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,68 @@ - (void)testViewabilityQueryParameter { XCTAssertTrue([viewabilityValue isEqualToString:@"1"]); } +# 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/MPNativeAd+Testing.h b/MoPubSDKTests/MPNativeAd+Testing.h index b92583e49..052c5dd69 100644 --- a/MoPubSDKTests/MPNativeAd+Testing.h +++ b/MoPubSDKTests/MPNativeAd+Testing.h @@ -6,7 +6,7 @@ // http://www.mopub.com/legal/sdk-license-agreement/ // -#import "MPNativeAd.h" +#import "MPNativeAd+Internal.h" @interface MPNativeAd (Testing) diff --git a/MoPubSDKTests/MPNativeAdRequestTests.m b/MoPubSDKTests/MPNativeAdRequestTests.m index 402bcecbd..303a0969b 100644 --- a/MoPubSDKTests/MPNativeAdRequestTests.m +++ b/MoPubSDKTests/MPNativeAdRequestTests.m @@ -23,6 +23,7 @@ #import "MPNativeAdDelegateHandler.h" #import "MPNativeAd+Testing.h" #import "MPAdServerKeys.h" +#import "MPImpressionTrackedNotification.h" static const NSTimeInterval kTestTimeout = 2; // seconds @@ -385,17 +386,32 @@ - (void)testLocalExtrasInCustomEvent { #pragma mark - Impression Level Revenue Data - (void)testImpressionDelegateFiresWithoutILRD { - XCTestExpectation * expectation = [self expectationWithDescription:@"Wait for impression"]; + 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) { - [expectation fulfill]; + [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]; @@ -412,6 +428,7 @@ - (void)testImpressionDelegateFiresWithoutILRD { XCTFail(@"Unexpected failure"); } + nativeAd = response; response.delegate = handler; [response trackImpression]; }]; @@ -421,21 +438,40 @@ - (void)testImpressionDelegateFiresWithoutILRD { XCTFail(@"Timed out"); } }]; + + [[NSNotificationCenter defaultCenter] removeObserver:notificationObserver]; } - (void)testImpressionDelegateFiresWithILRD { - XCTestExpectation * expectation = [self expectationWithDescription:@"Wait for impression"]; + 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) { - [expectation fulfill]; + [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:@{ @@ -455,6 +491,7 @@ - (void)testImpressionDelegateFiresWithILRD { XCTFail(@"Unexpected failure"); } + nativeAd = response; response.delegate = handler; [response trackImpression]; }]; @@ -464,6 +501,8 @@ - (void)testImpressionDelegateFiresWithILRD { XCTFail(@"Timed out"); } }]; + + [[NSNotificationCenter defaultCenter] removeObserver:notificationObserver]; } @end diff --git a/MoPubSDKTests/MPRewardedVideo+Testing.h b/MoPubSDKTests/MPRewardedVideo+Testing.h index 413a7b873..f2e459f86 100644 --- a/MoPubSDKTests/MPRewardedVideo+Testing.h +++ b/MoPubSDKTests/MPRewardedVideo+Testing.h @@ -21,4 +21,6 @@ + (void)loadRewardedVideoAdWithAdUnitID:(NSString *)adUnitID withTestConfiguration:(MPAdConfiguration *)config; + (MPRewardedVideoAdManager *)adManagerForAdUnitId:(NSString *)adUnitID; +- (void)rewardedVideoAdManager:(MPRewardedVideoAdManager *)manager didReceiveImpressionEventWithImpressionData:(MPImpressionData *)impressionData; + @end diff --git a/MoPubSDKTests/MPRewardedVideoAdManagerTests.m b/MoPubSDKTests/MPRewardedVideoAdManagerTests.m index 168231364..f4df621c9 100644 --- a/MoPubSDKTests/MPRewardedVideoAdManagerTests.m +++ b/MoPubSDKTests/MPRewardedVideoAdManagerTests.m @@ -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 @@ -551,20 +553,37 @@ - (void)testLocalExtrasInCustomEvent { #pragma mark - Impression Level Revenue Data - (void)testImpressionDelegateFiresWithoutILRD { - XCTestExpectation * expectation = [self expectationWithDescription:@"Wait for impression delegate"]; + 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) { - [expectation fulfill]; + [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]; - MPRewardedVideoAdManager * manager = [[MPRewardedVideoAdManager alloc] initWithAdUnitID:kTestAdUnitId delegate:handler]; + manager = [[MPRewardedVideoAdManager alloc] initWithAdUnitID:kTestAdUnitId delegate:handler]; MPMockAdServerCommunicator * communicator = [[MPMockAdServerCommunicator alloc] initWithDelegate:manager]; communicator.mockConfigurationsResponse = configurations; manager.communicator = communicator; @@ -573,6 +592,9 @@ - (void)testImpressionDelegateFiresWithoutILRD { // 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] init]; @@ -583,20 +605,40 @@ - (void)testImpressionDelegateFiresWithoutILRD { XCTFail(@"Timed out"); } }]; + + [[NSNotificationCenter defaultCenter] removeObserver:notificationObserver]; } - (void)testImpressionDelegateFiresWithILRD { - XCTestExpectation * expectation = [self expectationWithDescription:@"Wait for impression delegate"]; + 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) { - [expectation fulfill]; + [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:@{ @@ -604,7 +646,7 @@ - (void)testImpressionDelegateFiresWithILRD { }]; NSArray * configurations = @[rewardedVideoThatShouldLoad]; - MPRewardedVideoAdManager * manager = [[MPRewardedVideoAdManager alloc] initWithAdUnitID:kTestAdUnitId delegate:handler]; + manager = [[MPRewardedVideoAdManager alloc] initWithAdUnitID:kTestAdUnitId delegate:handler]; MPMockAdServerCommunicator * communicator = [[MPMockAdServerCommunicator alloc] initWithDelegate:manager]; communicator.mockConfigurationsResponse = configurations; manager.communicator = communicator; @@ -613,6 +655,9 @@ - (void)testImpressionDelegateFiresWithILRD { // 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] init]; @@ -623,6 +668,8 @@ - (void)testImpressionDelegateFiresWithILRD { XCTFail(@"Timed out"); } }]; + + [[NSNotificationCenter defaultCenter] removeObserver:notificationObserver]; } @end diff --git a/MoPubSDKTests/MPTimerTests.m b/MoPubSDKTests/MPTimerTests.m index fd6cd8278..bf42d968f 100644 --- a/MoPubSDKTests/MPTimerTests.m +++ b/MoPubSDKTests/MPTimerTests.m @@ -267,4 +267,91 @@ - (void)testRedundantSchedules { }]; } +// 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/README.md b/README.md index f90700929..db50e83b8 100644 --- a/README.md +++ b/README.md @@ -23,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.7.0 +The current version of the SDK is 5.7.1 ### Installation with CocoaPods @@ -56,7 +56,7 @@ $ pod install MoPub provides a prepackaged archive of the dynamic framework: -- **[MoPub SDK Framework.zip](https://github.com/mopub/mopub-ios-sdk/releases/download/5.7.0/mopub-framework-5.7.0.zip)** +- **[MoPub SDK Framework.zip](https://github.com/mopub/mopub-ios-sdk/releases/download/5.7.1/mopub-framework-5.7.1.zip)** Includes everything you need to serve HTML, MRAID, and Native MoPub advertisements. Third party ad networks are not included. @@ -66,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](https://github.com/mopub/mopub-ios-sdk/releases/download/5.7.0/mopub-base-5.7.0.zip)** +- **[MoPub Base SDK.zip](https://github.com/mopub/mopub-ios-sdk/releases/download/5.7.1/mopub-base-5.7.1.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](https://github.com/mopub/mopub-ios-sdk/releases/download/5.7.0/mopub-nonnative-5.7.0.zip)** +- **[MoPub Base SDK Excluding Native.zip](https://github.com/mopub/mopub-ios-sdk/releases/download/5.7.1/mopub-nonnative-5.7.1.zip)** Includes everything you need to serve HTML and MRAID advertisements. Third party ad networks and Native MoPub advertisements are not included. @@ -83,17 +83,10 @@ 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** - - Impression Level Revenue Data: A data object that includes revenue information associated with each impression - - Verizon Ads SDK now supported as a mediated network + - Impression Level Revenue Data can now be received via a notification - **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 + - Fixed occasional crash due to multithreading bug 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. diff --git a/mopub-ios-sdk.podspec b/mopub-ios-sdk.podspec index 2fb4b607f..d00edb4dd 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.7.0' + spec.version = '5.7.1' spec.license = { :type => 'New BSD', :file => 'LICENSE' } spec.homepage = 'https://github.com/mopub/mopub-ios-sdk' spec.authors = { 'MoPub' => 'support@mopub.com' } @@ -14,7 +14,7 @@ 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.7.0' } + spec.source = { :git => 'https://github.com/mopub/mopub-ios-sdk.git', :tag => '5.7.1' } spec.requires_arc = true spec.ios.deployment_target = '8.0' spec.frameworks = [ From 42bd0a4a365bacbbc9c4fe56fcec16c57629fd74 Mon Sep 17 00:00:00 2001 From: Haoxin Li Date: Fri, 19 Jul 2019 16:05:43 -0700 Subject: [PATCH 09/12] 5.8.0 --- CHANGELOG.md | 17 + Canary/Canary.xcodeproj/project.pbxproj | 48 +-- .../Canary/AppDelegate+Initialization.swift | 72 ++++ Canary/Canary/AppDelegate.swift | 18 +- Canary/Canary/Configuration/Info.plist | 4 +- .../Extensions/UIAlertController+Picker.swift | 6 +- Canary/Canary/Extensions/UIView+Nib.swift | 2 +- .../Extensions/UserDefaults+Subscript.swift | 19 + Canary/Canary/Formats/AdFormat.swift | 14 +- .../Formats/AdTableViewController.swift | 4 + .../Canary/Formats/BannerAdDataSource.swift | 26 +- .../Formats/BannerAdViewController.swift | 43 +- .../Formats/LeaderboardAdViewController.swift | 64 --- .../MediumRectangleAdViewController.swift | 43 +- Canary/Canary/Samples/SampleAds.plist | 2 +- Canary/CanaryUnitTests/Info.plist | 4 +- Canary/Podfile | 2 + Gemfile.lock | 230 ++++++++++ MoPubResources/Info.plist | 4 +- MoPubSDK.xcodeproj/project.pbxproj | 156 +++---- .../xcschemes/MoPubResources.xcscheme | 2 +- .../xcschemes/MoPubSDK-ExcludeNative.xcscheme | 2 +- .../xcshareddata/xcschemes/MoPubSDK.xcscheme | 2 +- .../xcschemes/MoPubSDKFramework.xcscheme | 2 +- .../xcschemes/MoPubSDKTests.xcscheme | 2 +- MoPubSDK/Internal/Banners/MPBannerAdManager.h | 1 + MoPubSDK/Internal/Banners/MPBannerAdManager.m | 26 +- .../Banners/MPBannerAdManagerDelegate.h | 7 + .../Internal/Banners/MPBaseBannerAdapter.m | 9 +- .../Common/AdAlerts/MPAdAlertManager.m | 2 +- .../Internal/Common/MPAdBrowserController.h | 65 --- .../Internal/Common/MPAdBrowserController.m | 346 --------------- .../Internal/Common/MPAdBrowserController.xib | 97 ----- MoPubSDK/Internal/Common/MPAdConfiguration.h | 9 +- MoPubSDK/Internal/Common/MPAdConfiguration.m | 24 +- .../Common/MPAdDestinationDisplayAgent.h | 4 +- .../Common/MPAdDestinationDisplayAgent.m | 102 ++--- .../Internal/Common/MPAdImpressionTimer.m | 16 +- .../Internal/Common/MPAdServerCommunicator.m | 10 +- .../Internal/Common/MPAdServerURLBuilder.h | 19 +- .../Internal/Common/MPAdServerURLBuilder.m | 87 ++-- MoPubSDK/Internal/Common/MPClosableView.m | 2 +- .../Internal/Common/MPCountdownTimerView.m | 6 +- .../Internal/Common/MPProgressOverlayView.h | 44 +- .../Internal/Common/MPProgressOverlayView.m | 395 +++++++----------- MoPubSDK/Internal/Common/MPRealTimeTimer.m | 4 +- MoPubSDK/Internal/Common/MPURLActionInfo.h | 4 +- MoPubSDK/Internal/Common/MPURLActionInfo.m | 6 +- MoPubSDK/Internal/Common/MPURLResolver.m | 148 +++++-- MoPubSDK/Internal/HTML/MPAdWebViewAgent.h | 1 - MoPubSDK/Internal/HTML/MPAdWebViewAgent.m | 24 +- .../Internal/HTML/MPHTMLBannerCustomEvent.m | 2 +- .../HTML/MPHTMLInterstitialViewController.m | 5 +- MoPubSDK/Internal/HTML/MPWebView.h | 5 - MoPubSDK/Internal/HTML/MPWebView.m | 68 +-- .../Interstitials/MPBaseInterstitialAdapter.m | 9 +- .../Interstitials/MPInterstitialAdManager.m | 7 +- .../MPInterstitialViewController.h | 3 +- .../MPInterstitialViewController.m | 27 +- MoPubSDK/Internal/MPAdServerKeys.h | 5 + MoPubSDK/Internal/MPAdServerKeys.m | 5 + .../Internal/MPConsentDialogViewController.m | 7 +- MoPubSDK/Internal/MPConsentManager.h | 15 +- MoPubSDK/Internal/MPConsentManager.m | 151 ++++--- MoPubSDK/Internal/MPCoreInstanceProvider.h | 2 - MoPubSDK/Internal/MPCoreInstanceProvider.m | 18 +- MoPubSDK/Internal/MPExtendedHitBoxButton.h | 24 ++ MoPubSDK/Internal/MPExtendedHitBoxButton.m | 28 ++ MoPubSDK/Internal/MPMediationManager.m | 2 +- MoPubSDK/Internal/MPRateLimitManager.h | 2 +- MoPubSDK/Internal/MPRateLimitManager.m | 18 +- MoPubSDK/Internal/MPVASTTracking.m | 2 +- .../MRAID/MPMRAIDInterstitialViewController.m | 16 +- MoPubSDK/Internal/MRAID/MRController.m | 19 +- .../MRAID/MRExpandModalViewController.h | 15 - .../MRAID/MRExpandModalViewController.m | 36 +- .../Utility/Categories/UIButton+MPAdditions.h | 15 - .../Utility/Categories/UIButton+MPAdditions.m | 36 -- MoPubSDK/Internal/Utility/MPError.h | 5 + MoPubSDK/Internal/Utility/MPError.m | 12 + .../Internal/Utility/MPGeolocationProvider.m | 10 +- MoPubSDK/Internal/Utility/MPGlobal.h | 11 - MoPubSDK/Internal/Utility/MPGlobal.m | 46 +- .../Internal/Utility/MPIdentityProvider.h | 5 + .../Internal/Utility/MPIdentityProvider.m | 11 +- .../Internal/Utility/MPStoreKitProvider.m | 6 +- MoPubSDK/Internal/Utility/MPTimer.h | 13 +- MoPubSDK/Internal/Utility/MPTimer.m | 98 ++--- MoPubSDK/Internal/VAST/MPVASTManager.h | 1 - MoPubSDK/Internal/VAST/MPVASTManager.m | 11 - MoPubSDK/Logging/MPLogging.h | 2 +- MoPubSDK/Logging/MPLogging.m | 3 +- MoPubSDK/MOPUBDisplayAgentType.h | 14 +- MoPubSDK/MPAdTargeting.h | 27 +- MoPubSDK/MPAdTargeting.m | 12 + MoPubSDK/MPAdView.h | 37 +- MoPubSDK/MPAdView.m | 125 +++++- MoPubSDK/MPAdViewDelegate.h | 14 +- MoPubSDK/MPConstants.h | 24 +- MoPubSDK/MPConstants.m | 8 + MoPubSDK/MPEngineInfo.h | 51 +++ MoPubSDK/MPEngineInfo.m | 55 +++ MoPubSDK/MPInterstitialAdController.m | 3 +- MoPubSDK/MoPub.h | 7 + MoPubSDK/MoPub.m | 29 +- MoPubSDK/NativeAds/Internal/MPNativeCache.m | 2 +- .../MPTableViewCellImpressionTracker.h | 26 -- .../MPTableViewCellImpressionTracker.m | 85 ---- MoPubSDK/NativeAds/MPCollectionViewAdPlacer.m | 7 +- MoPubSDK/NativeAds/MPNativeAdRequest.m | 17 +- .../NativeAds/MPNativeAdRequestTargeting.m | 2 +- MoPubSDK/NativeAds/MPTableViewAdPlacer.m | 7 +- MoPubSDK/NativeVideo/Internal/MOPUBAVPlayer.m | 7 +- .../MOPUBFullscreenPlayerViewController.m | 35 +- .../NativeVideo/Internal/MOPUBPlayerView.m | 8 +- .../Internal/MOPUBPlayerViewController.m | 8 +- .../NativeVideo/MOPUBNativeVideoAdRenderer.m | 7 +- .../MPMoPubRewardedPlayableCustomEvent.m | 24 +- .../Internal/MPRewardedVideoAdManager.m | 7 +- .../Internal/MPRewardedVideoAdapter.m | 9 +- MoPubSDK/RewardedVideo/MPRewardedVideo.m | 3 +- MoPubSDKFramework/Info.plist | 4 +- MoPubSDKTests/Info.plist | 4 +- MoPubSDKTests/MOPUBExperimentProviderTests.m | 9 + MoPubSDKTests/MPAdConfigurationTests.m | 3 + .../MPAdServerCommunicator+Testing.h | 1 + MoPubSDKTests/MPAdServerCommunicatorTests.m | 131 +++++- MoPubSDKTests/MPAdServerURLBuilderTests.m | 99 ++++- MoPubSDKTests/MPAdView+Testing.m | 4 + MoPubSDKTests/MPAdViewTests.m | 169 +++++++- .../MPAdserverCommunicatorDelegateHandler.h | 4 +- .../MPAdserverCommunicatorDelegateHandler.m | 6 +- MoPubSDKTests/MPBannerAdManagerTests.m | 6 +- .../MPBannerAdapterDelegateHandler.m | 2 +- MoPubSDKTests/MPConsentManager+Testing.h | 4 +- MoPubSDKTests/MPConsentManager+Testing.m | 18 +- MoPubSDKTests/MPConsentManagerTests.m | 246 ++++++++++- MoPubSDKTests/MPHTTPNetworkSession+Testing.h | 4 + MoPubSDKTests/MPHTTPNetworkSessionTests.m | 16 + .../MPInterstitialAdController+Testing.m | 4 + .../MPInterstitialAdControllerTests.m | 19 + .../MPInterstitialAdManagerDelegateHandler.h | 1 + MoPubSDKTests/MPInterstitialAdManagerTests.m | 6 +- MoPubSDKTests/MPLoggingHandler.h | 20 + MoPubSDKTests/MPLoggingHandler.m | 32 ++ MoPubSDKTests/MPMediationManager+Testing.h | 6 +- MoPubSDKTests/MPMediationManagerTests.m | 6 + MoPubSDKTests/MPNativeAd+Testing.m | 4 + .../MPNativeAdRequestTargetingTests.m | 24 ++ MoPubSDKTests/MPNativeAdRequestTests.m | 38 ++ MoPubSDKTests/MPRateLimitManagerTests.m | 15 + MoPubSDKTests/MPRewardedVideo+Testing.h | 1 + MoPubSDKTests/MPRewardedVideo+Testing.m | 8 + MoPubSDKTests/MPRewardedVideoAdManagerTests.m | 6 +- MoPubSDKTests/MPRewardedVideoTests.m | 48 +++ MoPubSDKTests/MPTimerTests.m | 30 -- MoPubSDKTests/MPURLResolverTests.m | 107 ++++- MoPubSDKTests/MRController+Testing.m | 4 + .../MPBannerAdDetailViewController.m | 7 +- .../MPNativeAdDetailViewController.m | 2 +- .../MoPubSampleApp.xcodeproj/project.pbxproj | 15 +- .../xcschemes/MoPubSampleApp.xcscheme | 10 +- README.md | 26 +- mopub-ios-sdk.podspec | 4 +- 164 files changed, 2885 insertions(+), 1970 deletions(-) create mode 100644 Canary/Canary/AppDelegate+Initialization.swift delete mode 100644 Canary/Canary/Formats/LeaderboardAdViewController.swift create mode 100644 Gemfile.lock delete mode 100644 MoPubSDK/Internal/Common/MPAdBrowserController.h delete mode 100644 MoPubSDK/Internal/Common/MPAdBrowserController.m delete mode 100644 MoPubSDK/Internal/Common/MPAdBrowserController.xib create mode 100644 MoPubSDK/Internal/MPExtendedHitBoxButton.h create mode 100644 MoPubSDK/Internal/MPExtendedHitBoxButton.m delete mode 100644 MoPubSDK/Internal/Utility/Categories/UIButton+MPAdditions.h delete mode 100644 MoPubSDK/Internal/Utility/Categories/UIButton+MPAdditions.m create mode 100644 MoPubSDK/MPEngineInfo.h create mode 100644 MoPubSDK/MPEngineInfo.m delete mode 100644 MoPubSDK/NativeAds/Internal/MPTableViewCellImpressionTracker.h delete mode 100644 MoPubSDK/NativeAds/Internal/MPTableViewCellImpressionTracker.m create mode 100644 MoPubSDKTests/MPLoggingHandler.h create mode 100644 MoPubSDKTests/MPLoggingHandler.m create mode 100644 MoPubSDKTests/MPNativeAdRequestTargetingTests.m diff --git a/CHANGELOG.md b/CHANGELOG.md index d79d82220..b379a4df4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,20 @@ +## 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 diff --git a/Canary/Canary.xcodeproj/project.pbxproj b/Canary/Canary.xcodeproj/project.pbxproj index ad6a13cb1..ecf506a34 100644 --- a/Canary/Canary.xcodeproj/project.pbxproj +++ b/Canary/Canary.xcodeproj/project.pbxproj @@ -79,6 +79,8 @@ 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 */; }; + BC4309C822BD811400A7CBE5 /* AppDelegate+Initialization.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC4309C722BD811400A7CBE5 /* AppDelegate+Initialization.swift */; }; + BC4309C922BD811400A7CBE5 /* AppDelegate+Initialization.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC4309C722BD811400A7CBE5 /* AppDelegate+Initialization.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 */; }; @@ -99,8 +101,6 @@ 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 */; }; @@ -321,6 +321,7 @@ 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 = ""; }; + BC4309C722BD811400A7CBE5 /* AppDelegate+Initialization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+Initialization.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 = ""; }; @@ -331,7 +332,6 @@ 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 = ""; }; @@ -560,6 +560,7 @@ BC7892E8220B9C22007A6218 /* AccessibilityIdentifier.swift */, BC4FE3052208C57A00B5D240 /* AdapterVersions */, BC33E0361FBB627B0060ECBE /* AppDelegate.swift */, + BC4309C722BD811400A7CBE5 /* AppDelegate+Initialization.swift */, BC00C5B7208FCF46008E0AEB /* AppDelegate+Consent.swift */, BC33E03F1FBB627B0060ECBE /* Assets.xcassets */, BC04944520F911C900CFD9C2 /* Base */, @@ -699,7 +700,6 @@ BC525EE22130A61D007B1761 /* BaseNativeAdDataSource.swift */, BC525ED3212F2D71007B1761 /* InterstitialAdDataSource.swift */, BC01F64F2004191400D6898B /* InterstitialAdViewController.swift */, - BC525ED0212F229A007B1761 /* LeaderboardAdViewController.swift */, BC525EC7212F15A5007B1761 /* MediumRectangleAdViewController.swift */, BC4CE2A42137343C00CA0220 /* NativeAdCollectionDataSource.swift */, BC3B0C942007DAA5002D28B1 /* NativeAdCollectionViewController.swift */, @@ -1127,16 +1127,12 @@ buildActionMask = 2147483647; files = ( ); - inputFileListPaths = ( - ); inputPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Internal Application/Pods-Internal Application-resources.sh", - "${PODS_ROOT}/TapjoySDK/TapjoySDK_iOS_v12.2.1/Libraries/Tapjoy.embeddedframework/Resources/TapjoyResources.bundle", - "${PODS_ROOT}/TapjoySDK/TapjoySDK_iOS_v12.2.1/Libraries/Tapjoy.embeddedframework/Tapjoy.framework/Versions/A/Resources/TapjoyResources.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"; - outputFileListPaths = ( - ); outputPaths = ( "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/TapjoyResources.bundle", ); @@ -1150,10 +1146,9 @@ buildActionMask = 2147483647; files = ( ); - inputFileListPaths = ( - ); inputPaths = ( "${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", @@ -1181,9 +1176,8 @@ "${BUILT_PRODUCTS_DIR}/nanopb/nanopb.framework", ); name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - ); 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", @@ -1220,16 +1214,12 @@ buildActionMask = 2147483647; files = ( ); - inputFileListPaths = ( - ); inputPaths = ( "${PODS_ROOT}/Target Support Files/Pods-AppStore Application/Pods-AppStore Application-resources.sh", - "${PODS_ROOT}/TapjoySDK/TapjoySDK_iOS_v12.2.1/Libraries/Tapjoy.embeddedframework/Resources/TapjoyResources.bundle", - "${PODS_ROOT}/TapjoySDK/TapjoySDK_iOS_v12.2.1/Libraries/Tapjoy.embeddedframework/Tapjoy.framework/Versions/A/Resources/TapjoyResources.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"; - outputFileListPaths = ( - ); outputPaths = ( "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/TapjoyResources.bundle", ); @@ -1265,10 +1255,9 @@ buildActionMask = 2147483647; files = ( ); - inputFileListPaths = ( - ); 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", @@ -1296,9 +1285,8 @@ "${BUILT_PRODUCTS_DIR}/nanopb/nanopb.framework", ); name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - ); 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", @@ -1407,7 +1395,6 @@ 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 */, @@ -1458,6 +1445,7 @@ BC4CE29F2136FB6100CA0220 /* AdViewController.swift in Sources */, BC3B0C8C20058D7C002D28B1 /* NativeAdView.swift in Sources */, EC469C072260EF1D006CABAA /* TypedNotification.swift in Sources */, + BC4309C822BD811400A7CBE5 /* AppDelegate+Initialization.swift in Sources */, 2A35FAAB21B5DA6200DC8805 /* QRCodeCameraInterfaceViewController.swift in Sources */, BCB4DF25211B913F005BD171 /* AdDataSource.swift in Sources */, BC7892E9220B9C22007A6218 /* AccessibilityIdentifier.swift in Sources */, @@ -1485,7 +1473,6 @@ 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 */, @@ -1507,6 +1494,7 @@ BC647D9B20F967C800FAA12C /* BaseSplitViewController.swift in Sources */, B275DB2E200FD64000F053F8 /* SavedAdsDataSource.swift in Sources */, BCEB5DA72008270300FE5165 /* CGFloat+Random.swift in Sources */, + BC4309C922BD811400A7CBE5 /* AppDelegate+Initialization.swift in Sources */, BC713BAE21700BBC003655B2 /* ReleaseTestingSplitViewController.swift in Sources */, BC003AFE20DC42FE00CD1FEB /* LogingLevelMenuDataSource.swift in Sources */, BC525EEA2135BA4E007B1761 /* PreferredWidthLabel.swift in Sources */, @@ -1787,7 +1775,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 5.7.1; + CURRENT_PROJECT_VERSION = 5.8.0; DEVELOPMENT_TEAM = 4S7XS533V3; INFOPLIST_FILE = Canary/Configuration/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; @@ -1804,7 +1792,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 5.7.1; + CURRENT_PROJECT_VERSION = 5.8.0; DEVELOPMENT_TEAM = 4S7XS533V3; INFOPLIST_FILE = Canary/Configuration/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; @@ -1821,7 +1809,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon.Internal; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 5.7.1; + CURRENT_PROJECT_VERSION = 5.8.0; DEVELOPMENT_TEAM = 4S7XS533V3; INFOPLIST_FILE = "Canary/Internal/Internal-Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; @@ -1839,7 +1827,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon.Internal; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 5.7.1; + CURRENT_PROJECT_VERSION = 5.8.0; DEVELOPMENT_TEAM = 4S7XS533V3; INFOPLIST_FILE = "Canary/Internal/Internal-Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; diff --git a/Canary/Canary/AppDelegate+Initialization.swift b/Canary/Canary/AppDelegate+Initialization.swift new file mode 100644 index 000000000..ab880afd7 --- /dev/null +++ b/Canary/Canary/AppDelegate+Initialization.swift @@ -0,0 +1,72 @@ +// +// AppDelegate+Initialization.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 + +fileprivate let kAdUnitId = "0ac59b0996d947309c33f59d6676399f" + +extension AppDelegate { + /** + 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. + */ + func checkAndInitializeSdk() { + // Already have a valid cached ad unit ID for consent. Just initialize the SDK. + if UserDefaults.standard.cachedAdUnitId.count > 0 { + initializeMoPubSdk(adUnitIdForConsent: UserDefaults.standard.cachedAdUnitId) + return + } + + // Need to prompt for an ad unit. + let prompt: UIAlertController = UIAlertController(title: "MoPub SDK initialization", message: "Enter an ad unit ID to use for consent:", preferredStyle: .alert) + var adUnitIdTextField: UITextField? = nil + 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, handler: { _ in + UserDefaults.standard.cachedAdUnitId = kAdUnitId; + self.initializeMoPubSdk(adUnitIdForConsent: kAdUnitId) + })) + prompt.addAction(UIAlertAction(title: "Use inputted ID", style: .default, handler: { _ in + let adUnitID = adUnitIdTextField?.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""; + UserDefaults.standard.cachedAdUnitId = adUnitID; + self.initializeMoPubSdk(adUnitIdForConsent: adUnitID); + })) + + DispatchQueue.main.async { + self.containerViewController.present(prompt, animated: true, completion: nil) + } + } + + /** + 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. + */ + func initializeMoPubSdk(adUnitIdForConsent: String) { + // MoPub SDK initialization + let sdkConfig = MPMoPubConfiguration(adUnitIdForAppInitialization: adUnitIdForConsent) + sdkConfig.globalMediationSettings = [] + sdkConfig.loggingLevel = .info + + MoPub.sharedInstance().initializeSdk(with: sdkConfig) { + // Update the state of the menu now that the SDK has completed initialization. + if let menuController = self.containerViewController.menuViewController { + menuController.updateIfNeeded() + } + + // Request user consent to collect personally identifiable information + // used for targeted ads + if let tabBarController = self.containerViewController.mainTabBarController { + self.displayConsentDialog(from: tabBarController) + } + } + } + +} diff --git a/Canary/Canary/AppDelegate.swift b/Canary/Canary/AppDelegate.swift index 81c381c3c..b5c9e667d 100644 --- a/Canary/Canary/AppDelegate.swift +++ b/Canary/Canary/AppDelegate.swift @@ -10,7 +10,6 @@ import UIKit import MoPub let kAppId = "112358" -let kAdUnitId = "0ac59b0996d947309c33f59d6676399f" @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { @@ -50,22 +49,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { #endif // MoPub SDK initialization - let sdkConfig = MPMoPubConfiguration(adUnitIdForAppInitialization: kAdUnitId) - sdkConfig.globalMediationSettings = [] - sdkConfig.loggingLevel = .info - - MoPub.sharedInstance().initializeSdk(with: sdkConfig) { - // Update the state of the menu now that the SDK has completed initialization. - if let menuController = self.containerViewController.menuViewController { - menuController.updateIfNeeded() - } - - // Request user consent to collect personally identifiable information - // used for targeted ads - if let tabBarController = self.containerViewController.mainTabBarController { - self.displayConsentDialog(from: tabBarController) - } - } + checkAndInitializeSdk() // Conversion tracking MPAdConversionTracker.shared().reportApplicationOpen(forApplicationID: kAppId) diff --git a/Canary/Canary/Configuration/Info.plist b/Canary/Canary/Configuration/Info.plist index 581143d75..631906eeb 100644 --- a/Canary/Canary/Configuration/Info.plist +++ b/Canary/Canary/Configuration/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 5.7.1 + 5.8.0 CFBundleURLTypes @@ -28,7 +28,7 @@ CFBundleVersion - 5.7.1 + 5.8.0 LSRequiresIPhoneOS NSAppTransportSecurity diff --git a/Canary/Canary/Extensions/UIAlertController+Picker.swift b/Canary/Canary/Extensions/UIAlertController+Picker.swift index 7d7b6077d..69f5eaf81 100644 --- a/Canary/Canary/Extensions/UIAlertController+Picker.swift +++ b/Canary/Canary/Extensions/UIAlertController+Picker.swift @@ -39,8 +39,10 @@ extension UIAlertController { // We need to constrain the edges of the pickerView to the UIAlertController // view so that the pickerView is aligned properly. let constraints: [NSLayoutConstraint] = [ - pickerView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0), - pickerView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0), + 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) diff --git a/Canary/Canary/Extensions/UIView+Nib.swift b/Canary/Canary/Extensions/UIView+Nib.swift index 75c61d859..248827aa3 100644 --- a/Canary/Canary/Extensions/UIView+Nib.swift +++ b/Canary/Canary/Extensions/UIView+Nib.swift @@ -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/UserDefaults+Subscript.swift b/Canary/Canary/Extensions/UserDefaults+Subscript.swift index 2a8a7503d..d4523b4f6 100644 --- a/Canary/Canary/Extensions/UserDefaults+Subscript.swift +++ b/Canary/Canary/Extensions/UserDefaults+Subscript.swift @@ -63,4 +63,23 @@ extension UserDefaults { 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/AdFormat.swift b/Canary/Canary/Formats/AdFormat.swift index ccca6821c..e7bdc2fa1 100644 --- a/Canary/Canary/Formats/AdFormat.swift +++ b/Canary/Canary/Formats/AdFormat.swift @@ -21,16 +21,11 @@ enum AdFormat: String, CaseIterable { 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, CaseIterable { 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 b3620f8fd..5e400bf3d 100644 --- a/Canary/Canary/Formats/AdTableViewController.swift +++ b/Canary/Canary/Formats/AdTableViewController.swift @@ -92,6 +92,10 @@ 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 } override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { diff --git a/Canary/Canary/Formats/BannerAdDataSource.swift b/Canary/Canary/Formats/BannerAdDataSource.swift index 36dc12fd6..f459dd8dd 100644 --- a/Canary/Canary/Formats/BannerAdDataSource.swift +++ b/Canary/Canary/Formats/BannerAdDataSource.swift @@ -29,12 +29,17 @@ class BannerAdDataSource: NSObject, AdDataSource { */ var eventTriggered: [AdEvent: Bool] = [:] + /** + The maximum desired ad size to request on a load + */ + private var maxDesiredAdSize: CGSize = kMPPresetMaxAdSizeMatchFrame + /** Status event titles that correspond to the events found in `MPAdViewDelegate` */ lazy var title: [AdEvent: String] = { var titleStrings: [AdEvent: String] = [:] - titleStrings[.didLoad] = "adViewDidLoadAd(_:)" + titleStrings[.didLoad] = "adViewDidLoadAd(_:, adSize _:)" titleStrings[.didFailToLoad] = "adView(_:, didFailToLoadAdWithError _:)" titleStrings[.willPresentModal] = "willPresentModalViewForAd(_:)" titleStrings[.didDismissModal] = "didDismissModalViewForAd(_:)" @@ -55,7 +60,7 @@ class BannerAdDataSource: NSObject, AdDataSource { /** 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() @@ -63,12 +68,14 @@ 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 @@ -137,7 +144,7 @@ class BannerAdDataSource: NSObject, AdDataSource { // to load. adView.keywords = adUnit.keywords adView.userDataKeywords = adUnit.userDataKeywords - adView.loadAd() + adView.loadAd(withMaxAdSize: maxDesiredAdSize) } } @@ -148,10 +155,15 @@ extension BannerAdDataSource: MPAdViewDelegate { return delegate?.adPresentationViewController } - func adViewDidLoadAd(_ view: MPAdView!) { + func adViewDidLoadAd(_ view: MPAdView!, adSize: CGSize) { isAdLoading = false isAdLoaded = true - setStatus(for: .didLoad) { [weak self] in + + // 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() } } diff --git a/Canary/Canary/Formats/BannerAdViewController.swift b/Canary/Canary/Formats/BannerAdViewController.swift index 796293a9d..a034f2e84 100644 --- a/Canary/Canary/Formats/BannerAdViewController.swift +++ b/Canary/Canary/Formats/BannerAdViewController.swift @@ -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() { @@ -40,8 +47,40 @@ class BannerAdViewController: AdTableViewController { // Fix the banner height so that Auto Layout will correctly resize the table header. if let header = tableView.tableHeaderView { - header.heightAnchor.constraint(equalToConstant: MOPUB_BANNER_SIZE.height).isActive = true + // 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/LeaderboardAdViewController.swift b/Canary/Canary/Formats/LeaderboardAdViewController.swift deleted file mode 100644 index 14a5f625a..000000000 --- a/Canary/Canary/Formats/LeaderboardAdViewController.swift +++ /dev/null @@ -1,64 +0,0 @@ -// -// LeaderboardAdViewController.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 - -@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() - - // Fix the leaderboard height so that Auto Layout will correctly resize the table header. - if let header = tableView.tableHeaderView { - header.heightAnchor.constraint(equalToConstant: MOPUB_LEADERBOARD_SIZE.height).isActive = true - } - } -} - -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 44eacb697..b806dadef 100644 --- a/Canary/Canary/Formats/MediumRectangleAdViewController.swift +++ b/Canary/Canary/Formats/MediumRectangleAdViewController.swift @@ -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() { @@ -40,8 +47,40 @@ class MediumRectangleAdViewController: AdTableViewController { // Fix the medium rectangle height so that Auto Layout will correctly resize the table header. if let header = tableView.tableHeaderView { - header.heightAnchor.constraint(equalToConstant: MOPUB_MEDIUM_RECT_SIZE.height).isActive = true + // 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/Samples/SampleAds.plist b/Canary/Canary/Samples/SampleAds.plist index 64d4044d6..50f969381 100644 --- a/Canary/Canary/Samples/SampleAds.plist +++ b/Canary/Canary/Samples/SampleAds.plist @@ -22,7 +22,7 @@ name - HTML MRECT Banner + HTML Medium Rectangle adUnitId 2aae44d2ab91424d9850870af33e5af7 override_class diff --git a/Canary/CanaryUnitTests/Info.plist b/Canary/CanaryUnitTests/Info.plist index 76824464a..916cd9387 100644 --- a/Canary/CanaryUnitTests/Info.plist +++ b/Canary/CanaryUnitTests/Info.plist @@ -15,8 +15,8 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 5.7.1 + 5.8.0 CFBundleVersion - 5.7.1 + 5.8.0 diff --git a/Canary/Podfile b/Canary/Podfile index f35a744f7..760e93198 100644 --- a/Canary/Podfile +++ b/Canary/Podfile @@ -49,6 +49,7 @@ if Dir.exists?(mediation_path) && !use_production_mediation 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 @@ -60,6 +61,7 @@ else 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' diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 000000000..2e1a09d7a --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,230 @@ +GEM + remote: https://rubygems.org/ + specs: + CFPropertyList (3.0.0) + activesupport (4.2.11.1) + i18n (~> 0.7) + minitest (~> 5.1) + thread_safe (~> 0.3, >= 0.3.4) + tzinfo (~> 1.1) + addressable (2.6.0) + public_suffix (>= 2.0.2, < 4.0) + apktools (0.7.2) + rubyzip (~> 1.2.1) + atomos (0.1.3) + aws-eventstream (1.0.3) + aws-sdk (2.11.292) + aws-sdk-resources (= 2.11.292) + aws-sdk-core (2.11.292) + aws-sigv4 (~> 1.0) + jmespath (~> 1.0) + aws-sdk-resources (2.11.292) + aws-sdk-core (= 2.11.292) + aws-sigv4 (1.1.0) + aws-eventstream (~> 1.0, >= 1.0.2) + babosa (1.0.2) + claide (1.0.2) + cocoapods (1.7.5) + activesupport (>= 4.0.2, < 5) + claide (>= 1.0.2, < 2.0) + cocoapods-core (= 1.7.5) + cocoapods-deintegrate (>= 1.0.3, < 2.0) + cocoapods-downloader (>= 1.2.2, < 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.1, < 2.0) + cocoapods-try (>= 1.1.0, < 2.0) + colored2 (~> 3.1) + escape (~> 0.0.4) + fourflusher (>= 2.3.0, < 3.0) + gh_inspector (~> 1.0) + molinillo (~> 0.6.6) + nap (~> 1.0) + ruby-macho (~> 1.4) + xcodeproj (>= 1.10.0, < 2.0) + cocoapods-core (1.7.5) + activesupport (>= 4.0.2, < 6) + fuzzy_match (~> 2.0.4) + nap (~> 1.0) + cocoapods-deintegrate (1.0.4) + cocoapods-downloader (1.2.2) + cocoapods-plugins (1.0.0) + nap + cocoapods-search (1.0.0) + cocoapods-stats (1.1.0) + cocoapods-trunk (1.3.1) + 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.1.5) + declarative (0.0.10) + declarative-option (0.1.0) + digest-crc (0.4.1) + domain_name (0.5.20190701) + unf (>= 0.0.5, < 1.0.0) + dotenv (2.7.4) + emoji_regex (1.0.1) + escape (0.0.4) + excon (0.64.0) + faraday (0.15.4) + multipart-post (>= 1.2, < 3) + faraday-cookie_jar (0.0.6) + faraday (>= 0.7.4) + http-cookie (~> 1.0.0) + faraday_middleware (0.13.1) + faraday (>= 0.7.4, < 1.0) + fastimage (2.1.5) + fastlane (2.126.0) + CFPropertyList (>= 2.3, < 4.0.0) + addressable (>= 2.3, < 3.0.0) + babosa (>= 1.0.2, < 2.0.0) + bundler (>= 1.12.0, < 3.0.0) + colored + commander-fastlane (>= 4.4.6, < 5.0.0) + dotenv (>= 2.1.1, < 3.0.0) + emoji_regex (>= 0.1, < 2.0) + 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.24.0) + google-cloud-storage (>= 1.15.0, < 2.0.0) + highline (>= 1.7.2, < 2.0.0) + json (< 3.0.0) + jwt (~> 2.1.0) + mini_magick (~> 4.5.1) + multi_xml (~> 0.5) + multipart-post (~> 2.0.0) + plist (>= 3.1.0, < 4.0.0) + public_suffix (~> 2.0.0) + rubyzip (>= 1.2.2, < 2.0.0) + security (= 0.1.3) + simctl (~> 1.6.3) + slack-notifier (>= 2.0.0, < 3.0.0) + terminal-notifier (>= 2.0.0, < 3.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.8.1, < 2.0.0) + xcpretty (~> 0.3.0) + xcpretty-travis-formatter (>= 0.0.3) + fastlane-plugin-aws_s3 (1.6.0) + apktools (~> 0.7) + aws-sdk (~> 2.3) + mime-types (~> 3.1) + fourflusher (2.3.1) + fuzzy_match (2.0.4) + gh_inspector (1.1.3) + google-api-client (0.23.9) + 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) + signet (~> 0.9) + google-cloud-core (1.3.0) + google-cloud-env (~> 1.0) + google-cloud-env (1.2.0) + faraday (~> 0.11) + google-cloud-storage (1.16.0) + digest-crc (~> 0.4) + google-api-client (~> 0.23) + google-cloud-core (~> 1.2) + googleauth (>= 0.6.2, < 0.10.0) + googleauth (0.6.7) + faraday (~> 0.12) + jwt (>= 1.4, < 3.0) + memoist (~> 0.16) + multi_json (~> 1.11) + os (>= 0.9, < 2.0) + 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) + jmespath (1.4.0) + json (2.2.0) + jwt (2.1.0) + memoist (0.16.0) + mime-types (3.2.2) + mime-types-data (~> 3.2015) + mime-types-data (3.2019.0331) + mini_magick (4.5.1) + minitest (5.11.3) + molinillo (0.6.6) + multi_json (1.13.1) + multi_xml (0.6.0) + multipart-post (2.0.0) + nanaimo (0.2.6) + nap (1.1.0) + naturally (2.2.0) + netrc (0.11.0) + os (1.0.1) + plist (3.5.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.4.0) + rubyzip (1.2.3) + security (0.1.3) + signet (0.11.0) + 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 (2.0.0) + terminal-table (1.8.0) + unicode-display_width (~> 1.1, >= 1.1.1) + thread_safe (0.3.6) + tty-cursor (0.7.0) + tty-screen (0.7.0) + tty-spinner (0.9.1) + tty-cursor (~> 0.7) + tzinfo (1.2.5) + thread_safe (~> 0.1) + uber (0.1.0) + unf (0.1.4) + unf_ext + unf_ext (0.0.7.6) + unicode-display_width (1.6.0) + word_wrap (1.0.0) + xcodeproj (1.10.0) + CFPropertyList (>= 2.3.3, < 4.0) + atomos (~> 0.1.3) + claide (>= 1.0.2, < 2.0) + colored2 (~> 3.1) + nanaimo (~> 0.2.6) + xcpretty (0.3.0) + rouge (~> 2.0.7) + xcpretty-travis-formatter (1.0.0) + xcpretty (~> 0.2, >= 0.0.7) + +PLATFORMS + ruby + +DEPENDENCIES + cocoapods + fastlane + fastlane-plugin-aws_s3 + +BUNDLED WITH + 2.0.1 diff --git a/MoPubResources/Info.plist b/MoPubResources/Info.plist index e848852bb..4bb41d74d 100644 --- a/MoPubResources/Info.plist +++ b/MoPubResources/Info.plist @@ -13,7 +13,7 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 5.7.1 + 5.8.0 CFBundleSignature ???? CFBundleSupportedPlatforms @@ -21,7 +21,7 @@ iPhoneOS CFBundleVersion - 5.7.1 + 5.8.0 NSHumanReadableCopyright Copyright 2019 Twitter Inc. All rights reserved. NSPrincipalClass diff --git a/MoPubSDK.xcodeproj/project.pbxproj b/MoPubSDK.xcodeproj/project.pbxproj index a5ed40d82..ab2d2d356 100644 --- a/MoPubSDK.xcodeproj/project.pbxproj +++ b/MoPubSDK.xcodeproj/project.pbxproj @@ -54,7 +54,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 */; }; @@ -74,7 +73,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 */; }; @@ -123,7 +121,6 @@ 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 */; }; @@ -170,7 +167,7 @@ 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 */; }; @@ -188,7 +185,6 @@ 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 */; }; @@ -345,12 +341,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, ); }; }; @@ -404,7 +398,6 @@ 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 */; }; @@ -482,7 +475,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 */; }; @@ -547,7 +539,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 */; }; @@ -571,7 +562,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 */; }; @@ -613,7 +603,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 */; }; @@ -636,6 +625,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 */; }; @@ -671,6 +661,9 @@ 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 */; }; @@ -702,6 +695,12 @@ 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 */; }; @@ -758,8 +757,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 */; }; @@ -938,8 +935,8 @@ 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 = ""; }; @@ -1091,7 +1088,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 = ""; }; @@ -1195,8 +1191,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 = ""; }; @@ -1274,8 +1268,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 = ""; }; @@ -1373,8 +1365,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 = ""; }; @@ -1407,6 +1397,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 = ""; }; @@ -1446,6 +1437,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 = ""; }; @@ -1482,6 +1475,10 @@ 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 = ""; }; @@ -1663,6 +1660,7 @@ B23C3B501E353F950003D79E /* Fixtures */, ECD106E92280FE3700398CA5 /* Internal */, 2AAA8DFD1D95C77B006962E8 /* Info.plist */, + BC7003D422679C72001222E7 /* Logging */, BCAED2661DF62E4200D45480 /* Mocks */, B2D54B661ED20C95004E3C7B /* MOPUBExperimentProviderTests.m */, 2AA73B9D1FCF869D001FB787 /* MOPUBNativeVideoAdAdapterTests.m */, @@ -1694,6 +1692,7 @@ 2A75215B1F70720F0099C33C /* MPMoPubNativeAdAdapterTests.m */, BCAED2531DF6194E00D45480 /* MPMoPubRewardedPlayableCustomEventTests.m */, 2A5C4DB71F6B25F20076C08C /* MPNativeAdConfigValuesTests.m */, + BC098E4C226E3B53001E44A6 /* MPNativeAdRequestTargetingTests.m */, BCC54C2B1ECFB81000A4FEF0 /* MPNativeAdRequestTests.m */, 2A89F15D22371ECC00E03010 /* MPRateLimitConfigurationTests.m */, 2A89F1552237136700E03010 /* MPRateLimitManagerTests.m */, @@ -1923,10 +1922,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 */, @@ -1954,8 +1953,6 @@ 2A73E33A226E45F4001FEE03 /* MPStreamAdPlacerDelegate.h */, 573A82EE1B8E488400ED4067 /* MPTableViewAdPlacerCell.h */, 573A82EF1B8E488400ED4067 /* MPTableViewAdPlacerCell.m */, - A77FBEDF18C533B400531E8A /* MPTableViewCellImpressionTracker.h */, - A77FBEE018C533B400531E8A /* MPTableViewCellImpressionTracker.m */, ); path = Internal; sourceTree = ""; @@ -2040,11 +2037,11 @@ 2A2701EE2020FCDC004A72E6 /* MOPUBDisplayAgentType.h */, B2D54B541ED20521004E3C7B /* MOPUBExperimentProvider.h */, B2D54B551ED20521004E3C7B /* MOPUBExperimentProvider.m */, + BC4A4D13217F99ED008A7410 /* MPAdapterConfiguration.h */, AEF9D52E171CA895005AAA5A /* MPAdConversionTracker.h */, AEF9D52F171CA895005AAA5A /* MPAdConversionTracker.m */, BC7F42842141CE7E007EC273 /* MPAdTargeting.h */, BC7F42852141CE7E007EC273 /* MPAdTargeting.m */, - BC4A4D13217F99ED008A7410 /* MPAdapterConfiguration.h */, AEF9D530171CA895005AAA5A /* MPAdView.h */, AEF9D531171CA895005AAA5A /* MPAdView.m */, 2A73E336226E43D3001FEE03 /* MPAdViewDelegate.h */, @@ -2062,6 +2059,8 @@ BC119219207BDD45005DF26E /* MPConsentStatus.h */, AEF9D535171CA895005AAA5A /* MPConstants.h */, 57B86B5619C78F5D00AD50EE /* MPConstants.m */, + BC6B9E6422C135D00027F2F9 /* MPEngineInfo.h */, + BC6B9E6522C135D00027F2F9 /* MPEngineInfo.m */, 2AFEE721225BC38400DD82C8 /* MPImpressionData.h */, 2AFEE722225BC38400DD82C8 /* MPImpressionData.m */, 2A48DF39229601C1003763C2 /* MPImpressionTrackedNotification.h */, @@ -2093,34 +2092,36 @@ AEF9D4D3171CA895005AAA5A /* Common */, AEF9D4E6171CA895005AAA5A /* HTML */, AEF9D4F1171CA895005AAA5A /* Interstitials */, + 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 */, AEF9D500171CA895005AAA5A /* MRAID */, AEF9D513171CA895005AAA5A /* Utility */, A776A5121B5DDE6200095706 /* VAST */, @@ -2153,9 +2154,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 */, @@ -2316,8 +2314,6 @@ BC0ADDEF2080023C000ADEA4 /* NSString+MPConsentStatus.m */, AEF9D515171CA895005AAA5A /* NSURL+MPAdditions.h */, AEF9D516171CA895005AAA5A /* NSURL+MPAdditions.m */, - B28725E01B9FB5D200C0D61B /* UIButton+MPAdditions.h */, - B28725E11B9FB5D200C0D61B /* UIButton+MPAdditions.m */, B28725E21B9FB5D200C0D61B /* UIColor+MPAdditions.h */, B28725E31B9FB5D200C0D61B /* UIColor+MPAdditions.m */, 57AC697F1BB21FC30053C556 /* UIView+MPAdditions.h */, @@ -2406,6 +2402,15 @@ name = Stubs; sourceTree = ""; }; + BC7003D422679C72001222E7 /* Logging */ = { + isa = PBXGroup; + children = ( + BC7003D122679C4F001222E7 /* MPLoggingHandler.h */, + BC7003D222679C4F001222E7 /* MPLoggingHandler.m */, + ); + name = Logging; + sourceTree = ""; + }; BCAED2621DF62CED00D45480 /* Categories */ = { isa = PBXGroup; children = ( @@ -2522,8 +2527,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 */, @@ -2679,7 +2684,6 @@ 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 */, @@ -2688,7 +2692,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 */, @@ -2740,6 +2743,7 @@ 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 */, @@ -2754,7 +2758,6 @@ 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 */, @@ -2781,6 +2784,7 @@ 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 */, @@ -2903,7 +2907,7 @@ AEF1F32016EF9AD700273462 /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0930; + LastUpgradeCheck = 1020; ORGANIZATIONNAME = MoPub; TargetAttributes = { 2AAA8DF81D95C77B006962E8 = { @@ -2923,11 +2927,11 @@ }; buildConfigurationList = AEF1F32316EF9AD700273462 /* Build configuration list for PBXProject "MoPubSDK" */; compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; + developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( - English, en, + Base, ); mainGroup = AEF1F31F16EF9AD700273462; productRefGroup = AEF1F32916EF9AD700273462 /* Products */; @@ -2984,7 +2988,6 @@ 2A711FEE202267B6007A2412 /* MPDAAIcon@2x.png in Resources */, 2A711FEF202267B6007A2412 /* MPDAAIcon@3x.png in Resources */, BC9EF9E0216811B3005BEA65 /* MPAdapters.plist in Resources */, - 2A711FDC20226737007A2412 /* MPAdBrowserController.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3006,7 +3009,6 @@ 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 */, 5724084C1A686770009271B8 /* MPCloseButtonX@2x.png in Resources */, @@ -3064,10 +3066,12 @@ 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 */, + BC098E4D226E3B53001E44A6 /* MPNativeAdRequestTargetingTests.m in Sources */, BC12D24B206304FA0073388B /* MPMediationManager+Testing.m in Sources */, BC86211C1DCBBD5A0012275D /* MPCountdownTimerViewTests.m in Sources */, BCC54C241ECFACE200A4FEF0 /* MPInterstitialAdControllerTests.m in Sources */, @@ -3079,7 +3083,7 @@ B23C3B6E1E36900C0003D79E /* XCTestCase+MPAddition.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 */, @@ -3217,7 +3221,7 @@ 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 */, @@ -3248,7 +3252,6 @@ 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 */, @@ -3306,7 +3309,6 @@ 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 */, @@ -3344,6 +3346,7 @@ 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 */, @@ -3380,9 +3383,6 @@ 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 */, @@ -3417,6 +3417,7 @@ 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 */, @@ -3544,6 +3545,7 @@ 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 */, @@ -3558,6 +3560,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + BC68415D22B179A2002B633C /* MPExtendedHitBoxButton.m in Sources */, BCA00AF31EF47A91006FF762 /* MPBannerAdManager.m in Sources */, BCA00AF41EF47A91006FF762 /* MPBannerCustomEventAdapter.m in Sources */, BCA00AF61EF47A91006FF762 /* MRConstants.m in Sources */, @@ -3568,11 +3571,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 */, @@ -3702,7 +3704,7 @@ CLANG_WARN_SUSPICIOUS_MOVES = YES; CLANG_WARN_UNREACHABLE_CODE = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 5.7.1; + CURRENT_PROJECT_VERSION = 5.8.0; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 4S7XS533V3; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -3743,7 +3745,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 5.7.1; + CURRENT_PROJECT_VERSION = 5.8.0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 4S7XS533V3; ENABLE_NS_ASSERTIONS = NO; @@ -3785,12 +3787,12 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 5.7.1; + CURRENT_PROJECT_VERSION = 5.8.0; DEBUG_INFORMATION_FORMAT = dwarf; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 4S7XS533V3; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 5.7.1; + DYLIB_CURRENT_VERSION = 5.8.0; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_PREPROCESSOR_DEFINITIONS = ( @@ -3832,12 +3834,12 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 5.7.1; + CURRENT_PROJECT_VERSION = 5.8.0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 4S7XS533V3; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 5.7.1; + DYLIB_CURRENT_VERSION = 5.8.0; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_NS_ASSERTIONS = NO; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -3871,7 +3873,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.7.1; + CURRENT_PROJECT_VERSION = 5.8.0; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_GENERATE_DEBUGGING_SYMBOLS = NO; GCC_PREPROCESSOR_DEFINITIONS = ( @@ -3905,7 +3907,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.7.1; + CURRENT_PROJECT_VERSION = 5.8.0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -3932,7 +3934,7 @@ AE515F9A171F1B110086B464 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - CURRENT_PROJECT_VERSION = 5.7.1; + CURRENT_PROJECT_VERSION = 5.8.0; DSTROOT = /tmp/MoPubSDK.dst; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -3952,7 +3954,7 @@ AE515F9B171F1B110086B464 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - CURRENT_PROJECT_VERSION = 5.7.1; + CURRENT_PROJECT_VERSION = 5.8.0; DSTROOT = /tmp/MoPubSDK.dst; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -3972,6 +3974,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; @@ -4025,6 +4028,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; @@ -4069,7 +4073,7 @@ BCA00B951EF47A91006FF762 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - CURRENT_PROJECT_VERSION = 5.7.1; + CURRENT_PROJECT_VERSION = 5.8.0; DSTROOT = /tmp/MoPubSDK.dst; GCC_PRECOMPILE_PREFIX_HEADER = NO; GCC_TREAT_WARNINGS_AS_ERRORS = YES; @@ -4086,7 +4090,7 @@ BCA00B961EF47A91006FF762 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - CURRENT_PROJECT_VERSION = 5.7.1; + CURRENT_PROJECT_VERSION = 5.8.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 @@ @property (nonatomic, weak) id delegate; +@property (nonatomic, readonly) BOOL isMraidAd; - (id)initWithDelegate:(id)delegate; diff --git a/MoPubSDK/Internal/Banners/MPBannerAdManager.m b/MoPubSDK/Internal/Banners/MPBannerAdManager.m index 95c54aea3..855c8c3e7 100644 --- a/MoPubSDK/Internal/Banners/MPBannerAdManager.m +++ b/MoPubSDK/Internal/Banners/MPBannerAdManager.m @@ -166,10 +166,7 @@ - (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.communicator loadURL:URL]; } @@ -181,6 +178,11 @@ - (void)rotateToOrientation:(UIInterfaceOrientation)orientation [self.onscreenAdapter rotateToOrientation:orientation]; } +- (BOOL)isMraidAd +{ + return self.requestingConfiguration.isMraidAd; +} + #pragma mark - Internal - (void)scheduleRefreshTimer @@ -188,11 +190,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); } @@ -200,8 +202,10 @@ - (void)scheduleRefreshTimer - (void)refreshTimerDidFire { - if (!self.loading && self.automaticallyRefreshesContents) { - [self loadAdWithTargeting:self.targeting]; + 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]; } } diff --git a/MoPubSDK/Internal/Banners/MPBannerAdManagerDelegate.h b/MoPubSDK/Internal/Banners/MPBannerAdManagerDelegate.h index ca4a19ea8..19a1ad8fc 100644 --- a/MoPubSDK/Internal/Banners/MPBannerAdManagerDelegate.h +++ b/MoPubSDK/Internal/Banners/MPBannerAdManagerDelegate.h @@ -9,6 +9,8 @@ #import @class MPAdView; +@class MPAdTargeting; + @protocol MPAdViewDelegate; @protocol MPBannerAdManagerDelegate @@ -20,6 +22,11 @@ - (CGSize)containerSize; - (UIViewController *)viewControllerForPresentingModalView; +/** + * The latest ad targeting information for ad refresh and other scenarios. + */ +- (MPAdTargeting *)adTargeting; + - (void)invalidateContentView; - (void)managerDidLoadAd:(UIView *)ad; diff --git a/MoPubSDK/Internal/Banners/MPBaseBannerAdapter.m b/MoPubSDK/Internal/Banners/MPBaseBannerAdapter.m index 541d655a5..a500a3d24 100644 --- a/MoPubSDK/Internal/Banners/MPBaseBannerAdapter.m +++ b/MoPubSDK/Internal/Banners/MPBaseBannerAdapter.m @@ -80,11 +80,10 @@ - (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]; } } diff --git a/MoPubSDK/Internal/Common/AdAlerts/MPAdAlertManager.m b/MoPubSDK/Internal/Common/AdAlerts/MPAdAlertManager.m index 0cbda33d0..954edf54c 100644 --- a/MoPubSDK/Internal/Common/AdAlerts/MPAdAlertManager.m +++ b/MoPubSDK/Internal/Common/AdAlerts/MPAdAlertManager.m @@ -223,7 +223,7 @@ - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceive return YES; // handle the touch } -- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer; +- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer { return YES; } diff --git a/MoPubSDK/Internal/Common/MPAdBrowserController.h b/MoPubSDK/Internal/Common/MPAdBrowserController.h deleted file mode 100644 index 829977226..000000000 --- a/MoPubSDK/Internal/Common/MPAdBrowserController.h +++ /dev/null @@ -1,65 +0,0 @@ -// -// MPAdBrowserController.h -// -// Copyright 2018-2019 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import -#import "MPWebView.h" - -#ifndef CF_RETURNS_RETAINED -#if __has_feature(attribute_cf_returns_retained) -#define CF_RETURNS_RETAINED __attribute__((cf_returns_retained)) -#else -#define CF_RETURNS_RETAINED -#endif -#endif - -@class MPAdConfiguration; - -@protocol MPAdBrowserControllerDelegate; - -@interface MPAdBrowserController : UIViewController - -@property (nonatomic, strong) IBOutlet MPWebView *webView; -@property (nonatomic, strong) IBOutlet UIBarButtonItem *backButton; -@property (nonatomic, strong) IBOutlet UIBarButtonItem *forwardButton; -@property (nonatomic, strong) IBOutlet UIBarButtonItem *refreshButton; -@property (nonatomic, strong) IBOutlet UIBarButtonItem *safariButton; -@property (nonatomic, strong) IBOutlet UIBarButtonItem *doneButton; -@property (nonatomic, strong) IBOutlet UIBarButtonItem *spinnerItem; -@property (nonatomic, strong) UIActivityIndicatorView *spinner; - -@property (nonatomic, weak) id delegate; -@property (nonatomic, copy) NSURL *URL; - -- (instancetype)initWithURL:(NSURL *)URL HTMLString:(NSString *)HTMLString delegate:(id)delegate; -- (instancetype)initWithURL:(NSURL *)URL delegate:(id)delegate; - -// Navigation methods. -- (IBAction)back; -- (IBAction)forward; -- (IBAction)refresh; -- (IBAction)safari; -- (IBAction)done; - -// Drawing methods. -- (CGContextRef)createContext CF_RETURNS_RETAINED; -- (UIImage *)backArrowImage; -- (UIImage *)forwardArrowImage; - -@end - -//////////////////////////////////////////////////////////////////////////////////////////////////// - -@protocol MPAdBrowserControllerDelegate - -- (void)dismissBrowserController:(MPAdBrowserController *)browserController animated:(BOOL)animated; - -@optional - -- (MPAdConfiguration *)adConfiguration; - -@end diff --git a/MoPubSDK/Internal/Common/MPAdBrowserController.m b/MoPubSDK/Internal/Common/MPAdBrowserController.m deleted file mode 100644 index fd0087753..000000000 --- a/MoPubSDK/Internal/Common/MPAdBrowserController.m +++ /dev/null @@ -1,346 +0,0 @@ -// -// MPAdBrowserController.m -// -// Copyright 2018-2019 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" -#import "MoPub+Utility.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. - [MoPub 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) { - [MoPub 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; - - MPLogEvent([MPLogEvent error:error message:nil]); -} - -#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 768503ea9..84415dcca 100644 --- a/MoPubSDK/Internal/Common/MPAdConfiguration.h +++ b/MoPubSDK/Internal/Common/MPAdConfiguration.h @@ -31,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; @@ -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. @@ -110,7 +112,12 @@ 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, strong) MPImpressionData *impressionData; + +/** + Unified ad unit format in its raw string representation. + */ +@property (nonatomic, copy) NSString *format; // viewable impression tracking experiment @property (nonatomic) NSTimeInterval impressionMinVisibleTimeInSec; diff --git a/MoPubSDK/Internal/Common/MPAdConfiguration.m b/MoPubSDK/Internal/Common/MPAdConfiguration.m index 38edceab4..f1b1ecbc6 100644 --- a/MoPubSDK/Internal/Common/MPAdConfiguration.m +++ b/MoPubSDK/Internal/Common/MPAdConfiguration.m @@ -33,6 +33,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"; @@ -141,6 +142,7 @@ - (instancetype)initWithMetadata:(NSDictionary *)metadata data:(NSData *)data ad 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]; @@ -295,8 +297,23 @@ - (Class)setUpCustomEventClassFromMetadata:(NSDictionary *)metadata - (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; @@ -364,6 +381,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 { diff --git a/MoPubSDK/Internal/Common/MPAdDestinationDisplayAgent.h b/MoPubSDK/Internal/Common/MPAdDestinationDisplayAgent.h index dda397c85..f8a06a1d7 100644 --- a/MoPubSDK/Internal/Common/MPAdDestinationDisplayAgent.h +++ b/MoPubSDK/Internal/Common/MPAdDestinationDisplayAgent.h @@ -10,21 +10,19 @@ #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 4bfa58d6e..97f961bd4 100644 --- a/MoPubSDK/Internal/Common/MPAdDestinationDisplayAgent.m +++ b/MoPubSDK/Internal/Common/MPAdDestinationDisplayAgent.m @@ -29,18 +29,11 @@ @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 //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -68,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 @@ -78,6 +68,20 @@ - (void)dismissAllModalContent [self.overlayView hide]; } ++ (BOOL)shouldDisplayContentInApp +{ + switch ([MOPUBExperimentProvider 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; @@ -175,7 +179,7 @@ - (void)handleEnhancedDeeplinkRequest:(MPEnhancedDeeplinkRequest *)request }]; } -- (void)handleEnhancedDeeplinkFallbackForRequest:(MPEnhancedDeeplinkRequest *)request; +- (void)handleEnhancedDeeplinkFallbackForRequest:(MPEnhancedDeeplinkRequest *)request { __weak __typeof__(self) weakSelf = self; [self.enhancedDeeplinkFallbackResolver cancel]; @@ -200,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: @@ -226,19 +226,15 @@ - (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]; + [self presentStoreKitControllerWithProductParameters:parameters fallbackURL:URL]; } else { [self openURLInApplication:URL]; } @@ -302,13 +298,11 @@ - (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.modalPresentationStyle = UIModalPresentationFullScreen; self.storeKitController.delegate = self; - - NSDictionary *parameters = [NSDictionary dictionaryWithObject:identifier - forKey:SKStoreProductParameterITunesItemIdentifier]; [self.storeKitController loadProductWithParameters:parameters completionBlock:nil]; [self hideOverlay]; @@ -323,23 +317,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 { @@ -363,16 +340,6 @@ - (void)hideModalAndNotifyDelegate }]; } -+ (BOOL)shouldUseSafariViewController -{ - MOPUBDisplayAgentType displayAgentType = [MOPUBExperimentProvider displayAgentType]; - if (@available(iOS 9.0, *)) { - return (displayAgentType == MOPUBDisplayAgentTypeSafariViewController); - } - - return NO; -} - - (void)hideOverlay { [self.overlayView hide]; @@ -403,9 +370,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.m b/MoPubSDK/Internal/Common/MPAdImpressionTimer.m index cecd7d787..9af007c27 100644 --- a/MoPubSDK/Internal/Common/MPAdImpressionTimer.m +++ b/MoPubSDK/Internal/Common/MPAdImpressionTimer.m @@ -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; @@ -71,7 +77,7 @@ - (void)startTrackingView:(UIView *)view return; } - if (self.viewVisibilityTimer.isScheduled) { + if (self.viewVisibilityTimer.isCountdownActive) { MPLogInfo(@"viewVisibilityTimer is already started."); return; } diff --git a/MoPubSDK/Internal/Common/MPAdServerCommunicator.m b/MoPubSDK/Internal/Common/MPAdServerCommunicator.m index 8c989c0f8..0491b9d36 100644 --- a/MoPubSDK/Internal/Common/MPAdServerCommunicator.m +++ b/MoPubSDK/Internal/Common/MPAdServerCommunicator.m @@ -64,7 +64,7 @@ - (id)initWithDelegate:(id)delegate self = [super init]; if (self) { _delegate = delegate; - _topLevelJsonKeys = @[kNextUrlMetadataKey]; + _topLevelJsonKeys = @[kNextUrlMetadataKey, kFormatMetadataKey]; } return self; } @@ -105,7 +105,6 @@ - (void)loadURL:(NSURL *)URL // Handle the response. [strongSelf didFinishLoadingWithData:data]; - } errorHandler:^(NSError * error) { // Capture strong self for the duration of this block. __typeof__(self) strongSelf = weakSelf; @@ -171,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. // diff --git a/MoPubSDK/Internal/Common/MPAdServerURLBuilder.h b/MoPubSDK/Internal/Common/MPAdServerURLBuilder.h index 6b42fc856..f90e3cb4a 100644 --- a/MoPubSDK/Internal/Common/MPAdServerURLBuilder.h +++ b/MoPubSDK/Internal/Common/MPAdServerURLBuilder.h @@ -7,12 +7,19 @@ // #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 322b13885..15d2880c2 100644 --- a/MoPubSDK/Internal/Common/MPAdServerURLBuilder.m +++ b/MoPubSDK/Internal/Common/MPAdServerURLBuilder.m @@ -63,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]; @@ -82,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]; @@ -100,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; @@ -132,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]; @@ -199,23 +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[kBackoffMsKey] = [self backoffMillisecondsValueForAdUnitID:adUnitID]; queryParams[kBackoffReasonKey] = [[MPRateLimitManager sharedInstance] lastRateLimitReasonForAdUnitId:adUnitID]; - [queryParams addEntriesFromDictionary:[self locationInformationDictionary:location]]; + [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 @@ -299,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])]; @@ -392,7 +421,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.m b/MoPubSDK/Internal/Common/MPClosableView.m index e68d95323..7f9c643d8 100644 --- a/MoPubSDK/Internal/Common/MPClosableView.m +++ b/MoPubSDK/Internal/Common/MPClosableView.m @@ -217,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.m b/MoPubSDK/Internal/Common/MPCountdownTimerView.m index a4d7023b2..bf7f43476 100644 --- a/MoPubSDK/Internal/Common/MPCountdownTimerView.m +++ b/MoPubSDK/Internal/Common/MPCountdownTimerView.m @@ -76,11 +76,7 @@ - (instancetype)initWithDuration:(NSTimeInterval)seconds timerCompletion:(void(^ _countdownLabel = ({ UILabel * label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, [MPCountdownTimerView intrinsicContentDimension], [MPCountdownTimerView intrinsicContentDimension])]; label.center = ringCenter; - if (@available(iOS 8.2, *)) { - label.font = [UIFont systemFontOfSize:12 weight:UIFontWeightBold]; - } else { - label.font = [UIFont systemFontOfSize:12]; - } + label.font = [UIFont systemFontOfSize:12 weight:UIFontWeightBold]; label.text = [NSString stringWithFormat:@"%.0f", ceil(seconds)]; label.textAlignment = NSTextAlignmentCenter; label.textColor = UIColor.whiteColor; diff --git a/MoPubSDK/Internal/Common/MPProgressOverlayView.h b/MoPubSDK/Internal/Common/MPProgressOverlayView.h index 89127e8f5..f9c624e7d 100644 --- a/MoPubSDK/Internal/Common/MPProgressOverlayView.h +++ b/MoPubSDK/Internal/Common/MPProgressOverlayView.h @@ -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 f93530b8b..19bb87318 100644 --- a/MoPubSDK/Internal/Common/MPProgressOverlayView.m +++ b/MoPubSDK/Internal/Common/MPProgressOverlayView.m @@ -6,316 +6,201 @@ // 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 -- (id)initWithDelegate:(id)delegate -{ - self = [self initWithFrame:MPKeyWindow().bounds]; - if (self) { +#pragma mark - Life Cycle + +- (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]; +- (void)dealloc { + // Unregister self from notifications + [NSNotificationCenter.defaultCenter removeObserver:self]; } -#pragma mark - Public Methods +#pragma mark - Layout -- (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)layoutSubviews { + [super layoutSubviews]; -- (void)hide -{ - [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(enableCloseButton) object:nil]; + // This layout is always full screen for the Application frame inclusive + // of the safe area. + CGRect appFrame = MPApplicationFrame(YES); - 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]; - } -} + // Update the size of this view to always match that of the key window. + self.frame = MPKeyWindow().bounds; -#pragma mark - Drawing and Layout + // 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); -- (void)drawRect:(CGRect)rect -{ - CGContextRef context = UIGraphicsGetCurrentContext(); + // Progress indicator container should be centered. + self.progressContainer.center = self.center; - CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); - - static const CGFloat input_value_range[2] = {0, 1}; - static const CGFloat output_value_range[8] = {0, 1, 0, 1, 0, 1, 0, 1}; - CGFunctionCallbacks callbacks = {0, exponentialDecayInterpolation, NULL}; - - CGFunctionRef shadingFunction = CGFunctionCreate((__bridge void *)(self), 1, input_value_range, 4, - output_value_range, &callbacks); + // 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.m b/MoPubSDK/Internal/Common/MPRealTimeTimer.m index 304467514..7c3e39160 100644 --- a/MoPubSDK/Internal/Common/MPRealTimeTimer.m +++ b/MoPubSDK/Internal/Common/MPRealTimeTimer.m @@ -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]; diff --git a/MoPubSDK/Internal/Common/MPURLActionInfo.h b/MoPubSDK/Internal/Common/MPURLActionInfo.h index 28f32692e..5df4db18c 100644 --- a/MoPubSDK/Internal/Common/MPURLActionInfo.h +++ b/MoPubSDK/Internal/Common/MPURLActionInfo.h @@ -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 7256c9879..6dea319a4 100644 --- a/MoPubSDK/Internal/Common/MPURLActionInfo.m +++ b/MoPubSDK/Internal/Common/MPURLActionInfo.m @@ -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.m b/MoPubSDK/Internal/Common/MPURLResolver.m index 2c14d03c5..07e02a58a 100644 --- a/MoPubSDK/Internal/Common/MPURLResolver.m +++ b/MoPubSDK/Internal/Common/MPURLResolver.m @@ -32,7 +32,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 +66,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 +153,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 +212,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 @@ -281,19 +366,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 +386,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/HTML/MPAdWebViewAgent.h b/MoPubSDK/Internal/HTML/MPAdWebViewAgent.h index c48f61a11..feadb0161 100644 --- a/MoPubSDK/Internal/HTML/MPAdWebViewAgent.h +++ b/MoPubSDK/Internal/HTML/MPAdWebViewAgent.h @@ -31,7 +31,6 @@ typedef NSUInteger MPAdWebViewEvent; - (id)initWithAdWebViewFrame:(CGRect)frame delegate:(id)delegate; - (void)loadConfiguration:(MPAdConfiguration *)configuration; -- (void)rotateToOrientation:(UIInterfaceOrientation)orientation; - (void)invokeJavaScriptForEvent:(MPAdWebViewEvent)event; - (void)forceRedraw; diff --git a/MoPubSDK/Internal/HTML/MPAdWebViewAgent.m b/MoPubSDK/Internal/HTML/MPAdWebViewAgent.m index d8f707cd9..b8fceca56 100644 --- a/MoPubSDK/Internal/HTML/MPAdWebViewAgent.m +++ b/MoPubSDK/Internal/HTML/MPAdWebViewAgent.m @@ -49,7 +49,7 @@ - (void)interceptURL:(NSURL *)URL; @implementation MPAdWebViewAgent -- (id)initWithAdWebViewFrame:(CGRect)frame delegate:(id)delegate; +- (id)initWithAdWebViewFrame:(CGRect)frame delegate:(id)delegate { self = [super init]; if (self) { @@ -87,7 +87,7 @@ - (void)handleInteraction:(UITapGestureRecognizer *)sender #pragma mark - -- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer; +- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer { return YES; } @@ -332,26 +332,6 @@ - (void)rotateToOrientation:(UIInterfaceOrientation)orientation - (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 diff --git a/MoPubSDK/Internal/HTML/MPHTMLBannerCustomEvent.m b/MoPubSDK/Internal/HTML/MPHTMLBannerCustomEvent.m index b75bac51e..5e4047be2 100644 --- a/MoPubSDK/Internal/HTML/MPHTMLBannerCustomEvent.m +++ b/MoPubSDK/Internal/HTML/MPHTMLBannerCustomEvent.m @@ -44,7 +44,7 @@ - (void)dealloc - (void)rotateToOrientation:(UIInterfaceOrientation)newOrientation { - [self.bannerAgent rotateToOrientation:newOrientation]; + [self.bannerAgent forceRedraw]; } #pragma mark - MPAdWebViewAgentDelegate diff --git a/MoPubSDK/Internal/HTML/MPHTMLInterstitialViewController.m b/MoPubSDK/Internal/HTML/MPHTMLInterstitialViewController.m index 7ca950f5d..c51ee88b1 100644 --- a/MoPubSDK/Internal/HTML/MPHTMLInterstitialViewController.m +++ b/MoPubSDK/Internal/HTML/MPHTMLInterstitialViewController.m @@ -102,9 +102,8 @@ - (void)didDismissInterstitial - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator { [coordinator animateAlongsideTransition:^(id context) { - UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation]; - [self.backingViewAgent rotateToOrientation:orientation]; - } completion:nil]; + [self.backingViewAgent forceRedraw]; + } completion:nil]; [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]; } diff --git a/MoPubSDK/Internal/HTML/MPWebView.h b/MoPubSDK/Internal/HTML/MPWebView.h index 20c2bf77d..ca760eb4d 100644 --- a/MoPubSDK/Internal/HTML/MPWebView.h +++ b/MoPubSDK/Internal/HTML/MPWebView.h @@ -66,10 +66,6 @@ typedef void (^MPWebViewJavascriptEvaluationCompletionHandler)(id result, NSErro @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,7 +73,6 @@ textEncodingName:(NSString *)encodingName @property (nonatomic) BOOL allowsLinkPreview; @property (nonatomic, readonly) BOOL allowsPictureInPictureMediaPlayback; -#endif + (void)forceWKWebView:(BOOL)shouldForce; + (BOOL)isForceWKWebView; diff --git a/MoPubSDK/Internal/HTML/MPWebView.m b/MoPubSDK/Internal/HTML/MPWebView.m index bc76249b1..17743c2bf 100644 --- a/MoPubSDK/Internal/HTML/MPWebView.m +++ b/MoPubSDK/Internal/HTML/MPWebView.m @@ -75,14 +75,10 @@ - (void)setUpStepsForceUIWebView:(BOOL)forceUIWebView { 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.requiresUserActionForMediaPlayback = kMoPubRequiresUserActionForMediaPlaybackDefault; config.userContentController = contentController; - if (@available(iOS 11.0, *)) { + if (@available(iOS 11, *)) { [WKContentRuleListStore.defaultStore compileContentRuleListForIdentifier:@"ContentBlockingRules" encodedContentRuleList:MPContentBlocker.blockedResourcesList completionHandler:^(WKContentRuleList * rulesList, NSError * error) { if (error == nil) { [config.userContentController addContentRuleList:rulesList]; @@ -207,7 +203,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 @@ -250,7 +246,7 @@ - (void)setShouldConformToSafeArea:(BOOL)shouldConformToSafeArea { } - (void)constrainView:(UIView *)view shouldUseSafeArea:(BOOL)shouldUseSafeArea { - if (@available(iOS 11.0, *)) { + if (@available(iOS 11, *)) { view.translatesAutoresizingMaskIntoConstraints = NO; if (self.webViewLayoutConstraints) { @@ -290,7 +286,7 @@ - (void)loadData:(NSData *)data MIMEType:MIMEType textEncodingName:encodingName baseURL:baseURL]; - } else if (@available(iOS 9.0, *)) { + } else { [self.wkWebView loadData:data MIMEType:MIMEType characterEncodingName:encodingName @@ -368,25 +364,19 @@ - (void)goForward { } - (void)setAllowsLinkPreview:(BOOL)allowsLinkPreview { - if (@available(iOS 9.0, *)) { - if (self.uiWebView) { - self.uiWebView.allowsLinkPreview = allowsLinkPreview; - } else { - self.wkWebView.allowsLinkPreview = allowsLinkPreview; - } + if (self.uiWebView) { + self.uiWebView.allowsLinkPreview = allowsLinkPreview; + } else { + self.wkWebView.allowsLinkPreview = allowsLinkPreview; } } - (BOOL)allowsLinkPreview { - if (@available(iOS 9.0, *)) { - if (self.uiWebView) { - return self.uiWebView.allowsLinkPreview; - } else { - return self.wkWebView.allowsLinkPreview; - } + if (self.uiWebView) { + return self.uiWebView.allowsLinkPreview; + } else { + return self.wkWebView.allowsLinkPreview; } - - return NO; } - (void)setScalesPageToFit:(BOOL)scalesPageToFit { @@ -452,13 +442,7 @@ - (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; } } @@ -466,26 +450,16 @@ - (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; - } + if (self.uiWebView) { + return self.uiWebView.allowsPictureInPictureMediaPlayback; + } else { + return self.wkWebView.configuration.allowsPictureInPictureMediaPlayback; } - - return NO; } #pragma mark - UIWebViewDelegate @@ -633,11 +607,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.m b/MoPubSDK/Internal/Interstitials/MPBaseInterstitialAdapter.m index 335cdcde8..f4b2f462b 100644 --- a/MoPubSDK/Internal/Interstitials/MPBaseInterstitialAdapter.m +++ b/MoPubSDK/Internal/Interstitials/MPBaseInterstitialAdapter.m @@ -68,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]; } } diff --git a/MoPubSDK/Internal/Interstitials/MPInterstitialAdManager.m b/MoPubSDK/Internal/Interstitials/MPInterstitialAdManager.m index decdb55b2..aa7dc728e 100644 --- a/MoPubSDK/Internal/Interstitials/MPInterstitialAdManager.m +++ b/MoPubSDK/Internal/Interstitials/MPInterstitialAdManager.m @@ -90,10 +90,7 @@ - (void)loadInterstitialWithAdUnitID:(NSString *)ID targeting:(MPAdTargeting *)t [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]]; } } @@ -171,7 +168,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]; diff --git a/MoPubSDK/Internal/Interstitials/MPInterstitialViewController.h b/MoPubSDK/Internal/Interstitials/MPInterstitialViewController.h index 06344da4c..0dd49904b 100644 --- a/MoPubSDK/Internal/Interstitials/MPInterstitialViewController.h +++ b/MoPubSDK/Internal/Interstitials/MPInterstitialViewController.h @@ -7,6 +7,7 @@ // #import +#import "MPExtendedHitBoxButton.h" #import "MPGlobal.h" @class CLLocation; @@ -19,7 +20,7 @@ @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 complete:(void(^)(NSError *))complete; diff --git a/MoPubSDK/Internal/Interstitials/MPInterstitialViewController.m b/MoPubSDK/Internal/Interstitials/MPInterstitialViewController.m index 12109ee01..3b19877a3 100644 --- a/MoPubSDK/Internal/Interstitials/MPInterstitialViewController.m +++ b/MoPubSDK/Internal/Interstitials/MPInterstitialViewController.m @@ -9,9 +9,7 @@ #import "MPInterstitialViewController.h" #import "MPError.h" -#import "MPGlobal.h" #import "MPLogging.h" -#import "UIButton+MPAdditions.h" static const CGFloat kCloseButtonPadding = 5.0; static const CGFloat kCloseButtonEdgeInset = 5.0; @@ -19,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 @@ -38,6 +33,7 @@ - (void)viewDidLoad [super viewDidLoad]; self.view.backgroundColor = [UIColor blackColor]; + self.modalPresentationStyle = UIModalPresentationFullScreen; } - (BOOL)prefersHomeIndicatorAutoHidden { @@ -56,10 +52,6 @@ - (void)presentInterstitialFromViewController:(UIViewController *)controller com } [self willPresentInterstitial]; - - self.applicationHasStatusBar = !([UIApplication sharedApplication].isStatusBarHidden); - [self setApplicationStatusBarHidden:YES]; - [self layoutCloseButton]; [controller presentViewController:self animated:MP_ANIMATED completion:^{ @@ -100,7 +92,7 @@ - (BOOL)shouldDisplayCloseButton - (UIButton *)closeButton { if (!_closeButton) { - _closeButton = [UIButton buttonWithType:UIButtonTypeCustom]; + _closeButton = [MPExtendedHitBoxButton buttonWithType:UIButtonTypeCustom]; _closeButton.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleBottomMargin; @@ -126,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], @@ -171,8 +163,6 @@ - (void)closeButtonPressed - (void)dismissInterstitialAnimated:(BOOL)animated { - [self setApplicationStatusBarHidden:!self.applicationHasStatusBar]; - [self willDismissInterstitial]; UIViewController *presentingViewController = self.presentingViewController; @@ -186,15 +176,6 @@ - (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; diff --git a/MoPubSDK/Internal/MPAdServerKeys.h b/MoPubSDK/Internal/MPAdServerKeys.h index 1d46af416..35b041777 100644 --- a/MoPubSDK/Internal/MPAdServerKeys.h +++ b/MoPubSDK/Internal/MPAdServerKeys.h @@ -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; @@ -44,6 +47,8 @@ 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; diff --git a/MoPubSDK/Internal/MPAdServerKeys.m b/MoPubSDK/Internal/MPAdServerKeys.m index 8edabbb90..06d3936b6 100644 --- a/MoPubSDK/Internal/MPAdServerKeys.m +++ b/MoPubSDK/Internal/MPAdServerKeys.m @@ -13,9 +13,12 @@ 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"; @@ -44,6 +47,8 @@ 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"; diff --git a/MoPubSDK/Internal/MPConsentDialogViewController.m b/MoPubSDK/Internal/MPConsentDialogViewController.m index c192ab22b..2b13b97f9 100644 --- a/MoPubSDK/Internal/MPConsentDialogViewController.m +++ b/MoPubSDK/Internal/MPConsentDialogViewController.m @@ -49,6 +49,9 @@ - (instancetype)initWithDialogHTML:(NSString *)dialogHTML { // Initialize web view [self setUpWebView]; + + // Ensure fullscreen presentation + self.modalPresentationStyle = UIModalPresentationFullScreen; } return self; @@ -106,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], @@ -134,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], diff --git a/MoPubSDK/Internal/MPConsentManager.h b/MoPubSDK/Internal/MPConsentManager.h index bd8803f98..0ad842c04 100644 --- a/MoPubSDK/Internal/MPConsentManager.h +++ b/MoPubSDK/Internal/MPConsentManager.h @@ -14,11 +14,24 @@ @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. */ diff --git a/MoPubSDK/Internal/MPConsentManager.m b/MoPubSDK/Internal/MPConsentManager.m index ad6b6937c..ada4d33dc 100644 --- a/MoPubSDK/Internal/MPConsentManager.m +++ b/MoPubSDK/Internal/MPConsentManager.m @@ -25,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"; @@ -137,16 +138,9 @@ - (instancetype)init { _consentDialogViewController = nil; _syncFrequency = kDefaultRefreshInterval; - // Initializing the timer must be done last since it depends on the value of _syncFrequency - __weak __typeof__(self) weakSelf = self; - dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - // 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 since - // the same `dispatch_once` is called twice for `MPConsentManager.sharedManager` in the - // same call stack. To avoid this crash, call `newNextUpdateTimer` asynchronusly for now. - weakSelf.nextUpdateTimer = [weakSelf newNextUpdateTimer]; - }); + // Initializing the timer must be done last since it depends on the + // value of _syncFrequency + _nextUpdateTimer = [self newNextUpdateTimer]; } return self; @@ -528,8 +522,10 @@ - (void)synchronizeConsentWithCompletion:(void (^ _Nonnull)(NSError * error))com // 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:@""]) { - MPLogInfo(@"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 { MPLogDebug(@"Ad unit used for GDPR sync: %@", self.adUnitIdUsedForConsent); } @@ -546,51 +542,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) { - // Complete with error. - MPLogEvent([MPLogEvent consentSyncFailedWithError:deserializationError]); - completion(deserializationError); - } - else if (![strongSelf 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` - strongSelf.nextUpdateTimer = [strongSelf newNextUpdateTimer]; + [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]; - MPLogEvent([MPLogEvent consentSyncFailedWithError:error]); - 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 /** @@ -878,6 +887,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) @@ -928,7 +975,7 @@ - (void)setForceIsGDPRApplicable:(BOOL)forceIsGDPRApplicable { } - (BOOL)forceIsGDPRApplicable { - return [[NSUserDefaults standardUserDefaults] boolForKey:kForceGDPRAppliesStorageKey]; + return [NSUserDefaults.standardUserDefaults boolForKey:kForceGDPRAppliesStorageKey]; } #pragma mark - Read Only Properties diff --git a/MoPubSDK/Internal/MPCoreInstanceProvider.h b/MoPubSDK/Internal/MPCoreInstanceProvider.h index 0f36d0ff9..18981be1d 100644 --- a/MoPubSDK/Internal/MPCoreInstanceProvider.h +++ b/MoPubSDK/Internal/MPCoreInstanceProvider.h @@ -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 71e30a125..b18a7353b 100644 --- a/MoPubSDK/Internal/MPCoreInstanceProvider.m +++ b/MoPubSDK/Internal/MPCoreInstanceProvider.m @@ -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"; @@ -180,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; @@ -201,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 @@ -235,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/MPMediationManager.m b/MoPubSDK/Internal/MPMediationManager.m index e672d92bc..677fbf709 100644 --- a/MoPubSDK/Internal/MPMediationManager.m +++ b/MoPubSDK/Internal/MPMediationManager.m @@ -221,7 +221,7 @@ + (NSString *)adapterInformationProvidersFilePath { there are no parameters, @c nil is returned. */ - (NSDictionary *)parametersForAdapter:(id)adapter - overrideConfiguration:(NSDictionary *)configuration { + 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]; diff --git a/MoPubSDK/Internal/MPRateLimitManager.h b/MoPubSDK/Internal/MPRateLimitManager.h index c3565d4d8..61644aec8 100644 --- a/MoPubSDK/Internal/MPRateLimitManager.h +++ b/MoPubSDK/Internal/MPRateLimitManager.h @@ -43,7 +43,7 @@ NS_ASSUME_NONNULL_BEGIN @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 *)lastRateLimitReasonForAdUnitId:(NSString *)adUnitId; +- (NSString * _Nullable)lastRateLimitReasonForAdUnitId:(NSString *)adUnitId; @end diff --git a/MoPubSDK/Internal/MPRateLimitManager.m b/MoPubSDK/Internal/MPRateLimitManager.m index fffbab62b..3b58aae0d 100644 --- a/MoPubSDK/Internal/MPRateLimitManager.m +++ b/MoPubSDK/Internal/MPRateLimitManager.m @@ -36,26 +36,38 @@ - (instancetype)init { } - (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 self.configurationDictionary[adUnitId].isRateLimited; + return adUnitId != nil ? self.configurationDictionary[adUnitId].isRateLimited : NO; } - (NSUInteger)lastRateLimitMillisecondsForAdUnitId:(NSString *)adUnitId { - return self.configurationDictionary[adUnitId].lastRateLimitMilliseconds; + return adUnitId != nil ? self.configurationDictionary[adUnitId].lastRateLimitMilliseconds : 0; } - (NSString *)lastRateLimitReasonForAdUnitId:(NSString *)adUnitId { - return self.configurationDictionary[adUnitId].lastRateLimitReason; + return adUnitId != nil ? self.configurationDictionary[adUnitId].lastRateLimitReason : nil; } @end diff --git a/MoPubSDK/Internal/MPVASTTracking.m b/MoPubSDK/Internal/MPVASTTracking.m index b780c3385..d049ecb3f 100644 --- a/MoPubSDK/Internal/MPVASTTracking.m +++ b/MoPubSDK/Internal/MPVASTTracking.m @@ -102,7 +102,7 @@ @interface MPVASTTracking() @implementation MPVASTTracking -- (instancetype)initWithMPVideoConfig:(MPVideoConfig *)videoConfig videoView:(UIView *)videoView; +- (instancetype)initWithMPVideoConfig:(MPVideoConfig *)videoConfig videoView:(UIView *)videoView { self = [super init]; if (self) { diff --git a/MoPubSDK/Internal/MRAID/MPMRAIDInterstitialViewController.m b/MoPubSDK/Internal/MRAID/MPMRAIDInterstitialViewController.m index a4de081fd..7b12e463c 100644 --- a/MoPubSDK/Internal/MRAID/MPMRAIDInterstitialViewController.m +++ b/MoPubSDK/Internal/MRAID/MPMRAIDInterstitialViewController.m @@ -118,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]; diff --git a/MoPubSDK/Internal/MRAID/MRController.m b/MoPubSDK/Internal/MRAID/MRController.m index b0610f8a5..8b502d018 100644 --- a/MoPubSDK/Internal/MRAID/MRController.m +++ b/MoPubSDK/Internal/MRAID/MRController.m @@ -114,11 +114,11 @@ - (instancetype)initWithAdViewFrame:(CGRect)adViewFrame _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) @@ -533,7 +533,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 @@ -605,8 +604,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; @@ -852,12 +849,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]; diff --git a/MoPubSDK/Internal/MRAID/MRExpandModalViewController.h b/MoPubSDK/Internal/MRAID/MRExpandModalViewController.h index 545495272..41fa8144e 100644 --- a/MoPubSDK/Internal/MRAID/MRExpandModalViewController.h +++ b/MoPubSDK/Internal/MRAID/MRExpandModalViewController.h @@ -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 5716a4ef4..ef9ea50fd 100644 --- a/MoPubSDK/Internal/MRAID/MRExpandModalViewController.m +++ b/MoPubSDK/Internal/MRAID/MRExpandModalViewController.m @@ -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 diff --git a/MoPubSDK/Internal/Utility/Categories/UIButton+MPAdditions.h b/MoPubSDK/Internal/Utility/Categories/UIButton+MPAdditions.h deleted file mode 100644 index 11abfd2d3..000000000 --- a/MoPubSDK/Internal/Utility/Categories/UIButton+MPAdditions.h +++ /dev/null @@ -1,15 +0,0 @@ -// -// UIButton+MPAdditions.h -// -// Copyright 2018-2019 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 3747d757c..000000000 --- a/MoPubSDK/Internal/Utility/Categories/UIButton+MPAdditions.m +++ /dev/null @@ -1,36 +0,0 @@ -// -// UIButton+MPAdditions.m -// -// Copyright 2018-2019 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/MPError.h b/MoPubSDK/Internal/Utility/MPError.h index 620adf240..e82e14465 100644 --- a/MoPubSDK/Internal/Utility/MPError.h +++ b/MoPubSDK/Internal/Utility/MPError.h @@ -36,6 +36,8 @@ typedef enum { MOPUBErrorAdapterFailedToLoadAd, MOPUBErrorFullScreenAdAlreadyOnScreen, MOPUBErrorTooManyRequests, + MOPUBErrorFrameWidthNotSetForFlexibleSize, + MOPUBErrorFrameHeightNotSetForFlexibleSize, } MOPUBErrorCode; @interface NSError (MoPub) @@ -46,6 +48,7 @@ typedef enum { @end @interface NSError (Initialization) ++ (instancetype)sdkMinimumOsVersion:(int)osVersion; + (instancetype)sdkInitializationInProgress; @end @@ -59,6 +62,8 @@ typedef enum { + (instancetype)adResponseFailedToParseWithError:(NSError *)serializationError; + (instancetype)adResponsesNotFound; + (instancetype)fullscreenAdAlreadyOnScreen; ++ (instancetype)frameWidthNotSetForFlexibleSize; ++ (instancetype)frameHeightNotSetForFlexibleSize; @end @interface NSError (Consent) diff --git a/MoPubSDK/Internal/Utility/MPError.m b/MoPubSDK/Internal/Utility/MPError.m index 40bdd79ac..363c92ce9 100644 --- a/MoPubSDK/Internal/Utility/MPError.m +++ b/MoPubSDK/Internal/Utility/MPError.m @@ -29,6 +29,10 @@ + (NSError *)errorWithCode:(MOPUBErrorCode)code localizedDescription:(NSString * @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."]; } @@ -76,6 +80,14 @@ + (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) diff --git a/MoPubSDK/Internal/Utility/MPGeolocationProvider.m b/MoPubSDK/Internal/Utility/MPGeolocationProvider.m index 34175d704..0690057ef 100644 --- a/MoPubSDK/Internal/Utility/MPGeolocationProvider.m +++ b/MoPubSDK/Internal/Utility/MPGeolocationProvider.m @@ -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 ef97c28ca..59d50ae7b 100644 --- a/MoPubSDK/Internal/Utility/MPGlobal.h +++ b/MoPubSDK/Internal/Utility/MPGlobal.h @@ -26,15 +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_7_0 70000 -#define MP_IOS_8_0 80000 -#define MP_IOS_9_0 90000 //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -64,8 +55,6 @@ UIInterfaceOrientationMask MPInterstitialOrientationTypeToUIInterfaceOrientation @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; diff --git a/MoPubSDK/Internal/Utility/MPGlobal.m b/MoPubSDK/Internal/Utility/MPGlobal.m index a41f7812d..df34d3a4e 100644 --- a/MoPubSDK/Internal/Utility/MPGlobal.m +++ b/MoPubSDK/Internal/Utility/MPGlobal.m @@ -42,9 +42,12 @@ CGFloat MPStatusBarHeight() { 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.0, *)) { + if (@available(iOS 11, *)) { if (includeSafeAreaInsets) { // Safe area insets include the status bar offset. UIEdgeInsets safeInsets = UIApplication.sharedApplication.keyWindow.safeAreaInsets; @@ -65,23 +68,9 @@ CGRect MPApplicationFrame(BOOL includeSafeAreaInsets) 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() @@ -202,7 +191,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. @@ -210,14 +199,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) @@ -264,15 +245,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"]; diff --git a/MoPubSDK/Internal/Utility/MPIdentityProvider.h b/MoPubSDK/Internal/Utility/MPIdentityProvider.h index 759a8d443..263cb58ac 100644 --- a/MoPubSDK/Internal/Utility/MPIdentityProvider.h +++ b/MoPubSDK/Internal/Utility/MPIdentityProvider.h @@ -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 df7580026..e420ea1ea 100644 --- a/MoPubSDK/Internal/Utility/MPIdentityProvider.m +++ b/MoPubSDK/Internal/Utility/MPIdentityProvider.m @@ -15,6 +15,7 @@ #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; @@ -48,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) { @@ -108,7 +117,7 @@ + (NSString *)mopubIdentifier:(BOOL)obfuscate 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/MPStoreKitProvider.m b/MoPubSDK/Internal/Utility/MPStoreKitProvider.m index aa4fb1deb..6e3a43a0e 100644 --- a/MoPubSDK/Internal/Utility/MPStoreKitProvider.m +++ b/MoPubSDK/Internal/Utility/MPStoreKitProvider.m @@ -41,11 +41,7 @@ + (BOOL)deviceHasStoreKit + (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]; - } + return [[MPiOS7SafeStoreProductViewController alloc] init]; } @end diff --git a/MoPubSDK/Internal/Utility/MPTimer.h b/MoPubSDK/Internal/Utility/MPTimer.h index 3dfb58db0..7fc00d240 100644 --- a/MoPubSDK/Internal/Utility/MPTimer.h +++ b/MoPubSDK/Internal/Utility/MPTimer.h @@ -15,17 +15,17 @@ NS_ASSUME_NONNULL_BEGIN */ @interface MPTimer : NSObject -/** - * The default run loop mode is @c NSDefaultRunLoopMode. If a new mode is assigned, it will be effective - * for the subsequent @c isScheduled or @c resume calls. - */ -@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 selector:(SEL)aSelector @@ -33,7 +33,6 @@ NS_ASSUME_NONNULL_BEGIN - (BOOL)isValid; - (void)invalidate; -- (BOOL)isScheduled; - (void)scheduleNow; - (void)pause; - (void)resume; diff --git a/MoPubSDK/Internal/Utility/MPTimer.m b/MoPubSDK/Internal/Utility/MPTimer.m index ee7596044..f2d121248 100644 --- a/MoPubSDK/Internal/Utility/MPTimer.m +++ b/MoPubSDK/Internal/Utility/MPTimer.m @@ -14,6 +14,7 @@ @interface MPTimer () @property (nonatomic, assign) NSTimeInterval timeInterval; @property (nonatomic, strong) NSTimer *timer; +@property (nonatomic, assign) BOOL isRepeatingTimer; @property (nonatomic, assign) BOOL isCountdownActive; @property (nonatomic, weak) id target; @@ -27,37 +28,58 @@ + (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; } -- (void)dealloc -{ - [self.timer invalidate]; ++ (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]; } -/** - This is the designated run loop that the timer should attach to. - */ -- (NSRunLoop *)runloop +- (void)dealloc { - return [NSRunLoop mainRunLoop]; // use the main run loop to make sure the timer stays alive + [self.timer invalidate]; } - (void)timerDidFire { @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; @@ -86,52 +108,29 @@ - (void)invalidate } } -- (BOOL)isScheduled -{ - @synchronized (self) { - if (!self.timer) { - return NO; - } - CFRunLoopRef runLoopRef = [self.runloop 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; - } -} - - (void)scheduleNow { + /* + 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); +// MPLogDebug(@"Could not schedule invalidated MPTimer (%p).", self); return; } if (self.isCountdownActive) { - MPLogDebug(@"Tried to schedule an MPTimer (%p) that is already ticking.",self); +// MPLogDebug(@"Tried to schedule an MPTimer (%p) that is already ticking.",self); return; } NSDate *newFireDate = [NSDate dateWithTimeInterval:self.timeInterval sinceDate:[NSDate date]]; [self.timer setFireDate:newFireDate]; - - if ([self isScheduled]) { - MPLogDebug(@"MPTimer is already scheduled (%p).", self); - } else { - MPLogDebug(@"Start MPTimer (%p), should fire in %.1f seconds.", self, self.timeInterval); - [self.runloop addTimer:self.timer forMode:self.runLoopMode]; - } - self.isCountdownActive = YES; } } @@ -149,11 +148,6 @@ - (void)pause return; } - if (![self isScheduled]) { - MPLogDebug(@"No-op: tried to pause an MPTimer (%p) that was never scheduled.", self); - return; - } - // `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]]; diff --git a/MoPubSDK/Internal/VAST/MPVASTManager.h b/MoPubSDK/Internal/VAST/MPVASTManager.h index 58e402c0c..c40716d0d 100644 --- a/MoPubSDK/Internal/VAST/MPVASTManager.h +++ b/MoPubSDK/Internal/VAST/MPVASTManager.h @@ -17,7 +17,6 @@ typedef enum { @interface MPVASTManager : NSObject -+ (void)fetchVASTWithURL:(NSURL *)URL completion:(void (^)(MPVASTResponse *, NSError *))completion; + (void)fetchVASTWithData:(NSData *)data completion:(void (^)(MPVASTResponse *, NSError *))completion; @end diff --git a/MoPubSDK/Internal/VAST/MPVASTManager.m b/MoPubSDK/Internal/VAST/MPVASTManager.m index 6b7598e03..f8d436b60 100644 --- a/MoPubSDK/Internal/VAST/MPVASTManager.m +++ b/MoPubSDK/Internal/VAST/MPVASTManager.m @@ -26,17 +26,6 @@ @interface MPVASTWrapper (MPVASTManager) @implementation MPVASTManager -+ (void)fetchVASTWithURL:(NSURL *)URL completion:(void (^)(MPVASTResponse *, NSError *))completion -{ - [MPHTTPNetworkSession startTaskWithHttpRequest:[MPURLRequest requestWithURL:URL] responseHandler:^(NSData * _Nonnull data, NSHTTPURLResponse * _Nonnull response) { - [MPVASTManager fetchVASTWithData:data completion:completion]; - } errorHandler:^(NSError * _Nonnull error) { - if (completion != nil) { - completion(nil, error); - } - }]; -} - + (void)fetchVASTWithData:(NSData *)data completion:(void (^)(MPVASTResponse *, NSError *))completion { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ diff --git a/MoPubSDK/Logging/MPLogging.h b/MoPubSDK/Logging/MPLogging.h index 26cbcad28..ae16546aa 100644 --- a/MoPubSDK/Logging/MPLogging.h +++ b/MoPubSDK/Logging/MPLogging.h @@ -63,7 +63,7 @@ Registers a logging destination. @param source Optional source of the event. This will generally be ad unit ID for ad-related events. @param aClass Class that generated the event. */ -+ (void)logEvent:(MPLogEvent *)event source:(NSString * _Nullable)source fromClass:(Class)aClass; ++ (void)logEvent:(MPLogEvent *)event source:(NSString * _Nullable)source fromClass:(Class _Nullable)aClass; @end diff --git a/MoPubSDK/Logging/MPLogging.m b/MoPubSDK/Logging/MPLogging.m index 9be72ab11..55fa2d17d 100644 --- a/MoPubSDK/Logging/MPLogging.m +++ b/MoPubSDK/Logging/MPLogging.m @@ -35,7 +35,8 @@ + (void)removeLogger:(id)logger { } + (void)logEvent:(MPLogEvent *)event source:(NSString *)source fromClass:(Class)aClass { - [MPLogManager.sharedInstance logEvent:event source:source fromClass:NSStringFromClass(aClass)]; + NSString * className = (aClass != Nil ? NSStringFromClass(aClass) : @""); + [MPLogManager.sharedInstance logEvent:event source:source fromClass:className]; } @end diff --git a/MoPubSDK/MOPUBDisplayAgentType.h b/MoPubSDK/MOPUBDisplayAgentType.h index aec2bbcb1..79c043f06 100644 --- a/MoPubSDK/MOPUBDisplayAgentType.h +++ b/MoPubSDK/MOPUBDisplayAgentType.h @@ -7,7 +7,19 @@ // typedef NS_ENUM(NSInteger, MOPUBDisplayAgentType) { + /** + Use in-app views for display agent without escaping the app. @c SFSafariViewController is used + for web browsing, and @c SKStoreProductViewController is used for supported App Store links. + */ MOPUBDisplayAgentTypeInApp = 0, + + /** + Use the iOS Native Safari browser app for display agent. + */ MOPUBDisplayAgentTypeNativeSafari, - MOPUBDisplayAgentTypeSafariViewController + + /** + This exists for historical reason, and it behaves the same as @c MOPUBDisplayAgentTypeInApp. + */ + MOPUBDisplayAgentTypeSafariViewController __attribute__((deprecated)) }; diff --git a/MoPubSDK/MPAdTargeting.h b/MoPubSDK/MPAdTargeting.h index f763c855d..d28248675 100644 --- a/MoPubSDK/MPAdTargeting.h +++ b/MoPubSDK/MPAdTargeting.h @@ -6,7 +6,7 @@ // http://www.mopub.com/legal/sdk-license-agreement/ // -#import +#import @class CLLocation; @@ -15,6 +15,12 @@ */ @interface MPAdTargeting : NSObject +/** + The maximum creative size that can be safely rendered in the ad container. + The size should be in points. + */ +@property (nonatomic, assign) CGSize creativeSafeSize; + /** A string representing a set of non-personally identifiable keywords that should be passed to the MoPub ad server to receive more relevant advertising. @@ -49,4 +55,23 @@ */ @property (nonatomic, copy) NSString * userDataKeywords; +/** + Initializes ad targeting information. + @param size The maximum creative size that can be safely rendered in the ad container. + The size should be in points. + */ +- (instancetype)initWithCreativeSafeSize:(CGSize)size; + +/** + Initializes ad targeting information. + @param size The maximum creative size that can be safely rendered in the ad container. + The size should be in points. + */ ++ (instancetype)targetingWithCreativeSafeSize:(CGSize)size; + +#pragma mark - Unavailable Initializers + +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; + @end diff --git a/MoPubSDK/MPAdTargeting.m b/MoPubSDK/MPAdTargeting.m index e7277f7fb..934c12abb 100644 --- a/MoPubSDK/MPAdTargeting.m +++ b/MoPubSDK/MPAdTargeting.m @@ -10,4 +10,16 @@ @implementation MPAdTargeting +- (instancetype)initWithCreativeSafeSize:(CGSize)size { + if (self = [super init]) { + self.creativeSafeSize = size; + } + + return self; +} + ++ (instancetype)targetingWithCreativeSafeSize:(CGSize)size { + return [[MPAdTargeting alloc] initWithCreativeSafeSize:size]; +} + @end diff --git a/MoPubSDK/MPAdView.h b/MoPubSDK/MPAdView.h index 8232bcab5..f0e8d3346 100644 --- a/MoPubSDK/MPAdView.h +++ b/MoPubSDK/MPAdView.h @@ -21,11 +21,19 @@ typedef enum /** * The MPAdView class provides a view that can display banner advertisements. */ - +IB_DESIGNABLE @interface MPAdView : UIView /** @name Initializing a Banner Ad */ +/** + * Initializes an MPAdView with the given ad unit ID. + * + * @param adUnitId A string representing a MoPub ad unit ID. + * @return A newly initialized ad view corresponding to the given ad unit ID and size. + */ +- (id)initWithAdUnitId:(NSString *)adUnitId; + /** * Initializes an MPAdView with the given ad unit ID and banner size. * @@ -33,7 +41,7 @@ typedef enum * @param size The desired ad size. A list of standard ad sizes is available in MPConstants.h. * @return A newly initialized ad view corresponding to the given ad unit ID and size. */ -- (id)initWithAdUnitId:(NSString *)adUnitId size:(CGSize)size; +- (id)initWithAdUnitId:(NSString *)adUnitId size:(CGSize)size __attribute__((deprecated("Use initWithAdUnitId: instead"))); /** @name Setting and Getting the Delegate */ @@ -54,7 +62,12 @@ typedef enum * application set aside for advertising. If no ad unit ID is set, the ad view will use a default * ID that only receives test ads. */ -@property (nonatomic, copy) NSString *adUnitId; +@property (nonatomic, copy) IBInspectable NSString *adUnitId; + +/** + * The maximum desired ad size. A list of standard ad sizes is available in MPConstants.h. + */ +@property (nonatomic, assign) IBInspectable CGSize maxAdSize; /** * A string representing a set of non-personally identifiable keywords that should be passed to the MoPub ad server to receive @@ -93,7 +106,8 @@ typedef enum /** @name Loading a Banner Ad */ /** - * Requests a new ad from the MoPub ad server. + * Requests a new ad from the MoPub ad server with a maximum desired ad size equal to + * the size of the current @c bounds of this view. * * If the ad view is already loading an ad, this call will be ignored. You may use `forceRefreshAd` * if you would like cancel any existing ad requests and force a new ad to load. @@ -101,7 +115,20 @@ typedef enum - (void)loadAd; /** - * Cancels any existing ad requests and requests a new ad from the MoPub ad server. + * Requests a new ad from the MoPub ad server with the specified maximum desired ad size. + * + * If the ad view is already loading an ad, this call will be ignored. You may use `forceRefreshAd` + * if you would like cancel any existing ad requests and force a new ad to load. + * + * @param size The maximum desired ad size to request. You may specify this value manually, + * or use one of the presets found in @c MPConstants.h for the most common types of maximum ad sizes. + * If using @c kMPPresetMaxAdSizeMatchFrame, the frame will be used as the maximum ad size. + */ +- (void)loadAdWithMaxAdSize:(CGSize)size; + +/** + * Cancels any existing ad requests and requests a new ad from the MoPub ad server + * using the previously loaded maximum desired ad size. */ - (void)forceRefreshAd; diff --git a/MoPubSDK/MPAdView.m b/MoPubSDK/MPAdView.m index b9925b148..ff4711231 100644 --- a/MoPubSDK/MPAdView.m +++ b/MoPubSDK/MPAdView.m @@ -13,6 +13,8 @@ #import "MPBannerAdManagerDelegate.h" #import "MPClosableView.h" #import "MPCoreInstanceProvider.h" +#import "MPError.h" +#import "MPGlobal.h" #import "MPImpressionTrackedNotification.h" #import "MPLogging.h" @@ -20,7 +22,6 @@ @interface MPAdView () @property (nonatomic, strong) MPBannerAdManager *adManager; @property (nonatomic, weak) UIView *adContentView; -@property (nonatomic, assign) CGSize originalSize; @property (nonatomic, assign) MPNativeAdOrientation allowedNativeAdOrientation; @end @@ -30,14 +31,13 @@ @implementation MPAdView #pragma mark - #pragma mark Lifecycle -- (id)initWithAdUnitId:(NSString *)adUnitId size:(CGSize)size +- (id)initWithAdUnitId:(NSString *)adUnitId { - CGRect f = (CGRect){{0, 0}, size}; - if (self = [super initWithFrame:f]) + if (self = [super initWithFrame:CGRectZero]) { self.backgroundColor = [UIColor clearColor]; self.clipsToBounds = YES; - self.originalSize = size; + self.maxAdSize = kMPPresetMaxAdSizeMatchFrame; self.allowedNativeAdOrientation = MPNativeAdOrientationAny; self.adUnitId = (adUnitId) ? adUnitId : DEFAULT_PUB_ID; self.adManager = [[MPBannerAdManager alloc] initWithDelegate:self]; @@ -46,29 +46,60 @@ - (id)initWithAdUnitId:(NSString *)adUnitId size:(CGSize)size return self; } +- (id)initWithAdUnitId:(NSString *)adUnitId size:(CGSize)size +{ + MPAdView * adView = [self initWithAdUnitId:adUnitId]; + adView.frame = ({ + CGRect frame = adView.frame; + frame.size = [MPAdView sizeForContainer:adView adSize:size adUnitId:adUnitId]; + frame; + }); + adView.maxAdSize = size; + return adView; +} + - (void)dealloc { self.adManager.delegate = nil; } +- (void)layoutSubviews +{ + [super layoutSubviews]; + + // Re-center the creative within this container only if the + // creative isn't MRAID. + if (!self.adManager.isMraidAd) { + // Calculate the center using the bounds instead of the center property since bases its + // center relative its superview which may not be correct. + CGPoint center = CGPointMake(floorf(self.bounds.size.width / 2.0), floorf(self.bounds.size.height / 2.0)); + self.adContentView.center = center; + } +} + #pragma mark - - (void)setAdContentView:(UIView *)view { [self.adContentView removeFromSuperview]; _adContentView = view; - [self addSubview:view]; if (view != nil) { + [self addSubview:view]; + [self setNeedsLayout]; + self.userInteractionEnabled = YES; } + else { + self.userInteractionEnabled = NO; + } } - (CGSize)adContentViewSize { // MPClosableView represents an MRAID ad. if (!self.adContentView || [self.adContentView isKindOfClass:[MPClosableView class]]) { - return self.originalSize; + return [MPAdView sizeForContainer:self adSize:self.maxAdSize adUnitId:self.adUnitId]; } else { return self.adContentView.bounds.size; } @@ -81,18 +112,21 @@ - (void)rotateToOrientation:(UIInterfaceOrientation)newOrientation - (void)loadAd { - MPAdTargeting * targeting = [[MPAdTargeting alloc] init]; - targeting.keywords = self.keywords; - targeting.localExtras = self.localExtras; - targeting.location = self.location; - targeting.userDataKeywords = self.userDataKeywords; + [self.adManager loadAdWithTargeting: self.adTargeting]; +} + +- (void)loadAdWithMaxAdSize:(CGSize)size +{ + // Update the maximum desired ad size + self.maxAdSize = size; - [self.adManager loadAdWithTargeting:targeting]; + // Attempt to load an ad. + [self loadAd]; } - (void)refreshAd { - [self loadAd]; + [self loadAdWithMaxAdSize:self.maxAdSize]; } - (void)forceRefreshAd @@ -125,6 +159,46 @@ - (MPNativeAdOrientation)allowedNativeAdsOrientation return self.allowedNativeAdOrientation; } +#pragma mark - Sizing + +/** + Hydrates an ad size to an explicit ad size in points for a given ad container. + If the size is already explicit, nothing will happen. + @param container Container view for the ad + @param adSize Ad size to rehydrate + @param adUnitId Ad unit ID used for logging purposes + @return Rehydrated ad size + */ ++ (CGSize)sizeForContainer:(UIView * _Nullable)container adSize:(CGSize)adSize adUnitId:(NSString * _Nullable)adUnitId +{ + // Hydrating an ad size means resolving the `kMPFlexibleAdSize` value + // into it's final size value based upon the container bounds. + CGSize hydratedAdSize = adSize; + + // Hydrate the width. + if (adSize.width == kMPFlexibleAdSize) { + // Frame hasn't been set, issue a warning. + if (container.bounds.size.width == 0) { + MPLogEvent * event = [MPLogEvent error:[NSError frameWidthNotSetForFlexibleSize] message:nil]; + [MPLogging logEvent:event source:adUnitId fromClass:self.class]; + } + + hydratedAdSize.width = container.bounds.size.width; + } + + if (adSize.height == kMPFlexibleAdSize) { + // Frame hasn't been set, issue a warning. + if (container.bounds.size.height == 0) { + MPLogEvent * event = [MPLogEvent error:[NSError frameHeightNotSetForFlexibleSize] message:nil]; + [MPLogging logEvent:event source:adUnitId fromClass:self.class]; + } + + hydratedAdSize.height = container.bounds.size.height; + } + + return hydratedAdSize; +} + #pragma mark - - (MPAdView *)banner @@ -139,7 +213,7 @@ - (MPAdView *)banner - (CGSize)containerSize { - return self.originalSize; + return [MPAdView sizeForContainer:self adSize:self.maxAdSize adUnitId:self.adUnitId]; } - (UIViewController *)viewControllerForPresentingModalView @@ -147,6 +221,20 @@ - (UIViewController *)viewControllerForPresentingModalView return [self.delegate viewControllerForPresentingModalView]; } +- (MPAdTargeting *)adTargeting { + // Generate the explicit creative safe area size. + CGSize realSize = [MPAdView sizeForContainer:self adSize:self.maxAdSize adUnitId:self.adUnitId]; + + // Build the targeting information + MPAdTargeting * targeting = [MPAdTargeting targetingWithCreativeSafeSize:realSize]; + targeting.keywords = self.keywords; + targeting.localExtras = self.localExtras; + targeting.location = self.location; + targeting.userDataKeywords = self.userDataKeywords; + + return targeting; +} + - (void)invalidateContentView { [self setAdContentView:nil]; @@ -175,7 +263,14 @@ - (void)managerDidLoadAd:(UIView *)ad { [self setAdContentView:ad]; if ([self.delegate respondsToSelector:@selector(adViewDidLoadAd:)]) { +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" [self.delegate adViewDidLoadAd:self]; +#pragma GCC diagnostic pop + } + + if ([self.delegate respondsToSelector:@selector(adViewDidLoadAd:adSize:)]) { + [self.delegate adViewDidLoadAd:self adSize:ad.bounds.size]; } } diff --git a/MoPubSDK/MPAdViewDelegate.h b/MoPubSDK/MPAdViewDelegate.h index 31ef8ac50..791893e6d 100644 --- a/MoPubSDK/MPAdViewDelegate.h +++ b/MoPubSDK/MPAdViewDelegate.h @@ -45,7 +45,19 @@ * * @param view The ad view sending the message. */ -- (void)adViewDidLoadAd:(MPAdView *)view; +- (void)adViewDidLoadAd:(MPAdView *)view __attribute__((deprecated("Deprecated; please use adViewDidLoadAd:adSize: instead."))); + +/** + * Sent when an ad view successfully loads an ad. + * + * Your implementation of this method should insert the ad view into the view hierarchy, if you + * have not already done so. + * + * @param view The ad view sending the message. + * @param adSize The size of the ad that was successfully loaded. It is recommended to resize + * the @c MPAdView frame to match the height of the loaded ad. + */ +- (void)adViewDidLoadAd:(MPAdView *)view adSize:(CGSize)adSize; /** * Sent when an ad view fails to load an ad. diff --git a/MoPubSDK/MPConstants.h b/MoPubSDK/MPConstants.h index 8bc36c473..cc348e467 100644 --- a/MoPubSDK/MPConstants.h +++ b/MoPubSDK/MPConstants.h @@ -14,13 +14,27 @@ #define MP_SERVER_VERSION @"8" #define MP_REWARDED_API_VERSION @"1" #define MP_BUNDLE_IDENTIFIER @"com.mopub.mopub" -#define MP_SDK_VERSION @"5.7.1" +#define MP_SDK_VERSION @"5.8.0" // Sizing constants. -extern CGSize const MOPUB_BANNER_SIZE; -extern CGSize const MOPUB_MEDIUM_RECT_SIZE; -extern CGSize const MOPUB_LEADERBOARD_SIZE; -extern CGSize const MOPUB_WIDE_SKYSCRAPER_SIZE; +extern CGSize const MOPUB_BANNER_SIZE __attribute__((deprecated("Use kMPPresetMaxAdSizeMatchFrame, kMPPresetMaxAdSizeMatchFrame, kMPPresetMaxAdSize50Height, kMPPresetMaxAdSizeBanner90Height, kMPPresetMaxAdSize90Height, kMPPresetMaxAdSize250Height, kMPPresetMaxAdSize280Height, or a custom maximum desired ad area instead"))); +extern CGSize const MOPUB_MEDIUM_RECT_SIZE __attribute__((deprecated("Use kMPPresetMaxAdSizeMatchFrame, kMPPresetMaxAdSizeMatchFrame, kMPPresetMaxAdSize50Height, kMPPresetMaxAdSizeBanner90Height, kMPPresetMaxAdSize90Height, kMPPresetMaxAdSize250Height, kMPPresetMaxAdSize280Height, or a custom maximum desired ad area instead"))); +extern CGSize const MOPUB_LEADERBOARD_SIZE __attribute__((deprecated("Use kMPPresetMaxAdSizeMatchFrame, kMPPresetMaxAdSizeMatchFrame, kMPPresetMaxAdSize50Height, kMPPresetMaxAdSizeBanner90Height, kMPPresetMaxAdSize90Height, kMPPresetMaxAdSize250Height, kMPPresetMaxAdSize280Height, or a custom maximum desired ad area instead"))); +extern CGSize const MOPUB_WIDE_SKYSCRAPER_SIZE __attribute__((deprecated("Use kMPPresetMaxAdSizeMatchFrame, kMPPresetMaxAdSizeMatchFrame, kMPPresetMaxAdSize50Height, kMPPresetMaxAdSizeBanner90Height, kMPPresetMaxAdSize90Height, kMPPresetMaxAdSize250Height, kMPPresetMaxAdSize280Height, or a custom maximum desired ad area instead"))); + +// Convenience presets for requesting maximum ad sizes. +// Custom maximum ad sizes can be requested using an explicit `CGSize`. +extern CGSize const kMPPresetMaxAdSizeMatchFrame; +extern CGSize const kMPPresetMaxAdSize50Height; +extern CGSize const kMPPresetMaxAdSize90Height; +extern CGSize const kMPPresetMaxAdSize250Height; +extern CGSize const kMPPresetMaxAdSize280Height; + +/** + Constant denoting that the dimension should be flexible with respect to + the container. + */ +extern CGFloat const kMPFlexibleAdSize; // Miscellaneous constants. #define MINIMUM_REFRESH_INTERVAL 10.0 diff --git a/MoPubSDK/MPConstants.m b/MoPubSDK/MPConstants.m index 9ac7962ef..4cdb88dbc 100644 --- a/MoPubSDK/MPConstants.m +++ b/MoPubSDK/MPConstants.m @@ -8,11 +8,19 @@ #import "MPConstants.h" +CGFloat const kMPFlexibleAdSize = -1.0f; + CGSize const MOPUB_BANNER_SIZE = { .width = 320.0f, .height = 50.0f }; CGSize const MOPUB_MEDIUM_RECT_SIZE = { .width = 300.0f, .height = 250.0f }; CGSize const MOPUB_LEADERBOARD_SIZE = { .width = 728.0f, .height = 90.0f }; CGSize const MOPUB_WIDE_SKYSCRAPER_SIZE = { .width = 160.0f, .height = 600.0f }; +CGSize const kMPPresetMaxAdSizeMatchFrame = { .width = kMPFlexibleAdSize, .height = kMPFlexibleAdSize }; +CGSize const kMPPresetMaxAdSize50Height = { .width = kMPFlexibleAdSize, .height = 50.0f }; +CGSize const kMPPresetMaxAdSize90Height = { .width = kMPFlexibleAdSize, .height = 90.0f }; +CGSize const kMPPresetMaxAdSize250Height = { .width = kMPFlexibleAdSize, .height = 250.0f }; +CGSize const kMPPresetMaxAdSize280Height = { .width = kMPFlexibleAdSize, .height = 280.0f }; + @implementation MPConstants + (NSTimeInterval)adsExpirationInterval { diff --git a/MoPubSDK/MPEngineInfo.h b/MoPubSDK/MPEngineInfo.h new file mode 100644 index 000000000..37a1ae797 --- /dev/null +++ b/MoPubSDK/MPEngineInfo.h @@ -0,0 +1,51 @@ +// +// MPEngineInfo.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 + +/** + SDK Engine information specifying the wrapper name and version for the MoPub SDK. + */ +@interface MPEngineInfo : NSObject +/** + Name of the engine using the MoPub SDK. This field is @c nil for platform native integrations. + */ +@property (nonatomic, copy, readonly, nullable) NSString * name; + +/** + Version of the engine using the MoPub SDK. This field is @c nil for platform native integrations. + */ +@property (nonatomic, copy, readonly, nullable) NSString * version; + +/** + Required initializer + @param name Name of the engine using the MoPub SDK. + @param version Version of the engine using the MoPub SDK. + @return An initialized instance of the engine information. + */ +- (instancetype)initWithName:(NSString *)name version:(NSString *)version; + +/** + Required initializer + @param name Name of the engine using the MoPub SDK. + @param version Version of the engine using the MoPub SDK. + @return An initialized instance of the engine information. + */ ++ (instancetype)named:(NSString *)name version:(NSString *)version; + +#pragma mark - Disallowed Initializers + +- (instancetype)init NS_UNAVAILABLE; + ++ (instancetype)new NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/MoPubSDK/MPEngineInfo.m b/MoPubSDK/MPEngineInfo.m new file mode 100644 index 000000000..34c33b62f --- /dev/null +++ b/MoPubSDK/MPEngineInfo.m @@ -0,0 +1,55 @@ +// +// MPEngineInfo.m +// +// Copyright 2018-2019 Twitter, Inc. +// Licensed under the MoPub SDK License Agreement +// http://www.mopub.com/legal/sdk-license-agreement/ +// + +#import "MPEngineInfo.h" +#import "MPAdServerKeys.h" + +@implementation MPEngineInfo + +- (instancetype)initWithName:(NSString *)name version:(NSString *)version { + // Both name and version are required fields. + if (name.length == 0 || version.length == 0) { + return nil; + } + + if (self = [super init]) { + _name = name; + _version = version; + } + + return self; +} + ++ (instancetype)named:(NSString *)name version:(NSString *)version { + return [[MPEngineInfo alloc] initWithName:name version:version]; +} + +#pragma mark - NSCoding + +- (instancetype)initWithCoder:(NSCoder *)coder { + // Validate that the required parameters exist. + NSString * name = [coder decodeObjectForKey:kSDKEngineNameKey]; + NSString * version = [coder decodeObjectForKey:kSDKEngineVersionKey]; + if (name.length == 0 || version.length == 0) { + return nil; + } + + return [self initWithName:name version:version]; +} + +- (void)encodeWithCoder:(NSCoder *)coder { + if (self.name.length > 0) { + [coder encodeObject:self.name forKey:kSDKEngineNameKey]; + } + + if (self.version.length > 0) { + [coder encodeObject:self.version forKey:kSDKEngineVersionKey]; + } +} + +@end diff --git a/MoPubSDK/MPInterstitialAdController.m b/MoPubSDK/MPInterstitialAdController.m index c12238622..6a2a7351b 100644 --- a/MoPubSDK/MPInterstitialAdController.m +++ b/MoPubSDK/MPInterstitialAdController.m @@ -9,6 +9,7 @@ #import "MPInterstitialAdController.h" #import "MoPub+Utility.h" #import "MPAdTargeting.h" +#import "MPGlobal.h" #import "MPImpressionTrackedNotification.h" #import "MPInterstitialAdManager.h" #import "MPInterstitialAdManagerDelegate.h" @@ -66,7 +67,7 @@ - (BOOL)ready - (void)loadAd { - MPAdTargeting * targeting = [[MPAdTargeting alloc] init]; + MPAdTargeting * targeting = [MPAdTargeting targetingWithCreativeSafeSize:MPApplicationFrame(YES).size]; targeting.keywords = self.keywords; targeting.localExtras = self.localExtras; targeting.location = self.location; diff --git a/MoPubSDK/MoPub.h b/MoPubSDK/MoPub.h index 0c20bf9b9..9fa627831 100644 --- a/MoPubSDK/MoPub.h +++ b/MoPubSDK/MoPub.h @@ -23,6 +23,7 @@ #import "MPConsentChangedReason.h" #import "MPConsentError.h" #import "MPConsentStatus.h" +#import "MPEngineInfo.h" #import "MPError.h" #import "MPGlobal.h" #import "MPIdentityProvider.h" @@ -187,6 +188,12 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)disableViewability:(MPViewabilityOption)vendors; +/** + Sets the engine that is using this MoPub SDK. + @param info Engine information. + */ +- (void)setEngineInformation:(MPEngineInfo *)info; + @end @interface MoPub (Mediation) diff --git a/MoPubSDK/MoPub.m b/MoPubSDK/MoPub.m index 3c2d545fc..f503f49fc 100644 --- a/MoPubSDK/MoPub.m +++ b/MoPubSDK/MoPub.m @@ -7,6 +7,7 @@ // #import "MoPub.h" +#import "MPAdServerURLBuilder.h" #import "MPConsentManager.h" #import "MPConstants.h" #import "MPCoreInstanceProvider.h" @@ -23,6 +24,8 @@ #import "MPConsentChangedNotification.h" #import "MPSessionTracker.h" +static NSString * const kPublisherEnteredAdUnitIdStorageKey = @"com.mopub.mopub-ios-sdk.initialization.publisher.entered.ad.unit.id"; + @interface MoPub () @property (nonatomic, strong) NSArray *globalMediationSettings; @@ -111,10 +114,15 @@ - (NSString *)bundleIdentifier - (void)initializeSdkWithConfiguration:(MPMoPubConfiguration *)configuration completion:(void(^_Nullable)(void))completionBlock { - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - [self setSdkWithConfiguration:configuration completion:completionBlock]; - }); + if (@available(iOS 9, *)) { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + [self setSdkWithConfiguration:configuration completion:completionBlock]; + }); + } else { + MPLogEvent([MPLogEvent error:[NSError sdkMinimumOsVersion:9] message:nil]); + NSAssert(false, @"MoPub SDK requires iOS 9 and up"); + } } - (void)setSdkWithConfiguration:(MPMoPubConfiguration *)configuration @@ -133,6 +141,12 @@ - (void)setSdkWithConfiguration:(MPMoPubConfiguration *)configuration // Configure the consent manager and synchronize regardless of the result // of `checkForDoNotTrackAndTransition`. dispatch_group_enter(initializationGroup); + // If the publisher has changed their adunit ID for app initialization, clear our adunit ID caches + NSString * cachedPublisherEnteredAdUnitID = [NSUserDefaults.standardUserDefaults stringForKey:kPublisherEnteredAdUnitIdStorageKey]; + if (![configuration.adUnitIdForAppInitialization isEqualToString:cachedPublisherEnteredAdUnitID]) { + [MPConsentManager.sharedManager clearAdUnitIdUsedForConsent]; + [NSUserDefaults.standardUserDefaults setObject:configuration.adUnitIdForAppInitialization forKey:kPublisherEnteredAdUnitIdStorageKey]; + } MPConsentManager.sharedManager.adUnitIdUsedForConsent = configuration.adUnitIdForAppInitialization; MPConsentManager.sharedManager.allowLegitimateInterest = configuration.allowLegitimateInterest; [MPConsentManager.sharedManager checkForDoNotTrackAndTransition]; @@ -189,6 +203,11 @@ - (void)disableViewability:(MPViewabilityOption)vendors [MPViewabilityTracker disableViewability:vendors]; } +- (void)setEngineInformation:(MPEngineInfo *)info +{ + MPAdServerURLBuilder.engineInformation = info; +} + @end @implementation MoPub (Mediation) @@ -223,7 +242,7 @@ @implementation MoPub (Mediation) } - (void)clearCachedNetworks { - return [MPMediationManager.sharedManager clearCache]; + [MPMediationManager.sharedManager clearCache]; } @end diff --git a/MoPubSDK/NativeAds/Internal/MPNativeCache.m b/MoPubSDK/NativeAds/Internal/MPNativeCache.m index b86b2d942..695942d6e 100644 --- a/MoPubSDK/NativeAds/Internal/MPNativeCache.m +++ b/MoPubSDK/NativeAds/Internal/MPNativeCache.m @@ -32,7 +32,7 @@ - (void)removeAllDataFromDisk; @implementation MPNativeCache -+ (instancetype)sharedCache; ++ (instancetype)sharedCache { static dispatch_once_t once; static MPNativeCache *sharedCache; diff --git a/MoPubSDK/NativeAds/Internal/MPTableViewCellImpressionTracker.h b/MoPubSDK/NativeAds/Internal/MPTableViewCellImpressionTracker.h deleted file mode 100644 index 5b7b24629..000000000 --- a/MoPubSDK/NativeAds/Internal/MPTableViewCellImpressionTracker.h +++ /dev/null @@ -1,26 +0,0 @@ -// -// MPTableViewCellImpressionTracker.h -// -// Copyright 2018-2019 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import -#import - -@protocol MPTableViewCellImpressionTrackerDelegate; - -@interface MPTableViewCellImpressionTracker : NSObject - -- (id)initWithTableView:(UITableView *)tableView delegate:(id)delegate; -- (void)startTracking; -- (void)stopTracking; - -@end - -@protocol MPTableViewCellImpressionTrackerDelegate - -- (void)tracker:(MPTableViewCellImpressionTracker *)tracker didDetectVisibleRowsAtIndexPaths:(NSArray *)indexPaths; - -@end diff --git a/MoPubSDK/NativeAds/Internal/MPTableViewCellImpressionTracker.m b/MoPubSDK/NativeAds/Internal/MPTableViewCellImpressionTracker.m deleted file mode 100644 index 7ee922025..000000000 --- a/MoPubSDK/NativeAds/Internal/MPTableViewCellImpressionTracker.m +++ /dev/null @@ -1,85 +0,0 @@ -// -// MPTableViewCellImpressionTracker.m -// -// Copyright 2018-2019 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import "MPTableViewCellImpressionTracker.h" - -@interface MPTableViewCellImpressionTracker () - -@property (nonatomic, strong) UITableView *tableView; -@property (nonatomic, weak) id delegate; -@property (nonatomic, strong) NSTimer *timer; - -@end - -#define MPTableViewCellImpressionTrackerTimeInterval 0.25 - -@implementation MPTableViewCellImpressionTracker - -- (id)initWithTableView:(UITableView *)tableView delegate:(id)delegate -{ - self = [super init]; - if (self) { - _tableView = tableView; - _delegate = delegate; - } - return self; -} - -- (void)dealloc -{ - [_timer invalidate]; -} - -- (void)startTracking -{ - [self.timer invalidate]; - self.timer = [NSTimer timerWithTimeInterval:MPTableViewCellImpressionTrackerTimeInterval target:self selector:@selector(tick:) userInfo:nil repeats:YES]; - - [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes]; -} - -- (void)stopTracking -{ - [self.timer invalidate]; - self.timer = nil; -} - -#pragma mark - Internal - -- (void)tick:(NSTimer *)timer -{ - NSMutableArray *indexPathsForVisibleRows = [[self.tableView indexPathsForVisibleRows] mutableCopy]; - NSUInteger rowCount = [indexPathsForVisibleRows count]; - - // For our purposes, "visible" means that more than half of the cell is on-screen. - // Filter -indexPathsForVisibleRows to fit this definition. - if (rowCount > 1) { - NSIndexPath *firstVisibleRow = [indexPathsForVisibleRows objectAtIndex:0]; - if (![self isMajorityOfCellAtIndexPathVisible:firstVisibleRow]) { - [indexPathsForVisibleRows removeObjectAtIndex:0]; - } - - NSIndexPath *lastVisibleRow = [indexPathsForVisibleRows lastObject]; - if (![self isMajorityOfCellAtIndexPathVisible:lastVisibleRow]) { - [indexPathsForVisibleRows removeLastObject]; - } - } - - if ([indexPathsForVisibleRows count]) { - [self.delegate tracker:self didDetectVisibleRowsAtIndexPaths:[NSArray arrayWithArray:indexPathsForVisibleRows]]; - } -} - -- (BOOL)isMajorityOfCellAtIndexPathVisible:(NSIndexPath *)indexPath -{ - CGRect cellRect = [self.tableView rectForRowAtIndexPath:indexPath]; - CGPoint cellRectMidY = CGPointMake(0, CGRectGetMidY(cellRect)); - return CGRectContainsPoint(self.tableView.bounds, cellRectMidY); -} - -@end diff --git a/MoPubSDK/NativeAds/MPCollectionViewAdPlacer.m b/MoPubSDK/NativeAds/MPCollectionViewAdPlacer.m index 93cb1de27..50d9655ff 100644 --- a/MoPubSDK/NativeAds/MPCollectionViewAdPlacer.m +++ b/MoPubSDK/NativeAds/MPCollectionViewAdPlacer.m @@ -55,8 +55,11 @@ - (instancetype)initWithCollectionView:(UICollectionView *)collectionView viewCo _streamAdPlacer = [MPStreamAdPlacer placerWithViewController:controller adPositioning:positioning rendererConfigurations:rendererConfigurations]; _streamAdPlacer.delegate = self; - _insertionTimer = [MPTimer timerWithTimeInterval:kUpdateVisibleCellsInterval target:self selector:@selector(updateVisibleCells) repeats:YES]; - _insertionTimer.runLoopMode = NSRunLoopCommonModes; + _insertionTimer = [MPTimer timerWithTimeInterval:kUpdateVisibleCellsInterval + target:self + selector:@selector(updateVisibleCells) + repeats:YES + runLoopMode:NSRunLoopCommonModes]; [_insertionTimer scheduleNow]; _originalDataSource = collectionView.dataSource; diff --git a/MoPubSDK/NativeAds/MPNativeAdRequest.m b/MoPubSDK/NativeAds/MPNativeAdRequest.m index cffb4842a..82ebdf9ca 100644 --- a/MoPubSDK/NativeAds/MPNativeAdRequest.m +++ b/MoPubSDK/NativeAds/MPNativeAdRequest.m @@ -84,9 +84,7 @@ - (void)startWithCompletionHandler:(MPNativeAdRequestHandler)handler { if (handler) { self.URL = [MPAdServerURLBuilder URLWithAdUnitID:self.adUnitIdentifier - keywords:self.targeting.keywords - userDataKeywords:self.targeting.userDataKeywords - location:self.targeting.location + targeting:self.targeting desiredAssets:[self.targeting.desiredAssets allObjects] viewability:NO]; @@ -102,9 +100,7 @@ - (void)startForAdSequence:(NSInteger)adSequence withCompletionHandler:(MPNative { if (handler) { self.URL = [MPAdServerURLBuilder URLWithAdUnitID:self.adUnitIdentifier - keywords:self.targeting.keywords - userDataKeywords:self.targeting.userDataKeywords - location:self.targeting.location + targeting:self.targeting desiredAssets:[self.targeting.desiredAssets allObjects] adSequence:adSequence viewability:NO]; @@ -376,11 +372,10 @@ - (void)startTimeoutTimer NSTimeInterval timeInterval = (self.adConfiguration && self.adConfiguration.adTimeoutInterval >= 0) ? self.adConfiguration.adTimeoutInterval : NATIVE_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]; } } diff --git a/MoPubSDK/NativeAds/MPNativeAdRequestTargeting.m b/MoPubSDK/NativeAds/MPNativeAdRequestTargeting.m index dd84c8e8e..1d0d7d252 100644 --- a/MoPubSDK/NativeAds/MPNativeAdRequestTargeting.m +++ b/MoPubSDK/NativeAds/MPNativeAdRequestTargeting.m @@ -15,7 +15,7 @@ @implementation MPNativeAdRequestTargeting + (MPNativeAdRequestTargeting *)targeting { - return [[MPNativeAdRequestTargeting alloc] init]; + return [[MPNativeAdRequestTargeting alloc] initWithCreativeSafeSize:CGSizeZero]; } - (void)setDesiredAssets:(NSSet *)desiredAssets diff --git a/MoPubSDK/NativeAds/MPTableViewAdPlacer.m b/MoPubSDK/NativeAds/MPTableViewAdPlacer.m index ab557bf9e..7b65e6f12 100644 --- a/MoPubSDK/NativeAds/MPTableViewAdPlacer.m +++ b/MoPubSDK/NativeAds/MPTableViewAdPlacer.m @@ -82,8 +82,11 @@ - (void)loadAdsForAdUnitID:(NSString *)adUnitID - (void)loadAdsForAdUnitID:(NSString *)adUnitID targeting:(MPNativeAdRequestTargeting *)targeting { if (!self.insertionTimer) { - self.insertionTimer = [MPTimer timerWithTimeInterval:kUpdateVisibleCellsInterval target:self selector:@selector(updateVisibleCells) repeats:YES]; - self.insertionTimer.runLoopMode = NSRunLoopCommonModes; + self.insertionTimer = [MPTimer timerWithTimeInterval:kUpdateVisibleCellsInterval + target:self + selector:@selector(updateVisibleCells) + repeats:YES + runLoopMode:NSRunLoopCommonModes]; [self.insertionTimer scheduleNow]; } [self.streamAdPlacer loadAdsForAdUnitID:adUnitID targeting:targeting]; diff --git a/MoPubSDK/NativeVideo/Internal/MOPUBAVPlayer.m b/MoPubSDK/NativeVideo/Internal/MOPUBAVPlayer.m index cb7daf570..730ddcdb8 100644 --- a/MoPubSDK/NativeVideo/Internal/MOPUBAVPlayer.m +++ b/MoPubSDK/NativeVideo/Internal/MOPUBAVPlayer.m @@ -105,9 +105,12 @@ - (void)startTimeObserver // in the AVPlayer time observing API that can cause crashes. Also, the AVPlayerItem stall notification // does not always report accurately. if (_playbackTimer == nil) { - _playbackTimer = [[MPCoreInstanceProvider sharedProvider] buildMPTimerWithTimeInterval:kAvPlayerTimerInterval target:self selector:@selector(timerTick) repeats:YES]; // Add timer to main run loop with common modes to allow the timer to tick while user is scrolling. - _playbackTimer.runLoopMode = NSRunLoopCommonModes; + _playbackTimer = [MPTimer timerWithTimeInterval:kAvPlayerTimerInterval + target:self + selector:@selector(timerTick) + repeats:YES + runLoopMode:NSRunLoopCommonModes]; [_playbackTimer scheduleNow]; _lastContinuousPlaybackCMTime = kCMTimeZero; diff --git a/MoPubSDK/NativeVideo/Internal/MOPUBFullscreenPlayerViewController.m b/MoPubSDK/NativeVideo/Internal/MOPUBFullscreenPlayerViewController.m index 8fd21064c..f179a2db3 100644 --- a/MoPubSDK/NativeVideo/Internal/MOPUBFullscreenPlayerViewController.m +++ b/MoPubSDK/NativeVideo/Internal/MOPUBFullscreenPlayerViewController.m @@ -12,6 +12,7 @@ #import "MOPUBPlayerViewController.h" #import "MPAdDestinationDisplayAgent.h" #import "MPCoreInstanceProvider.h" +#import "MPExtendedHitBoxButton.h" #import "MPHTTPNetworkSession.h" #import "MPGlobal.h" #import "MPLogging.h" @@ -20,7 +21,6 @@ #import "MPURLRequest.h" #import "MOPUBActivityIndicatorView.h" #import "UIView+MPAdditions.h" -#import "UIButton+MPAdditions.h" #import "UIColor+MPAdditions.h" static CGFloat const kPrivacyIconFullscreenLeftMargin = 16.0f; @@ -53,9 +53,9 @@ @interface MOPUBFullscreenPlayerViewController () // UI components -@property (nonatomic) UIButton *privacyButton; -@property (nonatomic) UIButton *closeButton; -@property (nonatomic) UIButton *ctaButton; +@property (nonatomic, strong) MPExtendedHitBoxButton *privacyButton; +@property (nonatomic, strong) MPExtendedHitBoxButton *closeButton; +@property (nonatomic, strong) MPExtendedHitBoxButton *ctaButton; @property (nonatomic) MOPUBActivityIndicatorView *stallSpinner; @property (nonatomic) UIActivityIndicatorView *playerNotReadySpinner; @property (nonatomic) UIView *gradientView; @@ -89,6 +89,7 @@ - (instancetype)initWithVideoPlayer:(MOPUBPlayerViewController *)playerControlle _overridePrivacyIcon = properties[kAdPrivacyIconImageUrlKey]; _overridePrivacyIconImage = properties[kAdPrivacyIconUIImageKey]; _overridePrivacyClickUrl = properties[kAdPrivacyIconClickUrlKey]; + self.modalPresentationStyle = UIModalPresentationFullScreen; } return self; } @@ -97,7 +98,6 @@ - (void)viewDidLoad { [super viewDidLoad]; - [self setApplicationStatusBarHidden:YES]; [self.playerController willEnterFullscreen]; self.view.backgroundColor = [UIColor blackColor]; @@ -105,21 +105,21 @@ - (void)viewDidLoad [self createAndAddGradientView]; - self.privacyButton = [UIButton buttonWithType:UIButtonTypeCustom]; + self.privacyButton = [MPExtendedHitBoxButton buttonWithType:UIButtonTypeCustom]; self.privacyButton.frame = CGRectMake(0, 0, kPrivacyIconSize, kPrivacyIconSize); [self setPrivacyIconImageForButton:self.privacyButton]; [self.privacyButton addTarget:self action:@selector(privacyButtonTapped) forControlEvents:UIControlEventTouchUpInside]; - self.privacyButton.mp_TouchAreaInsets = UIEdgeInsetsMake(kDefaultButtonTouchAreaInsets, kDefaultButtonTouchAreaInsets, kDefaultButtonTouchAreaInsets, kDefaultButtonTouchAreaInsets); + self.privacyButton.touchAreaInsets = UIEdgeInsetsMake(kDefaultButtonTouchAreaInsets, kDefaultButtonTouchAreaInsets, kDefaultButtonTouchAreaInsets, kDefaultButtonTouchAreaInsets); [self.view addSubview:self.privacyButton]; - self.closeButton = [UIButton buttonWithType:UIButtonTypeCustom]; + self.closeButton = [MPExtendedHitBoxButton buttonWithType:UIButtonTypeCustom]; [self.closeButton setImage:[UIImage imageNamed:MPResourcePathForResource(kCloseButtonImage)] forState:UIControlStateNormal]; [self.closeButton addTarget:self action:@selector(closeButtonTapped) forControlEvents:UIControlEventTouchUpInside]; - self.closeButton.mp_TouchAreaInsets = UIEdgeInsetsMake(kDefaultButtonTouchAreaInsets, kDefaultButtonTouchAreaInsets, kDefaultButtonTouchAreaInsets, kDefaultButtonTouchAreaInsets); + self.closeButton.touchAreaInsets = UIEdgeInsetsMake(kDefaultButtonTouchAreaInsets, kDefaultButtonTouchAreaInsets, kDefaultButtonTouchAreaInsets, kDefaultButtonTouchAreaInsets); [self.closeButton sizeToFit]; [self.view addSubview:self.closeButton]; - self.ctaButton = [UIButton buttonWithType:UIButtonTypeCustom]; + self.ctaButton = [MPExtendedHitBoxButton buttonWithType:UIButtonTypeCustom]; [self.ctaButton setTitle:kCtaButtonTitleText forState:UIControlStateNormal]; [self.ctaButton setBackgroundColor:[UIColor mp_colorFromHexString:kCtaButtonBackgroundColor alpha:kCtaButtonBackgroundAlpha]]; [self.ctaButton addTarget:self action:@selector(ctaButtonTapped) forControlEvents:UIControlEventTouchUpInside]; @@ -268,8 +268,10 @@ - (void)layoutCloseButton - (void)layoutCtaButton { - UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation; - if (UIInterfaceOrientationIsLandscape(orientation)) { + CGRect applicationFrame = MPApplicationFrame(YES); + BOOL isLandscapeOrientation = applicationFrame.size.width > applicationFrame.size.height; + + if (isLandscapeOrientation) { self.ctaButton.mp_x = CGRectGetMaxX(self.playerView.frame) - kCtaButtonTrailingMarginLandscape - CGRectGetWidth(self.ctaButton.bounds); self.ctaButton.mp_y = CGRectGetMaxY(self.playerView.frame) - kCtaButtonBottomMarginLandscape - CGRectGetHeight(self.ctaButton.bounds); } else { @@ -401,15 +403,6 @@ - (void)displayAgentDidDismissModal [self.playerController resume]; } -#pragma mark - Hidding status bar (pre-iOS 7) - -- (void)setApplicationStatusBarHidden:(BOOL)hidden -{ - [[UIApplication sharedApplication] mp_preIOS7setApplicationStatusBarHidden:hidden]; -} - -#pragma mark - Hidding status bar (iOS 7 and above) - - (BOOL)prefersStatusBarHidden { return YES; diff --git a/MoPubSDK/NativeVideo/Internal/MOPUBPlayerView.m b/MoPubSDK/NativeVideo/Internal/MOPUBPlayerView.m index 600ca896a..0ae3860b2 100644 --- a/MoPubSDK/NativeVideo/Internal/MOPUBPlayerView.m +++ b/MoPubSDK/NativeVideo/Internal/MOPUBPlayerView.m @@ -218,9 +218,11 @@ - (void)layoutGradientview - (void)layoutReplayView { if (self.replayView) { - CGSize screenSize = MPScreenBounds().size; - if (UIInterfaceOrientationIsLandscape([[UIApplication sharedApplication] statusBarOrientation]) && self.displayMode == MOPUBPlayerDisplayModeFullscreen) { - self.replayView.frame = CGRectMake(0, 0, screenSize.width, screenSize.height); + CGSize appFrameSize = MPApplicationFrame(YES).size; + BOOL isLandscapeOrientation = appFrameSize.width > appFrameSize.height; + + if (isLandscapeOrientation && self.displayMode == MOPUBPlayerDisplayModeFullscreen) { + self.replayView.frame = CGRectMake(0, 0, appFrameSize.width, appFrameSize.height); } else { self.replayView.frame = self.avView.frame; } diff --git a/MoPubSDK/NativeVideo/Internal/MOPUBPlayerViewController.m b/MoPubSDK/NativeVideo/Internal/MOPUBPlayerViewController.m index f6dfbc439..e3037ff30 100644 --- a/MoPubSDK/NativeVideo/Internal/MOPUBPlayerViewController.m +++ b/MoPubSDK/NativeVideo/Internal/MOPUBPlayerViewController.m @@ -13,6 +13,7 @@ #import "MOPUBActivityIndicatorView.h" #import "MPAdDestinationDisplayAgent.h" #import "MPCoreInstanceProvider.h" +#import "MPExtendedHitBoxButton.h" #import "MPGlobal.h" #import "MPLogging.h" #import "MOPUBNativeVideoAdConfigValues.h" @@ -21,7 +22,6 @@ #import "UIView+MPAdditions.h" #import "MOPUBNativeVideoAdConfigValues.h" #import "MPVideoConfig.h" -#import "UIButton+MPAdditions.h" #define kDefaultVideoAspectRatio 16.0f/9.0f @@ -55,7 +55,7 @@ @interface MOPUBPlayerViewController() -@property (nonatomic) UIButton *muteButton; +@property (nonatomic) MPExtendedHitBoxButton *muteButton; @property (nonatomic) UIActivityIndicatorView *loadingIndicator; @property (nonatomic) MPAdDestinationDisplayAgent *displayAgent; @@ -233,11 +233,11 @@ - (void)createView - (void)createMuteButton { if (!self.muteButton) { - self.muteButton = [UIButton buttonWithType:UIButtonTypeCustom]; + self.muteButton = [MPExtendedHitBoxButton buttonWithType:UIButtonTypeCustom]; [self.muteButton setImage:[UIImage imageNamed:MPResourcePathForResource(kMutedButtonImage)] forState:UIControlStateNormal]; [self.muteButton setImage:[UIImage imageNamed:MPResourcePathForResource(kUnmutedButtonImage)] forState:UIControlStateSelected]; [self.muteButton addTarget:self action:@selector(muteButtonTapped) forControlEvents:UIControlEventTouchUpInside]; - self.muteButton.mp_TouchAreaInsets = UIEdgeInsetsMake(kMuteIconInlineModeTouchAreaInsets, kMuteIconInlineModeTouchAreaInsets, kMuteIconInlineModeTouchAreaInsets, kMuteIconInlineModeTouchAreaInsets); + self.muteButton.touchAreaInsets = UIEdgeInsetsMake(kMuteIconInlineModeTouchAreaInsets, kMuteIconInlineModeTouchAreaInsets, kMuteIconInlineModeTouchAreaInsets, kMuteIconInlineModeTouchAreaInsets); [self.muteButton sizeToFit]; [self.view addSubview:self.muteButton]; self.muteButton.frame = CGRectMake(kMuteIconInlineModeLeftMargin, CGRectGetMaxY(self.view.bounds) - kMuteIconInlineModeBottomMargin - CGRectGetHeight(self.muteButton.bounds), CGRectGetWidth(self.muteButton.bounds), CGRectGetHeight(self.muteButton.bounds)); diff --git a/MoPubSDK/NativeVideo/MOPUBNativeVideoAdRenderer.m b/MoPubSDK/NativeVideo/MOPUBNativeVideoAdRenderer.m index 1e20758df..f5b5b6ffc 100644 --- a/MoPubSDK/NativeVideo/MOPUBNativeVideoAdRenderer.m +++ b/MoPubSDK/NativeVideo/MOPUBNativeVideoAdRenderer.m @@ -384,8 +384,11 @@ - (void)setupVideoView [self.adView bringSubviewToFront:self.adView.nativeVideoView]; if (!self.autoPlayTimer) { - self.autoPlayTimer = [MPTimer timerWithTimeInterval:kAutoPlayTimerInterval target:self selector:@selector(tick:) repeats:YES]; - self.autoPlayTimer.runLoopMode = NSRunLoopCommonModes; + self.autoPlayTimer = [MPTimer timerWithTimeInterval:kAutoPlayTimerInterval + target:self + selector:@selector(tick:) + repeats:YES + runLoopMode:NSRunLoopCommonModes]; [self.autoPlayTimer scheduleNow]; } diff --git a/MoPubSDK/RewardedVideo/Internal/MPMoPubRewardedPlayableCustomEvent.m b/MoPubSDK/RewardedVideo/Internal/MPMoPubRewardedPlayableCustomEvent.m index 8829c9b0f..760c0bd74 100644 --- a/MoPubSDK/RewardedVideo/Internal/MPMoPubRewardedPlayableCustomEvent.m +++ b/MoPubSDK/RewardedVideo/Internal/MPMoPubRewardedPlayableCustomEvent.m @@ -127,22 +127,16 @@ - (void)presentRewardedVideoFromViewController:(UIViewController *)viewControlle }]; [self.interstitial.view addSubview:self.timerView]; - if (@available(iOS 9.0, *)) { - NSArray * constraints; - if (@available(iOS 11.0, *)) { // consider safe area - constraints = @[[self.timerView.topAnchor constraintEqualToAnchor:self.interstitial.view.safeAreaLayoutGuide.topAnchor], - [self.timerView.rightAnchor constraintEqualToAnchor:self.interstitial.view.safeAreaLayoutGuide.rightAnchor]]; - } else { - constraints = @[[self.timerView.topAnchor constraintEqualToAnchor:self.interstitial.view.topAnchor], - [self.timerView.rightAnchor constraintEqualToAnchor:self.interstitial.view.rightAnchor]]; - } - [NSLayoutConstraint activateConstraints:constraints]; - self.timerView.translatesAutoresizingMaskIntoConstraints = NO; - } else { // use auto resizing since auto layout is not available - self.timerView.frame = CGRectMake(self.interstitial.view.bounds.size.width - self.timerView.intrinsicContentSize.width, 0, - self.timerView.intrinsicContentSize.width , self.timerView.intrinsicContentSize.height); - self.timerView.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin; + NSArray * constraints; + if (@available(iOS 11, *)) { // consider safe area + constraints = @[[self.timerView.topAnchor constraintEqualToAnchor:self.interstitial.view.safeAreaLayoutGuide.topAnchor], + [self.timerView.rightAnchor constraintEqualToAnchor:self.interstitial.view.safeAreaLayoutGuide.rightAnchor]]; + } else { + constraints = @[[self.timerView.topAnchor constraintEqualToAnchor:self.interstitial.view.topAnchor], + [self.timerView.rightAnchor constraintEqualToAnchor:self.interstitial.view.rightAnchor]]; } + [NSLayoutConstraint activateConstraints:constraints]; + self.timerView.translatesAutoresizingMaskIntoConstraints = NO; [self.timerView start]; diff --git a/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdManager.m b/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdManager.m index 6c24891b7..06dee3f26 100644 --- a/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdManager.m +++ b/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdManager.m @@ -95,10 +95,7 @@ - (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]]; } } @@ -320,7 +317,7 @@ - (void)rewardedVideoDidFailToPlayForAdapter:(MPRewardedVideoAdapter *)adapter e // 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 = YES; + self.playedAd = NO; MPLogAdEvent([MPLogEvent adShowFailedWithError:error], self.adUnitID); [self.delegate rewardedVideoDidFailToPlayForAdManager:self error:error]; diff --git a/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdapter.m b/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdapter.m index 94cfca45f..583c3255f 100644 --- a/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdapter.m +++ b/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdapter.m @@ -124,11 +124,10 @@ - (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]; } } diff --git a/MoPubSDK/RewardedVideo/MPRewardedVideo.m b/MoPubSDK/RewardedVideo/MPRewardedVideo.m index 9eca25a99..bb639cc0d 100644 --- a/MoPubSDK/RewardedVideo/MPRewardedVideo.m +++ b/MoPubSDK/RewardedVideo/MPRewardedVideo.m @@ -8,6 +8,7 @@ #import "MPRewardedVideo.h" #import "MPAdTargeting.h" +#import "MPGlobal.h" #import "MPImpressionTrackedNotification.h" #import "MPLogging.h" #import "MPRewardedVideoAdManager.h" @@ -119,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; diff --git a/MoPubSDKFramework/Info.plist b/MoPubSDKFramework/Info.plist index 35699cd1d..625de79da 100644 --- a/MoPubSDKFramework/Info.plist +++ b/MoPubSDKFramework/Info.plist @@ -15,9 +15,9 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 5.7.1 + 5.8.0 CFBundleVersion - 5.7.1 + 5.8.0 NSPrincipalClass diff --git a/MoPubSDKTests/Info.plist b/MoPubSDKTests/Info.plist index 59ab86668..13cf9df41 100644 --- a/MoPubSDKTests/Info.plist +++ b/MoPubSDKTests/Info.plist @@ -15,8 +15,8 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 5.7.1 + 5.8.0 CFBundleVersion - 5.7.1 + 5.8.0 diff --git a/MoPubSDKTests/MOPUBExperimentProviderTests.m b/MoPubSDKTests/MOPUBExperimentProviderTests.m index 34ce45e92..643a6a693 100644 --- a/MoPubSDKTests/MOPUBExperimentProviderTests.m +++ b/MoPubSDKTests/MOPUBExperimentProviderTests.m @@ -46,8 +46,11 @@ - (void)testClickthroughExperimentSafariViewController { [MOPUBExperimentProvider setDisplayAgentOverriddenByClientFlag:NO]; NSDictionary * headers = @{ kClickthroughExperimentBrowserAgent: @"2"}; MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil adType:MPAdTypeFullscreen]; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" XCTAssertEqual(config.clickthroughExperimentBrowserAgent, MOPUBDisplayAgentTypeSafariViewController); XCTAssertEqual([MOPUBExperimentProvider displayAgentType], MOPUBDisplayAgentTypeSafariViewController); +#pragma clang diagnostic pop } - (void)testClickthroughClientOverride { @@ -55,13 +58,19 @@ - (void)testClickthroughClientOverride { NSDictionary * headers = @{ kClickthroughExperimentBrowserAgent: @"2"}; MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil adType:MPAdTypeFullscreen]; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" XCTAssertEqual(config.clickthroughExperimentBrowserAgent, MOPUBDisplayAgentTypeSafariViewController); +#pragma clang diagnostic pop XCTAssertEqual([MOPUBExperimentProvider displayAgentType], MOPUBDisplayAgentTypeInApp); // Display agent type is overridden to MOPUBDisplayAgentTypeSafariViewController +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" [[MoPub sharedInstance] setClickthroughDisplayAgentType:MOPUBDisplayAgentTypeSafariViewController]; XCTAssertEqual([MOPUBExperimentProvider displayAgentType], MOPUBDisplayAgentTypeSafariViewController); +#pragma clang diagnostic pop // Display agent type is overridden to MOPUBDisplayAgentTypeNativeSafari [[MoPub sharedInstance] setClickthroughDisplayAgentType:MOPUBDisplayAgentTypeNativeSafari]; diff --git a/MoPubSDKTests/MPAdConfigurationTests.m b/MoPubSDKTests/MPAdConfigurationTests.m index c566687fa..d467cdc87 100644 --- a/MoPubSDKTests/MPAdConfigurationTests.m +++ b/MoPubSDKTests/MPAdConfigurationTests.m @@ -194,8 +194,11 @@ - (void)testClickthroughExperimentNativeBrowser { - (void)testClickthroughExperimentSafariViewController { NSDictionary * headers = @{ kClickthroughExperimentBrowserAgent: @"2"}; MPAdConfiguration * config = [[MPAdConfiguration alloc] initWithMetadata:headers data:nil adType:MPAdTypeFullscreen]; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" XCTAssertEqual(config.clickthroughExperimentBrowserAgent, MOPUBDisplayAgentTypeSafariViewController); XCTAssertEqual([MOPUBExperimentProvider displayAgentType], MOPUBDisplayAgentTypeSafariViewController); +#pragma clang diagnostic pop } #pragma mark - Viewability diff --git a/MoPubSDKTests/MPAdServerCommunicator+Testing.h b/MoPubSDKTests/MPAdServerCommunicator+Testing.h index c4dfcc3ca..abafbdcd1 100644 --- a/MoPubSDKTests/MPAdServerCommunicator+Testing.h +++ b/MoPubSDKTests/MPAdServerCommunicator+Testing.h @@ -18,6 +18,7 @@ // 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/MPAdServerCommunicatorTests.m b/MoPubSDKTests/MPAdServerCommunicatorTests.m index a3992897f..952b53b1d 100644 --- a/MoPubSDKTests/MPAdServerCommunicatorTests.m +++ b/MoPubSDKTests/MPAdServerCommunicatorTests.m @@ -8,7 +8,7 @@ #import #import "MPAdServerCommunicator.h" -#import "MPAdserverCommunicatorDelegateHandler.h" +#import "MPAdServerCommunicatorDelegateHandler.h" #import "MPAdServerCommunicator+Testing.h" #import "MPAdServerKeys.h" #import "MPConsentManager+Testing.h" @@ -29,7 +29,7 @@ @interface MPAdServerCommunicatorTests : XCTestCase @property (nonatomic, strong) MPAdServerCommunicator *communicator; -@property (nonatomic, strong) MPAdserverCommunicatorDelegateHandler *communicatorDelegateHandler; +@property (nonatomic, strong) MPAdServerCommunicatorDelegateHandler *communicatorDelegateHandler; @end @@ -40,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; } @@ -621,6 +621,131 @@ - (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 { diff --git a/MoPubSDKTests/MPAdServerURLBuilderTests.m b/MoPubSDKTests/MPAdServerURLBuilderTests.m index 144dd2cac..e3ecf54e3 100644 --- a/MoPubSDKTests/MPAdServerURLBuilderTests.m +++ b/MoPubSDKTests/MPAdServerURLBuilderTests.m @@ -11,6 +11,7 @@ #import "MPAdServerURLBuilder+Testing.h" #import "MPAPIEndpoints.h" #import "MPConsentManager.h" +#import "MPEngineInfo.h" #import "MPIdentityProvider.h" #import "MPMediationManager.h" #import "MPMediationManager+Testing.h" @@ -46,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]; @@ -63,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]; @@ -134,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]; @@ -221,10 +231,7 @@ - (NSString *)queryParameterValueForKey:(NSString *)key inUrl:(NSString *)url { - (void)testFilledReasonWithNonZeroRateLimitValue { [[MPRateLimitManager sharedInstance] setRateLimitTimerWithAdUnitId:@"fake_adunit" milliseconds:10 reason:@"Reason"]; - MPURL * url = [MPAdServerURLBuilder URLWithAdUnitID:@"fake_adunit" - keywords:nil - userDataKeywords:nil - location:nil]; + MPURL * url = [MPAdServerURLBuilder URLWithAdUnitID:@"fake_adunit" targeting:nil]; NSNumber * value = [url numberForPOSTDataKey:kBackoffMsKey]; XCTAssertEqual([value integerValue], 10); @@ -234,10 +241,7 @@ - (void)testFilledReasonWithNonZeroRateLimitValue { - (void)testZeroRateLimitValueDoesntShow { [[MPRateLimitManager sharedInstance] setRateLimitTimerWithAdUnitId:@"fake_adunit" milliseconds:0 reason:nil]; - MPURL * url = [MPAdServerURLBuilder URLWithAdUnitID:@"fake_adunit" - keywords:nil - userDataKeywords:nil - location:nil]; + MPURL * url = [MPAdServerURLBuilder URLWithAdUnitID:@"fake_adunit" targeting:nil]; NSNumber * value = [url numberForPOSTDataKey:kBackoffMsKey]; XCTAssertNil(value); @@ -247,14 +251,79 @@ - (void)testZeroRateLimitValueDoesntShow { - (void)testNilReasonWithNonZeroRateLimitValue { [[MPRateLimitManager sharedInstance] setRateLimitTimerWithAdUnitId:@"fake_adunit" milliseconds:10 reason:nil]; - MPURL * url = [MPAdServerURLBuilder URLWithAdUnitID:@"fake_adunit" - keywords:nil - userDataKeywords:nil - location: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.m b/MoPubSDKTests/MPAdView+Testing.m index cd768f9ba..f68fbb7fe 100644 --- a/MoPubSDKTests/MPAdView+Testing.m +++ b/MoPubSDKTests/MPAdView+Testing.m @@ -8,6 +8,10 @@ #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 921d88ccf..1e7644aea 100644 --- a/MoPubSDKTests/MPAdViewTests.m +++ b/MoPubSDKTests/MPAdViewTests.m @@ -12,12 +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; @@ -29,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; @@ -40,13 +44,21 @@ - (void)setUp { #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]; @@ -54,6 +66,153 @@ - (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 { diff --git a/MoPubSDKTests/MPAdserverCommunicatorDelegateHandler.h b/MoPubSDKTests/MPAdserverCommunicatorDelegateHandler.h index d25bf9803..15a9ae01c 100644 --- a/MoPubSDKTests/MPAdserverCommunicatorDelegateHandler.h +++ b/MoPubSDKTests/MPAdserverCommunicatorDelegateHandler.h @@ -1,5 +1,5 @@ // -// MPAdserverCommunicatorDelegateHandler.h +// MPAdServerCommunicatorDelegateHandler.h // // Copyright 2018-2019 Twitter, Inc. // Licensed under the MoPub SDK License Agreement @@ -9,7 +9,7 @@ #import #import "MPAdServerCommunicator.h" -@interface MPAdserverCommunicatorDelegateHandler : NSObject +@interface MPAdServerCommunicatorDelegateHandler : NSObject @property (nonatomic, copy) void (^communicatorDidReceiveAdConfigurations)(NSArray *configurations); @property (nonatomic, copy) void (^communicatorDidFailWithError)(NSError *error); diff --git a/MoPubSDKTests/MPAdserverCommunicatorDelegateHandler.m b/MoPubSDKTests/MPAdserverCommunicatorDelegateHandler.m index b545bd067..1e8c87c16 100644 --- a/MoPubSDKTests/MPAdserverCommunicatorDelegateHandler.m +++ b/MoPubSDKTests/MPAdserverCommunicatorDelegateHandler.m @@ -1,14 +1,14 @@ // -// MPAdserverCommunicatorDelegateHandler.m +// MPAdServerCommunicatorDelegateHandler.m // // 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); } diff --git a/MoPubSDKTests/MPBannerAdManagerTests.m b/MoPubSDKTests/MPBannerAdManagerTests.m index b11ad3412..2fe15de16 100644 --- a/MoPubSDKTests/MPBannerAdManagerTests.m +++ b/MoPubSDKTests/MPBannerAdManagerTests.m @@ -248,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]; @@ -289,7 +289,7 @@ - (void)testImpressionDelegateFiresWithoutILRD { communicator.mockConfigurationsResponse = configurations; manager.communicator = communicator; - MPAdTargeting * targeting = [[MPAdTargeting alloc] init]; + MPAdTargeting * targeting = [[MPAdTargeting alloc] initWithCreativeSafeSize:CGSizeZero]; [manager loadAdWithTargeting:targeting]; [self waitForExpectationsWithTimeout:kDefaultTimeout handler:^(NSError * _Nullable error) { @@ -324,7 +324,7 @@ - (void)testImpressionDelegateFiresWithILRD { communicator.mockConfigurationsResponse = configurations; manager.communicator = communicator; - MPAdTargeting * targeting = [[MPAdTargeting alloc] init]; + MPAdTargeting * targeting = [[MPAdTargeting alloc] initWithCreativeSafeSize:CGSizeZero]; [manager loadAdWithTargeting:targeting]; [self waitForExpectationsWithTimeout:kDefaultTimeout handler:^(NSError * _Nullable error) { diff --git a/MoPubSDKTests/MPBannerAdapterDelegateHandler.m b/MoPubSDKTests/MPBannerAdapterDelegateHandler.m index fba761b23..9de98d1cc 100644 --- a/MoPubSDKTests/MPBannerAdapterDelegateHandler.m +++ b/MoPubSDKTests/MPBannerAdapterDelegateHandler.m @@ -30,7 +30,7 @@ - (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(); } } diff --git a/MoPubSDKTests/MPConsentManager+Testing.h b/MoPubSDKTests/MPConsentManager+Testing.h index b2ec81988..49c24e394 100644 --- a/MoPubSDKTests/MPConsentManager+Testing.h +++ b/MoPubSDKTests/MPConsentManager+Testing.h @@ -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 2cc8fce73..83b455346 100644 --- a/MoPubSDKTests/MPConsentManager+Testing.m +++ b/MoPubSDKTests/MPConsentManager+Testing.m @@ -8,18 +8,27 @@ #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 9d15f8c80..0a412c7f3 100644 --- a/MoPubSDKTests/MPConsentManagerTests.m +++ b/MoPubSDKTests/MPConsentManagerTests.m @@ -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/MPHTTPNetworkSession+Testing.h b/MoPubSDKTests/MPHTTPNetworkSession+Testing.h index 55d660e46..e3cd58c83 100644 --- a/MoPubSDKTests/MPHTTPNetworkSession+Testing.h +++ b/MoPubSDKTests/MPHTTPNetworkSession+Testing.h @@ -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/MPHTTPNetworkSessionTests.m b/MoPubSDKTests/MPHTTPNetworkSessionTests.m index 2fcef3fac..b2ebd0e05 100644 --- a/MoPubSDKTests/MPHTTPNetworkSessionTests.m +++ b/MoPubSDKTests/MPHTTPNetworkSessionTests.m @@ -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/MPInterstitialAdController+Testing.m b/MoPubSDKTests/MPInterstitialAdController+Testing.m index adb1dc582..b608dec01 100644 --- a/MoPubSDKTests/MPInterstitialAdController+Testing.m +++ b/MoPubSDKTests/MPInterstitialAdController+Testing.m @@ -8,6 +8,10 @@ #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 3d4592a1c..a5c027136 100644 --- a/MoPubSDKTests/MPInterstitialAdControllerTests.m +++ b/MoPubSDKTests/MPInterstitialAdControllerTests.m @@ -53,6 +53,25 @@ - (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 { diff --git a/MoPubSDKTests/MPInterstitialAdManagerDelegateHandler.h b/MoPubSDKTests/MPInterstitialAdManagerDelegateHandler.h index 0fdb0715f..8a13fbb3e 100644 --- a/MoPubSDKTests/MPInterstitialAdManagerDelegateHandler.h +++ b/MoPubSDKTests/MPInterstitialAdManagerDelegateHandler.h @@ -17,6 +17,7 @@ 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; diff --git a/MoPubSDKTests/MPInterstitialAdManagerTests.m b/MoPubSDKTests/MPInterstitialAdManagerTests.m index 152eca1b7..8e9f56fcf 100644 --- a/MoPubSDKTests/MPInterstitialAdManagerTests.m +++ b/MoPubSDKTests/MPInterstitialAdManagerTests.m @@ -245,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]; @@ -293,7 +293,7 @@ - (void)testImpressionDelegateFiresWithoutILRD { [adapter trackImpression]; }; - MPAdTargeting * targeting = [[MPAdTargeting alloc] init]; + MPAdTargeting * targeting = [[MPAdTargeting alloc] initWithCreativeSafeSize:CGSizeZero]; [manager loadInterstitialWithAdUnitID:testAdUnitID targeting:targeting]; [self waitForExpectationsWithTimeout:kDefaultTimeout handler:^(NSError * _Nullable error) { @@ -333,7 +333,7 @@ - (void)testImpressionDelegateFiresWithILRD { [adapter trackImpression]; }; - MPAdTargeting * targeting = [[MPAdTargeting alloc] init]; + MPAdTargeting * targeting = [[MPAdTargeting alloc] initWithCreativeSafeSize:CGSizeZero]; [manager loadInterstitialWithAdUnitID:testAdUnitID targeting:targeting]; [self waitForExpectationsWithTimeout:kDefaultTimeout handler:^(NSError * _Nullable error) { 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 index bd8819e49..afeeca07c 100644 --- a/MoPubSDKTests/MPMediationManager+Testing.h +++ b/MoPubSDKTests/MPMediationManager+Testing.h @@ -8,8 +8,10 @@ #import "MPMediationManager.h" +NS_ASSUME_NONNULL_BEGIN + @interface MPMediationManager (Testing) -@property (class, nonatomic, copy) NSString * adapterInformationProvidersFilePath; +@property (class, nonatomic, copy, nullable) NSString * adapterInformationProvidersFilePath; @property (nonatomic, strong) NSMutableDictionary> * adapters; @property (nonatomic, strong, readonly) NSSet> * certifiedAdapterClasses; @@ -17,3 +19,5 @@ - (NSDictionary *)parametersForAdapter:(id)adapter overrideConfiguration:(NSDictionary *)configuration; @end + +NS_ASSUME_NONNULL_END diff --git a/MoPubSDKTests/MPMediationManagerTests.m b/MoPubSDKTests/MPMediationManagerTests.m index def60080a..c459e7492 100644 --- a/MoPubSDKTests/MPMediationManagerTests.m +++ b/MoPubSDKTests/MPMediationManagerTests.m @@ -215,7 +215,10 @@ - (void)testCachedParametersNoOverrides { // 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 XCTAssertNotNil(params); XCTAssert([params[@"appId"] isEqualToString:@"aaaa"]); @@ -260,7 +263,10 @@ - (void)testMockAdaptersPlistExists { } - (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); diff --git a/MoPubSDKTests/MPNativeAd+Testing.m b/MoPubSDKTests/MPNativeAd+Testing.m index 7c72b8fa6..47b160fd3 100644 --- a/MoPubSDKTests/MPNativeAd+Testing.m +++ b/MoPubSDKTests/MPNativeAd+Testing.m @@ -8,6 +8,10 @@ #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/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 303a0969b..723902576 100644 --- a/MoPubSDKTests/MPNativeAdRequestTests.m +++ b/MoPubSDKTests/MPNativeAdRequestTests.m @@ -8,6 +8,7 @@ #import #import "MPAdConfigurationFactory.h" +#import "MPAdServerKeys.h" #import "MPAPIEndpoints.h" #import "MPConstants.h" #import "MPError.h" @@ -19,6 +20,7 @@ #import "MPStaticNativeAdRenderer.h" #import "MPNativeAdRendererConfiguration.h" #import "MPStaticNativeAdRendererSettings.h" +#import "MPURL.h" #import "NSURLComponents+Testing.h" #import "MPNativeAdDelegateHandler.h" #import "MPNativeAd+Testing.h" @@ -505,4 +507,40 @@ - (void)testImpressionDelegateFiresWithILRD { [[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/MPRateLimitManagerTests.m b/MoPubSDKTests/MPRateLimitManagerTests.m index 6b71713b4..8798ab5f7 100644 --- a/MoPubSDKTests/MPRateLimitManagerTests.m +++ b/MoPubSDKTests/MPRateLimitManagerTests.m @@ -183,4 +183,19 @@ - (void)testForAdUnitThatWasNeverAdded { 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/MPRewardedVideo+Testing.h b/MoPubSDKTests/MPRewardedVideo+Testing.h index f2e459f86..161edb24e 100644 --- a/MoPubSDKTests/MPRewardedVideo+Testing.h +++ b/MoPubSDKTests/MPRewardedVideo+Testing.h @@ -20,6 +20,7 @@ + (void)loadRewardedVideoAdWithAdUnitID:(NSString *)adUnitID withTestConfiguration:(MPAdConfiguration *)config; + (MPRewardedVideoAdManager *)adManagerForAdUnitId:(NSString *)adUnitID; ++ (MPRewardedVideoAdManager *)makeAdManagerForAdUnitId:(NSString *)adUnitId; - (void)rewardedVideoAdManager:(MPRewardedVideoAdManager *)manager didReceiveImpressionEventWithImpressionData:(MPImpressionData *)impressionData; diff --git a/MoPubSDKTests/MPRewardedVideo+Testing.m b/MoPubSDKTests/MPRewardedVideo+Testing.m index e0dcb0388..866889260 100644 --- a/MoPubSDKTests/MPRewardedVideo+Testing.m +++ b/MoPubSDKTests/MPRewardedVideo+Testing.m @@ -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/MPRewardedVideoAdManagerTests.m b/MoPubSDKTests/MPRewardedVideoAdManagerTests.m index f4df621c9..036b26aec 100644 --- a/MoPubSDKTests/MPRewardedVideoAdManagerTests.m +++ b/MoPubSDKTests/MPRewardedVideoAdManagerTests.m @@ -530,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]; @@ -597,7 +597,7 @@ - (void)testImpressionDelegateFiresWithoutILRD { [[MPRewardedVideo sharedInstance] rewardedVideoAdManager:manager didReceiveImpressionEventWithImpressionData:nil]; }; - MPAdTargeting * targeting = [[MPAdTargeting alloc] init]; + MPAdTargeting * targeting = [[MPAdTargeting alloc] initWithCreativeSafeSize:CGSizeZero]; [manager loadRewardedVideoAdWithCustomerId:@"CUSTOMER_ID" targeting:targeting]; [self waitForExpectationsWithTimeout:kTestTimeout handler:^(NSError * _Nullable error) { @@ -660,7 +660,7 @@ - (void)testImpressionDelegateFiresWithILRD { [[MPRewardedVideo sharedInstance] rewardedVideoAdManager:manager didReceiveImpressionEventWithImpressionData:rewardedVideoThatShouldLoad.impressionData]; }; - MPAdTargeting * targeting = [[MPAdTargeting alloc] init]; + MPAdTargeting * targeting = [[MPAdTargeting alloc] initWithCreativeSafeSize:CGSizeZero]; [manager loadRewardedVideoAdWithCustomerId:@"CUSTOMER_ID" targeting:targeting]; [self waitForExpectationsWithTimeout:kTestTimeout handler:^(NSError * _Nullable error) { diff --git a/MoPubSDKTests/MPRewardedVideoTests.m b/MoPubSDKTests/MPRewardedVideoTests.m index 41df5183a..ab228169e 100644 --- a/MoPubSDKTests/MPRewardedVideoTests.m +++ b/MoPubSDKTests/MPRewardedVideoTests.m @@ -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" @@ -793,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/MPTimerTests.m b/MoPubSDKTests/MPTimerTests.m index bf42d968f..79f39d50f 100644 --- a/MoPubSDKTests/MPTimerTests.m +++ b/MoPubSDKTests/MPTimerTests.m @@ -74,11 +74,9 @@ - (void)testInvalidateAfterInstantiation { MPTimer * timer = [self generateTestTimerWithTitle:testName]; XCTAssertFalse(timer.isCountdownActive); - XCTAssertFalse(timer.isScheduled); XCTAssertTrue(timer.isValid); [timer invalidate]; XCTAssertFalse(timer.isCountdownActive); - XCTAssertFalse(timer.isScheduled); XCTAssertFalse(timer.isValid); } @@ -90,20 +88,16 @@ - (void)testInvalidateAfterStart { self.testNameVsExpectation[testName] = expectation; XCTAssertFalse(timer.isCountdownActive); - XCTAssertFalse(timer.isScheduled); XCTAssertTrue(timer.isValid); [timer scheduleNow]; XCTAssertTrue(timer.isCountdownActive); - XCTAssertTrue(timer.isScheduled); 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.isScheduled); XCTAssertTrue(timer.isValid); [timer invalidate]; XCTAssertFalse(timer.isCountdownActive); - XCTAssertFalse(timer.isScheduled); XCTAssertFalse(timer.isValid); }); [self waitForExpectationsWithTimeout:kTimerRepeatIntervalInSeconds * kWaitTimeTolerance handler:^(NSError * _Nullable error) { @@ -120,24 +114,19 @@ - (void)testInvalidateAfterStartedAndPause { self.testNameVsExpectation[testName] = expectation; XCTAssertFalse(timer.isCountdownActive); - XCTAssertFalse(timer.isScheduled); XCTAssertTrue(timer.isValid); [timer scheduleNow]; XCTAssertTrue(timer.isCountdownActive); - XCTAssertTrue(timer.isScheduled); 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.isScheduled); XCTAssertTrue(timer.isValid); [timer pause]; XCTAssertFalse(timer.isCountdownActive); - XCTAssertTrue(timer.isScheduled); XCTAssertTrue(timer.isValid); [timer invalidate]; XCTAssertFalse(timer.isCountdownActive); - XCTAssertFalse(timer.isScheduled); XCTAssertFalse(timer.isValid); }); [self waitForExpectationsWithTimeout:kTimerRepeatIntervalInSeconds * kWaitTimeTolerance handler:^(NSError * _Nullable error) { @@ -154,40 +143,31 @@ - (void)testPauseAndResume { self.testNameVsExpectation[testName] = expectation; XCTAssertFalse(timer.isCountdownActive); - XCTAssertFalse(timer.isScheduled); XCTAssertTrue(timer.isValid); [timer pause]; XCTAssertFalse(timer.isCountdownActive); - XCTAssertFalse(timer.isScheduled); XCTAssertTrue(timer.isValid); [timer scheduleNow]; XCTAssertTrue(timer.isCountdownActive); - XCTAssertTrue(timer.isScheduled); 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.isScheduled); XCTAssertTrue(timer.isValid); [timer pause]; XCTAssertFalse(timer.isCountdownActive); - XCTAssertTrue(timer.isScheduled); XCTAssertTrue(timer.isValid); [timer resume]; XCTAssertTrue(timer.isCountdownActive); - XCTAssertTrue(timer.isScheduled); XCTAssertTrue(timer.isValid); [timer invalidate]; XCTAssertFalse(timer.isCountdownActive); - XCTAssertFalse(timer.isScheduled); XCTAssertFalse(timer.isValid); [timer pause]; XCTAssertFalse(timer.isCountdownActive); - XCTAssertFalse(timer.isScheduled); XCTAssertFalse(timer.isValid); [timer resume]; XCTAssertFalse(timer.isCountdownActive); - XCTAssertFalse(timer.isScheduled); XCTAssertFalse(timer.isValid); }); [self waitForExpectationsWithTimeout:kTimerRepeatIntervalInSeconds * kWaitTimeTolerance handler:^(NSError * _Nullable error) { @@ -204,11 +184,9 @@ - (void)testRepeatingTimer { int firingCount = 10; XCTAssertFalse(timer.isCountdownActive); - XCTAssertFalse(timer.isScheduled); XCTAssertTrue(timer.isValid); [timer scheduleNow]; XCTAssertTrue(timer.isCountdownActive); - XCTAssertTrue(timer.isScheduled); XCTAssertTrue(timer.isValid); dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(kTimerRepeatIntervalInSeconds * firingCount * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ @@ -221,11 +199,9 @@ - (void)testRepeatingTimer { }]; XCTAssertTrue(timer.isCountdownActive); - XCTAssertTrue(timer.isScheduled); XCTAssertTrue(timer.isValid); [timer invalidate]; XCTAssertFalse(timer.isCountdownActive); - XCTAssertFalse(timer.isScheduled); XCTAssertFalse(timer.isValid); } @@ -237,28 +213,22 @@ - (void)testRedundantSchedules { self.testNameVsExpectation[testName] = expectation; XCTAssertFalse(timer.isCountdownActive); - XCTAssertFalse(timer.isScheduled); XCTAssertTrue(timer.isValid); [timer scheduleNow]; XCTAssertTrue(timer.isCountdownActive); - XCTAssertTrue(timer.isScheduled); XCTAssertTrue(timer.isValid); [timer scheduleNow]; XCTAssertTrue(timer.isCountdownActive); - XCTAssertTrue(timer.isScheduled); 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.isScheduled); XCTAssertTrue(timer.isValid); [timer invalidate]; XCTAssertFalse(timer.isCountdownActive); - XCTAssertFalse(timer.isScheduled); XCTAssertFalse(timer.isValid); [timer scheduleNow]; XCTAssertFalse(timer.isCountdownActive); - XCTAssertFalse(timer.isScheduled); XCTAssertFalse(timer.isValid); }); [self waitForExpectationsWithTimeout:kTimerRepeatIntervalInSeconds * kWaitTimeTolerance handler:^(NSError * _Nullable error) { diff --git a/MoPubSDKTests/MPURLResolverTests.m b/MoPubSDKTests/MPURLResolverTests.m index a399bd75b..db040d8f0 100644 --- a/MoPubSDKTests/MPURLResolverTests.m +++ b/MoPubSDKTests/MPURLResolverTests.m @@ -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/MRController+Testing.m b/MoPubSDKTests/MRController+Testing.m index 986b3deea..b00cf3ae6 100644 --- a/MoPubSDKTests/MRController+Testing.m +++ b/MoPubSDKTests/MRController+Testing.m @@ -8,6 +8,10 @@ #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/MoPubSampleApp/Controllers/MPBannerAdDetailViewController.m b/MoPubSampleApp/Controllers/MPBannerAdDetailViewController.m index b6d65738e..7dbe69a33 100644 --- a/MoPubSampleApp/Controllers/MPBannerAdDetailViewController.m +++ b/MoPubSampleApp/Controllers/MPBannerAdDetailViewController.m @@ -119,9 +119,14 @@ - (UIViewController *)viewControllerForPresentingModalView return self; } -- (void)adViewDidLoadAd:(MPAdView *)view +- (void)adViewDidLoadAd:(MPAdView *)view adSize:(CGSize)adSize { self.loadAdButton.enabled = YES; + self.adView.frame = ({ + CGRect frame = self.adView.frame; + frame.size.height = adSize.height; + frame; + }); [self.spinner stopAnimating]; [self endTimer]; diff --git a/MoPubSampleApp/Controllers/MPNativeAdDetailViewController.m b/MoPubSampleApp/Controllers/MPNativeAdDetailViewController.m index 419b9999a..d407bb623 100644 --- a/MoPubSampleApp/Controllers/MPNativeAdDetailViewController.m +++ b/MoPubSampleApp/Controllers/MPNativeAdDetailViewController.m @@ -96,7 +96,7 @@ - (IBAction)loadAd:(id)sender [configurations addObject:nativeVideoConfig]; MPNativeAdRequest *adRequest1 = [MPNativeAdRequest requestWithAdUnitIdentifier:self.info.ID rendererConfigurations:configurations]; - MPNativeAdRequestTargeting *targeting = [[MPNativeAdRequestTargeting alloc] init]; + MPNativeAdRequestTargeting *targeting = [MPNativeAdRequestTargeting targeting]; targeting.keywords = self.keywordsTextField.text; adRequest1.targeting = targeting; diff --git a/MoPubSampleApp/MoPubSampleApp.xcodeproj/project.pbxproj b/MoPubSampleApp/MoPubSampleApp.xcodeproj/project.pbxproj index 2f47b03e3..9b08b86b9 100644 --- a/MoPubSampleApp/MoPubSampleApp.xcodeproj/project.pbxproj +++ b/MoPubSampleApp/MoPubSampleApp.xcodeproj/project.pbxproj @@ -490,7 +490,7 @@ AE23238716F78A17002C2082 /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0900; + LastUpgradeCheck = 1100; ORGANIZATIONNAME = MoPub; TargetAttributes = { AE23238E16F78A17002C2082 = { @@ -501,10 +501,11 @@ }; buildConfigurationList = AE23238A16F78A17002C2082 /* Build configuration list for PBXProject "MoPubSampleApp" */; compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; + developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, + Base, ); mainGroup = AE23238616F78A17002C2082; productRefGroup = AE23239016F78A17002C2082 /* Products */; @@ -645,6 +646,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; @@ -652,11 +654,13 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = 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_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; @@ -682,7 +686,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; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; @@ -693,6 +697,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; @@ -700,11 +705,13 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = 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_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; @@ -723,7 +730,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 = "-DNS_BLOCK_ASSERTIONS=1"; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/MoPubSampleApp/MoPubSampleApp.xcodeproj/xcshareddata/xcschemes/MoPubSampleApp.xcscheme b/MoPubSampleApp/MoPubSampleApp.xcodeproj/xcshareddata/xcschemes/MoPubSampleApp.xcscheme index e9950f8e3..f82ab918b 100644 --- a/MoPubSampleApp/MoPubSampleApp.xcodeproj/xcshareddata/xcschemes/MoPubSampleApp.xcscheme +++ b/MoPubSampleApp/MoPubSampleApp.xcodeproj/xcshareddata/xcschemes/MoPubSampleApp.xcscheme @@ -1,6 +1,6 @@ - - - - + +
- - 'New BSD', :file => 'LICENSE' } spec.homepage = 'https://github.com/mopub/mopub-ios-sdk' spec.authors = { 'MoPub' => 'support@mopub.com' } @@ -14,7 +14,7 @@ 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.7.1' } + spec.source = { :git => 'https://github.com/mopub/mopub-ios-sdk.git', :tag => '5.8.0' } spec.requires_arc = true spec.ios.deployment_target = '8.0' spec.frameworks = [ From b6e6d91a0867c183e07aa53b1afab0c04cf8175c Mon Sep 17 00:00:00 2001 From: kdun Date: Tue, 20 Aug 2019 11:45:44 -0700 Subject: [PATCH 10/12] Removed deprecated Objective-C sample app, removed Gemfile and Gemfile.lock, and updated .gitignore file. (#282) --- .gitignore | 3 + Gemfile | 11 - Gemfile.lock | 230 ----- MoPubSampleApp/AppDelegate.h | 15 - MoPubSampleApp/AppDelegate.m | 112 --- MoPubSampleApp/Assets/Default-568h@2x.png | Bin 12198 -> 0 bytes MoPubSampleApp/Assets/Default.png | Bin 16432 -> 0 bytes MoPubSampleApp/Assets/Default@2x.png | Bin 25289 -> 0 bytes MoPubSampleApp/Assets/icon.png | Bin 2882 -> 0 bytes MoPubSampleApp/Assets/icon@2x.png | Bin 6656 -> 0 bytes MoPubSampleApp/Assets/white_button.png | Bin 12565 -> 0 bytes .../Controllers/MPAdEntryViewController.h | 17 - .../Controllers/MPAdEntryViewController.m | 245 ----- .../Controllers/MPAdEntryViewController.xib | 132 --- .../Controllers/MPAdTableViewController.h | 17 - .../Controllers/MPAdTableViewController.m | 255 ------ .../MPBannerAdDetailViewController.h | 25 - .../MPBannerAdDetailViewController.m | 145 --- .../MPBannerAdDetailViewController.xib | 81 -- .../MPInterstitialAdDetailViewController.h | 32 - .../MPInterstitialAdDetailViewController.m | 150 ---- .../MPInterstitialAdDetailViewController.xib | 154 ---- ...PLeaderboardBannerAdDetailViewController.h | 13 - ...PLeaderboardBannerAdDetailViewController.m | 44 - .../MPMRectBannerAdDetailViewController.h | 14 - .../MPMRectBannerAdDetailViewController.m | 42 - .../MPNativeAdDetailViewController.h | 20 - .../MPNativeAdDetailViewController.m | 180 ---- .../MPNativeAdDetailViewController.xib | 79 -- ...MPNativeAdPlacerCollectionViewController.h | 16 - ...MPNativeAdPlacerCollectionViewController.m | 149 ---- .../MPNativeAdPlacerPageViewController.h | 18 - .../MPNativeAdPlacerPageViewController.m | 283 ------ .../MPNativeAdPlacerTableViewController.h | 17 - .../MPNativeAdPlacerTableViewController.m | 189 ---- .../MPRewardedVideoAdDetailViewController.h | 32 - .../MPRewardedVideoAdDetailViewController.m | 201 ----- .../MPRewardedVideoAdDetailViewController.xib | 175 ---- MoPubSampleApp/Controllers/MPViewController.h | 16 - MoPubSampleApp/Controllers/MPViewController.m | 51 -- MoPubSampleApp/Domain/MPAdInfo.h | 44 - MoPubSampleApp/Domain/MPAdInfo.m | 127 --- MoPubSampleApp/Domain/MPAdSection.h | 23 - MoPubSampleApp/Domain/MPAdSection.m | 50 -- MoPubSampleApp/LaunchScreen.storyboard | 41 - MoPubSampleApp/MPAdPersistenceManager.h | 26 - MoPubSampleApp/MPAdPersistenceManager.m | 92 -- MoPubSampleApp/MPSampleAppInstanceProvider.h | 19 - MoPubSampleApp/MPSampleAppInstanceProvider.m | 35 - MoPubSampleApp/MPSampleAppLogReader.h | 17 - MoPubSampleApp/MPSampleAppLogReader.m | 73 -- .../MoPubSampleApp+Framework-Info.plist | 76 -- MoPubSampleApp/MoPubSampleApp-Info.plist | 76 -- MoPubSampleApp/MoPubSampleApp-Prefix.pch | 14 - .../MoPubSampleApp.xcodeproj/project.pbxproj | 836 ------------------ .../xcschemes/MoPubSampleApp.xcscheme | 87 -- .../Views/MPCollectionViewAdPlacerView.h | 20 - .../Views/MPCollectionViewAdPlacerView.m | 65 -- MoPubSampleApp/Views/MPIndexPathPickerView.h | 37 - MoPubSampleApp/Views/MPIndexPathPickerView.m | 90 -- MoPubSampleApp/Views/MPNativeAdCell.h | 21 - MoPubSampleApp/Views/MPNativeAdCell.m | 83 -- MoPubSampleApp/Views/MPNativeAdPageView.h | 21 - MoPubSampleApp/Views/MPNativeAdPageView.m | 86 -- .../Views/MPNativeAdTableHeaderView.h | 19 - .../Views/MPNativeAdTableHeaderView.m | 13 - .../Views/MPNativeAdTableHeaderView.xib | 58 -- ...NativeCollectionViewAdCollectionViewCell.h | 20 - ...NativeCollectionViewAdCollectionViewCell.m | 60 -- .../MPNativeVideoTableViewAdPlacerView.h | 21 - .../MPNativeVideoTableViewAdPlacerView.m | 109 --- MoPubSampleApp/Views/MPNativeVideoView.h | 23 - MoPubSampleApp/Views/MPNativeVideoView.m | 111 --- MoPubSampleApp/Views/MPStaticNativeAdView.h | 21 - MoPubSampleApp/Views/MPStaticNativeAdView.m | 100 --- .../Views/MPTableViewAdPlacerView.h | 21 - .../Views/MPTableViewAdPlacerView.m | 117 --- MoPubSampleApp/en.lproj/InfoPlist.strings | 2 - MoPubSampleApp/main.m | 18 - MoPubSampleApp/mopub_logo.png | Bin 36193 -> 0 bytes 80 files changed, 3 insertions(+), 5932 deletions(-) delete mode 100644 Gemfile delete mode 100644 Gemfile.lock delete mode 100644 MoPubSampleApp/AppDelegate.h delete mode 100644 MoPubSampleApp/AppDelegate.m delete mode 100644 MoPubSampleApp/Assets/Default-568h@2x.png delete mode 100644 MoPubSampleApp/Assets/Default.png delete mode 100644 MoPubSampleApp/Assets/Default@2x.png delete mode 100644 MoPubSampleApp/Assets/icon.png delete mode 100644 MoPubSampleApp/Assets/icon@2x.png delete mode 100644 MoPubSampleApp/Assets/white_button.png delete mode 100644 MoPubSampleApp/Controllers/MPAdEntryViewController.h delete mode 100644 MoPubSampleApp/Controllers/MPAdEntryViewController.m delete mode 100644 MoPubSampleApp/Controllers/MPAdEntryViewController.xib delete mode 100644 MoPubSampleApp/Controllers/MPAdTableViewController.h delete mode 100644 MoPubSampleApp/Controllers/MPAdTableViewController.m delete mode 100644 MoPubSampleApp/Controllers/MPBannerAdDetailViewController.h delete mode 100644 MoPubSampleApp/Controllers/MPBannerAdDetailViewController.m delete mode 100644 MoPubSampleApp/Controllers/MPBannerAdDetailViewController.xib delete mode 100644 MoPubSampleApp/Controllers/MPInterstitialAdDetailViewController.h delete mode 100644 MoPubSampleApp/Controllers/MPInterstitialAdDetailViewController.m delete mode 100644 MoPubSampleApp/Controllers/MPInterstitialAdDetailViewController.xib delete mode 100644 MoPubSampleApp/Controllers/MPLeaderboardBannerAdDetailViewController.h delete mode 100644 MoPubSampleApp/Controllers/MPLeaderboardBannerAdDetailViewController.m delete mode 100644 MoPubSampleApp/Controllers/MPMRectBannerAdDetailViewController.h delete mode 100644 MoPubSampleApp/Controllers/MPMRectBannerAdDetailViewController.m delete mode 100644 MoPubSampleApp/Controllers/MPNativeAdDetailViewController.h delete mode 100644 MoPubSampleApp/Controllers/MPNativeAdDetailViewController.m delete mode 100644 MoPubSampleApp/Controllers/MPNativeAdDetailViewController.xib delete mode 100644 MoPubSampleApp/Controllers/MPNativeAdPlacerCollectionViewController.h delete mode 100644 MoPubSampleApp/Controllers/MPNativeAdPlacerCollectionViewController.m delete mode 100644 MoPubSampleApp/Controllers/MPNativeAdPlacerPageViewController.h delete mode 100644 MoPubSampleApp/Controllers/MPNativeAdPlacerPageViewController.m delete mode 100644 MoPubSampleApp/Controllers/MPNativeAdPlacerTableViewController.h delete mode 100644 MoPubSampleApp/Controllers/MPNativeAdPlacerTableViewController.m delete mode 100644 MoPubSampleApp/Controllers/MPRewardedVideoAdDetailViewController.h delete mode 100644 MoPubSampleApp/Controllers/MPRewardedVideoAdDetailViewController.m delete mode 100644 MoPubSampleApp/Controllers/MPRewardedVideoAdDetailViewController.xib delete mode 100644 MoPubSampleApp/Controllers/MPViewController.h delete mode 100644 MoPubSampleApp/Controllers/MPViewController.m delete mode 100644 MoPubSampleApp/Domain/MPAdInfo.h delete mode 100644 MoPubSampleApp/Domain/MPAdInfo.m delete mode 100644 MoPubSampleApp/Domain/MPAdSection.h delete mode 100644 MoPubSampleApp/Domain/MPAdSection.m delete mode 100644 MoPubSampleApp/LaunchScreen.storyboard delete mode 100644 MoPubSampleApp/MPAdPersistenceManager.h delete mode 100644 MoPubSampleApp/MPAdPersistenceManager.m delete mode 100644 MoPubSampleApp/MPSampleAppInstanceProvider.h delete mode 100644 MoPubSampleApp/MPSampleAppInstanceProvider.m delete mode 100644 MoPubSampleApp/MPSampleAppLogReader.h delete mode 100644 MoPubSampleApp/MPSampleAppLogReader.m delete mode 100644 MoPubSampleApp/MoPubSampleApp+Framework-Info.plist delete mode 100644 MoPubSampleApp/MoPubSampleApp-Info.plist delete mode 100644 MoPubSampleApp/MoPubSampleApp-Prefix.pch delete mode 100644 MoPubSampleApp/MoPubSampleApp.xcodeproj/project.pbxproj delete mode 100644 MoPubSampleApp/MoPubSampleApp.xcodeproj/xcshareddata/xcschemes/MoPubSampleApp.xcscheme delete mode 100644 MoPubSampleApp/Views/MPCollectionViewAdPlacerView.h delete mode 100644 MoPubSampleApp/Views/MPCollectionViewAdPlacerView.m delete mode 100644 MoPubSampleApp/Views/MPIndexPathPickerView.h delete mode 100644 MoPubSampleApp/Views/MPIndexPathPickerView.m delete mode 100644 MoPubSampleApp/Views/MPNativeAdCell.h delete mode 100644 MoPubSampleApp/Views/MPNativeAdCell.m delete mode 100644 MoPubSampleApp/Views/MPNativeAdPageView.h delete mode 100644 MoPubSampleApp/Views/MPNativeAdPageView.m delete mode 100644 MoPubSampleApp/Views/MPNativeAdTableHeaderView.h delete mode 100644 MoPubSampleApp/Views/MPNativeAdTableHeaderView.m delete mode 100644 MoPubSampleApp/Views/MPNativeAdTableHeaderView.xib delete mode 100644 MoPubSampleApp/Views/MPNativeCollectionViewAdCollectionViewCell.h delete mode 100644 MoPubSampleApp/Views/MPNativeCollectionViewAdCollectionViewCell.m delete mode 100644 MoPubSampleApp/Views/MPNativeVideoTableViewAdPlacerView.h delete mode 100644 MoPubSampleApp/Views/MPNativeVideoTableViewAdPlacerView.m delete mode 100644 MoPubSampleApp/Views/MPNativeVideoView.h delete mode 100644 MoPubSampleApp/Views/MPNativeVideoView.m delete mode 100644 MoPubSampleApp/Views/MPStaticNativeAdView.h delete mode 100644 MoPubSampleApp/Views/MPStaticNativeAdView.m delete mode 100644 MoPubSampleApp/Views/MPTableViewAdPlacerView.h delete mode 100644 MoPubSampleApp/Views/MPTableViewAdPlacerView.m delete mode 100644 MoPubSampleApp/en.lproj/InfoPlist.strings delete mode 100644 MoPubSampleApp/main.m delete mode 100644 MoPubSampleApp/mopub_logo.png 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/Gemfile b/Gemfile deleted file mode 100644 index e4b5be5c3..000000000 --- a/Gemfile +++ /dev/null @@ -1,11 +0,0 @@ -# Autogenerated by fastlane -# -# Ensure this file is checked in to source control! - -source "https://rubygems.org" - -gem 'fastlane' -gem 'cocoapods' - -plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile') -eval_gemfile(plugins_path) if File.exist?(plugins_path) diff --git a/Gemfile.lock b/Gemfile.lock deleted file mode 100644 index 2e1a09d7a..000000000 --- a/Gemfile.lock +++ /dev/null @@ -1,230 +0,0 @@ -GEM - remote: https://rubygems.org/ - specs: - CFPropertyList (3.0.0) - activesupport (4.2.11.1) - i18n (~> 0.7) - minitest (~> 5.1) - thread_safe (~> 0.3, >= 0.3.4) - tzinfo (~> 1.1) - addressable (2.6.0) - public_suffix (>= 2.0.2, < 4.0) - apktools (0.7.2) - rubyzip (~> 1.2.1) - atomos (0.1.3) - aws-eventstream (1.0.3) - aws-sdk (2.11.292) - aws-sdk-resources (= 2.11.292) - aws-sdk-core (2.11.292) - aws-sigv4 (~> 1.0) - jmespath (~> 1.0) - aws-sdk-resources (2.11.292) - aws-sdk-core (= 2.11.292) - aws-sigv4 (1.1.0) - aws-eventstream (~> 1.0, >= 1.0.2) - babosa (1.0.2) - claide (1.0.2) - cocoapods (1.7.5) - activesupport (>= 4.0.2, < 5) - claide (>= 1.0.2, < 2.0) - cocoapods-core (= 1.7.5) - cocoapods-deintegrate (>= 1.0.3, < 2.0) - cocoapods-downloader (>= 1.2.2, < 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.1, < 2.0) - cocoapods-try (>= 1.1.0, < 2.0) - colored2 (~> 3.1) - escape (~> 0.0.4) - fourflusher (>= 2.3.0, < 3.0) - gh_inspector (~> 1.0) - molinillo (~> 0.6.6) - nap (~> 1.0) - ruby-macho (~> 1.4) - xcodeproj (>= 1.10.0, < 2.0) - cocoapods-core (1.7.5) - activesupport (>= 4.0.2, < 6) - fuzzy_match (~> 2.0.4) - nap (~> 1.0) - cocoapods-deintegrate (1.0.4) - cocoapods-downloader (1.2.2) - cocoapods-plugins (1.0.0) - nap - cocoapods-search (1.0.0) - cocoapods-stats (1.1.0) - cocoapods-trunk (1.3.1) - 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.1.5) - declarative (0.0.10) - declarative-option (0.1.0) - digest-crc (0.4.1) - domain_name (0.5.20190701) - unf (>= 0.0.5, < 1.0.0) - dotenv (2.7.4) - emoji_regex (1.0.1) - escape (0.0.4) - excon (0.64.0) - faraday (0.15.4) - multipart-post (>= 1.2, < 3) - faraday-cookie_jar (0.0.6) - faraday (>= 0.7.4) - http-cookie (~> 1.0.0) - faraday_middleware (0.13.1) - faraday (>= 0.7.4, < 1.0) - fastimage (2.1.5) - fastlane (2.126.0) - CFPropertyList (>= 2.3, < 4.0.0) - addressable (>= 2.3, < 3.0.0) - babosa (>= 1.0.2, < 2.0.0) - bundler (>= 1.12.0, < 3.0.0) - colored - commander-fastlane (>= 4.4.6, < 5.0.0) - dotenv (>= 2.1.1, < 3.0.0) - emoji_regex (>= 0.1, < 2.0) - 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.24.0) - google-cloud-storage (>= 1.15.0, < 2.0.0) - highline (>= 1.7.2, < 2.0.0) - json (< 3.0.0) - jwt (~> 2.1.0) - mini_magick (~> 4.5.1) - multi_xml (~> 0.5) - multipart-post (~> 2.0.0) - plist (>= 3.1.0, < 4.0.0) - public_suffix (~> 2.0.0) - rubyzip (>= 1.2.2, < 2.0.0) - security (= 0.1.3) - simctl (~> 1.6.3) - slack-notifier (>= 2.0.0, < 3.0.0) - terminal-notifier (>= 2.0.0, < 3.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.8.1, < 2.0.0) - xcpretty (~> 0.3.0) - xcpretty-travis-formatter (>= 0.0.3) - fastlane-plugin-aws_s3 (1.6.0) - apktools (~> 0.7) - aws-sdk (~> 2.3) - mime-types (~> 3.1) - fourflusher (2.3.1) - fuzzy_match (2.0.4) - gh_inspector (1.1.3) - google-api-client (0.23.9) - 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) - signet (~> 0.9) - google-cloud-core (1.3.0) - google-cloud-env (~> 1.0) - google-cloud-env (1.2.0) - faraday (~> 0.11) - google-cloud-storage (1.16.0) - digest-crc (~> 0.4) - google-api-client (~> 0.23) - google-cloud-core (~> 1.2) - googleauth (>= 0.6.2, < 0.10.0) - googleauth (0.6.7) - faraday (~> 0.12) - jwt (>= 1.4, < 3.0) - memoist (~> 0.16) - multi_json (~> 1.11) - os (>= 0.9, < 2.0) - 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) - jmespath (1.4.0) - json (2.2.0) - jwt (2.1.0) - memoist (0.16.0) - mime-types (3.2.2) - mime-types-data (~> 3.2015) - mime-types-data (3.2019.0331) - mini_magick (4.5.1) - minitest (5.11.3) - molinillo (0.6.6) - multi_json (1.13.1) - multi_xml (0.6.0) - multipart-post (2.0.0) - nanaimo (0.2.6) - nap (1.1.0) - naturally (2.2.0) - netrc (0.11.0) - os (1.0.1) - plist (3.5.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.4.0) - rubyzip (1.2.3) - security (0.1.3) - signet (0.11.0) - 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 (2.0.0) - terminal-table (1.8.0) - unicode-display_width (~> 1.1, >= 1.1.1) - thread_safe (0.3.6) - tty-cursor (0.7.0) - tty-screen (0.7.0) - tty-spinner (0.9.1) - tty-cursor (~> 0.7) - tzinfo (1.2.5) - thread_safe (~> 0.1) - uber (0.1.0) - unf (0.1.4) - unf_ext - unf_ext (0.0.7.6) - unicode-display_width (1.6.0) - word_wrap (1.0.0) - xcodeproj (1.10.0) - CFPropertyList (>= 2.3.3, < 4.0) - atomos (~> 0.1.3) - claide (>= 1.0.2, < 2.0) - colored2 (~> 3.1) - nanaimo (~> 0.2.6) - xcpretty (0.3.0) - rouge (~> 2.0.7) - xcpretty-travis-formatter (1.0.0) - xcpretty (~> 0.2, >= 0.0.7) - -PLATFORMS - ruby - -DEPENDENCIES - cocoapods - fastlane - fastlane-plugin-aws_s3 - -BUNDLED WITH - 2.0.1 diff --git a/MoPubSampleApp/AppDelegate.h b/MoPubSampleApp/AppDelegate.h deleted file mode 100644 index 0b6b177cc..000000000 --- a/MoPubSampleApp/AppDelegate.h +++ /dev/null @@ -1,15 +0,0 @@ -// -// AppDelegate.h -// -// Copyright 2018-2019 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 884641eec..000000000 --- a/MoPubSampleApp/AppDelegate.m +++ /dev/null @@ -1,112 +0,0 @@ -// -// AppDelegate.m -// -// Copyright 2018-2019 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.loggingLevel = MPBLogLevelInfo; - [[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 a8b29c27dab8078354ecf76690db30be1d2a3945..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12198 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 NyJCE~NZan={{ciPtbzal diff --git a/MoPubSampleApp/Assets/Default.png b/MoPubSampleApp/Assets/Default.png deleted file mode 100644 index 5010d5a555efb597804a30f6d61a80f607b3f302..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16432 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 diff --git a/MoPubSampleApp/Assets/icon@2x.png b/MoPubSampleApp/Assets/icon@2x.png deleted file mode 100644 index 2560d162c14da8769f5b7d40c5ecf540a2bfc198..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6656 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 - -@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 3003998f5..000000000 --- a/MoPubSampleApp/Controllers/MPAdEntryViewController.m +++ /dev/null @@ -1,245 +0,0 @@ -// -// MPAdEntryViewController.m -// -// Copyright 2018-2019 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 bfcbc6b8e..000000000 --- a/MoPubSampleApp/Controllers/MPAdTableViewController.h +++ /dev/null @@ -1,17 +0,0 @@ -// -// MPAdTableViewController.h -// -// Copyright 2018-2019 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 173bee61c..000000000 --- a/MoPubSampleApp/Controllers/MPAdTableViewController.m +++ /dev/null @@ -1,255 +0,0 @@ -// -// MPAdTableViewController.m -// -// Copyright 2018-2019 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 2c7103fc3..000000000 --- a/MoPubSampleApp/Controllers/MPBannerAdDetailViewController.h +++ /dev/null @@ -1,25 +0,0 @@ -// -// MPBannerAdDetailViewController.h -// -// Copyright 2018-2019 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 7dbe69a33..000000000 --- a/MoPubSampleApp/Controllers/MPBannerAdDetailViewController.m +++ /dev/null @@ -1,145 +0,0 @@ -// -// MPBannerAdDetailViewController.m -// -// Copyright 2018-2019 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 adSize:(CGSize)adSize -{ - self.loadAdButton.enabled = YES; - self.adView.frame = ({ - CGRect frame = self.adView.frame; - frame.size.height = adSize.height; - frame; - }); - - [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 2e0dbe032..000000000 --- a/MoPubSampleApp/Controllers/MPInterstitialAdDetailViewController.h +++ /dev/null @@ -1,32 +0,0 @@ -// -// MPInterstitialAdDetailViewController.h -// -// Copyright 2018-2019 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 e23331ee9..000000000 --- a/MoPubSampleApp/Controllers/MPInterstitialAdDetailViewController.m +++ /dev/null @@ -1,150 +0,0 @@ -// -// MPInterstitialAdDetailViewController.m -// -// Copyright 2018-2019 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 a37246a6c..000000000 --- a/MoPubSampleApp/Controllers/MPLeaderboardBannerAdDetailViewController.h +++ /dev/null @@ -1,13 +0,0 @@ -// -// MPLeaderboardBannerAdDetailViewController.h -// -// Copyright 2018-2019 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 e7a15d8a1..000000000 --- a/MoPubSampleApp/Controllers/MPLeaderboardBannerAdDetailViewController.m +++ /dev/null @@ -1,44 +0,0 @@ -// -// MPLeaderboardBannerAdDetailViewController.m -// -// Copyright 2018-2019 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 f99769fb1..000000000 --- a/MoPubSampleApp/Controllers/MPMRectBannerAdDetailViewController.h +++ /dev/null @@ -1,14 +0,0 @@ -// -// MPMRectBannerAdDetailViewController.h -// -// Copyright 2018-2019 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 b5b1669f8..000000000 --- a/MoPubSampleApp/Controllers/MPMRectBannerAdDetailViewController.m +++ /dev/null @@ -1,42 +0,0 @@ -// -// MPMRectBannerAdDetailViewController.m -// -// Copyright 2018-2019 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 19f61e3af..000000000 --- a/MoPubSampleApp/Controllers/MPNativeAdDetailViewController.h +++ /dev/null @@ -1,20 +0,0 @@ -// -// MPNativeAdDetailViewController.h -// -// Copyright 2018-2019 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 d407bb623..000000000 --- a/MoPubSampleApp/Controllers/MPNativeAdDetailViewController.m +++ /dev/null @@ -1,180 +0,0 @@ -// -// MPNativeAdDetailViewController.m -// -// Copyright 2018-2019 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 targeting]; - - 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 f8fc81446..000000000 --- a/MoPubSampleApp/Controllers/MPNativeAdPlacerCollectionViewController.h +++ /dev/null @@ -1,16 +0,0 @@ -// -// MPNativeAdPlacerCollectionViewController.h -// -// Copyright 2018-2019 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 fdbabe8a9..000000000 --- a/MoPubSampleApp/Controllers/MPNativeAdPlacerCollectionViewController.m +++ /dev/null @@ -1,149 +0,0 @@ -// -// MPNativeAdPlacerCollectionViewController.m -// -// Copyright 2018-2019 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 5fddca817..000000000 --- a/MoPubSampleApp/Controllers/MPNativeAdPlacerPageViewController.h +++ /dev/null @@ -1,18 +0,0 @@ -// -// MPNativeAdPlacerPageViewController.h -// -// Copyright 2018-2019 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 32a60a357..000000000 --- a/MoPubSampleApp/Controllers/MPNativeAdPlacerPageViewController.m +++ /dev/null @@ -1,283 +0,0 @@ -// -// MPNativeAdPlacerPageViewController.m -// -// Copyright 2018-2019 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 a69c359e5..000000000 --- a/MoPubSampleApp/Controllers/MPNativeAdPlacerTableViewController.h +++ /dev/null @@ -1,17 +0,0 @@ -// -// MPNativeAdPlacerTableViewController.h -// -// Copyright 2018-2019 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 0b910e2a3..000000000 --- a/MoPubSampleApp/Controllers/MPNativeAdPlacerTableViewController.m +++ /dev/null @@ -1,189 +0,0 @@ -// -// MPNativeAdPlacerTableViewController.m -// -// Copyright 2018-2019 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 e142cac5b..000000000 --- a/MoPubSampleApp/Controllers/MPRewardedVideoAdDetailViewController.h +++ /dev/null @@ -1,32 +0,0 @@ -// -// MPRewardedVideoAdDetailViewController.h -// -// Copyright 2018-2019 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 d135bb2d7..000000000 --- a/MoPubSampleApp/Controllers/MPRewardedVideoAdDetailViewController.m +++ /dev/null @@ -1,201 +0,0 @@ -// -// MPRewardedVideoAdDetailViewController.m -// -// Copyright 2018-2019 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 be5583c5c..000000000 --- a/MoPubSampleApp/Controllers/MPViewController.h +++ /dev/null @@ -1,16 +0,0 @@ -// -// MPViewController.h -// -// Copyright 2018-2019 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 3ed3b6937..000000000 --- a/MoPubSampleApp/Controllers/MPViewController.m +++ /dev/null @@ -1,51 +0,0 @@ -// -// MPViewController.m -// -// Copyright 2018-2019 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 8d2efa2a9..000000000 --- a/MoPubSampleApp/Domain/MPAdInfo.h +++ /dev/null @@ -1,44 +0,0 @@ -// -// MPAdInfo.h -// -// Copyright 2018-2019 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 984929dd5..000000000 --- a/MoPubSampleApp/Domain/MPAdInfo.m +++ /dev/null @@ -1,127 +0,0 @@ -// -// MPAdInfo.m -// -// Copyright 2018-2019 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 540185a39..000000000 --- a/MoPubSampleApp/Domain/MPAdSection.h +++ /dev/null @@ -1,23 +0,0 @@ -// -// MPAdSection.h -// -// Copyright 2018-2019 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 a11c0399b..000000000 --- a/MoPubSampleApp/Domain/MPAdSection.m +++ /dev/null @@ -1,50 +0,0 @@ -// -// MPAdSection.m -// -// Copyright 2018-2019 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 93a3d2959..000000000 --- a/MoPubSampleApp/MPAdPersistenceManager.h +++ /dev/null @@ -1,26 +0,0 @@ -// -// MPAdPersistenceManager.h -// -// Copyright 2018-2019 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 04ae3da5e..000000000 --- a/MoPubSampleApp/MPAdPersistenceManager.m +++ /dev/null @@ -1,92 +0,0 @@ -// -// MPAdPersistenceManager.m -// -// Copyright 2018-2019 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 909fc85a7..000000000 --- a/MoPubSampleApp/MPSampleAppInstanceProvider.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// MPSampleAppInstanceProvider.h -// -// Copyright 2018-2019 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 4ce9a3bf1..000000000 --- a/MoPubSampleApp/MPSampleAppInstanceProvider.m +++ /dev/null @@ -1,35 +0,0 @@ -// -// MPSampleAppInstanceProvider.m -// -// Copyright 2018-2019 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 b1937751f..000000000 --- a/MoPubSampleApp/MPSampleAppLogReader.h +++ /dev/null @@ -1,17 +0,0 @@ -// -// MPSampleAppLogReader.h -// -// Copyright 2018-2019 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 4ade75948..000000000 --- a/MoPubSampleApp/MPSampleAppLogReader.m +++ /dev/null @@ -1,73 +0,0 @@ -// -// MPSampleAppLogReader.m -// -// Copyright 2018-2019 Twitter, Inc. -// Licensed under the MoPub SDK License Agreement -// http://www.mopub.com/legal/sdk-license-agreement/ -// - -#import "MPSampleAppLogReader.h" -#import "MPLogging.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 -{ - [MPLogging removeLogger:self]; -} - -- (void)beginReadingLogMessages -{ - [MPLogging removeLogger:self]; - [MPLogging addLogger:self]; -} - -#pragma mark - - -- (MPBLogLevel)logLevel -{ - return MPBLogLevelDebug; -} - -- (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 9b08b86b9..000000000 --- a/MoPubSampleApp/MoPubSampleApp.xcodeproj/project.pbxproj +++ /dev/null @@ -1,836 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 46; - objects = { - -/* Begin PBXBuildFile section */ - 2A5874FB1F9033E800E8CDFA /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2A5874FA1F9033E800E8CDFA /* LaunchScreen.storyboard */; }; - 2A5874FE1F90341F00E8CDFA /* mopub_logo.png in Resources */ = {isa = PBXBuildFile; fileRef = 2A5874FD1F90341F00E8CDFA /* mopub_logo.png */; }; - 2A86E7F21D710CE60087594C /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 57EF00CD1BD96EB5002634DE /* WebKit.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; - 2AC79F441DF7814700195AC5 /* SafariServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2AC79F431DF7814700195AC5 /* SafariServices.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; - 462E156F17F0F5D100408E17 /* MPMRectBannerAdDetailViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 462E156E17F0F5D100408E17 /* MPMRectBannerAdDetailViewController.m */; }; - 467343F017FE3D6900D68BB6 /* MPAdPersistenceManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 467343EF17FE3D6900D68BB6 /* MPAdPersistenceManager.m */; }; - 469A15F01AC1F0BA00D6F0EF /* MPSampleAppLogReader.m in Sources */ = {isa = PBXBuildFile; fileRef = 469A15EF1AC1F0BA00D6F0EF /* MPSampleAppLogReader.m */; }; - 46B9E8AB17E0D7D100B78D11 /* libsqlite3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = AE10920D170120E700812E6E /* libsqlite3.dylib */; }; - 46B9E8AC17E0D7D800B78D11 /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = AE10920C170120E700812E6E /* libz.dylib */; }; - 46B9E8B217E0D80400B78D11 /* MessageUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 46B9E8B117E0D80400B78D11 /* MessageUI.framework */; }; - 46DA87B1185010DD00F34858 /* MPAdEntryViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 46DA87AF185010DD00F34858 /* MPAdEntryViewController.m */; }; - 46DA87B4185010DD00F34858 /* MPAdEntryViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 46DA87B0185010DD00F34858 /* MPAdEntryViewController.xib */; }; - 46F883C917FB7AC5004CE563 /* MPLeaderboardBannerAdDetailViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 46F883C817FB7AC5004CE563 /* MPLeaderboardBannerAdDetailViewController.m */; }; - 4A62566A19A7B45900ED18C8 /* MPNativeAdPlacerTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A1C1DB71980722600B6DB33 /* MPNativeAdPlacerTableViewController.m */; }; - 4A6FC1F718C561B3007A1197 /* MPNativeAdDetailViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A6FC1F518C561B3007A1197 /* MPNativeAdDetailViewController.m */; }; - 4A6FC1F918C561B3007A1197 /* MPNativeAdDetailViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4A6FC1F618C561B3007A1197 /* MPNativeAdDetailViewController.xib */; }; - 4A8DD10118C90542005E9389 /* MPTableViewAdPlacerView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A8DD10018C90542005E9389 /* MPTableViewAdPlacerView.m */; }; - 4A8DD10418C90620005E9389 /* MPNativeAdTableHeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A8DD10318C90620005E9389 /* MPNativeAdTableHeaderView.m */; }; - 4A8DD10618C90636005E9389 /* MPNativeAdTableHeaderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4A8DD10518C90636005E9389 /* MPNativeAdTableHeaderView.xib */; }; - 4ADE5FCE1B4FE836004C3673 /* MPRewardedVideoAdDetailViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4ADE5FCB1B4FE836004C3673 /* MPRewardedVideoAdDetailViewController.xib */; }; - 4ADE5FD01B4FE836004C3673 /* MPRewardedVideoAdDetailViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4ADE5FCD1B4FE836004C3673 /* MPRewardedVideoAdDetailViewController.m */; }; - 4AF797D519A7F9E8007CE34D /* MPNativeAdPlacerCollectionViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = E060FE6B1994000C00F4F33C /* MPNativeAdPlacerCollectionViewController.m */; }; - 571EF4281B7194D60035C9BB /* MPStaticNativeAdView.m in Sources */ = {isa = PBXBuildFile; fileRef = 571EF4271B7194D60035C9BB /* MPStaticNativeAdView.m */; }; - 577A1FA219AE7EA6006DA28B /* MPNativeAdPageView.m in Sources */ = {isa = PBXBuildFile; fileRef = 577A1FA119AE7EA6006DA28B /* MPNativeAdPageView.m */; }; - 577DF03C19A28786001514D3 /* MPNativeAdPlacerPageViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 577DF03B19A28786001514D3 /* MPNativeAdPlacerPageViewController.m */; }; - 57C784D61BCC4C6C00E4BB7D /* MPNativeVideoTableViewAdPlacerView.m in Sources */ = {isa = PBXBuildFile; fileRef = 57C784D51BCC4C6C00E4BB7D /* MPNativeVideoTableViewAdPlacerView.m */; }; - 57C784E91BCC4F4600E4BB7D /* MPNativeVideoView.m in Sources */ = {isa = PBXBuildFile; fileRef = 57C784E81BCC4F4600E4BB7D /* MPNativeVideoView.m */; }; - 57FFAB86199C110D00F655CF /* MPCollectionViewAdPlacerView.m in Sources */ = {isa = PBXBuildFile; fileRef = 57FFAB85199C110D00F655CF /* MPCollectionViewAdPlacerView.m */; }; - AE0F1FAD171F1D0A00FA3BE6 /* AdSupport.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AED9E47216F78DAD00EA71A7 /* AdSupport.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; - AE0F1FAE171F1D1000FA3BE6 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AED9E47016F78DA900EA71A7 /* StoreKit.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; - AE0F1FB2171F1D2800FA3BE6 /* CoreLocation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AE0F1FB1171F1D2800FA3BE6 /* CoreLocation.framework */; }; - AE0F1FB3171F1D3100FA3BE6 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AE23239416F78A17002C2082 /* Foundation.framework */; }; - AE23239D16F78A17002C2082 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = AE23239B16F78A17002C2082 /* InfoPlist.strings */; }; - AE23239F16F78A17002C2082 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = AE23239E16F78A17002C2082 /* main.m */; }; - AE2323A316F78A17002C2082 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = AE2323A216F78A17002C2082 /* AppDelegate.m */; }; - AE43413D16F8EEC500B73710 /* MPAdSection.m in Sources */ = {isa = PBXBuildFile; fileRef = AE43413C16F8EEC500B73710 /* MPAdSection.m */; }; - AE43414716F9068000B73710 /* MPInterstitialAdDetailViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = AE43414516F9068000B73710 /* MPInterstitialAdDetailViewController.m */; }; - AE43414A16F9068000B73710 /* MPInterstitialAdDetailViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = AE43414616F9068000B73710 /* MPInterstitialAdDetailViewController.xib */; }; - AE515FAE171F1BFF0086B464 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AE23239216F78A17002C2082 /* UIKit.framework */; }; - AE8E1D451729A76C0051FA6C /* icon.png in Resources */ = {isa = PBXBuildFile; fileRef = AE8E1D441729A76C0051FA6C /* icon.png */; }; - AE8E1D471729A76F0051FA6C /* icon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AE8E1D461729A76F0051FA6C /* icon@2x.png */; }; - AE8E1D491729A7730051FA6C /* Default.png in Resources */ = {isa = PBXBuildFile; fileRef = AE8E1D481729A7730051FA6C /* Default.png */; }; - AE8E1D4B1729A7760051FA6C /* Default@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AE8E1D4A1729A7760051FA6C /* Default@2x.png */; }; - AE8E1D4D1729A7790051FA6C /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AE8E1D4C1729A7790051FA6C /* Default-568h@2x.png */; }; - AE8E1D501729AA460051FA6C /* white_button.png in Resources */ = {isa = PBXBuildFile; fileRef = AE8E1D4F1729AA460051FA6C /* white_button.png */; }; - AED9E4BB16F7912900EA71A7 /* MPAdTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = AED9E4BA16F7912900EA71A7 /* MPAdTableViewController.m */; }; - AED9E4C516F7934900EA71A7 /* MPAdInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = AED9E4C416F7934900EA71A7 /* MPAdInfo.m */; }; - AED9E4CC16F7987F00EA71A7 /* MPBannerAdDetailViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = AED9E4CB16F7987F00EA71A7 /* MPBannerAdDetailViewController.m */; }; - AED9E4CF16F798AF00EA71A7 /* MPBannerAdDetailViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = AED9E4CE16F798AF00EA71A7 /* MPBannerAdDetailViewController.xib */; }; - AED9E4DE16F7B00100EA71A7 /* MPSampleAppInstanceProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = AED9E4DD16F7B00100EA71A7 /* MPSampleAppInstanceProvider.m */; }; - B26284151B83CACE00960F74 /* CoreMedia.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 46E5043F18035A26006A2FC3 /* CoreMedia.framework */; }; - BC3B98671E70C7E400C77554 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 46B3345919F083EC0078078A /* QuartzCore.framework */; }; - BC3B98681E70C7EC00C77554 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 46B3345B19F083F70078078A /* SystemConfiguration.framework */; }; - BC3B98691E70C7F500C77554 /* MediaPlayer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 46B3345719F083350078078A /* MediaPlayer.framework */; }; - BC3B986A1E70C85400C77554 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AE23239616F78A17002C2082 /* CoreGraphics.framework */; }; - BC6A9187216568A000C35DF5 /* MoPubSDKFramework.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BC6FFA7720E2EF5100FC7FAD /* MoPubSDKFramework.framework */; }; - BC6A9188216568A000C35DF5 /* MoPubSDKFramework.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BC6FFA7720E2EF5100FC7FAD /* MoPubSDKFramework.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - BC789C1C1F7AD333001CE308 /* CoreTelephony.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AE10920A170120E700812E6E /* CoreTelephony.framework */; }; - BCD33AD7201002E2003F4FFB /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 46B9E8AF17E0D7F600B78D11 /* AVFoundation.framework */; }; - BCEF09E51F72D6A2004CEBD6 /* MPViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = BCEF09E41F72D6A2004CEBD6 /* MPViewController.m */; }; -/* End PBXBuildFile section */ - -/* Begin PBXContainerItemProxy section */ - BC6A9189216568A000C35DF5 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = BC6FFA6620E2EF5000FC7FAD /* MoPubSDK.xcodeproj */; - proxyType = 1; - remoteGlobalIDString = 2AF030C02016723700909F29; - remoteInfo = MoPubSDKFramework; - }; - 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; - }; -/* End PBXContainerItemProxy section */ - -/* Begin PBXCopyFilesBuildPhase section */ - BC6A918B216568A000C35DF5 /* Embed Frameworks */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - BC6A9188216568A000C35DF5 /* MoPubSDKFramework.framework in Embed Frameworks */, - ); - name = "Embed Frameworks"; - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - -/* Begin PBXFileReference section */ - 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; }; - AE23238F16F78A17002C2082 /* MoPubSampleApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MoPubSampleApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 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 = ""; }; - AEEA5E4B16F940EC003D48F4 /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS6.1.sdk/System/Library/Frameworks/MobileCoreServices.framework; sourceTree = ""; }; - AEEA5E4F16F940EC003D48F4 /* MessageUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MessageUI.framework; path = /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS6.1.sdk/System/Library/Frameworks/MessageUI.framework; 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 */ - AE23238C16F78A17002C2082 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - BC3B98691E70C7F500C77554 /* MediaPlayer.framework in Frameworks */, - BC3B98681E70C7EC00C77554 /* SystemConfiguration.framework in Frameworks */, - BC3B98671E70C7E400C77554 /* QuartzCore.framework in Frameworks */, - BCD33AD7201002E2003F4FFB /* AVFoundation.framework in Frameworks */, - BC789C1C1F7AD333001CE308 /* CoreTelephony.framework in Frameworks */, - 2AC79F441DF7814700195AC5 /* SafariServices.framework in Frameworks */, - 2A86E7F21D710CE60087594C /* WebKit.framework in Frameworks */, - B26284151B83CACE00960F74 /* CoreMedia.framework in Frameworks */, - BC3B986A1E70C85400C77554 /* CoreGraphics.framework in Frameworks */, - 46B9E8B217E0D80400B78D11 /* MessageUI.framework in Frameworks */, - 46B9E8AC17E0D7D800B78D11 /* libz.dylib in Frameworks */, - 46B9E8AB17E0D7D100B78D11 /* libsqlite3.dylib in Frameworks */, - AE0F1FB3171F1D3100FA3BE6 /* Foundation.framework in Frameworks */, - AE0F1FB2171F1D2800FA3BE6 /* CoreLocation.framework in Frameworks */, - BC6A9187216568A000C35DF5 /* MoPubSDKFramework.framework in Frameworks */, - AE0F1FAE171F1D1000FA3BE6 /* StoreKit.framework in Frameworks */, - AE0F1FAD171F1D0A00FA3BE6 /* AdSupport.framework in Frameworks */, - AE515FAE171F1BFF0086B464 /* 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 = ( - AE23238F16F78A17002C2082 /* MoPubSampleApp.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 */, - AEEA5E4F16F940EC003D48F4 /* MessageUI.framework */, - 46B3348519F0874E0078078A /* MobileCoreServices.framework */, - AEEA5E4B16F940EC003D48F4 /* 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 */ - AE23238E16F78A17002C2082 /* MoPubSampleApp */ = { - isa = PBXNativeTarget; - buildConfigurationList = AE2323AC16F78A17002C2082 /* Build configuration list for PBXNativeTarget "MoPubSampleApp" */; - buildPhases = ( - AE23238B16F78A17002C2082 /* Sources */, - AE23238C16F78A17002C2082 /* Frameworks */, - AE23238D16F78A17002C2082 /* Resources */, - BC6A918B216568A000C35DF5 /* Embed Frameworks */, - ); - buildRules = ( - ); - dependencies = ( - BC6A918A216568A000C35DF5 /* PBXTargetDependency */, - ); - name = MoPubSampleApp; - productName = MoPubSampleApp; - productReference = AE23238F16F78A17002C2082 /* MoPubSampleApp.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - AE23238716F78A17002C2082 /* Project object */ = { - isa = PBXProject; - attributes = { - LastUpgradeCheck = 1100; - ORGANIZATIONNAME = MoPub; - TargetAttributes = { - AE23238E16F78A17002C2082 = { - DevelopmentTeam = 4S7XS533V3; - ProvisioningStyle = Automatic; - }; - }; - }; - buildConfigurationList = AE23238A16F78A17002C2082 /* Build configuration list for PBXProject "MoPubSampleApp" */; - compatibilityVersion = "Xcode 3.2"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = AE23238616F78A17002C2082; - productRefGroup = AE23239016F78A17002C2082 /* Products */; - projectDirPath = ""; - projectReferences = ( - { - ProductGroup = BC6FFA6720E2EF5000FC7FAD /* Products */; - ProjectRef = BC6FFA6620E2EF5000FC7FAD /* MoPubSDK.xcodeproj */; - }, - ); - projectRoot = ""; - targets = ( - AE23238E16F78A17002C2082 /* MoPubSampleApp */, - ); - }; -/* 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 */ - AE23238D16F78A17002C2082 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - AE23239D16F78A17002C2082 /* InfoPlist.strings in Resources */, - AED9E4CF16F798AF00EA71A7 /* MPBannerAdDetailViewController.xib in Resources */, - AE43414A16F9068000B73710 /* MPInterstitialAdDetailViewController.xib in Resources */, - 2A5874FE1F90341F00E8CDFA /* mopub_logo.png in Resources */, - 4A6FC1F918C561B3007A1197 /* MPNativeAdDetailViewController.xib in Resources */, - AE8E1D451729A76C0051FA6C /* icon.png in Resources */, - 46DA87B4185010DD00F34858 /* MPAdEntryViewController.xib in Resources */, - AE8E1D471729A76F0051FA6C /* icon@2x.png in Resources */, - AE8E1D491729A7730051FA6C /* Default.png in Resources */, - AE8E1D4B1729A7760051FA6C /* Default@2x.png in Resources */, - AE8E1D4D1729A7790051FA6C /* Default-568h@2x.png in Resources */, - AE8E1D501729AA460051FA6C /* white_button.png in Resources */, - 4ADE5FCE1B4FE836004C3673 /* MPRewardedVideoAdDetailViewController.xib in Resources */, - 2A5874FB1F9033E800E8CDFA /* LaunchScreen.storyboard in Resources */, - 4A8DD10618C90636005E9389 /* MPNativeAdTableHeaderView.xib in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - AE23238B16F78A17002C2082 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - AE23239F16F78A17002C2082 /* main.m in Sources */, - AE2323A316F78A17002C2082 /* AppDelegate.m in Sources */, - AED9E4BB16F7912900EA71A7 /* MPAdTableViewController.m in Sources */, - AED9E4C516F7934900EA71A7 /* MPAdInfo.m in Sources */, - AED9E4CC16F7987F00EA71A7 /* MPBannerAdDetailViewController.m in Sources */, - AED9E4DE16F7B00100EA71A7 /* MPSampleAppInstanceProvider.m in Sources */, - AE43413D16F8EEC500B73710 /* MPAdSection.m in Sources */, - AE43414716F9068000B73710 /* MPInterstitialAdDetailViewController.m in Sources */, - BCEF09E51F72D6A2004CEBD6 /* MPViewController.m in Sources */, - 467343F017FE3D6900D68BB6 /* MPAdPersistenceManager.m in Sources */, - 46F883C917FB7AC5004CE563 /* MPLeaderboardBannerAdDetailViewController.m in Sources */, - 4ADE5FD01B4FE836004C3673 /* MPRewardedVideoAdDetailViewController.m in Sources */, - 4A8DD10118C90542005E9389 /* MPTableViewAdPlacerView.m in Sources */, - 4A62566A19A7B45900ED18C8 /* MPNativeAdPlacerTableViewController.m in Sources */, - 57C784D61BCC4C6C00E4BB7D /* MPNativeVideoTableViewAdPlacerView.m in Sources */, - 57FFAB86199C110D00F655CF /* MPCollectionViewAdPlacerView.m in Sources */, - 4AF797D519A7F9E8007CE34D /* MPNativeAdPlacerCollectionViewController.m in Sources */, - 4A6FC1F718C561B3007A1197 /* MPNativeAdDetailViewController.m in Sources */, - 469A15F01AC1F0BA00D6F0EF /* MPSampleAppLogReader.m in Sources */, - 462E156F17F0F5D100408E17 /* MPMRectBannerAdDetailViewController.m in Sources */, - 46DA87B1185010DD00F34858 /* MPAdEntryViewController.m in Sources */, - 571EF4281B7194D60035C9BB /* MPStaticNativeAdView.m in Sources */, - 577A1FA219AE7EA6006DA28B /* MPNativeAdPageView.m in Sources */, - 577DF03C19A28786001514D3 /* MPNativeAdPlacerPageViewController.m in Sources */, - 4A8DD10418C90620005E9389 /* MPNativeAdTableHeaderView.m in Sources */, - 57C784E91BCC4F4600E4BB7D /* MPNativeVideoView.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXTargetDependency section */ - BC6A918A216568A000C35DF5 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - name = MoPubSDKFramework; - targetProxy = BC6A9189216568A000C35DF5 /* 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 */ - AE2323AA16F78A17002C2082 /* Debug */ = { - 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; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = 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_IMPLICIT_RETAIN_SELF = 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 = 9.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_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; - 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_DEPRECATED_OBJC_IMPLEMENTATIONS = 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_IMPLICIT_RETAIN_SELF = 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 = 9.0; - OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1"; - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - AE2323AD16F78A17002C2082 /* 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", - ); - GCC_PRECOMPILE_PREFIX_HEADER = YES; - GCC_PREFIX_HEADER = "MoPubSampleApp-Prefix.pch"; - HEADER_SEARCH_PATHS = ( - "$(inherited)", - "$(SRCROOT)/../MoPubSDK/**", - ); - INFOPLIST_FILE = "MoPubSampleApp-Info.plist"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/..", - "$(PROJECT_DIR)/../MoPubSDK/Viewability/Avid", - ); - ONLY_ACTIVE_ARCH = NO; - OTHER_LDFLAGS = ( - "-ObjC", - "$(inherited)", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.mopub.samples.objc.MoPubSampleApp; - PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE = ""; - PROVISIONING_PROFILE_SPECIFIER = ""; - WRAPPER_EXTENSION = app; - }; - name = Debug; - }; - AE2323AE16F78A17002C2082 /* 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", - ); - GCC_PRECOMPILE_PREFIX_HEADER = YES; - GCC_PREFIX_HEADER = "MoPubSampleApp-Prefix.pch"; - HEADER_SEARCH_PATHS = ( - "$(inherited)", - "$(SRCROOT)/../MoPubSDK/**", - ); - INFOPLIST_FILE = "MoPubSampleApp-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.MoPubSampleApp; - PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE = ""; - PROVISIONING_PROFILE_SPECIFIER = ""; - WRAPPER_EXTENSION = app; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - AE23238A16F78A17002C2082 /* Build configuration list for PBXProject "MoPubSampleApp" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - AE2323AA16F78A17002C2082 /* Debug */, - AE2323AB16F78A17002C2082 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - AE2323AC16F78A17002C2082 /* Build configuration list for PBXNativeTarget "MoPubSampleApp" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - AE2323AD16F78A17002C2082 /* Debug */, - AE2323AE16F78A17002C2082 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = AE23238716F78A17002C2082 /* Project object */; -} diff --git a/MoPubSampleApp/MoPubSampleApp.xcodeproj/xcshareddata/xcschemes/MoPubSampleApp.xcscheme b/MoPubSampleApp/MoPubSampleApp.xcodeproj/xcshareddata/xcschemes/MoPubSampleApp.xcscheme deleted file mode 100644 index f82ab918b..000000000 --- a/MoPubSampleApp/MoPubSampleApp.xcodeproj/xcshareddata/xcschemes/MoPubSampleApp.xcscheme +++ /dev/null @@ -1,87 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/MoPubSampleApp/Views/MPCollectionViewAdPlacerView.h b/MoPubSampleApp/Views/MPCollectionViewAdPlacerView.h deleted file mode 100644 index e8f6d4d97..000000000 --- a/MoPubSampleApp/Views/MPCollectionViewAdPlacerView.h +++ /dev/null @@ -1,20 +0,0 @@ -// -// MPCollectionViewAdPlacerView.h -// -// Copyright 2018-2019 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 29f99f892..000000000 --- a/MoPubSampleApp/Views/MPCollectionViewAdPlacerView.m +++ /dev/null @@ -1,65 +0,0 @@ -// -// MPCollectionViewAdPlacerView.m -// -// Copyright 2018-2019 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 8d14dcbe0..000000000 --- a/MoPubSampleApp/Views/MPIndexPathPickerView.h +++ /dev/null @@ -1,37 +0,0 @@ -// -// MPIndexPathPickerView.h -// -// Copyright 2018-2019 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 5ad120e9f..000000000 --- a/MoPubSampleApp/Views/MPIndexPathPickerView.m +++ /dev/null @@ -1,90 +0,0 @@ -// -// MPIndexPathPickerView.m -// -// Copyright 2018-2019 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 59f21db4f..000000000 --- a/MoPubSampleApp/Views/MPNativeAdCell.h +++ /dev/null @@ -1,21 +0,0 @@ -// -// MPNativeAdCell.h -// -// Copyright 2018-2019 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 664164a3f..000000000 --- a/MoPubSampleApp/Views/MPNativeAdCell.m +++ /dev/null @@ -1,83 +0,0 @@ -// -// MPNativeAdCell.m -// -// Copyright 2018-2019 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 a6d682a70..000000000 --- a/MoPubSampleApp/Views/MPNativeAdPageView.h +++ /dev/null @@ -1,21 +0,0 @@ -// -// MPNativeAdPageView.h -// -// Copyright 2018-2019 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 1aed67e39..000000000 --- a/MoPubSampleApp/Views/MPNativeAdPageView.m +++ /dev/null @@ -1,86 +0,0 @@ -// -// MPNativeAdPageView.m -// -// Copyright 2018-2019 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 527b54cdd..000000000 --- a/MoPubSampleApp/Views/MPNativeAdTableHeaderView.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// MPNativeAdTableHeaderView.h -// -// Copyright 2018-2019 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 5a64e5344..000000000 --- a/MoPubSampleApp/Views/MPNativeAdTableHeaderView.m +++ /dev/null @@ -1,13 +0,0 @@ -// -// MPNativeAdTableHeaderView.m -// -// Copyright 2018-2019 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 490414886..000000000 --- a/MoPubSampleApp/Views/MPNativeCollectionViewAdCollectionViewCell.h +++ /dev/null @@ -1,20 +0,0 @@ -// -// MPNativeCollectionViewAdCollectionViewCell.h -// -// Copyright 2018-2019 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 73729cbf1..000000000 --- a/MoPubSampleApp/Views/MPNativeCollectionViewAdCollectionViewCell.m +++ /dev/null @@ -1,60 +0,0 @@ -// -// MPNativeCollectionViewAdCollectionViewCell.m -// -// Copyright 2018-2019 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 8e257bca5..000000000 --- a/MoPubSampleApp/Views/MPNativeVideoTableViewAdPlacerView.h +++ /dev/null @@ -1,21 +0,0 @@ -// -// MPNativeVideoTableViewAdPlacerView.h -// -// Copyright 2018-2019 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 ea4c99f38..000000000 --- a/MoPubSampleApp/Views/MPNativeVideoTableViewAdPlacerView.m +++ /dev/null @@ -1,109 +0,0 @@ -// -// MPNativeVideoTableViewAdPlacerView.m -// -// Copyright 2018-2019 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 fd41612cf..000000000 --- a/MoPubSampleApp/Views/MPNativeVideoView.h +++ /dev/null @@ -1,23 +0,0 @@ -// -// MPNativeVideoView.h -// -// Copyright 2018-2019 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 6f450d1fa..000000000 --- a/MoPubSampleApp/Views/MPNativeVideoView.m +++ /dev/null @@ -1,111 +0,0 @@ -// -// MPNativeVideoView.m -// -// Copyright 2018-2019 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 52b765ec8..000000000 --- a/MoPubSampleApp/Views/MPStaticNativeAdView.h +++ /dev/null @@ -1,21 +0,0 @@ -// -// MPStaticNativeAdView.h -// -// Copyright 2018-2019 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 7c3bbced2..000000000 --- a/MoPubSampleApp/Views/MPStaticNativeAdView.m +++ /dev/null @@ -1,100 +0,0 @@ -// -// MPStaticNativeAdView.m -// -// Copyright 2018-2019 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 a3d0a7f23..000000000 --- a/MoPubSampleApp/Views/MPTableViewAdPlacerView.h +++ /dev/null @@ -1,21 +0,0 @@ -// -// MPTableViewAdPlacerView.h -// -// Copyright 2018-2019 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 d75f4ea8c..000000000 --- a/MoPubSampleApp/Views/MPTableViewAdPlacerView.m +++ /dev/null @@ -1,117 +0,0 @@ -// -// MPTableViewAdPlacerView.m -// -// Copyright 2018-2019 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 b749fa9db..000000000 --- a/MoPubSampleApp/main.m +++ /dev/null @@ -1,18 +0,0 @@ -// -// main.m -// -// Copyright 2018-2019 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 be8a1c94c4c020df39377e600e777cced3cc1108..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 36193 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 YToza}40^VGBD*YiO8I2=al>o>2fzByP5=M^ From 853cbf22ef14c3f5e4aedcf6f00c944d3a2b9843 Mon Sep 17 00:00:00 2001 From: Caleb Lee Date: Mon, 16 Sep 2019 12:07:41 -0700 Subject: [PATCH 11/12] 5.9.0 --- CHANGELOG.md | 22 +- Canary/Canary.xcodeproj/project.pbxproj | 282 +++++++-------- .../xcschemes/Canary Screenshots.xcscheme | 26 +- .../Canary-AppStore Application.xcscheme | 28 +- .../Canary-Internal Application.xcscheme | 32 +- .../xcschemes/Canary-Unit Tests.xcscheme | 30 +- Canary/Canary/AppDelegate+Consent.swift | 35 -- .../Canary/AppDelegate+Initialization.swift | 72 ---- Canary/Canary/AppDelegate.swift | 176 +++++----- .../Canary/Base.lproj/LaunchScreen.storyboard | 10 +- Canary/Canary/Base.lproj/Main.storyboard | 68 ++-- Canary/Canary/Base/AdUnit.swift | 64 ++++ .../Base/AdUnitTableViewController.swift | 28 ++ .../Canary/Cells/AdActionsTableViewCell.swift | 85 ++++- .../Canary/Cells/AdActionsTableViewCell.xib | 141 +++++--- Canary/Canary/Cells/StatusTableViewCell.swift | 6 +- .../Cells/TweetCollectionViewCell.swift | 5 + Canary/Canary/Configuration/Default.xcconfig | 8 +- Canary/Canary/Configuration/Info.plist | 61 +++- .../Extensions/UIViewController+Utility.swift | 64 ++++ Canary/Canary/Formats/AdDataSource.swift | 5 + .../Formats/AdTableViewController.swift | 17 +- .../Canary/Formats/BannerAdDataSource.swift | 17 + .../Formats/InterstitialAdDataSource.swift | 5 + .../NativeAdCollectionViewController.swift | 6 + .../Canary/Formats/NativeAdDataSource.swift | 5 + .../Formats/NativeAdTableViewController.swift | 6 + Canary/Canary/Formats/NativeAdView.swift | 3 + .../Canary/Formats/RewardedAdDataSource.swift | 5 + .../ManualEntryInterfaceViewController.swift | 21 +- Canary/Canary/Menu/MenuViewController.swift | 6 +- .../QRCodeCameraInterfaceViewController.swift | 21 +- Canary/Canary/SceneDelegate.swift | 322 +++++++++++++++++ Canary/CanaryUnitTests/Info.plist | 6 +- MoPubResources/Info.plist | 4 +- MoPubSDK.xcodeproj/project.pbxproj | 146 +++++--- .../xcschemes/MoPubSDKTests.xcscheme | 27 +- MoPubSDK/Internal/Banners/MPBannerAdManager.m | 6 +- .../AdAlerts/MPAdAlertGestureRecognizer.h | 29 +- .../AdAlerts/MPAdAlertGestureRecognizer.m | 7 + MoPubSDK/Internal/Common/MPAdConfiguration.h | 1 + MoPubSDK/Internal/Common/MPAdConfiguration.m | 289 ++++++++------- .../Common/MPAdDestinationDisplayAgent.h | 2 - .../Common/MPAdDestinationDisplayAgent.m | 52 +-- .../Internal/Common/MPAdServerURLBuilder.m | 4 +- MoPubSDK/Internal/Common/MPRealTimeTimer.m | 5 + MoPubSDK/Internal/Common/MPURLResolver.m | 3 +- MoPubSDK/Internal/Common/MPVideoConfig.h | 43 +-- MoPubSDK/Internal/Common/MPVideoConfig.m | 176 ++++------ MoPubSDK/Internal/HTML/MPAdWebViewAgent.h | 1 - MoPubSDK/Internal/HTML/MPAdWebViewAgent.m | 47 +-- .../Internal/HTML/MPHTMLBannerCustomEvent.h | 3 - .../Internal/HTML/MPHTMLBannerCustomEvent.m | 9 +- .../HTML/MPHTMLInterstitialCustomEvent.h | 6 +- .../HTML/MPHTMLInterstitialCustomEvent.m | 28 +- .../HTML/MPHTMLInterstitialViewController.m | 16 - MoPubSDK/Internal/HTML/MPWebView.h | 37 +- MoPubSDK/Internal/HTML/MPWebView.m | 328 ++++-------------- .../Interstitials/MPInterstitialAdManager.m | 5 +- .../MPInterstitialViewController.h | 32 +- .../Internal/MPConsentDialogViewController.m | 4 +- MoPubSDK/Internal/MPConsentManager.m | 18 + MoPubSDK/Internal/MPMediationManager.m | 9 +- MoPubSDK/Internal/MPURLRequest.m | 38 +- MoPubSDK/Internal/MPVASTTracking.h | 42 +-- MoPubSDK/Internal/MPVASTTracking.m | 309 ++++++----------- MoPubSDK/Internal/MPWebBrowserUserAgentInfo.h | 23 ++ MoPubSDK/Internal/MPWebBrowserUserAgentInfo.m | 74 ++++ .../Internal/MRAID/MPMRAIDBannerCustomEvent.h | 3 - .../Internal/MRAID/MPMRAIDBannerCustomEvent.m | 4 + .../MRAID/MPMRAIDInterstitialCustomEvent.h | 6 +- .../MRAID/MPMRAIDInterstitialCustomEvent.m | 30 +- .../MRAID/MPMRAIDInterstitialViewController.h | 1 - MoPubSDK/Internal/MRAID/MRBridge.m | 29 +- MoPubSDK/Internal/MRAID/MRController.m | 12 +- MoPubSDK/Internal/MRAID/MRProperty.m | 7 +- .../Internal/MRAID/MRVideoPlayerManager.m | 43 ++- .../Utility/Categories/NSString+MPAdditions.m | 9 +- .../Utility/Categories/NSURL+MPAdditions.h | 2 - .../Utility/Categories/NSURL+MPAdditions.m | 20 +- ...SKStoreProductViewController+MPAdditions.h | 24 ++ ...SKStoreProductViewController+MPAdditions.m | 35 ++ .../Utility/Categories/UIView+MPAdditions.h | 18 + .../Utility/Categories/UIView+MPAdditions.m | 84 +++++ .../Categories/UIWebView+MPAdditions.h | 19 - .../Categories/UIWebView+MPAdditions.m | 49 --- .../Utility/MOPUBExperimentProvider.h | 8 +- .../Utility/MOPUBExperimentProvider.m | 44 ++- .../Internal/Utility/MPAnalyticsTracker.h | 14 +- .../Internal/Utility/MPAnalyticsTracker.m | 7 +- MoPubSDK/Internal/Utility/MPGlobal.h | 15 - MoPubSDK/Internal/Utility/MPGlobal.m | 78 +---- .../Internal/Utility/MPStoreKitProvider.h | 23 -- .../Internal/Utility/MPStoreKitProvider.m | 47 --- .../Utility/Protocols/MPMediaFileCache.h | 52 +++ MoPubSDK/Internal/VAST/MPVASTCompanionAd.h | 49 ++- MoPubSDK/Internal/VAST/MPVASTCompanionAd.m | 9 +- MoPubSDK/Internal/VAST/MPVASTCreative.h | 5 +- MoPubSDK/Internal/VAST/MPVASTLinearAd.h | 14 +- MoPubSDK/Internal/VAST/MPVASTLinearAd.m | 26 -- MoPubSDK/Internal/VAST/MPVASTMediaFile.h | 13 +- MoPubSDK/Internal/VAST/MPVASTMediaFile.m | 83 +++++ MoPubSDK/Internal/VAST/MPVASTResource.h | 23 +- MoPubSDK/Internal/VAST/MPVASTResource.m | 10 + MoPubSDK/Internal/VAST/MPVASTTrackingEvent.h | 52 +-- MoPubSDK/Internal/VAST/MPVASTTrackingEvent.m | 54 ++- MoPubSDK/MPAdView.h | 3 - MoPubSDK/MPConstants.h | 2 +- MoPubSDK/MoPub.h | 6 +- MoPubSDK/MoPub.m | 27 +- MoPubSDK/NativeAds/Internal/MPDiskLRUCache.h | 4 + MoPubSDK/NativeAds/Internal/MPDiskLRUCache.m | 147 +++++--- .../NativeAds/Internal/MPImageDownloadQueue.h | 12 +- .../NativeAds/Internal/MPImageDownloadQueue.m | 19 +- .../Internal/MPNativeAdRendererImageHandler.m | 7 +- MoPubSDK/NativeAds/MPNativeAdRequest.m | 5 +- MoPubSDK/NativeAds/MPNativeCustomEvent.m | 2 +- .../MOPUBNativeVideoImpressionAgent.h | 18 - .../MOPUBNativeVideoImpressionAgent.m | 64 ---- .../Internal/MOPUBPlayerViewController.h | 6 +- .../Internal/MOPUBPlayerViewController.m | 58 +++- .../NativeVideo/MOPUBNativeVideoAdRenderer.m | 33 +- MoPubSDK/Resources/MPAdapters.plist | 1 + .../MPMoPubRewardedPlayableCustomEvent.m | 56 ++- .../MPMoPubRewardedVideoCustomEvent.m | 45 +-- .../Internal/MPRewardedVideoAdManager.m | 5 +- .../MOAT/MPViewabilityAdapterMoat.m | 5 +- MoPubSDK/Viewability/MPWebView+Viewability.h | 13 +- MoPubSDK/Viewability/MPWebView+Viewability.m | 3 +- MoPubSDKFramework/Info.plist | 4 +- .../VAST_3.0_linear_ad_comprehensive.xml | 94 +++++ MoPubSDKTests/Info.plist | 4 +- .../MOPUBExperimentProvider+Testing.h | 2 +- MoPubSDKTests/MOPUBExperimentProviderTests.m | 83 ++--- MoPubSDKTests/MPAdConfiguration+Testing.h | 10 + MoPubSDKTests/MPAdConfiguration+Testing.m | 15 + MoPubSDKTests/MPAdConfigurationTests.m | 44 +-- .../MPBannerAdManagerDelegateHandler.m | 4 + MoPubSDKTests/MPDiskLRUCacheTests.m | 102 ++++++ MoPubSDKTests/MPGeolocationProviderTest.m | 25 +- MoPubSDKTests/MPRealTimeTimer+Testing.h | 20 ++ MoPubSDKTests/MPRealTimeTimer+Testing.m | 15 + MoPubSDKTests/MPRealTimeTimerTests.m | 30 +- MoPubSDKTests/MPTimerTests.m | 3 +- MoPubSDKTests/MPURLRequestTests.m | 46 --- MoPubSDKTests/MPVASTLinearAdTests.m | 58 +--- MoPubSDKTests/MPVASTMediaFileTests.m | 124 +++++++ MoPubSDKTests/MPVASTTrackingTests.m | 223 ++++++++++++ MoPubSDKTests/MPVideoConfigTests.m | 189 ++++------ .../MPWebBrowserUserAgentInfoTests.m | 47 +++ MoPubSDKTests/MPWebView+Testing.h | 1 - MoPubSDKTests/MPWebView+Testing.m | 1 - MoPubSDKTests/MPWebViewTests.m | 49 +-- MoPubSDKTests/MoPub+Testing.h | 7 + MoPubSDKTests/MoPub+Testing.m | 7 + MoPubSDKTests/MoPubTests.m | 43 --- MoPubSDKTests/XCTestCase+MPAddition.h | 5 +- MoPubSDKTests/XCTestCase+MPAddition.m | 23 +- README.md | 39 ++- mopub-ios-sdk.podspec | 7 +- 160 files changed, 3720 insertions(+), 2677 deletions(-) delete mode 100644 Canary/Canary/AppDelegate+Consent.swift delete mode 100644 Canary/Canary/AppDelegate+Initialization.swift create mode 100644 Canary/Canary/Extensions/UIViewController+Utility.swift create mode 100644 Canary/Canary/SceneDelegate.swift create mode 100644 MoPubSDK/Internal/MPWebBrowserUserAgentInfo.h create mode 100644 MoPubSDK/Internal/MPWebBrowserUserAgentInfo.m create mode 100644 MoPubSDK/Internal/Utility/Categories/SKStoreProductViewController+MPAdditions.h create mode 100644 MoPubSDK/Internal/Utility/Categories/SKStoreProductViewController+MPAdditions.m delete mode 100644 MoPubSDK/Internal/Utility/Categories/UIWebView+MPAdditions.h delete mode 100644 MoPubSDK/Internal/Utility/Categories/UIWebView+MPAdditions.m delete mode 100644 MoPubSDK/Internal/Utility/MPStoreKitProvider.h delete mode 100644 MoPubSDK/Internal/Utility/MPStoreKitProvider.m create mode 100644 MoPubSDK/Internal/Utility/Protocols/MPMediaFileCache.h delete mode 100644 MoPubSDK/NativeVideo/Internal/MOPUBNativeVideoImpressionAgent.h delete mode 100644 MoPubSDK/NativeVideo/Internal/MOPUBNativeVideoImpressionAgent.m create mode 100644 MoPubSDKTests/Fixtures/VastXML/VAST_3.0_linear_ad_comprehensive.xml create mode 100644 MoPubSDKTests/MPDiskLRUCacheTests.m create mode 100644 MoPubSDKTests/MPRealTimeTimer+Testing.h create mode 100644 MoPubSDKTests/MPRealTimeTimer+Testing.m create mode 100644 MoPubSDKTests/MPVASTMediaFileTests.m create mode 100644 MoPubSDKTests/MPVASTTrackingTests.m create mode 100644 MoPubSDKTests/MPWebBrowserUserAgentInfoTests.m diff --git a/CHANGELOG.md b/CHANGELOG.md index b379a4df4..8fbacc673 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,24 @@ -## Version 5.8.0 (July 22, 2019) +## 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`. diff --git a/Canary/Canary.xcodeproj/project.pbxproj b/Canary/Canary.xcodeproj/project.pbxproj index ecf506a34..2f61f6ee9 100644 --- a/Canary/Canary.xcodeproj/project.pbxproj +++ b/Canary/Canary.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ 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 */; }; @@ -17,8 +18,7 @@ 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 */; }; - 3FFB21881C2230B8120188EF /* Pods_AppStore_Application.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DB1088039C5F170AE338BACD /* Pods_AppStore_Application.framework */; }; - 40FDA37129281ACEDA16C5D1 /* Pods_Internal_Application.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 04062B8FCC938E7869BA852B /* Pods_Internal_Application.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 */; }; @@ -26,8 +26,6 @@ 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 */; }; @@ -42,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 */; }; @@ -79,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 */; }; - BC4309C822BD811400A7CBE5 /* AppDelegate+Initialization.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC4309C722BD811400A7CBE5 /* AppDelegate+Initialization.swift */; }; - BC4309C922BD811400A7CBE5 /* AppDelegate+Initialization.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC4309C722BD811400A7CBE5 /* AppDelegate+Initialization.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 */; }; @@ -199,6 +195,7 @@ 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 */; }; @@ -215,6 +212,10 @@ 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 */; }; @@ -226,7 +227,6 @@ 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 */; }; - F5527B01889E33C5A2B6CB9E /* Pods_CanaryUnitTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B73A1E273FE9E424C016B22E /* Pods_CanaryUnitTests.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -267,7 +267,8 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 04062B8FCC938E7869BA852B /* Pods_Internal_Application.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Internal_Application.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 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; }; @@ -275,17 +276,13 @@ 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 = ""; }; - 792ED9133D866365E93CDB2E /* Pods-AppStore Application.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AppStore Application.release.xcconfig"; path = "Pods/Target Support Files/Pods-AppStore Application/Pods-AppStore Application.release.xcconfig"; sourceTree = ""; }; - 7B24E5575F339F1A17104381 /* Pods-Internal Application.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Internal Application.release.xcconfig"; path = "Pods/Target Support Files/Pods-Internal Application/Pods-Internal Application.release.xcconfig"; sourceTree = ""; }; - 7D892EB99170C15369014895 /* Pods-AppStore Application.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AppStore Application.debug.xcconfig"; path = "Pods/Target Support Files/Pods-AppStore Application/Pods-AppStore Application.debug.xcconfig"; sourceTree = ""; }; - 84ADB8A0A63B79D852517109 /* Pods-CanaryUnitTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CanaryUnitTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-CanaryUnitTests/Pods-CanaryUnitTests.debug.xcconfig"; sourceTree = ""; }; - A55602BC6D63A07F0C2F32F9 /* Pods-CanaryUnitTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CanaryUnitTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-CanaryUnitTests/Pods-CanaryUnitTests.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 = ""; }; - B73A1E273FE9E424C016B22E /* Pods_CanaryUnitTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_CanaryUnitTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 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 = ""; }; @@ -298,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 = ""; }; @@ -321,7 +318,6 @@ 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 = ""; }; - BC4309C722BD811400A7CBE5 /* AppDelegate+Initialization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+Initialization.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 = ""; }; @@ -377,6 +373,7 @@ 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 = ""; }; + 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 = ""; }; @@ -397,7 +394,8 @@ 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 = ""; }; - DB1088039C5F170AE338BACD /* Pods_AppStore_Application.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_AppStore_Application.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 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 = ""; }; @@ -409,13 +407,15 @@ 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 = ""; }; - F80CB2C182236FA7D7126F3A /* Pods-Internal Application.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Internal Application.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Internal Application/Pods-Internal Application.debug.xcconfig"; 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 */ @@ -431,7 +431,7 @@ buildActionMask = 2147483647; files = ( 2A35FAA921B5DA1C00DC8805 /* AVFoundation.framework in Frameworks */, - 3FFB21881C2230B8120188EF /* Pods_AppStore_Application.framework in Frameworks */, + 5A5CDC8C0D154CB3FBD61596 /* Pods_AppStore_Application.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -439,7 +439,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 40FDA37129281ACEDA16C5D1 /* Pods_Internal_Application.framework in Frameworks */, + DE0F2F349F83D71EFA10F713 /* Pods_Internal_Application.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -454,13 +454,26 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - F5527B01889E33C5A2B6CB9E /* Pods_CanaryUnitTests.framework in Frameworks */, + 1DE64A0B3BDD8FCD47D78D70 /* Pods_CanaryUnitTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 0579E20B6B7F9CB8F99A5FA4 /* Pods */ = { + isa = PBXGroup; + children = ( + 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 = Pods; + sourceTree = ""; + }; 2A49E1FE229876310049B61B /* Testing */ = { isa = PBXGroup; children = ( @@ -538,7 +551,7 @@ EC469C1122613063006CABAA /* CanaryUnitTests */, BC33E0341FBB627B0060ECBE /* Products */, BD565F79D171D27FE1BAD7EB /* Frameworks */, - DF7CF2094E2195D55BC35732 /* Pods */, + 0579E20B6B7F9CB8F99A5FA4 /* Pods */, ); sourceTree = ""; }; @@ -560,8 +573,6 @@ BC7892E8220B9C22007A6218 /* AccessibilityIdentifier.swift */, BC4FE3052208C57A00B5D240 /* AdapterVersions */, BC33E0361FBB627B0060ECBE /* AppDelegate.swift */, - BC4309C722BD811400A7CBE5 /* AppDelegate+Initialization.swift */, - BC00C5B7208FCF46008E0AEB /* AppDelegate+Consent.swift */, BC33E03F1FBB627B0060ECBE /* Assets.xcassets */, BC04944520F911C900CFD9C2 /* Base */, BCFE5B391FE4469D00D760E9 /* Cells */, @@ -582,6 +593,7 @@ BCA042EE211E049D001B1AF5 /* RoundedButton.swift */, BCFE5B3D1FE44F6D00D760E9 /* Samples */, B275DB2B200FD5F500F053F8 /* SavedAds */, + EC799949230F8C1500589ED4 /* SceneDelegate.swift */, BC04945920F91C9E00CFD9C2 /* StoryboardInstantiable.swift */, BCB4DF33211CD970005BD171 /* TableViewCellRegisterable.swift */, EC469C06226006FC006CABAA /* Utility */, @@ -605,7 +617,7 @@ CE237F1F216BF2C800A8134A /* CanaryUITests */, B2564EE020AB4F2B000B9F7A /* Internal-Info.plist */, BC04944620F91BF400CFD9C2 /* Internal.storyboard */, - BC04945C20F9266A00CFD9C2 /* Internal.swift */, + BC04945C20F9266A00CFD9C2 /* InternalState.swift */, BC713BAF21700BC6003655B2 /* ReleaseTesting */, 2A49E1FE229876310049B61B /* Testing */, BC04944920F91BFC00CFD9C2 /* NetworkAds */, @@ -731,6 +743,7 @@ BCEB5DA82008279900FE5165 /* UIColor+Random.swift */, EC32526322527BCB00D955C3 /* UITableView+Utility.swift */, BCD0506B2003F1FF00FFC36D /* UIView+Nib.swift */, + EC7999532311917100589ED4 /* UIViewController+Utility.swift */, EC32524C224ECF8E00D955C3 /* UserDefaults+Subscript.swift */, ); path = Extensions; @@ -741,9 +754,9 @@ children = ( 2A35FAA821B5DA1C00DC8805 /* AVFoundation.framework */, BCE738601FBB707500093CCD /* CoalMine.framework */, - DB1088039C5F170AE338BACD /* Pods_AppStore_Application.framework */, - 04062B8FCC938E7869BA852B /* Pods_Internal_Application.framework */, - B73A1E273FE9E424C016B22E /* Pods_CanaryUnitTests.framework */, + 7E7C4E30CE724CAC318447FB /* Pods_AppStore_Application.framework */, + 6C40A5887B79C7007EC58F2F /* Pods_CanaryUnitTests.framework */, + 22E0C64A8C1276259F1261BA /* Pods_Internal_Application.framework */, ); name = Frameworks; sourceTree = ""; @@ -822,19 +835,6 @@ path = Models; sourceTree = ""; }; - DF7CF2094E2195D55BC35732 /* Pods */ = { - isa = PBXGroup; - children = ( - 7D892EB99170C15369014895 /* Pods-AppStore Application.debug.xcconfig */, - 792ED9133D866365E93CDB2E /* Pods-AppStore Application.release.xcconfig */, - F80CB2C182236FA7D7126F3A /* Pods-Internal Application.debug.xcconfig */, - 7B24E5575F339F1A17104381 /* Pods-Internal Application.release.xcconfig */, - 84ADB8A0A63B79D852517109 /* Pods-CanaryUnitTests.debug.xcconfig */, - A55602BC6D63A07F0C2F32F9 /* Pods-CanaryUnitTests.release.xcconfig */, - ); - name = Pods; - sourceTree = ""; - }; EC0BF26D2279EBD5003DB141 /* Renderer */ = { isa = PBXGroup; children = ( @@ -913,12 +913,12 @@ isa = PBXNativeTarget; buildConfigurationList = BC33E0471FBB627B0060ECBE /* Build configuration list for PBXNativeTarget "AppStore Application" */; buildPhases = ( - D51BD0DE37345D03D205482E /* [CP] Check Pods Manifest.lock */, + 7350EAF1AF6ACE42BEAAC86B /* [CP] Check Pods Manifest.lock */, BC33E02F1FBB627B0060ECBE /* Sources */, BC33E0301FBB627B0060ECBE /* Frameworks */, BC33E0311FBB627B0060ECBE /* Resources */, - 13E326CCA598A76497758F8C /* [CP] Embed Pods Frameworks */, - 2D493E5599C29683E4828EA3 /* [CP] Copy Pods Resources */, + F13E46A693BE60715BE1DE50 /* [CP] Embed Pods Frameworks */, + 8370782BC7BDAFE77D68D311 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -933,13 +933,13 @@ isa = PBXNativeTarget; buildConfigurationList = BC33E0541FBB63260060ECBE /* Build configuration list for PBXNativeTarget "Internal Application" */; buildPhases = ( - 371F83FF04D20DE5CD91ADC2 /* [CP] Check Pods Manifest.lock */, + A31D1F12840809F9FE1C6487 /* [CP] Check Pods Manifest.lock */, BC33E04B1FBB63260060ECBE /* Sources */, BC33E04F1FBB63260060ECBE /* Frameworks */, BC33E0501FBB63260060ECBE /* Resources */, BC4A99D5200EBCDD00E3EB07 /* Embed Frameworks */, - 981882FB734329AFDEA3F443 /* [CP] Embed Pods Frameworks */, - 0FFF2E27255AD016DA6C1AEC /* [CP] Copy Pods Resources */, + ADDF9FE7C64C325F8798C08D /* [CP] Embed Pods Frameworks */, + 7D04CED96FDEE549DEA336F2 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -972,7 +972,7 @@ isa = PBXNativeTarget; buildConfigurationList = EC469C1922613063006CABAA /* Build configuration list for PBXNativeTarget "CanaryUnitTests" */; buildPhases = ( - E872B166018E00107914C51A /* [CP] Check Pods Manifest.lock */, + DDB6F252CAC6B77CAA9926BD /* [CP] Check Pods Manifest.lock */, EC469C0C22613063006CABAA /* Sources */, EC469C0D22613063006CABAA /* Frameworks */, EC469C0E22613063006CABAA /* Resources */, @@ -1122,18 +1122,42 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 0FFF2E27255AD016DA6C1AEC /* [CP] Copy Pods Resources */ = { + 7350EAF1AF6ACE42BEAAC86B /* [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-AppStore 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; + }; + 7D04CED96FDEE549DEA336F2 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( "${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; @@ -1141,13 +1165,56 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Internal Application/Pods-Internal Application-resources.sh\"\n"; showEnvVarsInLog = 0; }; - 13E326CCA598A76497758F8C /* [CP] Embed Pods Frameworks */ = { + 8370782BC7BDAFE77D68D311 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-AppStore Application/Pods-AppStore Application-frameworks.sh", + "${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 = "\"${PODS_ROOT}/Target Support Files/Pods-AppStore Application/Pods-AppStore Application-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + A31D1F12840809F9FE1C6487 /* [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-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", @@ -1206,29 +1273,10 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-AppStore Application/Pods-AppStore Application-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; - 2D493E5599C29683E4828EA3 /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-AppStore Application/Pods-AppStore Application-resources.sh", - "${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}/TapjoyResources.bundle", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-AppStore Application/Pods-AppStore Application-resources.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Internal Application/Pods-Internal Application-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - 371F83FF04D20DE5CD91ADC2 /* [CP] Check Pods Manifest.lock */ = { + DDB6F252CAC6B77CAA9926BD /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -1243,20 +1291,20 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Internal Application-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; }; - 981882FB734329AFDEA3F443 /* [CP] Embed Pods Frameworks */ = { + F13E46A693BE60715BE1DE50 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Internal Application/Pods-Internal Application-frameworks.sh", + "${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", @@ -1315,51 +1363,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Internal Application/Pods-Internal Application-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; - D51BD0DE37345D03D205482E /* [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-AppStore 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; - }; - E872B166018E00107914C51A /* [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-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"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-AppStore Application/Pods-AppStore Application-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -1405,7 +1409,6 @@ BCA042E9211E004D001B1AF5 /* AdActionsTableViewCell.swift in Sources */, 2A1F52F422160A0300933277 /* ManualEntryInterfaceViewController.swift in Sources */, BC4FE3032208C57000B5D240 /* AdapterVersionsMenuDataSource.swift in Sources */, - BC00C5B8208FCF46008E0AEB /* AppDelegate+Consent.swift in Sources */, BC3B0C952007DAA5002D28B1 /* NativeAdCollectionViewController.swift in Sources */, BCFE5B371FE4469200D760E9 /* AdUnitTableViewCell.swift in Sources */, BC647D9A20F967C800FAA12C /* BaseSplitViewController.swift in Sources */, @@ -1421,6 +1424,7 @@ BC4CE29C2136054500CA0220 /* NativeAdTableDataSource.swift in Sources */, BC04945A20F91C9E00CFD9C2 /* StoryboardInstantiable.swift in Sources */, BC525ED4212F2D71007B1761 /* InterstitialAdDataSource.swift in Sources */, + EC7999542311917100589ED4 /* UIViewController+Utility.swift in Sources */, BC33E0371FBB627B0060ECBE /* AppDelegate.swift in Sources */, BCA042EF211E049D001B1AF5 /* RoundedButton.swift in Sources */, BC3B0C982007DBAA002D28B1 /* TweetCollectionViewCell.swift in Sources */, @@ -1428,6 +1432,7 @@ 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 */, @@ -1445,7 +1450,6 @@ BC4CE29F2136FB6100CA0220 /* AdViewController.swift in Sources */, BC3B0C8C20058D7C002D28B1 /* NativeAdView.swift in Sources */, EC469C072260EF1D006CABAA /* TypedNotification.swift in Sources */, - BC4309C822BD811400A7CBE5 /* AppDelegate+Initialization.swift in Sources */, 2A35FAAB21B5DA6200DC8805 /* QRCodeCameraInterfaceViewController.swift in Sources */, BCB4DF25211B913F005BD171 /* AdDataSource.swift in Sources */, BC7892E9220B9C22007A6218 /* AccessibilityIdentifier.swift in Sources */, @@ -1468,6 +1472,7 @@ 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 */, @@ -1483,7 +1488,6 @@ 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 */, @@ -1494,7 +1498,6 @@ BC647D9B20F967C800FAA12C /* BaseSplitViewController.swift in Sources */, B275DB2E200FD64000F053F8 /* SavedAdsDataSource.swift in Sources */, BCEB5DA72008270300FE5165 /* CGFloat+Random.swift in Sources */, - BC4309C922BD811400A7CBE5 /* AppDelegate+Initialization.swift in Sources */, BC713BAE21700BBC003655B2 /* ReleaseTestingSplitViewController.swift in Sources */, BC003AFE20DC42FE00CD1FEB /* LogingLevelMenuDataSource.swift in Sources */, BC525EEA2135BA4E007B1761 /* PreferredWidthLabel.swift in Sources */, @@ -1513,7 +1516,7 @@ 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 */, @@ -1535,6 +1538,7 @@ 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 */, @@ -1771,13 +1775,14 @@ }; BC33E0481FBB627B0060ECBE /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7D892EB99170C15369014895 /* Pods-AppStore Application.debug.xcconfig */; + baseConfigurationReference = CA1DEC059DCDC57801D88A60 /* Pods-AppStore Application.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 5.8.0; + CURRENT_PROJECT_VERSION = 5.9.0; DEVELOPMENT_TEAM = 4S7XS533V3; 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; SWIFT_VERSION = 4.2; @@ -1788,13 +1793,14 @@ }; BC33E0491FBB627B0060ECBE /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 792ED9133D866365E93CDB2E /* Pods-AppStore Application.release.xcconfig */; + baseConfigurationReference = 38B93E7C897560D8A670BE08 /* Pods-AppStore Application.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 5.8.0; + CURRENT_PROJECT_VERSION = 5.9.0; DEVELOPMENT_TEAM = 4S7XS533V3; 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; SWIFT_VERSION = 4.2; @@ -1805,13 +1811,14 @@ }; BC33E0551FBB63260060ECBE /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = F80CB2C182236FA7D7126F3A /* Pods-Internal Application.debug.xcconfig */; + baseConfigurationReference = E280CCD44F6A8B4AF8F3766C /* Pods-Internal Application.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon.Internal; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 5.8.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; @@ -1823,13 +1830,14 @@ }; BC33E0561FBB63260060ECBE /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7B24E5575F339F1A17104381 /* Pods-Internal Application.release.xcconfig */; + baseConfigurationReference = F8AD0C2B4CE6D8A1D096CD2F /* Pods-Internal Application.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon.Internal; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 5.8.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; @@ -1878,7 +1886,7 @@ }; EC469C1722613063006CABAA /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 84ADB8A0A63B79D852517109 /* Pods-CanaryUnitTests.debug.xcconfig */; + baseConfigurationReference = E2E466BCBD3F681948D93068 /* Pods-CanaryUnitTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ENABLE_OBJC_WEAK = YES; @@ -1900,7 +1908,7 @@ }; EC469C1822613063006CABAA /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = A55602BC6D63A07F0C2F32F9 /* Pods-CanaryUnitTests.release.xcconfig */; + baseConfigurationReference = 2783EDBC05C4535EE4245274 /* Pods-CanaryUnitTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ENABLE_OBJC_WEAK = YES; diff --git a/Canary/Canary.xcodeproj/xcshareddata/xcschemes/Canary Screenshots.xcscheme b/Canary/Canary.xcodeproj/xcshareddata/xcschemes/Canary Screenshots.xcscheme index c7404c28d..9f042f53b 100644 --- a/Canary/Canary.xcodeproj/xcshareddata/xcschemes/Canary Screenshots.xcscheme +++ b/Canary/Canary.xcodeproj/xcshareddata/xcschemes/Canary Screenshots.xcscheme @@ -11,6 +11,15 @@ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" shouldUseLaunchSchemeArgsEnv = "YES"> + + + + @@ -23,17 +32,6 @@ - - - - - - - - diff --git a/Canary/Canary.xcodeproj/xcshareddata/xcschemes/Canary-AppStore Application.xcscheme b/Canary/Canary.xcodeproj/xcshareddata/xcschemes/Canary-AppStore Application.xcscheme index 44b7424db..b3d76709e 100644 --- a/Canary/Canary.xcodeproj/xcshareddata/xcschemes/Canary-AppStore Application.xcscheme +++ b/Canary/Canary.xcodeproj/xcshareddata/xcschemes/Canary-AppStore Application.xcscheme @@ -15,7 +15,7 @@ @@ -27,6 +27,15 @@ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" shouldUseLaunchSchemeArgsEnv = "YES"> + + + + @@ -39,17 +48,6 @@ - - - - - - - - diff --git a/Canary/Canary.xcodeproj/xcshareddata/xcschemes/Canary-Internal Application.xcscheme b/Canary/Canary.xcodeproj/xcshareddata/xcschemes/Canary-Internal Application.xcscheme index f998966a0..ef73b2f5b 100644 --- a/Canary/Canary.xcodeproj/xcshareddata/xcschemes/Canary-Internal Application.xcscheme +++ b/Canary/Canary.xcodeproj/xcshareddata/xcschemes/Canary-Internal Application.xcscheme @@ -15,7 +15,7 @@ @@ -26,8 +26,17 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - codeCoverageEnabled = "YES" - shouldUseLaunchSchemeArgsEnv = "YES"> + shouldUseLaunchSchemeArgsEnv = "YES" + codeCoverageEnabled = "YES"> + + + + - - - - - - - - diff --git a/Canary/Canary.xcodeproj/xcshareddata/xcschemes/Canary-Unit Tests.xcscheme b/Canary/Canary.xcodeproj/xcshareddata/xcschemes/Canary-Unit Tests.xcscheme index fa0a7d5ce..86565994c 100644 --- a/Canary/Canary.xcodeproj/xcshareddata/xcschemes/Canary-Unit Tests.xcscheme +++ b/Canary/Canary.xcodeproj/xcshareddata/xcschemes/Canary-Unit Tests.xcscheme @@ -10,9 +10,18 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + shouldUseLaunchSchemeArgsEnv = "YES" enableThreadSanitizer = "YES" - codeCoverageEnabled = "YES" - shouldUseLaunchSchemeArgsEnv = "YES"> + codeCoverageEnabled = "YES"> + + + + @@ -25,17 +34,6 @@ - - - - - - - - diff --git a/Canary/Canary/AppDelegate+Consent.swift b/Canary/Canary/AppDelegate+Consent.swift deleted file mode 100644 index 334d94d7e..000000000 --- a/Canary/Canary/AppDelegate+Consent.swift +++ /dev/null @@ -1,35 +0,0 @@ -// -// AppDelegate+Consent.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 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+Initialization.swift b/Canary/Canary/AppDelegate+Initialization.swift deleted file mode 100644 index ab880afd7..000000000 --- a/Canary/Canary/AppDelegate+Initialization.swift +++ /dev/null @@ -1,72 +0,0 @@ -// -// AppDelegate+Initialization.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 - -fileprivate let kAdUnitId = "0ac59b0996d947309c33f59d6676399f" - -extension AppDelegate { - /** - 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. - */ - func checkAndInitializeSdk() { - // Already have a valid cached ad unit ID for consent. Just initialize the SDK. - if UserDefaults.standard.cachedAdUnitId.count > 0 { - initializeMoPubSdk(adUnitIdForConsent: UserDefaults.standard.cachedAdUnitId) - return - } - - // Need to prompt for an ad unit. - let prompt: UIAlertController = UIAlertController(title: "MoPub SDK initialization", message: "Enter an ad unit ID to use for consent:", preferredStyle: .alert) - var adUnitIdTextField: UITextField? = nil - 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, handler: { _ in - UserDefaults.standard.cachedAdUnitId = kAdUnitId; - self.initializeMoPubSdk(adUnitIdForConsent: kAdUnitId) - })) - prompt.addAction(UIAlertAction(title: "Use inputted ID", style: .default, handler: { _ in - let adUnitID = adUnitIdTextField?.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""; - UserDefaults.standard.cachedAdUnitId = adUnitID; - self.initializeMoPubSdk(adUnitIdForConsent: adUnitID); - })) - - DispatchQueue.main.async { - self.containerViewController.present(prompt, animated: true, completion: nil) - } - } - - /** - 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. - */ - func initializeMoPubSdk(adUnitIdForConsent: String) { - // MoPub SDK initialization - let sdkConfig = MPMoPubConfiguration(adUnitIdForAppInitialization: adUnitIdForConsent) - sdkConfig.globalMediationSettings = [] - sdkConfig.loggingLevel = .info - - MoPub.sharedInstance().initializeSdk(with: sdkConfig) { - // Update the state of the menu now that the SDK has completed initialization. - if let menuController = self.containerViewController.menuViewController { - menuController.updateIfNeeded() - } - - // Request user consent to collect personally identifiable information - // used for targeted ads - if let tabBarController = self.containerViewController.mainTabBarController { - self.displayConsentDialog(from: tabBarController) - } - } - } - -} diff --git a/Canary/Canary/AppDelegate.swift b/Canary/Canary/AppDelegate.swift index b5c9e667d..e345226ca 100644 --- a/Canary/Canary/AppDelegate.swift +++ b/Canary/Canary/AppDelegate.swift @@ -7,107 +7,125 @@ // import UIKit -import MoPub - -let kAppId = "112358" @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 { - if UserDefaults.standard.shouldClearCachedNetworks { - MoPub.sharedInstance().clearCachedNetworks() // do this before initializing the MoPub SDK - print("\(#function) cached networks are cleared") + if #available(iOS 13, *) { + // Do nothing here. App launch will be handled by `SceneDelegate.scene(_:willConnectTo:options:)` + } else { + sceneDelegate.handleMainSceneStart() } - - // 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 - checkAndInitializeSdk() - - // 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 - - Returns: true if successfully shown, false if not - */ - func openMoPubUrl(url: URL, onto splitViewController: UISplitViewController?, shouldSave: Bool) -> Bool { - // Get adUnit object from the URL. If an adUnit is not obtainable from the URL, return false. - guard let adUnit = AdUnit(url: url) else { - return false - } - - return openMoPubAdUnit(adUnit: adUnit, onto: splitViewController, shouldSave: shouldSave) - } - - - /** - 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 - - Returns: true if successfully shown, false if not - */ - func openMoPubAdUnit(adUnit: AdUnit, onto splitViewController: UISplitViewController?, shouldSave: Bool) -> 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. - self.containerViewController.mainTabBarController?.selectedIndex = 1 - if shouldSave { - 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/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 f8b6c9d28..256da284d 100644 --- a/Canary/Canary/Base.lproj/Main.storyboard +++ b/Canary/Canary/Base.lproj/Main.storyboard @@ -1,11 +1,9 @@ - - - - + + - + @@ -15,7 +13,7 @@ - + @@ -73,7 +71,7 @@ - + @@ -94,7 +92,7 @@ - + @@ -457,12 +455,12 @@ - + @@ -470,17 +468,17 @@ - + @@ -520,7 +518,7 @@ @@ -591,7 +589,7 @@ - + @@ -611,6 +609,6 @@ - + diff --git a/Canary/Canary/Base/AdUnit.swift b/Canary/Canary/Base/AdUnit.swift index 898a9cad7..b1ae73a73 100644 --- a/Canary/Canary/Base/AdUnit.swift +++ b/Canary/Canary/Base/AdUnit.swift @@ -146,3 +146,67 @@ extension AdUnit { 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/AdUnitTableViewController.swift b/Canary/Canary/Base/AdUnitTableViewController.swift index 13caa00a5..d351ec17f 100644 --- a/Canary/Canary/Base/AdUnitTableViewController.swift +++ b/Canary/Canary/Base/AdUnitTableViewController.swift @@ -43,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 @@ -161,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/Cells/AdActionsTableViewCell.swift b/Canary/Canary/Cells/AdActionsTableViewCell.swift index b3b133dc8..813fe41aa 100644 --- a/Canary/Canary/Cells/AdActionsTableViewCell.swift +++ b/Canary/Canary/Cells/AdActionsTableViewCell.swift @@ -8,16 +8,49 @@ import UIKit +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) @@ -32,13 +65,34 @@ final class AdActionsTableViewCell: UITableViewCell, TableViewCellRegisterable { 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(isAdLoading: Bool = false, loadAdHandler: AdActionHandler? = nil, showAdHandler: AdActionHandler? = nil, showButtonEnabled: Bool = false) { + + 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 @@ -47,9 +101,38 @@ final class AdActionsTableViewCell: UITableViewCell, TableViewCellRegisterable { // 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: 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/StatusTableViewCell.swift b/Canary/Canary/Cells/StatusTableViewCell.swift index ab852aac5..497379182 100644 --- a/Canary/Canary/Cells/StatusTableViewCell.swift +++ b/Canary/Canary/Cells/StatusTableViewCell.swift @@ -29,7 +29,11 @@ final class StatusTableViewCell: UITableViewCell, TableViewCellRegisterable { 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 diff --git a/Canary/Canary/Cells/TweetCollectionViewCell.swift b/Canary/Canary/Cells/TweetCollectionViewCell.swift index 148be85b3..fa0c6df5f 100644 --- a/Canary/Canary/Cells/TweetCollectionViewCell.swift +++ b/Canary/Canary/Cells/TweetCollectionViewCell.swift @@ -29,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 diff --git a/Canary/Canary/Configuration/Default.xcconfig b/Canary/Canary/Configuration/Default.xcconfig index a84f6faad..4652caeb7 100644 --- a/Canary/Canary/Configuration/Default.xcconfig +++ b/Canary/Canary/Configuration/Default.xcconfig @@ -9,4 +9,10 @@ // Configuration settings file format documentation can be found at: // https://help.apple.com/xcode/#/dev745c5c974 -PRODUCT_NAME = Canary +// 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/Configuration/Info.plist b/Canary/Canary/Configuration/Info.plist index 631906eeb..efc68681a 100644 --- a/Canary/Canary/Configuration/Info.plist +++ b/Canary/Canary/Configuration/Info.plist @@ -11,11 +11,11 @@ CFBundleInfoDictionaryVersion 6.0 CFBundleName - $(PRODUCT_NAME) + $(BUNDLE_NAME) CFBundlePackageType APPL CFBundleShortVersionString - 5.8.0 + 5.9.0 CFBundleURLTypes @@ -28,7 +28,11 @@ CFBundleVersion - 5.8.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 @@ -67,17 +110,5 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight - NSCameraUsageDescription - The MoPub Canary app uses the camera for the ease-of-use feature of reading MoPub ad QR codes. - NSCalendarsUsageDescription - Calendar access requested on behalf of Verizon Ads Network. - NSPhotoLibraryUsageDescription - Photo access requested on behalf of Verizon Ads Network. - NSBluetoothPeripheralUsageDescription - Bluetooth access requested on behalf of Verizon Ads Network. - ITSAppUsesNonExemptEncryption - - GADApplicationIdentifier - ca-app-pub-3940256099942544~1458002511 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/Formats/AdDataSource.swift b/Canary/Canary/Formats/AdDataSource.swift index 2967ad64e..0b8825d73 100644 --- a/Canary/Canary/Formats/AdDataSource.swift +++ b/Canary/Canary/Formats/AdDataSource.swift @@ -132,6 +132,11 @@ protocol AdDataSource: class { */ 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. diff --git a/Canary/Canary/Formats/AdTableViewController.swift b/Canary/Canary/Formats/AdTableViewController.swift index 5e400bf3d..f763e549b 100644 --- a/Canary/Canary/Formats/AdTableViewController.swift +++ b/Canary/Canary/Formats/AdTableViewController.swift @@ -96,6 +96,11 @@ class AdTableViewController: UIViewController, AdViewController { // 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) { @@ -233,11 +238,13 @@ extension AdTableViewController: UITableViewDataSource { */ func tableView(_ tableView: UITableView, actionCellForRowAt indexPath: IndexPath) -> 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] let showEnabled = dataSource.isAdLoaded - cell.refresh(isAdLoading: isAdLoading, loadAdHandler: loadHandler, showAdHandler: showHandler, showButtonEnabled: showEnabled) + cell.refresh(adSize: dataSource.requestedAdSize, isAdLoading: isAdLoading, loadAdHandler: loadHandler, showAdHandler: showHandler, showButtonEnabled: showEnabled) return cell } @@ -260,6 +267,14 @@ extension AdTableViewController: UITableViewDataSource { } } +extension AdTableViewController: AdActionsTableViewCellDelegate { + // MARK: - AdActionsTableViewCellDelegate + + func requestedAdSizeUpdated(to size: CGSize) { + dataSource.requestedAdSize = size + } +} + extension AdTableViewController: UITableViewDelegate { // MARK: - UITableViewDelegate diff --git a/Canary/Canary/Formats/BannerAdDataSource.swift b/Canary/Canary/Formats/BannerAdDataSource.swift index f459dd8dd..21da66a64 100644 --- a/Canary/Canary/Formats/BannerAdDataSource.swift +++ b/Canary/Canary/Formats/BannerAdDataSource.swift @@ -128,6 +128,23 @@ class BannerAdDataSource: NSObject, AdDataSource { */ private(set) var isAdLoading: Bool = false + /** + 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() { diff --git a/Canary/Canary/Formats/InterstitialAdDataSource.swift b/Canary/Canary/Formats/InterstitialAdDataSource.swift index 4cf793c63..18a9c1a24 100644 --- a/Canary/Canary/Formats/InterstitialAdDataSource.swift +++ b/Canary/Canary/Formats/InterstitialAdDataSource.swift @@ -117,6 +117,11 @@ class InterstitialAdDataSource: NSObject, AdDataSource { */ private(set) var isAdLoading: Bool = false + /** + 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() { diff --git a/Canary/Canary/Formats/NativeAdCollectionViewController.swift b/Canary/Canary/Formats/NativeAdCollectionViewController.swift index 3e6985485..21389406a 100644 --- a/Canary/Canary/Formats/NativeAdCollectionViewController.swift +++ b/Canary/Canary/Formats/NativeAdCollectionViewController.swift @@ -67,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) { diff --git a/Canary/Canary/Formats/NativeAdDataSource.swift b/Canary/Canary/Formats/NativeAdDataSource.swift index 9d122414b..89a71aa39 100644 --- a/Canary/Canary/Formats/NativeAdDataSource.swift +++ b/Canary/Canary/Formats/NativeAdDataSource.swift @@ -119,6 +119,11 @@ class NativeAdDataSource: BaseNativeAdDataSource, AdDataSource { */ private(set) var isAdLoading: Bool = false + /** + Optional ad size used for requesting inline ads. This should be `nil` for non-inline ads. + */ + var requestedAdSize: CGSize? = nil + // MARK: - Ad Loading /** diff --git a/Canary/Canary/Formats/NativeAdTableViewController.swift b/Canary/Canary/Formats/NativeAdTableViewController.swift index 673740112..7d2dbba9d 100644 --- a/Canary/Canary/Formats/NativeAdTableViewController.swift +++ b/Canary/Canary/Formats/NativeAdTableViewController.swift @@ -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 diff --git a/Canary/Canary/Formats/NativeAdView.swift b/Canary/Canary/Formats/NativeAdView.swift index a15970738..767e4d7bd 100644 --- a/Canary/Canary/Formats/NativeAdView.swift +++ b/Canary/Canary/Formats/NativeAdView.swift @@ -56,6 +56,9 @@ class NativeAdView: UIView { // 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/RewardedAdDataSource.swift b/Canary/Canary/Formats/RewardedAdDataSource.swift index fd92dec33..a52c21a1f 100644 --- a/Canary/Canary/Formats/RewardedAdDataSource.swift +++ b/Canary/Canary/Formats/RewardedAdDataSource.swift @@ -130,6 +130,11 @@ class RewardedAdDataSource: NSObject, AdDataSource { */ private(set) var isAdLoading: Bool = false + /** + Optional ad size used for requesting inline ads. This should be `nil` for non-inline ads. + */ + var requestedAdSize: CGSize? = nil + // MARK: - Reward Selection /** diff --git a/Canary/Canary/ManualEntryInterfaceViewController.swift b/Canary/Canary/ManualEntryInterfaceViewController.swift index 2f4d9a762..23627074c 100644 --- a/Canary/Canary/ManualEntryInterfaceViewController.swift +++ b/Canary/Canary/ManualEntryInterfaceViewController.swift @@ -9,10 +9,6 @@ import UIKit class ManualEntryInterfaceViewController: UIViewController { - /** - The shared app delegate. This is used to access deep linking functionality contained in app delegate. - */ - let appDelegate = UIApplication.shared.delegate as? AppDelegate var selectedFormat: AdFormat = AdFormat.allCases[0] { didSet { @@ -25,10 +21,15 @@ class ManualEntryInterfaceViewController: UIViewController { // 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 appDelegate = appDelegate else { + guard let savedAdSplitViewController = savedAdSplitViewController else { return } @@ -56,11 +57,11 @@ class ManualEntryInterfaceViewController: UIViewController { } // If everything is formed correctly, dismiss and show ad. - navigationController?.dismiss(animated: true, completion: { - _ = appDelegate.openMoPubAdUnit(adUnit: adUnit, - onto: appDelegate.savedAdSplitViewController, - shouldSave: shouldSave) - }) + dismiss(animated: true) { + SceneDelegate.openMoPubAdUnit(adUnit: adUnit, + onto: savedAdSplitViewController, + shouldSave: shouldSave) + } } // MARK: - IBOutlets diff --git a/Canary/Canary/Menu/MenuViewController.swift b/Canary/Canary/Menu/MenuViewController.swift index 580a5cd02..504614eb0 100644 --- a/Canary/Canary/Menu/MenuViewController.swift +++ b/Canary/Canary/Menu/MenuViewController.swift @@ -99,11 +99,11 @@ 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 { + if let containerViewController = containerViewController, + let presentingController = containerViewController.mainTabBarController { let shouldCloseMenu: Bool = dataSource.didSelect(itemAtIndexPath: indexPath, inTableView: tableView, presentingFrom: presentingController) if shouldCloseMenu { - container.closeMenu() + containerViewController.closeMenu() } } } diff --git a/Canary/Canary/QRCodeCameraInterfaceViewController.swift b/Canary/Canary/QRCodeCameraInterfaceViewController.swift index 42c23be3e..d2ef217cd 100644 --- a/Canary/Canary/QRCodeCameraInterfaceViewController.swift +++ b/Canary/Canary/QRCodeCameraInterfaceViewController.swift @@ -34,12 +34,6 @@ class QRCodeCameraInterfaceViewController: UIViewController { */ let cameraDispatchQueue = DispatchQueue(label: "Camera Dispatch Queue") - /** - The shared app delegate. This is used to access deep linking functionality contained in app delegate when - `metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection)` is called. - */ - let appDelegate = UIApplication.shared.delegate as? AppDelegate - // Always leave the status bar hidden because this is a fullscreen camera feed. override var prefersStatusBarHidden: Bool { return true @@ -176,6 +170,10 @@ extension QRCodeCameraInterfaceViewController: AVCaptureMetadataOutputObjectsDel 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. @@ -183,19 +181,18 @@ extension QRCodeCameraInterfaceViewController: AVCaptureMetadataOutputObjectsDel let urlString = metadataObject.stringValue, let url = URL(string: urlString), let adUnit = AdUnit(url: url), - let appDelegate = appDelegate, 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(completion: { + self.dismissCamera { // Open `adUnit` via the same path that deep linking uses - _ = appDelegate.openMoPubAdUnit(adUnit: adUnit, - onto: appDelegate.savedAdSplitViewController, - shouldSave: true) - }) + SceneDelegate.openMoPubAdUnit(adUnit: adUnit, + onto: savedAdSplitViewController, + shouldSave: true) + } } break } 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/CanaryUnitTests/Info.plist b/Canary/CanaryUnitTests/Info.plist index 916cd9387..456f445ec 100644 --- a/Canary/CanaryUnitTests/Info.plist +++ b/Canary/CanaryUnitTests/Info.plist @@ -11,12 +11,12 @@ CFBundleInfoDictionaryVersion 6.0 CFBundleName - $(PRODUCT_NAME) + $(BUNDLE_NAME) CFBundlePackageType BNDL CFBundleShortVersionString - 5.8.0 + 5.9.0 CFBundleVersion - 5.8.0 + 5.9.0 diff --git a/MoPubResources/Info.plist b/MoPubResources/Info.plist index 4bb41d74d..597fecc54 100644 --- a/MoPubResources/Info.plist +++ b/MoPubResources/Info.plist @@ -13,7 +13,7 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 5.8.0 + 5.9.0 CFBundleSignature ???? CFBundleSupportedPlatforms @@ -21,7 +21,7 @@ iPhoneOS CFBundleVersion - 5.8.0 + 5.9.0 NSHumanReadableCopyright Copyright 2019 Twitter Inc. All rights reserved. NSPrincipalClass diff --git a/MoPubSDK.xcodeproj/project.pbxproj b/MoPubSDK.xcodeproj/project.pbxproj index ab2d2d356..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 */; }; @@ -123,7 +122,6 @@ 2A27028620214502004A72E6 /* NSURL+MPAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = AEF9D516171CA895005AAA5A /* NSURL+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 */; }; @@ -131,7 +129,6 @@ 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 */; }; 2A27029620214502004A72E6 /* MPGeolocationProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = A7759F051A1EB19500087E00 /* MPGeolocationProvider.m */; }; @@ -216,6 +213,10 @@ 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 */; }; @@ -246,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 */; }; @@ -306,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 */; }; @@ -400,7 +401,6 @@ 2AF032642016B78800909F29 /* NSURL+MPAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = AEF9D515171CA895005AAA5A /* NSURL+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, ); }; }; @@ -408,7 +408,6 @@ 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 */; }; 2AF032742016B78800909F29 /* MPGeolocationProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = A7759F041A1EB19500087E00 /* MPGeolocationProvider.h */; }; @@ -482,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 */; }; @@ -582,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 */; }; @@ -590,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 */; }; @@ -798,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 */; }; @@ -813,7 +808,6 @@ 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 */; }; @@ -893,9 +887,18 @@ 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 */ @@ -972,6 +975,8 @@ 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 = ""; }; @@ -1004,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 = ""; }; @@ -1102,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 = ""; }; @@ -1311,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 = ""; }; @@ -1327,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 = ""; }; @@ -1566,10 +1567,18 @@ 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 */ @@ -1577,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 */, @@ -1660,6 +1670,8 @@ B23C3B501E353F950003D79E /* Fixtures */, ECD106E92280FE3700398CA5 /* Internal */, 2AAA8DFD1D95C77B006962E8 /* Info.plist */, + EC481B0423209E65003AA3B9 /* MPWebBrowserUserAgentInfoTests.m */, + ECF653C422DD3D0700B7DE1D /* MPDiskLRUCacheTests.m */, BC7003D422679C72001222E7 /* Logging */, BCAED2661DF62E4200D45480 /* Mocks */, B2D54B661ED20C95004E3C7B /* MOPUBExperimentProviderTests.m */, @@ -1769,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 */, @@ -2122,6 +2132,8 @@ BC94CC8820FF97FE00FB018A /* MPURL.m */, BCECF3092047715E005AF3BD /* MPURLRequest.h */, BCECF30A2047715E005AF3BD /* MPURLRequest.m */, + EC481B0123209D84003AA3B9 /* MPWebBrowserUserAgentInfo.h */, + EC481B0223209D84003AA3B9 /* MPWebBrowserUserAgentInfo.m */, AEF9D500171CA895005AAA5A /* MRAID */, AEF9D513171CA895005AAA5A /* Utility */, A776A5121B5DDE6200095706 /* VAST */, @@ -2264,6 +2276,7 @@ AEF9D513171CA895005AAA5A /* Utility */ = { isa = PBXGroup; children = ( + EC87668B22EB6E6B00D4B3D9 /* Protocols */, AEF9D514171CA895005AAA5A /* Categories */, AEF9D51B171CA895005AAA5A /* MPAnalyticsTracker.h */, AEF9D51C171CA895005AAA5A /* MPAnalyticsTracker.m */, @@ -2277,8 +2290,6 @@ AEF9D526171CA895005AAA5A /* MPReachability.m */, AEF9D527171CA895005AAA5A /* MPSessionTracker.h */, AEF9D528171CA895005AAA5A /* MPSessionTracker.m */, - AEF9D529171CA895005AAA5A /* MPStoreKitProvider.h */, - AEF9D52A171CA895005AAA5A /* MPStoreKitProvider.m */, AEF9D52B171CA895005AAA5A /* MPTimer.h */, AEF9D52C171CA895005AAA5A /* MPTimer.m */, 4614B0A4195B6B2200B39F8A /* MPUserInteractionGestureRecognizer.h */, @@ -2314,12 +2325,12 @@ BC0ADDEF2080023C000ADEA4 /* NSString+MPConsentStatus.m */, AEF9D515171CA895005AAA5A /* NSURL+MPAdditions.h */, AEF9D516171CA895005AAA5A /* NSURL+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 = ""; @@ -2362,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 = ""; @@ -2462,6 +2474,8 @@ 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 */, @@ -2565,10 +2579,28 @@ 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 = ""; @@ -2642,7 +2674,6 @@ 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 */, @@ -2748,6 +2779,7 @@ 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 /* MPLogManager.h in Headers */, @@ -2760,7 +2792,6 @@ 2AF032642016B78800909F29 /* NSURL+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 */, @@ -2769,7 +2800,6 @@ 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 */, 2AF032742016B78800909F29 /* MPGeolocationProvider.h in Headers */, @@ -2956,6 +2986,7 @@ 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 */, 2AA73BA01FCF8AD0001FB787 /* MPDAAIcon@2x.png in Resources */, BC08C7A51E36BB8200444F16 /* linear-mime-types.xml in Resources */, @@ -3045,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 */ @@ -3055,7 +3086,9 @@ 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 */, @@ -3074,10 +3107,12 @@ 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 */, @@ -3106,6 +3141,7 @@ 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 */, @@ -3178,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 */, @@ -3311,7 +3346,6 @@ 2A27028620214502004A72E6 /* NSURL+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 */, @@ -3320,7 +3354,6 @@ 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 */, 2A27029620214502004A72E6 /* MPGeolocationProvider.m in Sources */, @@ -3332,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 */, @@ -3342,6 +3376,7 @@ 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 */, @@ -3448,7 +3483,6 @@ A7A1CDE71974904A0082A6FA /* MPStreamAdPlacementData.m in Sources */, A714FE641B699587000EAEC4 /* MPVASTStringUtilities.m in Sources */, BCA762332149B4B100D55A05 /* MPLogEvent.m in Sources */, - 57AC69621BB21FA20053C556 /* MOPUBNativeVideoImpressionAgent.m in Sources */, AE515F59171F1B110086B464 /* MPInterstitialViewController.m in Sources */, 57AC698A1BB21FFC0053C556 /* MPNativeAdRendererImageHandler.m in Sources */, A776A5361B5DDF9900095706 /* MPVASTWrapper.m in Sources */, @@ -3486,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 */, @@ -3496,6 +3529,7 @@ AE515F67171F1B110086B464 /* MPAnalyticsTracker.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 */, @@ -3521,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 */, @@ -3541,6 +3574,7 @@ 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 */, @@ -3630,7 +3664,6 @@ 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 */, @@ -3644,6 +3677,7 @@ 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 /* MPLogManager.m in Sources */, @@ -3653,7 +3687,6 @@ 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 */, @@ -3667,6 +3700,7 @@ 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 */, @@ -3704,7 +3738,7 @@ CLANG_WARN_SUSPICIOUS_MOVES = YES; CLANG_WARN_UNREACHABLE_CODE = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 5.8.0; + CURRENT_PROJECT_VERSION = 5.9.0; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 4S7XS533V3; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -3716,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 = ( @@ -3726,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; @@ -3745,7 +3778,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 5.8.0; + CURRENT_PROJECT_VERSION = 5.9.0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 4S7XS533V3; ENABLE_NS_ASSERTIONS = NO; @@ -3757,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 = ( @@ -3767,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; @@ -3776,6 +3808,7 @@ 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"; @@ -3787,12 +3820,12 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 5.8.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.8.0; + DYLIB_CURRENT_VERSION = 5.9.0; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_PREPROCESSOR_DEFINITIONS = ( @@ -3804,7 +3837,6 @@ 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; @@ -3822,6 +3854,7 @@ 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"; @@ -3834,12 +3867,12 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 5.8.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.8.0; + DYLIB_CURRENT_VERSION = 5.9.0; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_NS_ASSERTIONS = NO; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -3851,7 +3884,6 @@ 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; @@ -3873,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.8.0; + CURRENT_PROJECT_VERSION = 5.9.0; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_GENERATE_DEBUGGING_SYMBOLS = NO; GCC_PREPROCESSOR_DEFINITIONS = ( @@ -3907,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.8.0; + CURRENT_PROJECT_VERSION = 5.9.0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -3934,7 +3966,7 @@ AE515F9A171F1B110086B464 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - CURRENT_PROJECT_VERSION = 5.8.0; + CURRENT_PROJECT_VERSION = 5.9.0; DSTROOT = /tmp/MoPubSDK.dst; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -3954,7 +3986,7 @@ AE515F9B171F1B110086B464 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - CURRENT_PROJECT_VERSION = 5.8.0; + CURRENT_PROJECT_VERSION = 5.9.0; DSTROOT = /tmp/MoPubSDK.dst; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -4013,7 +4045,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; ONLY_ACTIVE_ARCH = YES; OTHER_CFLAGS = "-fembed-bitcode"; OTHER_LDFLAGS = ( @@ -4059,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", @@ -4073,7 +4105,7 @@ BCA00B951EF47A91006FF762 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - CURRENT_PROJECT_VERSION = 5.8.0; + CURRENT_PROJECT_VERSION = 5.9.0; DSTROOT = /tmp/MoPubSDK.dst; GCC_PRECOMPILE_PREFIX_HEADER = NO; GCC_TREAT_WARNINGS_AS_ERRORS = YES; @@ -4090,7 +4122,7 @@ BCA00B961EF47A91006FF762 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - CURRENT_PROJECT_VERSION = 5.8.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/MoPubSDKTests.xcscheme b/MoPubSDK.xcodeproj/xcshareddata/xcschemes/MoPubSDKTests.xcscheme index 63ba74244..67984ba5d 100644 --- a/MoPubSDK.xcodeproj/xcshareddata/xcschemes/MoPubSDKTests.xcscheme +++ b/MoPubSDK.xcodeproj/xcshareddata/xcschemes/MoPubSDKTests.xcscheme @@ -26,8 +26,17 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - codeCoverageEnabled = "YES" - shouldUseLaunchSchemeArgsEnv = "YES"> + shouldUseLaunchSchemeArgsEnv = "YES" + codeCoverageEnabled = "YES"> + + + + - - - - - - - - *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; @@ -168,6 +169,8 @@ - (void)loadAdWithURL:(NSURL *)URL URL = (URL) ? URL : [MPAdServerURLBuilder URLWithAdUnitID:[self.delegate adUnitId] targeting:self.targeting]; + self.mostRecentlyLoadedURL = URL; + [self.communicator loadURL:URL]; } @@ -363,7 +366,8 @@ - (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. diff --git a/MoPubSDK/Internal/Common/AdAlerts/MPAdAlertGestureRecognizer.h b/MoPubSDK/Internal/Common/AdAlerts/MPAdAlertGestureRecognizer.h index 3ed7fa0f5..427c64498 100644 --- a/MoPubSDK/Internal/Common/AdAlerts/MPAdAlertGestureRecognizer.h +++ b/MoPubSDK/Internal/Common/AdAlerts/MPAdAlertGestureRecognizer.h @@ -6,28 +6,21 @@ // 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 38de5851a..c642e937d 100644 --- a/MoPubSDK/Internal/Common/AdAlerts/MPAdAlertGestureRecognizer.m +++ b/MoPubSDK/Internal/Common/AdAlerts/MPAdAlertGestureRecognizer.m @@ -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,6 +29,7 @@ @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 diff --git a/MoPubSDK/Internal/Common/MPAdConfiguration.h b/MoPubSDK/Internal/Common/MPAdConfiguration.h index 84415dcca..aa04b0aa0 100644 --- a/MoPubSDK/Internal/Common/MPAdConfiguration.h +++ b/MoPubSDK/Internal/Common/MPAdConfiguration.h @@ -113,6 +113,7 @@ extern NSString * const kBannerImpressionMinPixelMetadataKey; @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. diff --git a/MoPubSDK/Internal/Common/MPAdConfiguration.m b/MoPubSDK/Internal/Common/MPAdConfiguration.m index f1b1ecbc6..761c7bb29 100644 --- a/MoPubSDK/Internal/Common/MPAdConfiguration.m +++ b/MoPubSDK/Internal/Common/MPAdConfiguration.m @@ -6,17 +6,25 @@ // 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" @@ -99,24 +107,20 @@ // 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; -- (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; -- (MPImpressionData *)impressionDataFromMetadata:(NSDictionary *)metadata; - @end //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -127,165 +131,188 @@ - (instancetype)initWithMetadata:(NSDictionary *)metadata data:(NSData *)data ad { self = [super init]; if (self) { - self.adResponseData = data; + [self commonInitWithMetadata:metadata + data:data + adType:adType + experimentProvider:MOPUBExperimentProvider.sharedInstance]; + } + return self; +} - self.adType = adType; - self.adUnitWarmingUp = [metadata mp_boolForKey:kAdUnitWarmingUpMetadataKey]; +/** + 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.networkType = [self networkTypeFromMetadata:metadata]; - self.networkType = self.networkType ? self.networkType : @""; + self.adType = adType; + self.adUnitWarmingUp = [metadata mp_boolForKey:kAdUnitWarmingUpMetadataKey]; - self.preferredSize = CGSizeMake([metadata mp_floatForKey:kWidthMetadataKey], - [metadata mp_floatForKey:kHeightMetadataKey]); + self.networkType = [self networkTypeFromMetadata:metadata]; + self.networkType = self.networkType ? self.networkType : @""; - 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.preferredSize = CGSizeMake([metadata mp_floatForKey:kWidthMetadataKey], + [metadata mp_floatForKey:kHeightMetadataKey]); - self.refreshInterval = [self refreshIntervalFromMetadata:metadata]; - self.adTimeoutInterval = [self timeIntervalFromMsmetadata:metadata forKey:kAdTimeoutMetadataKey]; + 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.nativeSDKParameters = [self dictionaryFromMetadata:metadata - forKey:kNativeSDKParametersMetadataKey]; + self.refreshInterval = [self refreshIntervalFromMetadata:metadata]; + self.adTimeoutInterval = [self timeIntervalFromMsmetadata:metadata forKey:kAdTimeoutMetadataKey]; - self.orientationType = [self orientationTypeFromMetadata:metadata]; + self.nativeSDKParameters = [self dictionaryFromMetadata:metadata + forKey:kNativeSDKParametersMetadataKey]; - self.customEventClass = [self setUpCustomEventClassFromMetadata:metadata]; + self.orientationType = [self orientationTypeFromMetadata:metadata]; - self.customEventClassData = [self customEventClassDataFromMetadata:metadata]; + self.customEventClass = [self setUpCustomEventClassFromMetadata:metadata]; - self.dspCreativeId = [metadata objectForKey:kDspCreativeIdKey]; + self.customEventClassData = [self customEventClassDataFromMetadata:metadata]; - self.precacheRequired = [metadata mp_boolForKey:kPrecacheRequiredKey]; + self.dspCreativeId = [metadata objectForKey:kDspCreativeIdKey]; - self.isVastVideoPlayer = [metadata mp_boolForKey:kIsVastVideoPlayerKey]; + self.precacheRequired = [metadata mp_boolForKey:kPrecacheRequiredKey]; - self.creationTimestamp = [NSDate date]; + self.isVastVideoPlayer = [metadata mp_boolForKey:kIsVastVideoPlayerKey]; - self.creativeId = [metadata objectForKey:kCreativeIdMetadataKey]; + self.creationTimestamp = [NSDate date]; - self.metadataAdType = [metadata objectForKey:kAdTypeMetadataKey]; + self.creativeId = [metadata objectForKey:kCreativeIdMetadataKey]; - self.nativeVideoPlayVisiblePercent = [self percentFromMetadata:metadata forKey:kNativeVideoPlayVisiblePercentMetadataKey]; + self.metadataAdType = [metadata objectForKey:kAdTypeMetadataKey]; - self.nativeVideoPauseVisiblePercent = [self percentFromMetadata:metadata forKey:kNativeVideoPauseVisiblePercentMetadataKey]; + self.nativeVideoPlayVisiblePercent = [self percentFromMetadata:metadata forKey:kNativeVideoPlayVisiblePercentMetadataKey]; - self.nativeImpressionMinVisiblePixels = [[self adAmountFromMetadata:metadata key:kNativeImpressionMinVisiblePixelsMetadataKey] floatValue]; + self.nativeVideoPauseVisiblePercent = [self percentFromMetadata:metadata forKey:kNativeVideoPauseVisiblePercentMetadataKey]; - self.nativeImpressionMinVisiblePercent = [self percentFromMetadata:metadata forKey:kNativeImpressionMinVisiblePercentMetadataKey]; + self.nativeImpressionMinVisiblePixels = [[self adAmountFromMetadata:metadata key:kNativeImpressionMinVisiblePixelsMetadataKey] floatValue]; - self.nativeImpressionMinVisibleTimeInterval = [self timeIntervalFromMsmetadata:metadata forKey:kNativeImpressionVisibleMsMetadataKey]; + self.nativeImpressionMinVisiblePercent = [self percentFromMetadata:metadata forKey:kNativeImpressionMinVisiblePercentMetadataKey]; - self.nativeVideoMaxBufferingTime = [self timeIntervalFromMsmetadata:metadata forKey:kNativeVideoMaxBufferingTimeMsMetadataKey]; + self.nativeImpressionMinVisibleTimeInterval = [self timeIntervalFromMsmetadata:metadata forKey:kNativeImpressionVisibleMsMetadataKey]; + + 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]; + self.impressionMinVisibleTimeInSec = [self timeIntervalFromMsmetadata:metadata forKey:kBannerImpressionVisableMsMetadataKey]; + self.impressionMinVisiblePixels = [[self adAmountFromMetadata:metadata key:kBannerImpressionMinPixelMetadataKey] floatValue]; - self.impressionData = [self impressionDataFromMetadata:metadata]; + self.impressionData = [self impressionDataFromMetadata:metadata]; + self.isVASTClickabilityExperimentEnabled = [metadata mp_boolForKey:kVASTClickabilityExperimentKey defaultValue:NO]; - // 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]; - } + // 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]; + } - // rewarded video + // rewarded video - // 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; - } + // 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]; } - // Multiple currencies are not available; attempt to process single currency - // metadata. + // 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 { - NSString *currencyName = [metadata objectForKey:kRewardedVideoCurrencyNameMetadataKey] ?: kMPRewardedVideoRewardCurrencyTypeUnspecified; - - NSNumber *currencyAmount = [self adAmountFromMetadata:metadata key:kRewardedVideoCurrencyAmountMetadataKey]; - if (currencyAmount.integerValue <= 0) { - currencyAmount = @(kMPRewardedVideoRewardCurrencyAmountUnspecified); - } + 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; - MPRewardedVideoReward * reward = [[MPRewardedVideoReward alloc] initWithCurrencyType:currencyName amount:currencyAmount]; - self.availableRewards = [NSArray arrayWithObject:reward]; - self.selectedReward = reward; + 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 == MPAdTypeInline) { - [convertedCustomEvents setObject:@"MPGoogleAdMobBannerCustomEvent" forKey:@"admob_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 == MPAdTypeFullscreen) { - [convertedCustomEvents setObject:@"MPGoogleAdMobInterstitialCustomEvent" forKey:@"admob_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) { MPLogInfo(@"Could not find custom event class named %@", customEventClassName); } @@ -293,8 +320,6 @@ - (Class)setUpCustomEventClassFromMetadata:(NSDictionary *)metadata return customEventClass; } - - - (NSDictionary *)customEventClassDataFromMetadata:(NSDictionary *)metadata { // Parse out custom event data if its present @@ -556,8 +581,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; } @@ -569,7 +600,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) { diff --git a/MoPubSDK/Internal/Common/MPAdDestinationDisplayAgent.h b/MoPubSDK/Internal/Common/MPAdDestinationDisplayAgent.h index f8a06a1d7..25e300993 100644 --- a/MoPubSDK/Internal/Common/MPAdDestinationDisplayAgent.h +++ b/MoPubSDK/Internal/Common/MPAdDestinationDisplayAgent.h @@ -10,13 +10,11 @@ #import "MPActivityViewControllerHelper+TweetShare.h" #import "MPURLResolver.h" #import "MPProgressOverlayView.h" -#import "MPStoreKitProvider.h" #import "MOPUBDisplayAgentType.h" @protocol MPAdDestinationDisplayAgentDelegate; @interface MPAdDestinationDisplayAgent : NSObject @property (nonatomic, weak) id delegate; diff --git a/MoPubSDK/Internal/Common/MPAdDestinationDisplayAgent.m b/MoPubSDK/Internal/Common/MPAdDestinationDisplayAgent.m index 97f961bd4..f3a22d8af 100644 --- a/MoPubSDK/Internal/Common/MPAdDestinationDisplayAgent.m +++ b/MoPubSDK/Internal/Common/MPAdDestinationDisplayAgent.m @@ -15,13 +15,14 @@ #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; @@ -31,7 +32,6 @@ @interface MPAdDestinationDisplayAgent () @property (nonatomic, strong) SKStoreProductViewController *storeKitController; @property (nonatomic, strong) SFSafariViewController *safariController; -@property (nonatomic, strong) MPTelephoneConfirmationController *telephoneConfirmationController; @property (nonatomic, strong) MPActivityViewControllerHelper *activityViewControllerHelper; @end @@ -46,7 +46,7 @@ + (MPAdDestinationDisplayAgent *)agentWithDelegate:(id +#pragma mark - - (void)productViewControllerDidFinish:(SKStoreProductViewController *)viewController { diff --git a/MoPubSDK/Internal/Common/MPAdServerURLBuilder.m b/MoPubSDK/Internal/Common/MPAdServerURLBuilder.m index 15d2880c2..f96f8c3b1 100644 --- a/MoPubSDK/Internal/Common/MPAdServerURLBuilder.m +++ b/MoPubSDK/Internal/Common/MPAdServerURLBuilder.m @@ -385,7 +385,8 @@ + (NSDictionary *)adapterInformation { } + (NSDictionary *)locationInformationDictionary:(CLLocation *)location { - if (![MPConsentManager.sharedManager canCollectPersonalInfo] || !location) { + // Not allowed to collect location because it is PII + if (![MPConsentManager.sharedManager canCollectPersonalInfo]) { return @{}; } @@ -394,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; } diff --git a/MoPubSDK/Internal/Common/MPRealTimeTimer.m b/MoPubSDK/Internal/Common/MPRealTimeTimer.m index 7c3e39160..6731fe4e1 100644 --- a/MoPubSDK/Internal/Common/MPRealTimeTimer.m +++ b/MoPubSDK/Internal/Common/MPRealTimeTimer.m @@ -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/MPURLResolver.m b/MoPubSDK/Internal/Common/MPURLResolver.m index 07e02a58a..1341cf96e 100644 --- a/MoPubSDK/Internal/Common/MPURLResolver.m +++ b/MoPubSDK/Internal/Common/MPURLResolver.m @@ -6,6 +6,7 @@ // http://www.mopub.com/legal/sdk-license-agreement/ // +#import #import #import "MPURLResolver.h" #import "MPHTTPNetworkSession.h" @@ -241,7 +242,7 @@ - (BOOL)URLIsAppleScheme:(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. + @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 diff --git a/MoPubSDK/Internal/Common/MPVideoConfig.h b/MoPubSDK/Internal/Common/MPVideoConfig.h index a92740e77..df7d4c155 100644 --- a/MoPubSDK/Internal/Common/MPVideoConfig.h +++ b/MoPubSDK/Internal/Common/MPVideoConfig.h @@ -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 d160b6e8f..ee893e0d5 100644 --- a/MoPubSDK/Internal/Common/MPVideoConfig.m +++ b/MoPubSDK/Internal/Common/MPVideoConfig.m @@ -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 diff --git a/MoPubSDK/Internal/HTML/MPAdWebViewAgent.h b/MoPubSDK/Internal/HTML/MPAdWebViewAgent.h index feadb0161..cb1e9ce95 100644 --- a/MoPubSDK/Internal/HTML/MPAdWebViewAgent.h +++ b/MoPubSDK/Internal/HTML/MPAdWebViewAgent.h @@ -32,7 +32,6 @@ typedef NSUInteger MPAdWebViewEvent; - (id)initWithAdWebViewFrame:(CGRect)frame delegate:(id)delegate; - (void)loadConfiguration:(MPAdConfiguration *)configuration; - (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 b8fceca56..db082f394 100644 --- a/MoPubSDK/Internal/HTML/MPAdWebViewAgent.m +++ b/MoPubSDK/Internal/HTML/MPAdWebViewAgent.m @@ -6,13 +6,13 @@ // 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" @@ -27,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; @@ -42,7 +40,7 @@ @interface MPAdWebViewAgent () @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 @@ -134,7 +132,6 @@ - (void)loadConfiguration:(MPAdConfiguration *)configuration } [self.view mp_setScrollable:NO]; - [self.view disableJavaScriptDialogs]; // Initialize viewability trackers before loading self.view [self init3rdPartyViewabilityTrackers]; @@ -211,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; @@ -240,7 +238,7 @@ - (BOOL)webView:(MPWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *) - (void)webViewDidStartLoad:(MPWebView *)webView { - [self.view disableJavaScriptDialogs]; + // no op } #pragma mark - MoPub-specific URL handlers @@ -265,13 +263,11 @@ - (void)performActionForMoPubSpecificURL:(NSURL *)URL } #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]) { - return YES; - } else if (navigationType == UIWebViewNavigationTypeLinkClicked) { + if (navigationType == WKNavigationTypeLinkActivated) { return YES; - } else if (navigationType == UIWebViewNavigationTypeOther && self.userInteractedWithWebView) { + } else if (navigationType == WKNavigationTypeOther && self.userInteractedWithWebView) { return YES; } else { return NO; @@ -325,29 +321,4 @@ - (void)initAdAlertManager [self.adAlertManager beginMonitoringAlerts]; } -- (void)rotateToOrientation:(UIInterfaceOrientation)orientation -{ - [self forceRedraw]; -} - -- (void)forceRedraw -{ - // 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/MPHTMLBannerCustomEvent.h b/MoPubSDK/Internal/HTML/MPHTMLBannerCustomEvent.h index 8613d21b9..5de2985d0 100644 --- a/MoPubSDK/Internal/HTML/MPHTMLBannerCustomEvent.h +++ b/MoPubSDK/Internal/HTML/MPHTMLBannerCustomEvent.h @@ -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 5e4047be2..1ea76cc1f 100644 --- a/MoPubSDK/Internal/HTML/MPHTMLBannerCustomEvent.m +++ b/MoPubSDK/Internal/HTML/MPHTMLBannerCustomEvent.m @@ -21,6 +21,10 @@ @interface MPHTMLBannerCustomEvent () @implementation MPHTMLBannerCustomEvent +// 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 { return NO; @@ -42,11 +46,6 @@ - (void)dealloc self.bannerAgent.delegate = nil; } -- (void)rotateToOrientation:(UIInterfaceOrientation)newOrientation -{ - [self.bannerAgent forceRedraw]; -} - #pragma mark - MPAdWebViewAgentDelegate - (CLLocation *)location diff --git a/MoPubSDK/Internal/HTML/MPHTMLInterstitialCustomEvent.h b/MoPubSDK/Internal/HTML/MPHTMLInterstitialCustomEvent.h index 30ade61c3..dcd36f0cf 100644 --- a/MoPubSDK/Internal/HTML/MPHTMLInterstitialCustomEvent.h +++ b/MoPubSDK/Internal/HTML/MPHTMLInterstitialCustomEvent.h @@ -7,14 +7,10 @@ // #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 e8c639827..c1a4325f9 100644 --- a/MoPubSDK/Internal/HTML/MPHTMLInterstitialCustomEvent.m +++ b/MoPubSDK/Internal/HTML/MPHTMLInterstitialCustomEvent.m @@ -7,6 +7,7 @@ // #import "MPHTMLInterstitialCustomEvent.h" +#import "MPHTMLInterstitialViewController.h" #import "MPAdConfiguration.h" #import "MPError.h" #import "MPLogging.h" @@ -18,8 +19,15 @@ @interface MPHTMLInterstitialCustomEvent () @end +@interface MPHTMLInterstitialCustomEvent (MPInterstitialViewControllerDelegate) +@end + @implementation MPHTMLInterstitialCustomEvent +// 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 { // An HTML interstitial tracks its own clicks. Turn off automatic tracking to prevent the tap event callback @@ -54,8 +62,12 @@ - (void)showInterstitialFromRootViewController:(UIViewController *)rootViewContr }]; } +@end + #pragma mark - MPInterstitialViewControllerDelegate +@implementation MPHTMLInterstitialCustomEvent (MPInterstitialViewControllerDelegate) + - (CLLocation *)location { return [self.delegate location]; @@ -66,13 +78,13 @@ - (NSString *)adUnitId return [self.delegate adUnitId]; } -- (void)interstitialDidLoadAd:(MPInterstitialViewController *)interstitial +- (void)interstitialDidLoadAd:(id)interstitial { MPLogAdEvent([MPLogEvent adLoadSuccessForAdapter:NSStringFromClass(self.class)], self.adUnitId); [self.delegate interstitialCustomEvent:self didLoadAd:self.interstitial]; } -- (void)interstitialDidFailToLoadAd:(MPInterstitialViewController *)interstitial +- (void)interstitialDidFailToLoadAd:(id)interstitial { NSString * message = [NSString stringWithFormat:@"Failed to load creative:\n%@", self.delegate.configuration.adResponseHTMLString]; NSError * error = [NSError errorWithCode:MOPUBErrorAdapterFailedToLoadAd localizedDescription:message]; @@ -81,12 +93,12 @@ - (void)interstitialDidFailToLoadAd:(MPInterstitialViewController *)interstitial [self.delegate interstitialCustomEvent:self didFailToLoadAdWithError:error]; } -- (void)interstitialWillAppear:(MPInterstitialViewController *)interstitial +- (void)interstitialWillAppear:(id)interstitial { [self.delegate interstitialCustomEventWillAppear:self]; } -- (void)interstitialDidAppear:(MPInterstitialViewController *)interstitial +- (void)interstitialDidAppear:(id)interstitial { [self.delegate interstitialCustomEventDidAppear:self]; @@ -96,12 +108,12 @@ - (void)interstitialDidAppear:(MPInterstitialViewController *)interstitial } } -- (void)interstitialWillDisappear:(MPInterstitialViewController *)interstitial +- (void)interstitialWillDisappear:(id)interstitial { [self.delegate interstitialCustomEventWillDisappear:self]; } -- (void)interstitialDidDisappear:(MPInterstitialViewController *)interstitial +- (void)interstitialDidDisappear:(id)interstitial { [self.delegate interstitialCustomEventDidDisappear:self]; @@ -111,12 +123,12 @@ - (void)interstitialDidDisappear:(MPInterstitialViewController *)interstitial self.interstitial = nil; } -- (void)interstitialDidReceiveTapEvent:(MPInterstitialViewController *)interstitial +- (void)interstitialDidReceiveTapEvent:(id)interstitial { [self.delegate interstitialCustomEventDidReceiveTapEvent:self]; } -- (void)interstitialWillLeaveApplication:(MPInterstitialViewController *)interstitial +- (void)interstitialWillLeaveApplication:(id)interstitial { [self.delegate interstitialCustomEventWillLeaveApplication:self]; } diff --git a/MoPubSDK/Internal/HTML/MPHTMLInterstitialViewController.m b/MoPubSDK/Internal/HTML/MPHTMLInterstitialViewController.m index c51ee88b1..b2fb47e53 100644 --- a/MoPubSDK/Internal/HTML/MPHTMLInterstitialViewController.m +++ b/MoPubSDK/Internal/HTML/MPHTMLInterstitialViewController.m @@ -72,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; @@ -97,17 +92,6 @@ - (void)didDismissInterstitial [self.delegate interstitialDidDisappear:self]; } -#pragma mark - Autorotation - -- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator -{ - [coordinator animateAlongsideTransition:^(id context) { - [self.backingViewAgent forceRedraw]; - } 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 ca760eb4d..31c01a4d0 100644 --- a/MoPubSDK/Internal/HTML/MPWebView.h +++ b/MoPubSDK/Internal/HTML/MPWebView.h @@ -6,23 +6,19 @@ // 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,9 +50,6 @@ 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; @@ -74,9 +62,6 @@ textEncodingName:(NSString *)encodingName @property (nonatomic) BOOL allowsLinkPreview; @property (nonatomic, readonly) BOOL allowsPictureInPictureMediaPlayback; -+ (void)forceWKWebView:(BOOL)shouldForce; -+ (BOOL)isForceWKWebView; - - (void)loadHTMLString:(NSString *)string baseURL:(NSURL *)baseURL; @@ -104,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 17743c2bf..8463b12d2 100644 --- a/MoPubSDK/Internal/HTML/MPWebView.m +++ b/MoPubSDK/Internal/HTML/MPWebView.m @@ -13,19 +13,16 @@ 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 *webViewLayoutConstraints; @@ -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,69 +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; - config.requiresUserActionForMediaPlayback = kMoPubRequiresUserActionForMediaPlaybackDefault; - config.userContentController = contentController; - - if (@available(iOS 11, *)) { - [WKContentRuleListStore.defaultStore compileContentRuleListForIdentifier:@"ContentBlockingRules" encodedContentRuleList:MPContentBlocker.blockedResourcesList completionHandler:^(WKContentRuleList * rulesList, NSError * error) { - if (error == nil) { - [config.userContentController addContentRuleList:rulesList]; - } - }]; - } - - 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; + + // Put WKWebView onto the offscreen view so any loading will complete correctly; see comment below. + [self retainWKWebViewOffscreen:wkWebView]; - webView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + wkWebView.backgroundColor = [UIColor clearColor]; + wkWebView.opaque = NO; + wkWebView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; // set default scalesPageToFit self.scalesPageToFit = NO; @@ -127,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 @@ -183,13 +151,6 @@ - (void)didMoveToWindow { // Don't keep OffscreenView if we don't need it; it can always be re-allocated again later [self cleanUpOffscreenView]; } - // UIWebView doesn't need to be moved to the window per se, but the constraints - // binding it to the view need to be activated. - else if (self.uiWebView != nil && !self.hasMovedToWindow) { - self.uiWebView.frame = self.bounds; - [self constrainView:self.uiWebView shouldUseSafeArea:self.shouldConformToSafeArea]; - self.hasMovedToWindow = YES; - } } // Occasionally, we encounter an issue where, when MPWebView is initialized at a different frame size than when it's shown, @@ -240,8 +201,7 @@ - (void)setShouldConformToSafeArea:(BOOL)shouldConformToSafeArea { _shouldConformToSafeArea = shouldConformToSafeArea; if (self.hasMovedToWindow) { - UIView * webviewToConstrain = (self.uiWebView != nil ? self.uiWebView : self.wkWebView); - [self constrainView:webviewToConstrain shouldUseSafeArea:shouldConformToSafeArea]; + [self constrainView:self.wkWebView shouldUseSafeArea:shouldConformToSafeArea]; } } @@ -274,227 +234,114 @@ - (void)constrainView:(UIView *)view shouldUseSafeArea:(BOOL)shouldUseSafeArea { } - (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 { - [self.wkWebView loadData:data - MIMEType:MIMEType - characterEncodingName:encodingName - baseURL:baseURL]; - } -} - -+ (void)forceWKWebView:(BOOL)shouldForce -{ - gForceWKWebView = shouldForce; + [self.wkWebView loadData:data + MIMEType:MIMEType + characterEncodingName:encodingName + baseURL:baseURL]; } -+ (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 (self.uiWebView) { - self.uiWebView.allowsLinkPreview = allowsLinkPreview; - } else { - self.wkWebView.allowsLinkPreview = allowsLinkPreview; - } + self.wkWebView.allowsLinkPreview = allowsLinkPreview; } - (BOOL)allowsLinkPreview { - if (self.uiWebView) { - return self.uiWebView.allowsLinkPreview; - } else { - return self.wkWebView.allowsLinkPreview; - } + 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 { - return self.wkWebView.configuration.requiresUserActionForMediaPlayback; - } + return self.wkWebView.configuration.requiresUserActionForMediaPlayback; } - (BOOL)mediaPlaybackAllowsAirPlay { - if (self.uiWebView) { - return self.uiWebView.mediaPlaybackAllowsAirPlay; - } else { - return self.wkWebView.configuration.allowsAirPlayForMediaPlayback; - } + return self.wkWebView.configuration.allowsAirPlayForMediaPlayback; } - (BOOL)allowsPictureInPictureMediaPlayback { - if (self.uiWebView) { - return self.uiWebView.allowsPictureInPictureMediaPlayback; - } else { - 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]; - } + return self.wkWebView.configuration.allowsPictureInPictureMediaPlayback; } -#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 { @@ -503,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 { @@ -547,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); diff --git a/MoPubSDK/Internal/Interstitials/MPInterstitialAdManager.m b/MoPubSDK/Internal/Interstitials/MPInterstitialAdManager.m index aa7dc728e..772a6cd8d 100644 --- a/MoPubSDK/Internal/Interstitials/MPInterstitialAdManager.m +++ b/MoPubSDK/Internal/Interstitials/MPInterstitialAdManager.m @@ -33,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; @@ -78,6 +79,7 @@ - (void)loadAdWithURL:(NSURL *)URL } self.loading = YES; + self.mostRecentlyLoadedURL = URL; [self.communicator loadURL:URL]; } @@ -224,7 +226,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]; diff --git a/MoPubSDK/Internal/Interstitials/MPInterstitialViewController.h b/MoPubSDK/Internal/Interstitials/MPInterstitialViewController.h index 0dd49904b..ccf03f00e 100644 --- a/MoPubSDK/Internal/Interstitials/MPInterstitialViewController.h +++ b/MoPubSDK/Internal/Interstitials/MPInterstitialViewController.h @@ -12,11 +12,18 @@ @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; @@ -36,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/MPConsentDialogViewController.m b/MoPubSDK/Internal/MPConsentDialogViewController.m index 2b13b97f9..2b510e913 100644 --- a/MoPubSDK/Internal/MPConsentDialogViewController.m +++ b/MoPubSDK/Internal/MPConsentDialogViewController.m @@ -97,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]; @@ -202,7 +202,7 @@ - (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]; diff --git a/MoPubSDK/Internal/MPConsentManager.m b/MoPubSDK/Internal/MPConsentManager.m index ada4d33dc..856414486 100644 --- a/MoPubSDK/Internal/MPConsentManager.m +++ b/MoPubSDK/Internal/MPConsentManager.m @@ -439,6 +439,24 @@ - (void)onApplicationWillEnterForeground:(NSNotification *)notification { // 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) { // Consent synchronization success/fail logging is already handled diff --git a/MoPubSDK/Internal/MPMediationManager.m b/MoPubSDK/Internal/MPMediationManager.m index 677fbf709..c581f6e57 100644 --- a/MoPubSDK/Internal/MPMediationManager.m +++ b/MoPubSDK/Internal/MPMediationManager.m @@ -264,15 +264,10 @@ - (NSDictionary * _Nullable)cachedInitializationParametersForNetwork:(Class - -@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 d049ecb3f..e6fc22e9e 100644 --- a/MoPubSDK/Internal/MPVASTTracking.m +++ b/MoPubSDK/Internal/MPVASTTracking.m @@ -6,249 +6,150 @@ // 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/MPMRAIDBannerCustomEvent.h b/MoPubSDK/Internal/MRAID/MPMRAIDBannerCustomEvent.h index 4c52062f4..735724496 100644 --- a/MoPubSDK/Internal/MRAID/MPMRAIDBannerCustomEvent.h +++ b/MoPubSDK/Internal/MRAID/MPMRAIDBannerCustomEvent.h @@ -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 4c9e09df3..43d0be18e 100644 --- a/MoPubSDK/Internal/MRAID/MPMRAIDBannerCustomEvent.m +++ b/MoPubSDK/Internal/MRAID/MPMRAIDBannerCustomEvent.m @@ -22,6 +22,10 @@ @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 { MPAdConfiguration *configuration = self.delegate.configuration; diff --git a/MoPubSDK/Internal/MRAID/MPMRAIDInterstitialCustomEvent.h b/MoPubSDK/Internal/MRAID/MPMRAIDInterstitialCustomEvent.h index 2e2321d9f..eadbb4d9f 100644 --- a/MoPubSDK/Internal/MRAID/MPMRAIDInterstitialCustomEvent.h +++ b/MoPubSDK/Internal/MRAID/MPMRAIDInterstitialCustomEvent.h @@ -7,14 +7,10 @@ // #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 830b0fadc..697f33420 100644 --- a/MoPubSDK/Internal/MRAID/MPMRAIDInterstitialCustomEvent.m +++ b/MoPubSDK/Internal/MRAID/MPMRAIDInterstitialCustomEvent.m @@ -7,6 +7,7 @@ // #import "MPMRAIDInterstitialCustomEvent.h" +#import "MPMRAIDInterstitialViewController.h" #import "MPAdConfiguration.h" #import "MPError.h" #import "MPLogging.h" @@ -17,8 +18,15 @@ @interface MPMRAIDInterstitialCustomEvent () @end +@interface MPMRAIDInterstitialCustomEvent (MPInterstitialViewControllerDelegate) +@end + @implementation MPMRAIDInterstitialCustomEvent +// 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 { MPAdConfiguration * configuration = self.delegate.configuration; @@ -45,7 +53,11 @@ - (void)showInterstitialFromRootViewController:(UIViewController *)controller }]; } -#pragma mark - MPMRAIDInterstitialViewControllerDelegate +@end + +#pragma mark - MPInterstitialViewControllerDelegate + +@implementation MPMRAIDInterstitialCustomEvent (MPInterstitialViewControllerDelegate) - (CLLocation *)location { @@ -57,13 +69,13 @@ - (NSString *)adUnitId return [self.delegate adUnitId]; } -- (void)interstitialDidLoadAd:(MPInterstitialViewController *)interstitial +- (void)interstitialDidLoadAd:(id)interstitial { MPLogAdEvent([MPLogEvent adLoadSuccessForAdapter:NSStringFromClass(self.class)], self.adUnitId); [self.delegate interstitialCustomEvent:self didLoadAd:self.interstitial]; } -- (void)interstitialDidFailToLoadAd:(MPInterstitialViewController *)interstitial +- (void)interstitialDidFailToLoadAd:(id)interstitial { NSString * message = [NSString stringWithFormat:@"Failed to load creative:\n%@", self.delegate.configuration.adResponseHTMLString]; NSError * error = [NSError errorWithCode:MOPUBErrorAdapterFailedToLoadAd localizedDescription:message]; @@ -72,22 +84,22 @@ - (void)interstitialDidFailToLoadAd:(MPInterstitialViewController *)interstitial [self.delegate interstitialCustomEvent:self didFailToLoadAdWithError:error]; } -- (void)interstitialWillAppear:(MPInterstitialViewController *)interstitial +- (void)interstitialWillAppear:(id)interstitial { [self.delegate interstitialCustomEventWillAppear:self]; } -- (void)interstitialDidAppear:(MPInterstitialViewController *)interstitial +- (void)interstitialDidAppear:(id)interstitial { [self.delegate interstitialCustomEventDidAppear:self]; } -- (void)interstitialWillDisappear:(MPInterstitialViewController *)interstitial +- (void)interstitialWillDisappear:(id)interstitial { [self.delegate interstitialCustomEventWillDisappear:self]; } -- (void)interstitialDidDisappear:(MPInterstitialViewController *)interstitial +- (void)interstitialDidDisappear:(id)interstitial { [self.delegate interstitialCustomEventDidDisappear:self]; @@ -97,12 +109,12 @@ - (void)interstitialDidDisappear:(MPInterstitialViewController *)interstitial self.interstitial = nil; } -- (void)interstitialDidReceiveTapEvent:(MPInterstitialViewController *)interstitial +- (void)interstitialDidReceiveTapEvent:(id)interstitial { [self.delegate interstitialCustomEventDidReceiveTapEvent:self]; } -- (void)interstitialWillLeaveApplication:(MPInterstitialViewController *)interstitial +- (void)interstitialWillLeaveApplication:(id)interstitial { [self.delegate interstitialCustomEventWillLeaveApplication:self]; } diff --git a/MoPubSDK/Internal/MRAID/MPMRAIDInterstitialViewController.h b/MoPubSDK/Internal/MRAID/MPMRAIDInterstitialViewController.h index 12a2aa6b9..c995f92aa 100644 --- a/MoPubSDK/Internal/MRAID/MPMRAIDInterstitialViewController.h +++ b/MoPubSDK/Internal/MRAID/MPMRAIDInterstitialViewController.h @@ -11,7 +11,6 @@ //////////////////////////////////////////////////////////////////////////////////////////////////// -@protocol MPMRAIDInterstitialViewControllerDelegate; @class MPAdConfiguration; //////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/MoPubSDK/Internal/MRAID/MRBridge.m b/MoPubSDK/Internal/MRAID/MRBridge.m index d0d32c714..cd3fc5642 100644 --- a/MoPubSDK/Internal/MRAID/MRBridge.m +++ b/MoPubSDK/Internal/MRAID/MRBridge.m @@ -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]]; @@ -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/MRController.m b/MoPubSDK/Internal/MRAID/MRController.m index 8b502d018..40ec186e3 100644 --- a/MoPubSDK/Internal/MRAID/MRController.m +++ b/MoPubSDK/Internal/MRAID/MRController.m @@ -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" @@ -53,7 +52,6 @@ @interface MRController () #import #import "MPGlobal.h" +#import "MRVideoPlayerManager.h" + +@interface MRVideoPlayerManager () + +@property (nonatomic, strong) AVPlayerViewController *playerViewController; + +@end @implementation MRVideoPlayerManager @@ -17,15 +24,17 @@ - (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 @@ -35,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/NSString+MPAdditions.m b/MoPubSDK/Internal/Utility/Categories/NSString+MPAdditions.m index 386406d2c..45278cfa9 100644 --- a/MoPubSDK/Internal/Utility/Categories/NSString+MPAdditions.m +++ b/MoPubSDK/Internal/Utility/Categories/NSString+MPAdditions.m @@ -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/NSURL+MPAdditions.h b/MoPubSDK/Internal/Utility/Categories/NSURL+MPAdditions.h index 35140e043..02ff320ef 100644 --- a/MoPubSDK/Internal/Utility/Categories/NSURL+MPAdditions.h +++ b/MoPubSDK/Internal/Utility/Categories/NSURL+MPAdditions.h @@ -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 e5e6cd59e..db8d048c1 100644 --- a/MoPubSDK/Internal/Utility/Categories/NSURL+MPAdditions.m +++ b/MoPubSDK/Internal/Utility/Categories/NSURL+MPAdditions.m @@ -8,9 +8,6 @@ #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/UIView+MPAdditions.h b/MoPubSDK/Internal/Utility/Categories/UIView+MPAdditions.h index 4c9ae20b3..da6857574 100644 --- a/MoPubSDK/Internal/Utility/Categories/UIView+MPAdditions.h +++ b/MoPubSDK/Internal/Utility/Categories/UIView+MPAdditions.h @@ -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 d04a07e68..cf3144f86 100644 --- a/MoPubSDK/Internal/Utility/Categories/UIView+MPAdditions.m +++ b/MoPubSDK/Internal/Utility/Categories/UIView+MPAdditions.m @@ -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 959d4ffda..000000000 --- a/MoPubSDK/Internal/Utility/Categories/UIWebView+MPAdditions.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// UIWebView+MPAdditions.h -// -// Copyright 2018-2019 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 0d2bad141..000000000 --- a/MoPubSDK/Internal/Utility/Categories/UIWebView+MPAdditions.m +++ /dev/null @@ -1,49 +0,0 @@ -// -// UIWebView+MPAdditions.m -// -// Copyright 2018-2019 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 906d749ec..dbc8bca17 100644 --- a/MoPubSDK/Internal/Utility/MOPUBExperimentProvider.h +++ b/MoPubSDK/Internal/Utility/MOPUBExperimentProvider.h @@ -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 f8af71f71..f5cc74e95 100644 --- a/MoPubSDK/Internal/Utility/MOPUBExperimentProvider.m +++ b/MoPubSDK/Internal/Utility/MOPUBExperimentProvider.m @@ -8,33 +8,41 @@ #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 20bed6cd0..76ed0a40f 100644 --- a/MoPubSDK/Internal/Utility/MPAnalyticsTracker.h +++ b/MoPubSDK/Internal/Utility/MPAnalyticsTracker.h @@ -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 61dba4c6e..5032ccf6e 100644 --- a/MoPubSDK/Internal/Utility/MPAnalyticsTracker.m +++ b/MoPubSDK/Internal/Utility/MPAnalyticsTracker.m @@ -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/MPGlobal.h b/MoPubSDK/Internal/Utility/MPGlobal.h index 59d50ae7b..709d950b0 100644 --- a/MoPubSDK/Internal/Utility/MPGlobal.h +++ b/MoPubSDK/Internal/Utility/MPGlobal.h @@ -79,18 +79,3 @@ UIInterfaceOrientationMask MPInterstitialOrientationTypeToUIInterfaceOrientation - (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 df34d3a4e..5be46ae8b 100644 --- a/MoPubSDK/Internal/Utility/MPGlobal.m +++ b/MoPubSDK/Internal/Utility/MPGlobal.m @@ -98,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; } @@ -303,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. - MPLogInfo(@"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) { - MPLogInfo(@"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/MPStoreKitProvider.h b/MoPubSDK/Internal/Utility/MPStoreKitProvider.h deleted file mode 100644 index e96af4d3e..000000000 --- a/MoPubSDK/Internal/Utility/MPStoreKitProvider.h +++ /dev/null @@ -1,23 +0,0 @@ -// -// MPStoreKitProvider.h -// -// 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 - -@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 6e3a43a0e..000000000 --- a/MoPubSDK/Internal/Utility/MPStoreKitProvider.m +++ /dev/null @@ -1,47 +0,0 @@ -// -// MPStoreKitProvider.m -// -// Copyright 2018-2019 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 - return [[MPiOS7SafeStoreProductViewController alloc] init]; -} - -@end 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/MPVASTCompanionAd.h b/MoPubSDK/Internal/VAST/MPVASTCompanionAd.h index 0d2630cfc..0ea3563ad 100644 --- a/MoPubSDK/Internal/VAST/MPVASTCompanionAd.h +++ b/MoPubSDK/Internal/VAST/MPVASTCompanionAd.h @@ -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", 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/MoPub+Testing.h b/MoPubSDKTests/MoPub+Testing.h index 097260cdf..39f69682f 100644 --- a/MoPubSDKTests/MoPub+Testing.h +++ b/MoPubSDKTests/MoPub+Testing.h @@ -7,11 +7,16 @@ // #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 @@ -24,6 +29,8 @@ NS_ASSUME_NONNULL_BEGIN */ - (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 25f02c1bb..317100500 100644 --- a/MoPubSDKTests/MoPub+Testing.m +++ b/MoPubSDKTests/MoPub+Testing.m @@ -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 fe89c1605..9b2efb4e4 100644 --- a/MoPubSDKTests/MoPubTests.m +++ b/MoPubSDKTests/MoPubTests.m @@ -30,8 +30,6 @@ @implementation MoPubTests - (void)setUp { [super setUp]; [MPMediationManager.sharedManager clearCache]; - - [MoPub sharedInstance].forceWKWebView = NO; MPLogging.consoleLogLevel = MPBLogLevelInfo; } @@ -192,47 +190,6 @@ - (void)testInitializingWithoutLegitimateInterest { XCTAssertFalse(MoPub.sharedInstance.allowLegitimateInterest); } - -#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 adType:MPAdTypeFullscreen]; - - MRController * controller = [[MRController alloc] initWithAdViewFrame:CGRectZero supportedOrientations:MPInterstitialOrientationTypeAll 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 adType:MPAdTypeFullscreen]; - - MRController * controller = [[MRController alloc] initWithAdViewFrame:CGRectZero supportedOrientations:MPInterstitialOrientationTypeAll adPlacementType:MRAdViewPlacementTypeInterstitial delegate:nil]; - [controller loadAdWithConfiguration:config]; - - XCTAssertNotNil(controller.mraidWebView.wkWebView); - XCTAssertNil(controller.mraidWebView.uiWebView); -} - #pragma mark - Logging - (void)testSetLogLevel { diff --git a/MoPubSDKTests/XCTestCase+MPAddition.h b/MoPubSDKTests/XCTestCase+MPAddition.h index 7df6fd00e..6ce2dc59f 100644 --- a/MoPubSDKTests/XCTestCase+MPAddition.h +++ b/MoPubSDKTests/XCTestCase+MPAddition.h @@ -7,9 +7,12 @@ // #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 77802ac7e..67e80265e 100644 --- a/MoPubSDKTests/XCTestCase+MPAddition.m +++ b/MoPubSDKTests/XCTestCase+MPAddition.m @@ -6,14 +6,33 @@ // 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/README.md b/README.md index 356316479..933363887 100644 --- a/README.md +++ b/README.md @@ -23,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.8.0 +The current version of the SDK is 5.9.0 ### Installation with CocoaPods @@ -42,7 +42,7 @@ platform :ios, '9.0' use_frameworks! target 'MyApp' do - pod 'mopub-ios-sdk', '~> 5.0' + pod 'mopub-ios-sdk', '~> 5.9' end ``` @@ -56,7 +56,7 @@ $ pod install MoPub provides a prepackaged archive of the dynamic framework: -- **[MoPub SDK Framework.zip](https://github.com/mopub/mopub-ios-sdk/releases/download/5.8.0/mopub-framework-5.8.0.zip)** +- **[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. @@ -66,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](https://github.com/mopub/mopub-ios-sdk/releases/download/5.8.0/mopub-base-5.8.0.zip)** +- **[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](https://github.com/mopub/mopub-ios-sdk/releases/download/5.8.0/mopub-nonnative-5.8.0.zip)** +- **[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. @@ -83,20 +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** - - 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. + - 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** - - 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. + - 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. @@ -123,7 +126,7 @@ If you would like to opt out of viewability measurement but do not want to modif ## Requirements - iOS 9.0 and up -- Xcode 9.0 and up +- Xcode 11.0 and up ## License diff --git a/mopub-ios-sdk.podspec b/mopub-ios-sdk.podspec index 6ef13ac31..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.8.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.8.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', From 393432007f25654b0a289a0b06cb8a8d22c9354c Mon Sep 17 00:00:00 2001 From: Mario Diaz Justicia Date: Fri, 11 Oct 2019 12:49:45 +0200 Subject: [PATCH 12/12] scopely local changes --- MoPubSDK/Internal/Banners/MPBannerAdManager.h | 5 +++ MoPubSDK/Internal/Banners/MPBannerAdManager.m | 22 +++++++++++++ .../Banners/MPBannerAdManagerDelegate.h | 5 +++ .../Banners/MPBannerCustomEventAdapter.h | 2 ++ .../Banners/MPBannerCustomEventAdapter.m | 7 ++++ MoPubSDK/Internal/Common/MPAdConfiguration.h | 1 + MoPubSDK/Internal/Common/MPAdConfiguration.m | 3 ++ .../Interstitials/MPInterstitialAdManager.h | 4 +++ .../Interstitials/MPInterstitialAdManager.m | 20 +++++++++++ .../MPInterstitialAdManagerDelegate.h | 5 +++ MoPubSDK/MPAdView.h | 2 ++ MoPubSDK/MPAdView.m | 33 +++++++++++++++++++ MoPubSDK/MPAdViewDelegate.h | 31 +++++++++++++++++ MoPubSDK/MPInterstitialAdController.m | 22 +++++++++++++ MoPubSDK/MPInterstitialAdControllerDelegate.h | 25 ++++++++++++++ .../Internal/MPRewardedVideoAdManager.h | 7 ++++ .../Internal/MPRewardedVideoAdManager.m | 18 ++++++++++ MoPubSDK/RewardedVideo/MPRewardedVideo.h | 27 +++++++++++++++ MoPubSDK/RewardedVideo/MPRewardedVideo.m | 32 ++++++++++++++++++ 19 files changed, 271 insertions(+) diff --git a/MoPubSDK/Internal/Banners/MPBannerAdManager.h b/MoPubSDK/Internal/Banners/MPBannerAdManager.h index 400fd7d9f..3a5e6ba16 100644 --- a/MoPubSDK/Internal/Banners/MPBannerAdManager.h +++ b/MoPubSDK/Internal/Banners/MPBannerAdManager.h @@ -19,6 +19,10 @@ @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; - (void)loadAdWithTargeting:(MPAdTargeting *)targeting; @@ -26,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 24e9792a8..c4fbc99f5 100644 --- a/MoPubSDK/Internal/Banners/MPBannerAdManager.m +++ b/MoPubSDK/Internal/Banners/MPBannerAdManager.m @@ -90,6 +90,20 @@ - (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); @@ -205,6 +219,7 @@ - (void)scheduleRefreshTimer - (void)refreshTimerDidFire { + [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. @@ -246,6 +261,7 @@ - (void)fetchAdWithConfiguration:(MPAdConfiguration *)configuration { return; } + [self.delegate bannerWillStartAttemptForAdManager:self]; [self.requestingAdapter _getAdWithConfiguration:configuration targeting:self.targeting containerSize:self.delegate.containerSize]; } @@ -338,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; @@ -353,6 +370,7 @@ - (void)adapter:(MPBaseBannerAdapter *)adapter didFinishLoadingAd:(UIView *)ad - (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; @@ -450,6 +468,10 @@ - (void)adDidCollapseForAdapter:(MPBaseBannerAdapter *)adapter [self resumeRefreshTimer]; } +- (NSString *)getDspCreativeId { + return [_requestingConfiguration dspCreativeId]; +} + @end diff --git a/MoPubSDK/Internal/Banners/MPBannerAdManagerDelegate.h b/MoPubSDK/Internal/Banners/MPBannerAdManagerDelegate.h index 19a1ad8fc..8142034c9 100644 --- a/MoPubSDK/Internal/Banners/MPBannerAdManagerDelegate.h +++ b/MoPubSDK/Internal/Banners/MPBannerAdManagerDelegate.h @@ -29,11 +29,16 @@ - (void)invalidateContentView; +- (void)bannerWillStartAttemptForAdManager:(MPBannerAdManager *)manager; +- (void)bannerDidSucceedAttemptForAdManager:(MPBannerAdManager *)manager; +- (void)bannerDidFailAttemptForAdManager:(MPBannerAdManager *)manager error:(NSError*)error; + - (void)managerDidLoadAd:(UIView *)ad; - (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/MPBannerCustomEventAdapter.h b/MoPubSDK/Internal/Banners/MPBannerCustomEventAdapter.h index eeedfabf0..27abf0f88 100644 --- a/MoPubSDK/Internal/Banners/MPBannerCustomEventAdapter.h +++ b/MoPubSDK/Internal/Banners/MPBannerCustomEventAdapter.h @@ -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 d94015b85..ea2dbb4a9 100644 --- a/MoPubSDK/Internal/Banners/MPBannerCustomEventAdapter.m +++ b/MoPubSDK/Internal/Banners/MPBannerCustomEventAdapter.m @@ -32,6 +32,8 @@ - (void)trackClickOnce; @implementation MPBannerCustomEventAdapter +WBBannerProxy *bannerProxy; + - (instancetype)initWithConfiguration:(MPAdConfiguration *)configuration delegate:(id)delegate { if (!configuration.customEventClass) { @@ -40,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)]) { diff --git a/MoPubSDK/Internal/Common/MPAdConfiguration.h b/MoPubSDK/Internal/Common/MPAdConfiguration.h index aa04b0aa0..15dd0699f 100644 --- a/MoPubSDK/Internal/Common/MPAdConfiguration.h +++ b/MoPubSDK/Internal/Common/MPAdConfiguration.h @@ -94,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; diff --git a/MoPubSDK/Internal/Common/MPAdConfiguration.m b/MoPubSDK/Internal/Common/MPAdConfiguration.m index 761c7bb29..6335dabd9 100644 --- a/MoPubSDK/Internal/Common/MPAdConfiguration.m +++ b/MoPubSDK/Internal/Common/MPAdConfiguration.m @@ -34,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"; @@ -190,6 +191,8 @@ - (void)commonInitWithMetadata:(NSDictionary *)metadata self.creativeId = [metadata objectForKey:kCreativeIdMetadataKey]; + self.lineItemId = [metadata objectForKey:kAdLineItemIdKey]; + self.metadataAdType = [metadata objectForKey:kAdTypeMetadataKey]; self.nativeVideoPlayVisiblePercent = [self percentFromMetadata:metadata forKey:kNativeVideoPlayVisiblePercentMetadataKey]; diff --git a/MoPubSDK/Internal/Interstitials/MPInterstitialAdManager.h b/MoPubSDK/Internal/Interstitials/MPInterstitialAdManager.h index eeb65e987..7f29cb935 100644 --- a/MoPubSDK/Internal/Interstitials/MPInterstitialAdManager.h +++ b/MoPubSDK/Internal/Interstitials/MPInterstitialAdManager.h @@ -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 772a6cd8d..29da5bb84 100644 --- a/MoPubSDK/Internal/Interstitials/MPInterstitialAdManager.m +++ b/MoPubSDK/Internal/Interstitials/MPInterstitialAdManager.m @@ -61,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) { @@ -183,6 +197,8 @@ - (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]; @@ -204,6 +220,8 @@ - (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]; @@ -214,6 +232,8 @@ - (void)adapterDidFinishLoadingAd:(MPBaseInterstitialAdapter *)adapter - (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; diff --git a/MoPubSDK/Internal/Interstitials/MPInterstitialAdManagerDelegate.h b/MoPubSDK/Internal/Interstitials/MPInterstitialAdManagerDelegate.h index cc4cd925f..4f8c0733c 100644 --- a/MoPubSDK/Internal/Interstitials/MPInterstitialAdManagerDelegate.h +++ b/MoPubSDK/Internal/Interstitials/MPInterstitialAdManagerDelegate.h @@ -19,6 +19,11 @@ - (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; diff --git a/MoPubSDK/MPAdView.h b/MoPubSDK/MPAdView.h index 0e47798dc..9259a759a 100644 --- a/MoPubSDK/MPAdView.h +++ b/MoPubSDK/MPAdView.h @@ -221,6 +221,8 @@ IB_DESIGNABLE */ - (void)stopAutomaticallyRefreshingContents; +- (NSString *)getCreativeId; + /** * Causes the ad view to periodically load new advertisements in accordance with user-defined * refresh settings on the MoPub website. diff --git a/MoPubSDK/MPAdView.m b/MoPubSDK/MPAdView.m index ff4711231..d8a9bf429 100644 --- a/MoPubSDK/MPAdView.m +++ b/MoPubSDK/MPAdView.m @@ -240,6 +240,28 @@ - (void)invalidateContentView [self setAdContentView:nil]; } +- (void)bannerWillStartAttemptForAdManager:(MPBannerAdManager *)manager +{ + if ([self.delegate respondsToSelector:@selector(bannerWillStartAttemptForAd:withCustomEventClass:withLineItemId:)]) { + NSString *customEventClass = NSStringFromClass([manager customEventClass]); + [self.delegate bannerWillStartAttemptForAd:self withCustomEventClass:customEventClass withLineItemId: [manager lineItemId]]; + } +} + +- (void)bannerDidSucceedAttemptForAdManager:(MPBannerAdManager *)manager +{ + if ([self.delegate respondsToSelector:@selector(bannerDidSucceedAttemptForAd:withCreativeId:)]) { + [self.delegate bannerDidSucceedAttemptForAd:self withCreativeId:[manager dspCreativeId]]; + } +} + +- (void)bannerDidFailAttemptForAdManager:(MPBannerAdManager *)manager error:(NSError *)error +{ + if ([self.delegate respondsToSelector:@selector(bannerDidFailAttemptForAd:error:)]) { + [self.delegate bannerDidFailAttemptForAd:self error:error]; + } +} + - (void)managerDidFailToLoadAdWithError:(NSError *)error { if ([self.delegate respondsToSelector:@selector(adViewDidFailToLoadAd:)]) { @@ -301,4 +323,15 @@ - (void)impressionDidFireWithImpressionData:(MPImpressionData *)impressionData { impressionData:impressionData]; } +- (NSString *)getCreativeId { + return [_adManager getDspCreativeId]; +} + +- (void)managerRefreshAd:(UIView *)ad +{ + if ([self.delegate respondsToSelector:@selector(adViewRefreshAd:)]) { + [self.delegate adViewRefreshAd:self]; + } +} + @end diff --git a/MoPubSDK/MPAdViewDelegate.h b/MoPubSDK/MPAdViewDelegate.h index 791893e6d..40b411408 100644 --- a/MoPubSDK/MPAdViewDelegate.h +++ b/MoPubSDK/MPAdViewDelegate.h @@ -35,6 +35,31 @@ @optional +/** + * This method is called before an ad attempts to load. + * + * @param view The ad view sending the message. + * @param customEventClass The MPCustomEvent class name to identify the AdNetwork. + * @param lineItemId The id of line item the ad belongs to. + */ +- (void)bannerWillStartAttemptForAd:(MPAdView *)view withCustomEventClass:(NSString*)customEventClass withLineItemId:(NSString*)lineItemId; + +/** + * This method is called after an ad attempt succeeds to load. + * + * @param view The ad view sending the message. + * @param creativeId The id of the creative loaded. + */ +- (void)bannerDidSucceedAttemptForAd:(MPAdView *)view withCreativeId:(NSString*)creativeId; + +/** + * This method is called after an ad attempt fails to load. + * + * @param view The ad view sending the message. + * @param error The error that occurred during the load. + */ +- (void)bannerDidFailAttemptForAd:(MPAdView *)view error:(NSError*)error; + /** @name Detecting When a Banner Ad is Loaded */ /** @@ -112,4 +137,10 @@ */ - (void)willLeaveApplicationFromAd:(MPAdView *)view; +/** + * Sent when ad is refreshed + * @param view + */ +- (void)adViewRefreshAd:(MPAdView *)view; + @end diff --git a/MoPubSDK/MPInterstitialAdController.m b/MoPubSDK/MPInterstitialAdController.m index 6a2a7351b..51442995d 100644 --- a/MoPubSDK/MPInterstitialAdController.m +++ b/MoPubSDK/MPInterstitialAdController.m @@ -118,6 +118,28 @@ - (id)interstitialDelegate return self.delegate; } +- (void)managerWillStartInterstitialAttempt:(MPInterstitialAdManager *)manager +{ + if ([self.delegate respondsToSelector:@selector(interstitialWillStartAttemptToLoadAd:customEventClass:withLineItemId:)]) { + NSString *customEventClass = NSStringFromClass([manager customEventClass]); + [self.delegate interstitialWillStartAttemptToLoadAd:self customEventClass:customEventClass withLineItemId:[manager lineItemId]]; + } +} + +- (void)managerDidSucceedInterstitialAttempt:(MPInterstitialAdManager *)manager +{ + if ([self.delegate respondsToSelector:@selector(interstitialDidSucceedAttemptToLoadAd:withCreativeId:)]) { + [self.delegate interstitialDidSucceedAttemptToLoadAd:self withCreativeId:[manager dspCreativeId]]; + } +} + +- (void)manager:(MPInterstitialAdManager *)manager didFailInterstitialAttemptWithError:(NSError*)error +{ + if ([self.delegate respondsToSelector:@selector(interstitialDidFailAttemptToLoadAd:error:)]) { + [self.delegate interstitialDidFailAttemptToLoadAd:self error:error]; + } +} + - (void)managerDidLoadInterstitial:(MPInterstitialAdManager *)manager { if ([self.delegate respondsToSelector:@selector(interstitialDidLoadAd:)]) { diff --git a/MoPubSDK/MPInterstitialAdControllerDelegate.h b/MoPubSDK/MPInterstitialAdControllerDelegate.h index e89bbc41e..fbd4ba3b8 100644 --- a/MoPubSDK/MPInterstitialAdControllerDelegate.h +++ b/MoPubSDK/MPInterstitialAdControllerDelegate.h @@ -25,6 +25,31 @@ /** @name Detecting When an Interstitial Ad is Loaded */ +/** + * This method is called before an ad attempts to load. + * + * @param interstitial The interstitial ad object sending the message. + * @param customEventClass The MPCustomEvent class name to identify the AdNetwork. + * @param lineItemId The id of line item the ad belongs to. + */ +- (void)interstitialWillStartAttemptToLoadAd:(MPInterstitialAdController *)interstitial customEventClass:(NSString*)customEventClass withLineItemId:(NSString*)lineItemId; + +/** + * This method is called after an ad attempt succeeds to load. + * + * @param interstitial The interstitial ad object sending the message. + * @param creativeId The id of the creative loaded. + */ +- (void)interstitialDidSucceedAttemptToLoadAd:(MPInterstitialAdController *)interstitial withCreativeId:(NSString*)creativeId; + +/** + * This method is called after an ad attempt fails to load. + * + * @param interstitial The interstitial ad object sending the message. + * @param error The error that occurred during the load. + */ +- (void)interstitialDidFailAttemptToLoadAd:(MPInterstitialAdController *)interstitial error:(NSError*)error; + /** * Sent when an interstitial ad object successfully loads an ad. * diff --git a/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdManager.h b/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdManager.h index ffb9c71a8..1f24e7e62 100644 --- a/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdManager.h +++ b/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdManager.h @@ -23,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; /** @@ -76,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; diff --git a/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdManager.m b/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdManager.m index c658664dd..b17f31e5b 100644 --- a/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdManager.m +++ b/MoPubSDK/RewardedVideo/Internal/MPRewardedVideoAdManager.m @@ -66,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. @@ -211,6 +220,7 @@ - (void)fetchAdWithConfiguration:(MPAdConfiguration *)configuration { } self.adapter = adapter; + [self.delegate rewardedVideoWillStartAttemptForAdManager:self]; [self.adapter getAdWithConfiguration:configuration targeting:self.targeting]; } @@ -264,6 +274,7 @@ - (NSString *)adUnitIDForAdServerCommunicator:(MPAdServerCommunicator *)adServer - (void)rewardedVideoDidLoadForAdapter:(MPRewardedVideoAdapter *)adapter { + [self.delegate rewardedVideoDidSucceedAttemptForAdManager:self]; self.remainingConfigurations = nil; self.ready = YES; self.loading = NO; @@ -278,6 +289,8 @@ - (void)rewardedVideoDidLoadForAdapter:(MPRewardedVideoAdapter *)adapter - (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; @@ -386,4 +399,9 @@ - (NSString *)rewardedVideoCustomerId return self.customerId; } +- (NSString *) getCreativeId +{ + return self.configuration.dspCreativeId; +} + @end diff --git a/MoPubSDK/RewardedVideo/MPRewardedVideo.h b/MoPubSDK/RewardedVideo/MPRewardedVideo.h index 4f484e4a6..c87ee2e02 100644 --- a/MoPubSDK/RewardedVideo/MPRewardedVideo.h +++ b/MoPubSDK/RewardedVideo/MPRewardedVideo.h @@ -170,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. * diff --git a/MoPubSDK/RewardedVideo/MPRewardedVideo.m b/MoPubSDK/RewardedVideo/MPRewardedVideo.m index bb639cc0d..bf773a6df 100644 --- a/MoPubSDK/RewardedVideo/MPRewardedVideo.m +++ b/MoPubSDK/RewardedVideo/MPRewardedVideo.m @@ -199,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]; @@ -328,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