From bf9bb9120d3d7b5fa56aebf3bc00865fb2a515e1 Mon Sep 17 00:00:00 2001 From: "drsanta@google.com" Date: Fri, 19 Nov 2021 16:15:26 -0500 Subject: [PATCH 1/3] ios rewarded ads --- admob/CMakeLists.txt | 1 + .../integration_test/src/integration_test.cc | 8 - admob/src/ios/FADRewardedAdDelegate.h | 43 ++++++ admob/src/ios/FADRewardedAdDelegate.mm | 74 +++++++++ admob/src/ios/rewarded_ad_internal_ios.h | 13 +- admob/src/ios/rewarded_ad_internal_ios.mm | 142 +++++++++++++++++- 6 files changed, 265 insertions(+), 16 deletions(-) create mode 100644 admob/src/ios/FADRewardedAdDelegate.h create mode 100644 admob/src/ios/FADRewardedAdDelegate.mm diff --git a/admob/CMakeLists.txt b/admob/CMakeLists.txt index 8cf25e42b3..2fcbf1ec8b 100644 --- a/admob/CMakeLists.txt +++ b/admob/CMakeLists.txt @@ -51,6 +51,7 @@ set(ios_SRCS src/ios/FADBannerView.mm src/ios/FADInterstitialDelegate.mm src/ios/FADRequest.mm + src/ios/FADRewardedAdDelegate.mm src/ios/ad_result_ios.mm src/ios/adapter_response_info_ios.mm src/ios/admob_ios.mm diff --git a/admob/integration_test/src/integration_test.cc b/admob/integration_test/src/integration_test.cc index 9151056e72..e9ae5079d5 100644 --- a/admob/integration_test/src/integration_test.cc +++ b/admob/integration_test/src/integration_test.cc @@ -588,7 +588,6 @@ TEST_F(FirebaseAdMobTest, TestInterstitialAdLoad) { TEST_F(FirebaseAdMobTest, TestRewardedAdLoad) { SKIP_TEST_ON_DESKTOP; - SKIP_TEST_ON_IOS; // Note: while showing an ad requires user interaction (below), // we test that we can simply load an ad first. @@ -726,7 +725,6 @@ TEST_F(FirebaseAdMobTest, TestInterstitialAdLoadAndShow) { TEST_F(FirebaseAdMobTest, TestRewardedAdLoadAndShow) { TEST_REQUIRES_USER_INTERACTION; SKIP_TEST_ON_DESKTOP; - SKIP_TEST_ON_IOS; firebase::admob::RewardedAd* rewarded = new firebase::admob::RewardedAd(); @@ -1272,7 +1270,6 @@ TEST_F(FirebaseAdMobTest, TestInterstitialAdErrorBadExtrasClassName) { TEST_F(FirebaseAdMobTest, TestRewardedAdErrorNotInitialized) { SKIP_TEST_ON_DESKTOP; - SKIP_TEST_ON_IOS; firebase::admob::RewardedAd* rewarded_ad = new firebase::admob::RewardedAd(); @@ -1287,7 +1284,6 @@ TEST_F(FirebaseAdMobTest, TestRewardedAdErrorNotInitialized) { TEST_F(FirebaseAdMobTest, TesRewardedAdErrorAlreadyInitialized) { SKIP_TEST_ON_DESKTOP; - SKIP_TEST_ON_IOS; { firebase::admob::RewardedAd* rewarded = new firebase::admob::RewardedAd(); @@ -1327,7 +1323,6 @@ TEST_F(FirebaseAdMobTest, TesRewardedAdErrorAlreadyInitialized) { TEST_F(FirebaseAdMobTest, TestRewardedAdErrorLoadInProgress) { SKIP_TEST_ON_DESKTOP; - SKIP_TEST_ON_IOS; firebase::admob::RewardedAd* rewarded = new firebase::admob::RewardedAd(); WaitForCompletion(rewarded->Initialize(app_framework::GetWindowContext()), @@ -1362,7 +1357,6 @@ TEST_F(FirebaseAdMobTest, TestRewardedAdErrorLoadInProgress) { TEST_F(FirebaseAdMobTest, TestRewardedAdErrorBadAdUnitId) { SKIP_TEST_ON_DESKTOP; - SKIP_TEST_ON_IOS; firebase::admob::RewardedAd* rewarded = new firebase::admob::RewardedAd(); WaitForCompletion(rewarded->Initialize(app_framework::GetWindowContext()), @@ -1389,7 +1383,6 @@ TEST_F(FirebaseAdMobTest, TestRewardedAdErrorBadAdUnitId) { TEST_F(FirebaseAdMobTest, TestRewardedAdErrorBadExtrasClassName) { SKIP_TEST_ON_DESKTOP; - SKIP_TEST_ON_IOS; firebase::admob::RewardedAd* rewarded = new firebase::admob::RewardedAd(); WaitForCompletion(rewarded->Initialize(app_framework::GetWindowContext()), @@ -1446,7 +1439,6 @@ TEST_F(FirebaseAdMobTest, TestInterstitialAdStress) { TEST_F(FirebaseAdMobTest, TestRewardedAdStress) { TEST_REQUIRES_USER_INTERACTION; SKIP_TEST_ON_DESKTOP; - SKIP_TEST_ON_IOS; for (int i = 0; i < 10; ++i) { firebase::admob::RewardedAd* rewarded = new firebase::admob::RewardedAd(); diff --git a/admob/src/ios/FADRewardedAdDelegate.h b/admob/src/ios/FADRewardedAdDelegate.h new file mode 100644 index 0000000000..c6028fa4b1 --- /dev/null +++ b/admob/src/ios/FADRewardedAdDelegate.h @@ -0,0 +1,43 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// An Objective-C++ wrapper class that conforms to the GADRewardedAdDelegate protocol. When the +// delegate for receiving state change messages from a GADRewardedAd is notified, this wrapper +// class forwards the notification to the RewardedAdInternalIOS object to handle the state +// changes for an rewarded ad. + +#import +#import + +namespace firebase { +namespace admob { +namespace internal { +class RewardedAdInternalIOS; +} // namespace internal +} // namespace admob +} // namespace firebase + +NS_ASSUME_NONNULL_BEGIN + +@interface FADRewardedAdDelegate : NSObject + +/// Returns a FADInterstitialDelegate object with InterstitialAdInternalIOS. +- (FADRewardedAdDelegate *)initWithInternalRewardedAd: + (firebase::admob::internal::RewardedAdInternalIOS *)rewardedAd; + +@end + +NS_ASSUME_NONNULL_END diff --git a/admob/src/ios/FADRewardedAdDelegate.mm b/admob/src/ios/FADRewardedAdDelegate.mm new file mode 100644 index 0000000000..ed7d3fd954 --- /dev/null +++ b/admob/src/ios/FADRewardedAdDelegate.mm @@ -0,0 +1,74 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "admob/src/ios/FADRewardedAdDelegate.h" + +#include "admob/src/ios/ad_result_ios.h" +#include "admob/src/ios/rewarded_ad_internal_ios.h" + +@interface FADRewardedAdDelegate () { + /// The RewardedAdInternalIOS object. + firebase::admob::internal::RewardedAdInternalIOS *_rewardedAd; +} +@end + +@implementation FADRewardedAdDelegate : NSObject + +#pragma mark - Initialization + +- (instancetype)initWithInternalRewardedAd: + (firebase::admob::internal::RewardedAdInternalIOS *)rewardedAd { + self = [super init]; + if (self) { + _rewardedAd = rewardedAd; + } + + return self; +} + +#pragma mark - GADFullScreenContentDelegate + +// Capture AdMob iOS Full screen events and forward them to our C++ +// translation layer. +- (void)adDidRecordImpression:(nonnull id)ad { + _rewardedAd->NotifyListenerOfAdImpression(); +} + +- (void)adDidRecordClick:(nonnull id)ad { + _rewardedAd->NotifyListenerOfAdClickedFullScreenContent(); +} + +- (void)ad:(nonnull id)ad +didFailToPresentFullScreenContentWithError:(nonnull NSError *)error { + firebase::admob::AdResultInternal ad_result_internal; + ad_result_internal.is_wrapper_error = false; + ad_result_internal.is_successful = false; + ad_result_internal.ios_error = error; + // Invoke AdMobInternal, a friend of AdResult, to have it access its + // protected constructor with the AdError data. + const firebase::admob::AdResult& ad_result = firebase::admob::AdMobInternal::CreateAdResult(ad_result_internal); + _rewardedAd->NotifyListenerOfAdFailedToShowFullScreenContent(ad_result); +} + +- (void)adDidPresentFullScreenContent:(nonnull id)ad { + _rewardedAd->NotifyListenerOfAdShowedFullScreenContent(); +} + +- (void)adDidDismissFullScreenContent:(nonnull id)ad { + _rewardedAd->NotifyListenerOfAdDismissedFullScreenContent(); +} + +@end diff --git a/admob/src/ios/rewarded_ad_internal_ios.h b/admob/src/ios/rewarded_ad_internal_ios.h index 8b87fc1bb3..ef785600fe 100644 --- a/admob/src/ios/rewarded_ad_internal_ios.h +++ b/admob/src/ios/rewarded_ad_internal_ios.h @@ -17,6 +17,10 @@ #ifndef FIREBASE_ADMOB_SRC_IOS_REWARDED_AD_INTERNAL_IOS_H_ #define FIREBASE_ADMOB_SRC_IOS_REWARDED_AD_INTERNAL_IOS_H_ +#ifdef __OBJC__ +#import "admob/src/ios/FADRewardedAdDelegate.h" +#endif // __OBJC__ + extern "C" { #include } // extern "C" @@ -39,8 +43,15 @@ class RewardedAdInternalIOS : public RewardedAdInternal { Future Show(UserEarnedRewardListener* listener) override; bool is_initialized() const override { return initialized_; } +#ifdef __OBJC__ + void RewardedAdDidReceiveAd(GADRewardedAd* ad); + void RewardedAdDidFailToReceiveAdWithError(NSError *gad_error); + void RewardedAdWillPresentScreen(); + void RewardedAdDidDismissScreen(); +#endif // __OBJC__ + private: - /// Prevents duplicate invocations of initailize on the Interstitial Ad. + /// Prevents duplicate invocations of initailize on the Rewarded Ad. bool initialized_; /// Contains information to asynchronously complete the LoadAd Future. diff --git a/admob/src/ios/rewarded_ad_internal_ios.mm b/admob/src/ios/rewarded_ad_internal_ios.mm index 17883e2a91..4f4c71c87a 100644 --- a/admob/src/ios/rewarded_ad_internal_ios.mm +++ b/admob/src/ios/rewarded_ad_internal_ios.mm @@ -30,23 +30,151 @@ ad_load_callback_data_(nil), rewarded_ad_(nil), parent_view_(nil), rewarded_ad_delegate_(nil) {} -RewardedAdInternalIOS::~RewardedAdInternalIOS() { } +RewardedAdInternalIOS::~RewardedAdInternalIOS() { + firebase::MutexLock lock(mutex_); + // Clean up any resources created in RewardedAdInternalIOS. + Mutex mutex(Mutex::kModeNonRecursive); + __block Mutex *mutex_in_block = &mutex; + mutex.Acquire(); + void (^destroyBlock)() = ^{ + ((GADRewardedAd*)rewarded_ad_).fullScreenContentDelegate = nil; + rewarded_ad_delegate_ = nil; + rewarded_ad_ = nil; + if(ad_load_callback_data_ != nil) { + delete ad_load_callback_data_; + ad_load_callback_data_ = nil; + } + mutex_in_block->Release(); + }; + util::DispatchAsyncSafeMainQueue(destroyBlock); + mutex.Acquire(); + mutex.Release(); +} Future RewardedAdInternalIOS::Initialize(AdParent parent) { firebase::MutexLock lock(mutex_); - return CreateAndCompleteFuture( - kRewardedAdFnInitialize, kAdMobErrorNone, nullptr, &future_data_); + const SafeFutureHandle future_handle = + future_data_.future_impl.SafeAlloc(kRewardedAdFnInitialize); + + if(initialized_) { + CompleteFuture(kAdMobErrorAlreadyInitialized, + kAdAlreadyInitializedErrorMessage, future_handle, &future_data_); + } else { + initialized_ = true; + parent_view_ = (UIView *)parent; + CompleteFuture(kAdMobErrorNone, nullptr, future_handle, &future_data_); + } + return MakeFuture(&future_data_.future_impl, future_handle); } Future RewardedAdInternalIOS::LoadAd( const char* ad_unit_id, const AdRequest& request) { - return CreateAndCompleteFutureWithResult( - kRewardedAdFnLoadAd, kAdMobErrorNone, nullptr, &future_data_, AdResult()); + firebase::MutexLock lock(mutex_); + FutureCallbackData* callback_data = + CreateAdResultFutureCallbackData(kRewardedAdFnLoadAd, + &future_data_); + SafeFutureHandle future_handle = callback_data->future_handle; + + if (ad_load_callback_data_ != nil) { + CompleteLoadAdInternalResult(callback_data, kAdMobErrorLoadInProgress, + kAdLoadInProgressErrorMessage); + return MakeFuture(&future_data_.future_impl, future_handle); + } + + // Persist a pointer to the callback data so that we may use it after the iOS + // SDK returns the AdResult. + ad_load_callback_data_ = callback_data; + + rewarded_ad_delegate_ = + [[FADRewardedAdDelegate alloc] initWithInternalRewardedAd:this]; + + dispatch_async(dispatch_get_main_queue(), ^{ + // Create a GADRequest from an admob::AdRequest. + AdMobError error_code = kAdMobErrorNone; + std::string error_message; + GADRequest *ad_request = + GADRequestFromCppAdRequest(request, &error_code, &error_message); + if (ad_request == nullptr) { + if (error_code == kAdMobErrorNone) { + error_code = kAdMobErrorInternalError; + error_message = kAdCouldNotParseAdRequestErrorMessage; + } + CompleteLoadAdInternalResult(ad_load_callback_data_, error_code, + error_message.c_str()); + ad_load_callback_data_ = nil; + } else { + // Make the rewarded ad request. + [GADRewardedAd loadWithAdUnitID:@(ad_unit_id) + request:ad_request + completionHandler:^(GADRewardedAd *ad, NSError *error) // NO LINT + { + if (error) { + RewardedAdDidFailToReceiveAdWithError(error); + } else { + RewardedAdDidReceiveAd(ad); + } + }]; + } + }); + + return MakeFuture(&future_data_.future_impl, future_handle); } Future RewardedAdInternalIOS::Show(UserEarnedRewardListener* listener) { - return CreateAndCompleteFuture( - kRewardedAdFnShow, kAdMobErrorNone, nullptr, &future_data_); + firebase::MutexLock lock(mutex_); + const firebase::SafeFutureHandle handle = + future_data_.future_impl.SafeAlloc(kRewardedAdFnShow); + + user_earned_reward_listener_ = listener; + + dispatch_async(dispatch_get_main_queue(), ^{ + AdMobError error_code = kAdMobErrorLoadInProgress; + const char* error_message = kAdLoadInProgressErrorMessage; + if (rewarded_ad_ == nil) { + error_code = kAdMobErrorUninitialized; + error_message = kAdUninitializedErrorMessage; + } else { + [rewarded_ad_ + presentFromRootViewController:[parent_view_ window].rootViewController + userDidEarnRewardHandler:^{ + NSLog(@"DEDB user did earn reward"); + GADAdReward *reward = ((GADRewardedAd*)rewarded_ad_).adReward; + + NotifyListenerOfUserEarnedReward( + util::NSStringToString(reward.type), + reward.amount.integerValue); + }]; + error_code = kAdMobErrorNone; + error_message = nullptr; + } + CompleteFuture(error_code, error_message, handle, &future_data_); + }); + return MakeFuture(&future_data_.future_impl, handle); +} + +void RewardedAdInternalIOS::RewardedAdDidReceiveAd(GADRewardedAd* ad) { + firebase::MutexLock lock(mutex_); + rewarded_ad_ = ad; + ad.fullScreenContentDelegate = rewarded_ad_delegate_; + ad.paidEventHandler = ^void(GADAdValue *_Nonnull adValue) { + NotifyListenerOfPaidEvent( + firebase::admob::ConvertGADAdValueToCppAdValue(adValue)); + }; + + if (ad_load_callback_data_ != nil) { + CompleteLoadAdInternalResult(ad_load_callback_data_, kAdMobErrorNone, + /*error_message=*/""); + ad_load_callback_data_ = nil; + } +} + +void RewardedAdInternalIOS::RewardedAdDidFailToReceiveAdWithError(NSError *gad_error) { + firebase::MutexLock lock(mutex_); + FIREBASE_ASSERT(gad_error); + if (ad_load_callback_data_ != nil) { + CompleteAdResultIOS(ad_load_callback_data_, gad_error); + ad_load_callback_data_ = nil; + } } } // namespace internal From 80d6742790a6c69698a7aebeb69a7d32c7989e47 Mon Sep 17 00:00:00 2001 From: "drsanta@google.com" Date: Fri, 19 Nov 2021 17:19:45 -0500 Subject: [PATCH 2/3] lint fixes --- admob/src/ios/FADRewardedAdDelegate.h | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/admob/src/ios/FADRewardedAdDelegate.h b/admob/src/ios/FADRewardedAdDelegate.h index c6028fa4b1..4ffb7a1199 100644 --- a/admob/src/ios/FADRewardedAdDelegate.h +++ b/admob/src/ios/FADRewardedAdDelegate.h @@ -14,10 +14,11 @@ * limitations under the License. */ -// An Objective-C++ wrapper class that conforms to the GADRewardedAdDelegate protocol. When the -// delegate for receiving state change messages from a GADRewardedAd is notified, this wrapper -// class forwards the notification to the RewardedAdInternalIOS object to handle the state -// changes for an rewarded ad. +// An Objective-C++ wrapper class that conforms to the +// GADRewardedAdDelegate protocol. When the delegate for receiving state +// change messages from a GADRewardedAd is notified, this wrapper class +// forwards the notification to the RewardedAdInternalIOS object to handle +// the state changes for an rewarded ad. #import #import From e9b061979b3aa5609a5e178c4e495acaf73b7f61 Mon Sep 17 00:00:00 2001 From: "drsanta@google.com" Date: Sat, 20 Nov 2021 13:09:44 -0500 Subject: [PATCH 3/3] remove debug log --- admob/src/ios/rewarded_ad_internal_ios.mm | 3 --- 1 file changed, 3 deletions(-) diff --git a/admob/src/ios/rewarded_ad_internal_ios.mm b/admob/src/ios/rewarded_ad_internal_ios.mm index 4f4c71c87a..592f4e6784 100644 --- a/admob/src/ios/rewarded_ad_internal_ios.mm +++ b/admob/src/ios/rewarded_ad_internal_ios.mm @@ -124,7 +124,6 @@ firebase::MutexLock lock(mutex_); const firebase::SafeFutureHandle handle = future_data_.future_impl.SafeAlloc(kRewardedAdFnShow); - user_earned_reward_listener_ = listener; dispatch_async(dispatch_get_main_queue(), ^{ @@ -137,9 +136,7 @@ [rewarded_ad_ presentFromRootViewController:[parent_view_ window].rootViewController userDidEarnRewardHandler:^{ - NSLog(@"DEDB user did earn reward"); GADAdReward *reward = ((GADRewardedAd*)rewarded_ad_).adReward; - NotifyListenerOfUserEarnedReward( util::NSStringToString(reward.type), reward.amount.integerValue);