diff --git a/gma/integration_test/src/integration_test.cc b/gma/integration_test/src/integration_test.cc index 31c599cda9..025a521969 100644 --- a/gma/integration_test/src/integration_test.cc +++ b/gma/integration_test/src/integration_test.cc @@ -2170,6 +2170,10 @@ TEST_F(FirebaseGmaTest, TestNativeRecordImpression) { WaitForCompletion(native_ad->Initialize(app_framework::GetWindowContext()), "Initialize"); + // Set the listener. + TestAdListener ad_listener; + native_ad->SetAdListener(&ad_listener); + // When the NativeAd is initialized, load an ad. firebase::Future load_ad_future = native_ad->LoadAd(kNativeAdUnit, GetAdRequest()); @@ -2199,6 +2203,10 @@ TEST_F(FirebaseGmaTest, TestNativeRecordImpression) { firebase::gma::kAdErrorCodeInvalidRequest); #endif + // Use an allowlisted Ad unit ID that can record an impression, to verify + // the impression count while testing locally. + EXPECT_EQ(ad_listener.num_on_ad_impression_, 0); + firebase::Variant str_variant = firebase::Variant::FromMutableString("test"); WaitForCompletion(native_ad->RecordImpression(str_variant), @@ -2221,6 +2229,10 @@ TEST_F(FirebaseGmaTest, TestNativePerformClick) { WaitForCompletion(native_ad->Initialize(app_framework::GetWindowContext()), "Initialize"); + // Set the listener. + TestAdListener ad_listener; + native_ad->SetAdListener(&ad_listener); + // When the NativeAd is initialized, load an ad. firebase::Future load_ad_future = native_ad->LoadAd(kNativeAdUnit, GetAdRequest()); @@ -2241,6 +2253,11 @@ TEST_F(FirebaseGmaTest, TestNativePerformClick) { // Android and iOS doesn't have a return type for this API. WaitForCompletion(native_ad->PerformClick(click_payload), "PerformClick"); + // Test Ad unit IDs are not allowlisted to use PerformClick API and the + // request is expected to be rejected by the server. Use an allowlisted Ad + // unit ID to verify the ad click count while testing locally. + EXPECT_EQ(ad_listener.num_on_ad_clicked_, 0); + firebase::Variant str_variant = firebase::Variant::FromMutableString("test"); WaitForCompletion(native_ad->PerformClick(str_variant), "PerformClick 2", diff --git a/gma/src/android/gma_android.cc b/gma/src/android/gma_android.cc index 3ec5103f82..62a53575e9 100644 --- a/gma/src/android/gma_android.cc +++ b/gma/src/android/gma_android.cc @@ -973,6 +973,43 @@ void JNI_NativeAd_completeLoadedAd(JNIEnv* env, jclass clazz, jlong data_ptr, env->DeleteLocalRef(j_response_info); } +void JNI_NativeAd_notifyAdClicked(JNIEnv* env, jclass clazz, jlong data_ptr) { + FIREBASE_ASSERT(env); + FIREBASE_ASSERT(data_ptr); + + firebase::gma::internal::NativeAdInternal* internal = + reinterpret_cast(data_ptr); + internal->NotifyListenerAdClicked(); +} + +void JNI_NativeAd_notifyAdClosed(JNIEnv* env, jclass clazz, jlong data_ptr) { + FIREBASE_ASSERT(env); + FIREBASE_ASSERT(data_ptr); + + firebase::gma::internal::NativeAdInternal* internal = + reinterpret_cast(data_ptr); + internal->NotifyListenerAdClosed(); +} + +void JNI_NativeAd_notifyAdImpression(JNIEnv* env, jclass clazz, + jlong data_ptr) { + FIREBASE_ASSERT(env); + FIREBASE_ASSERT(data_ptr); + + firebase::gma::internal::NativeAdInternal* internal = + reinterpret_cast(data_ptr); + internal->NotifyListenerAdImpression(); +} + +void JNI_NativeAd_notifyAdOpened(JNIEnv* env, jclass clazz, jlong data_ptr) { + FIREBASE_ASSERT(env); + FIREBASE_ASSERT(data_ptr); + + firebase::gma::internal::NativeAdInternal* internal = + reinterpret_cast(data_ptr); + internal->NotifyListenerAdOpened(); +} + void JNI_NativeImage_completeLoadedImage(JNIEnv* env, jclass clazz, jlong data_ptr, jobject j_image_bytes) { @@ -1260,6 +1297,14 @@ bool RegisterNatives() { reinterpret_cast(&JNI_completeLoadAdError)}, {"completeNativeLoadAdInternalError", "(JILjava/lang/String;)V", reinterpret_cast(&JNI_completeLoadAdInternalError)}, + {"notifyAdClicked", "(J)V", + reinterpret_cast(&JNI_NativeAd_notifyAdClicked)}, + {"notifyAdClosed", "(J)V", + reinterpret_cast(&JNI_NativeAd_notifyAdClosed)}, + {"notifyAdImpression", "(J)V", + reinterpret_cast(&JNI_NativeAd_notifyAdImpression)}, + {"notifyAdOpened", "(J)V", + reinterpret_cast(&JNI_NativeAd_notifyAdOpened)}, }; static const JNINativeMethod kNativeImageMethods[] = { diff --git a/gma/src/common/native_ad.cc b/gma/src/common/native_ad.cc index c479293b41..8a1a62e9a5 100644 --- a/gma/src/common/native_ad.cc +++ b/gma/src/common/native_ad.cc @@ -79,6 +79,10 @@ Future NativeAd::LoadAdLastResult() const { return internal_->GetLoadAdLastResult(); } +void NativeAd::SetAdListener(AdListener* listener) { + internal_->SetAdListener(listener); +} + const NativeAdImage& NativeAd::icon() const { return internal_->icon(); } const std::vector& NativeAd::images() const { diff --git a/gma/src/common/native_ad_internal.cc b/gma/src/common/native_ad_internal.cc index c0a568beed..78d9e07abd 100644 --- a/gma/src/common/native_ad_internal.cc +++ b/gma/src/common/native_ad_internal.cc @@ -38,7 +38,7 @@ namespace gma { namespace internal { NativeAdInternal::NativeAdInternal(NativeAd* base) - : base_(base), future_data_(kNativeAdFnCount) {} + : base_(base), future_data_(kNativeAdFnCount), ad_listener_(nullptr) {} NativeAdInternal* NativeAdInternal::CreateInstance(NativeAd* base) { #if FIREBASE_PLATFORM_ANDROID @@ -61,6 +61,39 @@ Future NativeAdInternal::GetLoadAdLastResult() { future_data_.future_impl.LastResult(kNativeAdFnLoadAd)); } +void NativeAdInternal::SetAdListener(AdListener* listener) { + MutexLock lock(listener_mutex_); + ad_listener_ = listener; +} + +void NativeAdInternal::NotifyListenerAdClicked() { + MutexLock lock(listener_mutex_); + if (ad_listener_ != nullptr) { + ad_listener_->OnAdClicked(); + } +} + +void NativeAdInternal::NotifyListenerAdClosed() { + MutexLock lock(listener_mutex_); + if (ad_listener_ != nullptr) { + ad_listener_->OnAdClosed(); + } +} + +void NativeAdInternal::NotifyListenerAdImpression() { + MutexLock lock(listener_mutex_); + if (ad_listener_ != nullptr) { + ad_listener_->OnAdImpression(); + } +} + +void NativeAdInternal::NotifyListenerAdOpened() { + MutexLock lock(listener_mutex_); + if (ad_listener_ != nullptr) { + ad_listener_->OnAdOpened(); + } +} + void NativeAdInternal::insert_image(const NativeAdImage& image, const std::string& image_type) { if (image_type == "icon") { diff --git a/gma/src/common/native_ad_internal.h b/gma/src/common/native_ad_internal.h index 2f60433d22..cb9dd822d4 100644 --- a/gma/src/common/native_ad_internal.h +++ b/gma/src/common/native_ad_internal.h @@ -61,6 +61,15 @@ class NativeAdInternal { // Retrieves the most recent AdResult future for the LoadAd function. Future GetLoadAdLastResult(); + // Sets an AdListener for this ad view. + virtual void SetAdListener(AdListener* listener); + + // Notifies the Ad listener (if one exists) that an event has occurred. + void NotifyListenerAdClicked(); + void NotifyListenerAdClosed(); + void NotifyListenerAdImpression(); + void NotifyListenerAdOpened(); + // Returns true if the NativeAd has been initialized. virtual bool is_initialized() const = 0; @@ -101,6 +110,9 @@ class NativeAdInternal { // Future data used to synchronize asynchronous calls. FutureData future_data_; + // Listener for NativeAd Lifecycle event callbacks. + AdListener* ad_listener_; + // Tracks the native ad icon asset. NativeAdImage icon_; @@ -109,6 +121,9 @@ class NativeAdInternal { // Tracks the native ad choices icon asset. NativeAdImage adchoices_icon_; + + // Lock object for accessing ad_listener_. + Mutex listener_mutex_; }; } // namespace internal diff --git a/gma/src/include/firebase/gma/internal/native_ad.h b/gma/src/include/firebase/gma/internal/native_ad.h index 674cbcac82..a35d8f90b4 100644 --- a/gma/src/include/firebase/gma/internal/native_ad.h +++ b/gma/src/include/firebase/gma/internal/native_ad.h @@ -67,6 +67,12 @@ class NativeAd { /// LoadAd. Future LoadAdLastResult() const; + /// Sets an AdListener for this native ad. + /// + /// @param[in] listener An AdListener object which will be invoked + /// when lifecycle events occur on this NativeAd. + void SetAdListener(AdListener* listener); + /// Returns the associated icon asset of the native ad. const NativeAdImage& icon() const; diff --git a/gma/src/ios/FADNativeDelegate.mm b/gma/src/ios/FADNativeDelegate.mm index 82600eaf21..b8984f81c3 100644 --- a/gma/src/ios/FADNativeDelegate.mm +++ b/gma/src/ios/FADNativeDelegate.mm @@ -50,4 +50,22 @@ - (void)adLoader:(GADAdLoader *)adLoader didReceiveNativeAd:(GADNativeAd *)nativ _nativeAd->NativeAdDidReceiveAd(nativeAd); } +#pragma mark GADNativeAdDelegate implementation + +- (void)nativeAdDidRecordClick:(nonnull GADNativeAd *)nativeAd { + _nativeAd->NotifyListenerAdClicked(); +} + +- (void)nativeAdDidRecordImpression:(nonnull GADNativeAd *)nativeAd { + _nativeAd->NotifyListenerAdImpression(); +} + +- (void)nativeAdWillPresentScreen:(nonnull GADNativeAd *)nativeAd { + _nativeAd->NotifyListenerAdOpened(); +} + +- (void)nativeAdDidDismissScreen:(nonnull GADNativeAd *)nativeAd { + _nativeAd->NotifyListenerAdClosed(); +} + @end diff --git a/gma/src_java/com/google/firebase/gma/internal/cpp/NativeAdHelper.java b/gma/src_java/com/google/firebase/gma/internal/cpp/NativeAdHelper.java index 69fca0eaa9..a2829e8bcb 100644 --- a/gma/src_java/com/google/firebase/gma/internal/cpp/NativeAdHelper.java +++ b/gma/src_java/com/google/firebase/gma/internal/cpp/NativeAdHelper.java @@ -98,7 +98,7 @@ public void run() { }); } - /** Disconnect the helper from the interstital ad. */ + /** Disconnect the helper from the native ad. */ public void disconnect() { synchronized (mNativeLock) { mNativeAdInternalPtr = CPP_NULLPTR; @@ -263,6 +263,42 @@ public void onNativeAdLoaded(NativeAd ad) { } } } + + @Override + public void onAdClicked() { + synchronized (mNativeLock) { + if (mNativeAdInternalPtr != CPP_NULLPTR) { + notifyAdClicked(mNativeAdInternalPtr); + } + } + } + + @Override + public void onAdImpression() { + synchronized (mNativeLock) { + if (mNativeAdInternalPtr != CPP_NULLPTR) { + notifyAdImpression(mNativeAdInternalPtr); + } + } + } + + @Override + public void onAdClosed() { + synchronized (mNativeLock) { + if (mNativeAdInternalPtr != CPP_NULLPTR) { + notifyAdClosed(mNativeAdInternalPtr); + } + } + } + + @Override + public void onAdOpened() { + synchronized (mNativeLock) { + if (mNativeAdInternalPtr != CPP_NULLPTR) { + notifyAdOpened(mNativeAdInternalPtr); + } + } + } } /** Native callback to instruct the C++ wrapper to complete the corresponding future. */ @@ -287,4 +323,16 @@ public static native void completeNativeLoadAdError( */ public static native void completeNativeLoadAdInternalError( long nativeInternalPtr, int gmaErrorCode, String errorMessage); + + /** Native callback to notify the C++ wrapper of an ad clicked event */ + public static native void notifyAdClicked(long nativeInternalPtr); + + /** Native callback to notify the C++ wrapper of an ad closed event */ + public static native void notifyAdClosed(long nativeInternalPtr); + + /** Native callback to notify the C++ wrapper of an ad impression event */ + public static native void notifyAdImpression(long nativeInternalPtr); + + /** Native callback to notify the C++ wrapper of an ad opened event */ + public static native void notifyAdOpened(long nativeInternalPtr); }