diff --git a/admob/integration_test/src/integration_test.cc b/admob/integration_test/src/integration_test.cc index cc0e5bc6eb..d325efb148 100644 --- a/admob/integration_test/src/integration_test.cc +++ b/admob/integration_test/src/integration_test.cc @@ -435,6 +435,7 @@ class TestRewardedVideoListener got_reward_ = true; reward_type_ = reward.reward_type; reward_amount_ = reward.amount; + ssv_user_id_ = reward.ssv_user_id; } void OnPresentationStateChanged( firebase::admob::rewarded_video::PresentationState state) override { @@ -442,6 +443,7 @@ class TestRewardedVideoListener } bool got_reward_; std::string reward_type_; + std::string ssv_user_id_; float reward_amount_; std::vector presentation_states_; @@ -457,7 +459,7 @@ TEST_F(FirebaseAdMobTest, TestRewardedVideoAd) { rewarded_video::SetListener(&rewarded_listener); firebase::admob::AdRequest request = GetAdRequest(); - WaitForCompletion(rewarded_video::LoadAd(kRewardedVideoAdUnit, request), + WaitForCompletion(rewarded_video::LoadAd(kRewardedVideoAdUnit, kSSVUserId, request), "LoadAd"); std::vector expected_presentation_states; @@ -489,6 +491,7 @@ TEST_F(FirebaseAdMobTest, TestRewardedVideoAd) { EXPECT_TRUE(rewarded_listener.got_reward_); EXPECT_NE(rewarded_listener.reward_type_, ""); EXPECT_NE(rewarded_listener.reward_amount_, 0); + EXPECT_EQ(rewarded_listener.ssv_user_id_, kSSVUserId); LogDebug("Got reward: %.02f %s", rewarded_listener.reward_amount_, rewarded_listener.reward_type_.c_str()); diff --git a/admob/src/android/admob_android.cc b/admob/src/android/admob_android.cc index e1c6fe188e..ab5e1b2f50 100644 --- a/admob/src/android/admob_android.cc +++ b/admob/src/android/admob_android.cc @@ -373,7 +373,7 @@ Java_com_google_firebase_admob_internal_cpp_RewardedVideoHelper_notifyPresentati extern "C" JNIEXPORT void JNICALL Java_com_google_firebase_admob_internal_cpp_RewardedVideoHelper_grantReward( JNIEnv* env, jclass clazz, jlong data_ptr, jint amount, - jstring rewardType) { + jstring rewardType, jstring juserid) { if (data_ptr == 0) return; // test call only firebase::admob::rewarded_video::internal::RewardedVideoInternal* internal = @@ -389,6 +389,11 @@ Java_com_google_firebase_admob_internal_cpp_RewardedVideoHelper_grantReward( const char* chars = env->GetStringUTFChars(rewardType, 0); reward.reward_type = chars; env->ReleaseStringUTFChars(rewardType, chars); + + const char* cuserid = env->GetStringUTFChars(juserid, 0); + reward.reward_type = cuserid; + env->ReleaseStringUTFChars(juserid, cuserid); + internal->NotifyListenerOfReward(reward); } diff --git a/admob/src/android/rewarded_video_internal_android.cc b/admob/src/android/rewarded_video_internal_android.cc index 2d4e92bb30..68cc9653de 100644 --- a/admob/src/android/rewarded_video_internal_android.cc +++ b/admob/src/android/rewarded_video_internal_android.cc @@ -76,6 +76,7 @@ Future RewardedVideoInternalAndroid::Initialize() { } Future RewardedVideoInternalAndroid::LoadAd(const char* ad_unit_id, + const char* user_id, const AdRequest& request) { FutureCallbackData* callback_data = CreateFutureCallbackData(&future_data_, kRewardedVideoFnLoadAd); @@ -83,6 +84,8 @@ Future RewardedVideoInternalAndroid::LoadAd(const char* ad_unit_id, JNIEnv* env = ::firebase::admob::GetJNI(); jstring ad_unit_id_str = env->NewStringUTF(ad_unit_id); + jstring user_id_str = NULL; + if (user_id) user_id_str = env->NewStringUTF(user_id); AdRequestConverter converter(request); jobject request_ref = converter.GetJavaRequestObject(); @@ -90,9 +93,10 @@ Future RewardedVideoInternalAndroid::LoadAd(const char* ad_unit_id, env->CallVoidMethod( helper_, rewarded_video_helper::GetMethodId(rewarded_video_helper::kLoadAd), - reinterpret_cast(callback_data), ad_unit_id_str, request_ref); + reinterpret_cast(callback_data), ad_unit_id_str, user_id_str, request_ref); env->DeleteLocalRef(ad_unit_id_str); + if (user_id) env->DeleteLocalRef(user_id_str); return GetLastResult(kRewardedVideoFnLoadAd); } diff --git a/admob/src/android/rewarded_video_internal_android.h b/admob/src/android/rewarded_video_internal_android.h index d7b9ed17e5..3c6edc1755 100644 --- a/admob/src/android/rewarded_video_internal_android.h +++ b/admob/src/android/rewarded_video_internal_android.h @@ -35,7 +35,7 @@ namespace rewarded_video { X(Resume, "resume", "(J)V"), \ X(Show, "show", "(J)V"), \ X(LoadAd, "loadAd", \ - "(JLjava/lang/String;Lcom/google/android/gms/ads/AdRequest;)V"), \ + "(JLjava/lang/String;Ljava/lang/String;Lcom/google/android/gms/ads/AdRequest;)V"), \ X(GetPresentationState, "getPresentationState", "()I") // clang-format on @@ -50,6 +50,7 @@ class RewardedVideoInternalAndroid : public RewardedVideoInternal { Future Initialize() override; Future LoadAd(const char* ad_unit_id, + const char* user_id, const AdRequest& request) override; Future Show(AdParent parent) override; Future Pause() override; diff --git a/admob/src/common/rewarded_video.cc b/admob/src/common/rewarded_video.cc index a18662e384..35d8732978 100644 --- a/admob/src/common/rewarded_video.cc +++ b/admob/src/common/rewarded_video.cc @@ -25,156 +25,164 @@ namespace firebase { namespace admob { namespace rewarded_video { - // An internal, platform-specific implementation object that this class uses to // interact with the Google Mobile Ads SDKs for iOS and Android. static internal::RewardedVideoInternal* g_internal = nullptr; const char kUninitializedError[] = - "rewarded_video::Initialize() must be called before this method."; + "rewarded_video::Initialize() must be called before this method."; const char kCannotInitTwiceError[] = - "rewarded_video::Initialize cannot be called twice."; + "rewarded_video::Initialize cannot be called twice."; PollableRewardListener::PollableRewardListener() : mutex_(new Mutex()) {} -PollableRewardListener::~PollableRewardListener() { delete mutex_; } +PollableRewardListener::~PollableRewardListener() { + delete mutex_; +} bool PollableRewardListener::PollReward(RewardItem* reward) { - FIREBASE_ASSERT(reward != nullptr); - MutexLock lock(*mutex_); - if (rewards_.empty()) { - return false; - } - reward->amount = rewards_.front().amount; - reward->reward_type = rewards_.front().reward_type; - rewards_.pop(); - return true; + FIREBASE_ASSERT(reward != nullptr); + MutexLock lock(*mutex_); + + if (rewards_.empty()) { + return false; + } + reward->amount = rewards_.front().amount; + reward->reward_type = rewards_.front().reward_type; + rewards_.pop(); + return true; } void PollableRewardListener::OnRewarded(RewardItem reward) { - MutexLock lock(*mutex_); - RewardItem new_item; - new_item.amount = reward.amount; - new_item.reward_type = reward.reward_type; - rewards_.push(new_item); + MutexLock lock(*mutex_); + RewardItem new_item; + + new_item.amount = reward.amount; + new_item.reward_type = reward.reward_type; + rewards_.push(new_item); } void PollableRewardListener::OnPresentationStateChanged( - PresentationState state) { - // Nothing done here. The publisher already has the ability to poll - // rewarded_video::presentation_state for presentation state info. + PresentationState state) { + // Nothing done here. The publisher already has the ability to poll + // rewarded_video::presentation_state for presentation state info. } // Initialize must be called before any other methods in the namespace. This // method asserts that Initialize has been invoked and allowed to complete. static bool CheckIsInitialized() { - bool initialized = + bool initialized = g_internal != nullptr && g_internal->GetLastResult(internal::kRewardedVideoFnInitialize) - .status() == kFutureStatusComplete; - FIREBASE_ASSERT_MESSAGE_RETURN(false, initialized, kUninitializedError); - return true; + .status() == kFutureStatusComplete; + + FIREBASE_ASSERT_MESSAGE_RETURN(false, initialized, kUninitializedError); + return true; } Future Initialize() { - FIREBASE_ASSERT_RETURN(Future(), admob::IsInitialized()); - // Initialize cannot be called more than once. - FIREBASE_ASSERT_MESSAGE_RETURN(Future(), g_internal == nullptr, - kCannotInitTwiceError); - g_internal = internal::RewardedVideoInternal::CreateInstance(); - GetOrCreateCleanupNotifier()->RegisterObject(g_internal, [](void*) { - // Since there is no way to shut down this module after initialization - LogWarning( - "rewardedvideo::Destroy should be called before " - "admob::Terminate."); - Destroy(); - }); - return g_internal->Initialize(); + FIREBASE_ASSERT_RETURN(Future(), admob::IsInitialized()); + + // Initialize cannot be called more than once. + FIREBASE_ASSERT_MESSAGE_RETURN(Future(), g_internal == nullptr, + kCannotInitTwiceError); + g_internal = internal::RewardedVideoInternal::CreateInstance(); + GetOrCreateCleanupNotifier()->RegisterObject(g_internal, [](void*) { + // Since there is no way to shut down this module after initialization + LogWarning( + "rewardedvideo::Destroy should be called before " + "admob::Terminate."); + Destroy(); + }); + return g_internal->Initialize(); } Future InitializeLastResult() { - // This result can't be checked unless the RewardedVideoInternal has been - // created, but it must be available to publishers *before* Initialize has - // completed (so they can know when it's done). That's why this result uses a - // different conditional than the others. - FIREBASE_ASSERT_MESSAGE_RETURN(Future(), g_internal != nullptr, - kUninitializedError); - return g_internal->GetLastResult(internal::kRewardedVideoFnInitialize); + // This result can't be checked unless the RewardedVideoInternal has been + // created, but it must be available to publishers *before* Initialize has + // completed (so they can know when it's done). That's why this result uses a + // different conditional than the others. + FIREBASE_ASSERT_MESSAGE_RETURN(Future(), g_internal != nullptr, + kUninitializedError); + return g_internal->GetLastResult(internal::kRewardedVideoFnInitialize); } Future LoadAd(const char* ad_unit_id, const AdRequest& request) { - if (!CheckIsInitialized()) return Future(); - return g_internal->LoadAd(ad_unit_id, request); + return LoadAd(ad_unit_id, NULL, request); +} + +Future LoadAd(const char* ad_unit_id, const char* user_id, const AdRequest& request) { + if (!CheckIsInitialized()) return Future(); + return g_internal->LoadAd(ad_unit_id, user_id, request); } Future LoadAdLastResult() { - if (!CheckIsInitialized()) return Future(); - return g_internal->GetLastResult(internal::kRewardedVideoFnLoadAd); + if (!CheckIsInitialized()) return Future(); + return g_internal->GetLastResult(internal::kRewardedVideoFnLoadAd); } Future Show(AdParent parent) { - if (!CheckIsInitialized()) return Future(); - return g_internal->Show(parent); + if (!CheckIsInitialized()) return Future(); + return g_internal->Show(parent); } Future ShowLastResult() { - if (!CheckIsInitialized()) return Future(); - return g_internal->GetLastResult(internal::kRewardedVideoFnShow); + if (!CheckIsInitialized()) return Future(); + return g_internal->GetLastResult(internal::kRewardedVideoFnShow); } Future Pause() { - if (!CheckIsInitialized()) return Future(); - return g_internal->Pause(); + if (!CheckIsInitialized()) return Future(); + return g_internal->Pause(); } Future PauseLastResult() { - if (!CheckIsInitialized()) return Future(); - return g_internal->GetLastResult(internal::kRewardedVideoFnPause); + if (!CheckIsInitialized()) return Future(); + return g_internal->GetLastResult(internal::kRewardedVideoFnPause); } Future Resume() { - if (!CheckIsInitialized()) return Future(); - return g_internal->Resume(); + if (!CheckIsInitialized()) return Future(); + return g_internal->Resume(); } Future ResumeLastResult() { - if (!CheckIsInitialized()) return Future(); - return g_internal->GetLastResult(internal::kRewardedVideoFnResume); + if (!CheckIsInitialized()) return Future(); + return g_internal->GetLastResult(internal::kRewardedVideoFnResume); } // This method is synchronous. It does not return a future, but instead waits // until the internal Destroy has completed, so that it's safe to delete // g_internal. void Destroy() { - if (!CheckIsInitialized()) return; + if (!CheckIsInitialized()) return; - Mutex mutex(Mutex::kModeNonRecursive); - mutex.Acquire(); + Mutex mutex(Mutex::kModeNonRecursive); + mutex.Acquire(); - GetOrCreateCleanupNotifier()->UnregisterObject(g_internal); + GetOrCreateCleanupNotifier()->UnregisterObject(g_internal); - g_internal->Destroy().OnCompletion( + g_internal->Destroy().OnCompletion( [](const Future&, void* mutex) { - (reinterpret_cast(mutex))->Release(); - }, + (reinterpret_cast(mutex))->Release(); + }, &mutex); - mutex.Acquire(); - mutex.Release(); - delete g_internal; - g_internal = nullptr; + mutex.Acquire(); + mutex.Release(); + delete g_internal; + g_internal = nullptr; } PresentationState presentation_state() { - if (!CheckIsInitialized()) return kPresentationStateHidden; - return g_internal->GetPresentationState(); + if (!CheckIsInitialized()) return kPresentationStateHidden; + return g_internal->GetPresentationState(); } void SetListener(Listener* listener) { - if (!CheckIsInitialized()) return; - g_internal->SetListener(listener); + if (!CheckIsInitialized()) return; + g_internal->SetListener(listener); } - -} // namespace rewarded_video -} // namespace admob -} // namespace firebase +} // namespace rewarded_video +} // namespace admob +} // namespace firebase diff --git a/admob/src/common/rewarded_video_internal.h b/admob/src/common/rewarded_video_internal.h index 2a9233ac64..96562ff7a5 100644 --- a/admob/src/common/rewarded_video_internal.h +++ b/admob/src/common/rewarded_video_internal.h @@ -58,6 +58,7 @@ class RewardedVideoInternal { // Initiates an ad request. virtual Future LoadAd(const char* ad_unit_id, + const char* user_id, const AdRequest& request) = 0; // Displays a rewarded video ad. diff --git a/admob/src/include/firebase/admob/rewarded_video.h b/admob/src/include/firebase/admob/rewarded_video.h index 5f59657f78..13c2133238 100644 --- a/admob/src/include/firebase/admob/rewarded_video.h +++ b/admob/src/include/firebase/admob/rewarded_video.h @@ -121,6 +121,8 @@ struct RewardItem { float amount; /// A string description of the type of reward (such as "coins" or "points"). std::string reward_type; + /// A string containing the User ID for Server-Side Verification + std::string ssv_user_id; }; /// A listener class that developers can extend and pass to @ref SetListener @@ -178,6 +180,7 @@ Future InitializeLastResult(); /// @param[in] request An AdRequest struct with information about the request /// to be made (such as targeting info). Future LoadAd(const char* ad_unit_id, const AdRequest& request); +Future LoadAd(const char* ad_unit_id, const char* user_id, const AdRequest& request); /// Returns a @ref Future containing the status of the last call to /// @ref LoadAd. diff --git a/admob/src/ios/rewarded_video_internal_ios.h b/admob/src/ios/rewarded_video_internal_ios.h index 26ce454ea7..95603500f3 100644 --- a/admob/src/ios/rewarded_video_internal_ios.h +++ b/admob/src/ios/rewarded_video_internal_ios.h @@ -35,6 +35,7 @@ class RewardedVideoInternalIOS : public RewardedVideoInternal { Future Initialize() override; Future LoadAd(const char* ad_unit_id, + const char* user_id, const AdRequest& request) override; Future Show(AdParent parent) override; Future Pause() override; diff --git a/admob/src/ios/rewarded_video_internal_ios.mm b/admob/src/ios/rewarded_video_internal_ios.mm index bb2ddb2416..db798e705f 100644 --- a/admob/src/ios/rewarded_video_internal_ios.mm +++ b/admob/src/ios/rewarded_video_internal_ios.mm @@ -59,7 +59,7 @@ return GetLastResult(kRewardedVideoFnInitialize); } -Future RewardedVideoInternalIOS::LoadAd(const char* ad_unit_id, const AdRequest& request) { +Future RewardedVideoInternalIOS::LoadAd(const char* ad_unit_id, const char* user_id, const AdRequest& request) { if (future_handle_for_load != ReferenceCountedFutureImpl::kInvalidHandle) { // Checks if an outstanding Future exists. // It is safe to call the LoadAd method concurrently on multiple threads; however, the second @@ -79,13 +79,15 @@ AdRequest *request_copy = new AdRequest; *request_copy = request; NSString *adUnitId = @(ad_unit_id ? ad_unit_id : ""); + NSString *userId = @(user_id ? user_id : ""); dispatch_async(dispatch_get_main_queue(), ^{ // A GADRequest from an admob::AdRequest. GADRequest *ad_request = GADRequestFromCppAdRequest(*request_copy); delete request_copy; // Make the reward based video ad request. - [[GADRewardBasedVideoAd sharedInstance] loadRequest:ad_request - withAdUnitID:adUnitId]; + GADRewardBasedVideoAd* vid = [GADRewardBasedVideoAd sharedInstance]; + vid.userIdentifier = userId; + [vid loadRequest:ad_request withAdUnitID:adUnitId]; }); return GetLastResult(kRewardedVideoFnLoadAd); } @@ -180,6 +182,7 @@ RewardItem reward_item; reward_item.amount = reward.amount.floatValue; reward_item.reward_type = reward.type.UTF8String; + reward_item.ssv_user_id = reward_based_video_ad.userIdentifier.UTF8String; NotifyListenerOfReward(reward_item); } diff --git a/admob/src/stub/rewarded_video_internal_stub.h b/admob/src/stub/rewarded_video_internal_stub.h index 8256c47459..6bc5b3305c 100644 --- a/admob/src/stub/rewarded_video_internal_stub.h +++ b/admob/src/stub/rewarded_video_internal_stub.h @@ -35,7 +35,7 @@ class RewardedVideoInternalStub : public RewardedVideoInternal { return CreateAndCompleteFutureStub(kRewardedVideoFnInitialize); } - Future LoadAd(const char* ad_unit_id, + Future LoadAd(const char* ad_unit_id, const char* user_id, const AdRequest& request) override { return CreateAndCompleteFutureStub(kRewardedVideoFnLoadAd); } diff --git a/admob/src_java/com/google/firebase/admob/internal/cpp/RewardedVideoHelper.java b/admob/src_java/com/google/firebase/admob/internal/cpp/RewardedVideoHelper.java index ce1c03d6be..9ee7de26df 100644 --- a/admob/src_java/com/google/firebase/admob/internal/cpp/RewardedVideoHelper.java +++ b/admob/src_java/com/google/firebase/admob/internal/cpp/RewardedVideoHelper.java @@ -116,7 +116,7 @@ public RewardedVideoHelper(long rewardedVideoInternalPtr, Activity activity) { completeRewardedVideoFutureCallback( CPP_NULLPTR, 0, ConstantsHelper.CALLBACK_ERROR_MESSAGE_NONE); notifyPresentationStateChanged(CPP_NULLPTR, mCurrentPresentationState); - grantReward(CPP_NULLPTR, 0, DEFAULT_REWARD_TYPE); + grantReward(CPP_NULLPTR, 0, DEFAULT_REWARD_TYPE, ""); } /** @@ -158,7 +158,7 @@ public void run() { * @param adUnitId the AdMob ad unit ID to use in making the request. * @param request an AdRequest object with targeting/extra info. */ - public void loadAd(long callbackDataPtr, final String adUnitId, final AdRequest request) { + public void loadAd(long callbackDataPtr, final String adUnitId, final String userId, final AdRequest request) { synchronized (mRewardedVideoLock) { if (mLoadAdCallbackDataPtr != CPP_NULLPTR) { completeRewardedVideoFutureCallback( @@ -176,6 +176,7 @@ public void loadAd(long callbackDataPtr, final String adUnitId, final AdRequest @Override public void run() { RewardedVideoAd ad = MobileAds.getRewardedVideoAdInstance(mActivity); + if (userId != null) ad.setUserId(userId); ad.loadAd(adUnitId, request); } }); @@ -380,7 +381,11 @@ public void onRewarded(RewardItem rewardItem) { rewardType = DEFAULT_REWARD_TYPE; } - grantReward(mRewardedVideoInternalPtr, rewardItem.getAmount(), rewardType); + RewardedVideoAd ad = MobileAds.getRewardedVideoAdInstance(mActivity); + String ssvUserId = ad.getUserId(); + if (ssvUserId == null) ssvUserId = ""; + + grantReward(mRewardedVideoInternalPtr, rewardItem.getAmount(), rewardType, ssvUserId); } } } @@ -414,5 +419,5 @@ public static native void completeRewardedVideoFutureCallback( public static native void notifyPresentationStateChanged(long nativeInternalPtr, int state); /** Native callback to notify the C++ wrapper that a reward should be given. */ - public static native void grantReward(long nativeInternalPtr, int amount, String rewardType); + public static native void grantReward(long nativeInternalPtr, int amount, String rewardType, String ssvUserId); }