From c416fcc5aea94da6297b0f9ddfa6248041a1205e Mon Sep 17 00:00:00 2001 From: "nakirekommula@google.com" Date: Mon, 12 Jun 2023 15:22:11 -0700 Subject: [PATCH 1/6] Internal updates --- app/src/util_android.h | 16 ++- gma/integration_test/src/integration_test.cc | 61 +++++++++ gma/src/android/native_ad_internal_android.cc | 125 ++++++++++++++++++ gma/src/android/native_ad_internal_android.h | 13 +- gma/src/common/gma_common.cc | 3 + gma/src/common/gma_common.h | 2 + gma/src/common/native_ad.cc | 31 +++++ gma/src/common/native_ad_internal.h | 15 ++- .../include/firebase/gma/internal/native_ad.h | 15 +++ gma/src/ios/GADNativeAdCpp.h | 30 +++++ gma/src/ios/native_ad_internal_ios.h | 3 + gma/src/ios/native_ad_internal_ios.mm | 102 ++++++++++++++ .../gma/internal/cpp/NativeAdHelper.java | 59 +++++++++ 13 files changed, 463 insertions(+), 12 deletions(-) create mode 100644 gma/src/ios/GADNativeAdCpp.h diff --git a/app/src/util_android.h b/app/src/util_android.h index c80a33d952..3be548f6ca 100644 --- a/app/src/util_android.h +++ b/app/src/util_android.h @@ -491,13 +491,14 @@ METHOD_LOOKUP_DECLARATION(activity, ACTIVITY_METHODS) // Used to setup the cache of Bundle class method IDs to reduce time spent // looking up methods by string. // clang-format off -#define BUNDLE_METHODS(X) \ - X(Constructor, "", "()V"), \ - X(GetString, "getString", "(Ljava/lang/String;)Ljava/lang/String;"), \ - X(KeySet, "keySet", "()Ljava/util/Set;"), \ - X(PutFloat, "putFloat", "(Ljava/lang/String;F)V"), \ - X(PutLong, "putLong", "(Ljava/lang/String;J)V"), \ - X(PutString, "putString", "(Ljava/lang/String;Ljava/lang/String;)V") +#define BUNDLE_METHODS(X) \ + X(Constructor, "", "()V"), \ + X(GetString, "getString", "(Ljava/lang/String;)Ljava/lang/String;"), \ + X(KeySet, "keySet", "()Ljava/util/Set;"), \ + X(PutFloat, "putFloat", "(Ljava/lang/String;F)V"), \ + X(PutLong, "putLong", "(Ljava/lang/String;J)V"), \ + X(PutString, "putString", "(Ljava/lang/String;Ljava/lang/String;)V"), \ + X(PutBundle, "putBundle", "(Ljava/lang/String;Landroid/os/Bundle;)V") // clang-format on METHOD_LOOKUP_DECLARATION(bundle, BUNDLE_METHODS) @@ -1125,6 +1126,7 @@ jint AttachCurrentThread(JavaVM* java_vm, JNIEnv** env); // firebase::App, either the default App (if it exists) or any valid // App. If there is no instantiated App, returns nullptr. JNIEnv* GetJNIEnvFromApp(); + } // namespace util // NOLINTNEXTLINE - allow namespace overridden } // namespace firebase diff --git a/gma/integration_test/src/integration_test.cc b/gma/integration_test/src/integration_test.cc index b5ccfc8c6a..ff75dc110d 100644 --- a/gma/integration_test/src/integration_test.cc +++ b/gma/integration_test/src/integration_test.cc @@ -2058,6 +2058,61 @@ TEST_F(FirebaseGmaTest, TestNativeAdLoadEmptyRequest) { delete native_ad; } +TEST_F(FirebaseGmaTest, TestNativeRecordImpressionAndClick) { + SKIP_TEST_ON_DESKTOP; + SKIP_TEST_ON_SIMULATOR; + + firebase::gma::NativeAd* native_ad = new firebase::gma::NativeAd(); + + WaitForCompletion(native_ad->Initialize(app_framework::GetWindowContext()), + "Initialize"); + + // When the NativeAd is initialized, load an ad. + firebase::Future load_ad_future = + native_ad->LoadAd(kNativeAdUnit, GetAdRequest()); + + WaitForCompletion(load_ad_future, "LoadAd"); + const firebase::gma::AdResult* result_ptr = load_ad_future.result(); + ASSERT_NE(result_ptr, nullptr); + EXPECT_TRUE(result_ptr->is_successful()); + + load_ad_future.Release(); + + firebase::Variant in_key = firebase::Variant::FromMutableString("inner_key"); + firebase::Variant in_val = firebase::Variant::FromMutableString("inner_val"); + firebase::Variant out_key = firebase::Variant::FromMutableString("outer_key"); + + firebase::Variant out_val = firebase::Variant::EmptyMap(); + out_val.map()[in_key] = in_val; + + firebase::Variant impression_payload = firebase::Variant::EmptyMap(); + impression_payload.map()[out_key] = out_val; + +#if defined(ANDROID) + // Android doesn't have a return type for this API. + WaitForCompletion(native_ad->RecordImpression(impression_payload), + "RecordImpression"); +#else // iOS + // Test Ad unit IDs are not allowlisted to record impression and the request + // is expected to be rejected by the server. iOS returns the failure. + WaitForCompletion(native_ad->RecordImpression(impression_payload), + "RecordImpression", + firebase::gma::kAdErrorCodeInvalidRequest); +#endif + + WaitForCompletion(native_ad->RecordImpression(in_key), "RecordImpression 2", + firebase::gma::kAdErrorCodeInvalidArgument); + + // Android and iOS doesn't have a return type for this API. + WaitForCompletion(native_ad->PerformClick(impression_payload), + "PerformClick"); + + WaitForCompletion(native_ad->PerformClick(in_key), "PerformClick 2", + firebase::gma::kAdErrorCodeInvalidArgument); + + delete native_ad; +} + TEST_F(FirebaseGmaTest, TestNativeAdErrorNotInitialized) { SKIP_TEST_ON_DESKTOP; @@ -2067,6 +2122,12 @@ TEST_F(FirebaseGmaTest, TestNativeAdErrorNotInitialized) { WaitForCompletion(native_ad->LoadAd(kNativeAdUnit, request), "LoadAd", firebase::gma::kAdErrorCodeUninitialized); + firebase::Variant variant = firebase::Variant::EmptyMap(); + WaitForCompletion(native_ad->RecordImpression(variant), "RecordImpression", + firebase::gma::kAdErrorCodeUninitialized); + WaitForCompletion(native_ad->PerformClick(variant), "PerformClick", + firebase::gma::kAdErrorCodeUninitialized); + delete native_ad; } diff --git a/gma/src/android/native_ad_internal_android.cc b/gma/src/android/native_ad_internal_android.cc index 407c66cde7..b1caf92298 100644 --- a/gma/src/android/native_ad_internal_android.cc +++ b/gma/src/android/native_ad_internal_android.cc @@ -138,6 +138,131 @@ Future NativeAdInternalAndroid::LoadAd(const char* ad_unit_id, return future; } +Future NativeAdInternalAndroid::RecordImpression( + const Variant& impression_data) { + firebase::MutexLock lock(mutex_); + + if (!initialized_) { + return CreateAndCompleteFuture(kNativeAdFnRecordImpression, + kAdErrorCodeUninitialized, + kAdUninitializedErrorMessage, &future_data_); + } + + jobject impression_bundle = variantmap_to_bundle(impression_data); + + if (impression_bundle == nullptr) { + return CreateAndCompleteFuture( + kNativeAdFnRecordImpression, kAdErrorCodeInvalidArgument, + kUnsupportedVariantTypeErrorMessage, &future_data_); + } + + JNIEnv* env = ::firebase::gma::GetJNI(); + FIREBASE_ASSERT(env); + + FutureCallbackData* callback_data = + CreateVoidFutureCallbackData(kNativeAdFnRecordImpression, &future_data_); + Future future = + MakeFuture(&future_data_.future_impl, callback_data->future_handle); + env->CallVoidMethod( + helper_, + native_ad_helper::GetMethodId(native_ad_helper::kRecordImpression), + reinterpret_cast(callback_data), impression_bundle); + + util::CheckAndClearJniExceptions(env); + + return future; +} + +Future NativeAdInternalAndroid::PerformClick(const Variant& click_data) { + firebase::MutexLock lock(mutex_); + + if (!initialized_) { + return CreateAndCompleteFuture(kNativeAdFnPerformClick, + kAdErrorCodeUninitialized, + kAdUninitializedErrorMessage, &future_data_); + } + + jobject click_bundle = variantmap_to_bundle(click_data); + + if (click_bundle == nullptr) { + return CreateAndCompleteFuture( + kNativeAdFnPerformClick, kAdErrorCodeInvalidArgument, + kUnsupportedVariantTypeErrorMessage, &future_data_); + } + + JNIEnv* env = ::firebase::gma::GetJNI(); + FIREBASE_ASSERT(env); + + FutureCallbackData* callback_data = + CreateVoidFutureCallbackData(kNativeAdFnPerformClick, &future_data_); + Future future = + MakeFuture(&future_data_.future_impl, callback_data->future_handle); + env->CallVoidMethod( + helper_, native_ad_helper::GetMethodId(native_ad_helper::kPerformClick), + reinterpret_cast(callback_data), click_bundle); + + util::CheckAndClearJniExceptions(env); + + return future; +} + +jobject NativeAdInternalAndroid::variantmap_to_bundle( + const Variant& variant_data) { + if (!variant_data.is_map()) { + return nullptr; + } + + JNIEnv* env = ::firebase::gma::GetJNI(); + FIREBASE_ASSERT(env); + + jobject variant_bundle = + env->NewObject(util::bundle::GetClass(), + util::bundle::GetMethodId(util::bundle::kConstructor)); + FIREBASE_ASSERT(variant_bundle); + + for (const auto& kvp : variant_data.map()) { + const Variant& key = kvp.first; + const Variant& value = kvp.second; + + if (!key.is_string()) { + return nullptr; + } + jstring key_str = env->NewStringUTF(key.string_value()); + + if (value.is_int64()) { + jlong val_long = (jlong)value.int64_value(); + env->CallVoidMethod(variant_bundle, + util::bundle::GetMethodId(util::bundle::kPutLong), + key_str, val_long); + } else if (value.is_double()) { + jfloat val_float = (jfloat)value.double_value(); + env->CallVoidMethod(variant_bundle, + util::bundle::GetMethodId(util::bundle::kPutFloat), + key_str, val_float); + } else if (value.is_string()) { + jstring val_str = env->NewStringUTF(value.string_value()); + env->CallVoidMethod(variant_bundle, + util::bundle::GetMethodId(util::bundle::kPutString), + key_str, val_str); + env->DeleteLocalRef(val_str); + } else if (value.is_map()) { + jobject val_bundle = variantmap_to_bundle(value); + env->CallVoidMethod(variant_bundle, + util::bundle::GetMethodId(util::bundle::kPutBundle), + key_str, val_bundle); + env->DeleteLocalRef(val_bundle); + } else { + // Unsupported value type. + return nullptr; + } + + util::CheckAndClearJniExceptions(env); + env->DeleteLocalRef(key_str); + } + + return variant_bundle; +} + } // namespace internal } // namespace gma } // namespace firebase diff --git a/gma/src/android/native_ad_internal_android.h b/gma/src/android/native_ad_internal_android.h index 2af3163d96..c4b02e056e 100644 --- a/gma/src/android/native_ad_internal_android.h +++ b/gma/src/android/native_ad_internal_android.h @@ -28,10 +28,12 @@ namespace gma { // time spent looking up methods by string. // clang-format off #define NATIVEADHELPER_METHODS(X) \ - X(Constructor, "", "(J)V"), \ - X(Initialize, "initialize", "(JLandroid/app/Activity;)V"), \ - X(LoadAd, "loadAd", \ - "(JLjava/lang/String;Lcom/google/android/gms/ads/AdRequest;)V"), \ + X(Constructor, "", "(J)V"), \ + X(Initialize, "initialize", "(JLandroid/app/Activity;)V"), \ + X(LoadAd, "loadAd", \ + "(JLjava/lang/String;Lcom/google/android/gms/ads/AdRequest;)V"), \ + X(RecordImpression, "recordImpression", "(JLandroid/os/Bundle;)V"), \ + X(PerformClick, "performClick", "(JLandroid/os/Bundle;)V"), \ X(Disconnect, "disconnect", "()V") // clang-format on @@ -48,6 +50,9 @@ class NativeAdInternalAndroid : public NativeAdInternal { Future LoadAd(const char* ad_unit_id, const AdRequest& request) override; bool is_initialized() const override { return initialized_; } + Future RecordImpression(const Variant& impression_data) override; + Future PerformClick(const Variant& click_data) override; + jobject variantmap_to_bundle(const Variant& variant_data); private: // Reference to the Java helper object used to interact with the Mobile Ads diff --git a/gma/src/common/gma_common.cc b/gma/src/common/gma_common.cc index 38bc869756..eed08ca387 100644 --- a/gma/src/common/gma_common.cc +++ b/gma/src/common/gma_common.cc @@ -67,6 +67,9 @@ const char* kAdLoadInProgressErrorMessage = "Ad is currently loading."; const char* kAdUninitializedErrorMessage = "Ad has not been fully initialized."; const char* kImageUrlMalformedErrorMessage = "Image URL is malformed or missing."; +const char* kUnsupportedVariantTypeErrorMessage = "Unsupported variant type."; +const char* kRecordImpressionFailureErrorMessage = + "Failed to record impression."; // GmaInternal void GmaInternal::CompleteLoadAdFutureSuccess( diff --git a/gma/src/common/gma_common.h b/gma/src/common/gma_common.h index a4cce65266..38fcf0a185 100644 --- a/gma/src/common/gma_common.h +++ b/gma/src/common/gma_common.h @@ -39,6 +39,8 @@ extern const char* kAdCouldNotParseAdRequestErrorMessage; extern const char* kAdLoadInProgressErrorMessage; extern const char* kAdUninitializedErrorMessage; extern const char* kImageUrlMalformedErrorMessage; +extern const char* kUnsupportedVariantTypeErrorMessage; +extern const char* kRecordImpressionFailureErrorMessage; namespace internal { class AdViewInternal; diff --git a/gma/src/common/native_ad.cc b/gma/src/common/native_ad.cc index 239c731576..fd110f1d70 100644 --- a/gma/src/common/native_ad.cc +++ b/gma/src/common/native_ad.cc @@ -18,6 +18,7 @@ #include "app/src/assert.h" #include "app/src/include/firebase/future.h" +#include "app/src/include/firebase/variant.h" #include "gma/src/common/gma_common.h" #include "gma/src/common/native_ad_internal.h" @@ -84,5 +85,35 @@ const std::vector& NativeAd::images() const { return internal_->images(); } +Future NativeAd::RecordImpression(const Variant& impression_data) { + if (!impression_data.is_map()) { + return CreateAndCompleteFuture( + firebase::gma::internal::kNativeAdFnRecordImpression, + kAdErrorCodeInvalidArgument, kUnsupportedVariantTypeErrorMessage, + &internal_->future_data_); + } + return internal_->RecordImpression(impression_data); +} + +Future NativeAd::RecordImpressionLastResult() const { + return internal_->GetLastResult( + firebase::gma::internal::kNativeAdFnRecordImpression); +} + +Future NativeAd::PerformClick(const Variant& click_data) { + if (!click_data.is_map()) { + return CreateAndCompleteFuture( + firebase::gma::internal::kNativeAdFnPerformClick, + kAdErrorCodeInvalidArgument, kUnsupportedVariantTypeErrorMessage, + &internal_->future_data_); + } + return internal_->PerformClick(click_data); +} + +Future NativeAd::PerformClickLastResult() const { + return internal_->GetLastResult( + firebase::gma::internal::kNativeAdFnPerformClick); +} + } // namespace gma } // namespace firebase diff --git a/gma/src/common/native_ad_internal.h b/gma/src/common/native_ad_internal.h index 362a1881bc..f07843d7a2 100644 --- a/gma/src/common/native_ad_internal.h +++ b/gma/src/common/native_ad_internal.h @@ -22,6 +22,7 @@ #include "app/src/include/firebase/future.h" #include "app/src/include/firebase/internal/mutex.h" +#include "app/src/include/firebase/variant.h" #include "gma/src/common/gma_common.h" #include "gma/src/include/firebase/gma/internal/native_ad.h" @@ -30,7 +31,13 @@ namespace gma { namespace internal { // Constants representing each NativeAd function that returns a Future. -enum NativeAdFn { kNativeAdFnInitialize, kNativeAdFnLoadAd, kNativeAdFnCount }; +enum NativeAdFn { + kNativeAdFnInitialize, + kNativeAdFnLoadAd, + kNativeAdFnRecordImpression, + kNativeAdFnPerformClick, + kNativeAdFnCount +}; class NativeAdInternal { public: @@ -63,6 +70,12 @@ class NativeAdInternal { // Returns the associated image assets of the native ad. const std::vector& images() const { return images_; } + // Only used by allowlisted ad units. + virtual Future RecordImpression(const Variant& impression_data) = 0; + + // Only used by allowlisted ad units. + virtual Future PerformClick(const Variant& click_data) = 0; + protected: friend class firebase::gma::NativeAd; friend class firebase::gma::NativeAdImage; diff --git a/gma/src/include/firebase/gma/internal/native_ad.h b/gma/src/include/firebase/gma/internal/native_ad.h index 0f5aaf7589..bc60a6ce68 100644 --- a/gma/src/include/firebase/gma/internal/native_ad.h +++ b/gma/src/include/firebase/gma/internal/native_ad.h @@ -23,6 +23,7 @@ #include "firebase/future.h" #include "firebase/gma/types.h" #include "firebase/internal/common.h" +#include "firebase/variant.h" // Doxygen breaks trying to parse this file, and since it is internal logic, // it doesn't need to be included in the generated documentation. @@ -72,6 +73,20 @@ class NativeAd { /// Returns the associated image assets of the native ad. const std::vector& images() const; + /// Only allowlisted ad units use this api. + Future RecordImpression(const Variant& impression_data); + + /// Returns a Future containing the status of the last call to + /// RecordImpression. + Future RecordImpressionLastResult() const; + + /// Only allowlisted ad units use this api. + Future PerformClick(const Variant& click_data); + + /// Returns a Future containing the status of the last call to + /// PerformClick. + Future PerformClickLastResult() const; + private: // An internal, platform-specific implementation object that this class uses // to interact with the Google Mobile Ads SDKs for iOS and Android. diff --git a/gma/src/ios/GADNativeAdCpp.h b/gma/src/ios/GADNativeAdCpp.h new file mode 100644 index 0000000000..e6d09d646f --- /dev/null +++ b/gma/src/ios/GADNativeAdCpp.h @@ -0,0 +1,30 @@ +/* + * Copyright 2023 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 +#import +#import + +@interface GADNativeAd () + +/// Used only by allowlisted ad units. Provide a dictionary containing click data. +- (void)performClickWithData:(nonnull NSDictionary *)clickData; + +/// Used only by allowlisted ad units. Provide a dictionary containing impression data. Returns YES +/// if the impression is successfully recorded, otherwise returns NO. +- (BOOL)recordImpressionWithData:(nonnull NSDictionary *)impressionData; + +@end \ No newline at end of file diff --git a/gma/src/ios/native_ad_internal_ios.h b/gma/src/ios/native_ad_internal_ios.h index d9b8fbe3f1..f2caae4cc1 100644 --- a/gma/src/ios/native_ad_internal_ios.h +++ b/gma/src/ios/native_ad_internal_ios.h @@ -41,8 +41,11 @@ class NativeAdInternalIOS : public NativeAdInternal { Future LoadAd(const char* ad_unit_id, const AdRequest& request) override; bool is_initialized() const override { return initialized_; } + Future RecordImpression(const Variant& impression_data) override; + Future PerformClick(const Variant& click_data) override; #ifdef __OBJC__ + NSDictionary* variantmap_to_nsdictionary(const Variant &variant_data); void NativeAdDidReceiveAd(GADNativeAd* ad); void NativeAdDidFailToReceiveAdWithError(NSError* gad_error); #endif // __OBJC__ diff --git a/gma/src/ios/native_ad_internal_ios.mm b/gma/src/ios/native_ad_internal_ios.mm index cf16222c77..135888b29e 100644 --- a/gma/src/ios/native_ad_internal_ios.mm +++ b/gma/src/ios/native_ad_internal_ios.mm @@ -14,9 +14,14 @@ * limitations under the License. */ +extern "C" { +#include +} // extern "C" + #include "gma/src/ios/native_ad_internal_ios.h" #import "gma/src/ios/FADRequest.h" +#import "gma/src/ios/GADNativeAdCpp.h" #import "gma/src/ios/gma_ios.h" #include "app/src/util_ios.h" @@ -64,6 +69,103 @@ return future; } +Future NativeAdInternalIOS::RecordImpression(const Variant& impression_data) { + firebase::MutexLock lock(mutex_); + if (!initialized_) { + return CreateAndCompleteFuture(kNativeAdFnRecordImpression, + kAdErrorCodeUninitialized, + kAdUninitializedErrorMessage, &future_data_); + } + + NSDictionary *impression_payload = variantmap_to_nsdictionary(impression_data); + if (impression_payload == nullptr) { + return CreateAndCompleteFuture( + kNativeAdFnRecordImpression, kAdErrorCodeInvalidArgument, + kUnsupportedVariantTypeErrorMessage, &future_data_); + } + + const SafeFutureHandle future_handle = + future_data_.future_impl.SafeAlloc(kNativeAdFnRecordImpression); + Future future = MakeFuture(&future_data_.future_impl, future_handle); + + dispatch_async(dispatch_get_main_queue(), ^{ + bool recorded = [(GADNativeAd*)native_ad_ recordImpressionWithData:impression_payload]; + if(recorded) { + CompleteFuture(kAdErrorCodeNone, nullptr, future_handle, &future_data_); + } else { + CompleteFuture(kAdErrorCodeInvalidRequest, kRecordImpressionFailureErrorMessage, + future_handle, &future_data_); + } + }); + + return future; +} + +Future NativeAdInternalIOS::PerformClick(const Variant& click_data) { + firebase::MutexLock lock(mutex_); + if (!initialized_) { + return CreateAndCompleteFuture(kNativeAdFnPerformClick, + kAdErrorCodeUninitialized, + kAdUninitializedErrorMessage, &future_data_); + } + + NSDictionary *click_payload = variantmap_to_nsdictionary(click_data); + if (click_payload == nullptr) { + return CreateAndCompleteFuture( + kNativeAdFnPerformClick, kAdErrorCodeInvalidArgument, + kUnsupportedVariantTypeErrorMessage, &future_data_); + } + + const SafeFutureHandle future_handle = + future_data_.future_impl.SafeAlloc(kNativeAdFnPerformClick); + Future future = MakeFuture(&future_data_.future_impl, future_handle); + + dispatch_async(dispatch_get_main_queue(), ^{ + [(GADNativeAd*)native_ad_ performClickWithData:click_payload]; + CompleteFuture(kAdErrorCodeNone, nullptr, future_handle, &future_data_); + }); + + return future; +} + +NSDictionary* NativeAdInternalIOS::variantmap_to_nsdictionary(const Variant &variant_data) { + if (!variant_data.is_map()) { + return nullptr; + } + + NSMutableDictionary *dict = [[NSMutableDictionary alloc] init]; + + for (const auto& kvp : variant_data.map()) { + const Variant& key = kvp.first; + const Variant& value = kvp.second; + + if (!key.is_string()) { + return nullptr; + } + + if (value.is_int64()) { + [dict setObject:[NSNumber numberWithLongLong:value.int64_value()] + forKey:@(key.string_value())]; + } else if (value.is_double()) { + [dict setObject:[NSNumber numberWithDouble:value.double_value()] + forKey:@(key.string_value())]; + } else if (value.is_string()) { + [dict setObject:@(value.string_value()) + forKey:@(key.string_value())]; + } else if (value.is_map()) { + NSDictionary *val_dict = variantmap_to_nsdictionary(value); + [dict setObject:val_dict + forKey:@(key.string_value())]; + } else { + // Unsupported value type. + return nullptr; + } + } + + NSDictionary *ret = [dict copy]; + return ret; +} + Future NativeAdInternalIOS::LoadAd(const char *ad_unit_id, const AdRequest &request) { firebase::MutexLock lock(mutex_); FutureCallbackData *callback_data = 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 55159005df..d002cc1723 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 @@ -17,6 +17,7 @@ package com.google.firebase.gma.internal.cpp; import android.app.Activity; +import android.os.Bundle; import com.google.android.gms.ads.AdListener; import com.google.android.gms.ads.AdLoader; import com.google.android.gms.ads.AdRequest; @@ -120,6 +121,64 @@ public void run() { }); } + /** Record Impression for allowlisted ad units. */ + public void recordImpression(final long callbackDataPtr, final Bundle payload) { + if (mActivity == null) { + return; + } + + mActivity.runOnUiThread(new Runnable() { + @Override + public void run() { + synchronized (mNativeLock) { + int errorCode; + String errorMessage; + if (mAdUnitId == null) { + errorCode = ConstantsHelper.CALLBACK_ERROR_UNINITIALIZED; + errorMessage = ConstantsHelper.CALLBACK_ERROR_MESSAGE_UNINITIALIZED; + } else if (mNative == null) { + errorCode = ConstantsHelper.CALLBACK_ERROR_LOAD_IN_PROGRESS; + errorMessage = ConstantsHelper.CALLBACK_ERROR_MESSAGE_LOAD_IN_PROGRESS; + } else { + errorCode = ConstantsHelper.CALLBACK_ERROR_NONE; + errorMessage = ConstantsHelper.CALLBACK_ERROR_MESSAGE_NONE; + mNative.recordImpression(payload); + } + completeNativeAdFutureCallback(callbackDataPtr, errorCode, errorMessage); + } + } + }); + } + + /** Perform click for allowlisted ad units. */ + public void performClick(final long callbackDataPtr, final Bundle payload) { + if (mActivity == null) { + return; + } + + mActivity.runOnUiThread(new Runnable() { + @Override + public void run() { + synchronized (mNativeLock) { + int errorCode; + String errorMessage; + if (mAdUnitId == null) { + errorCode = ConstantsHelper.CALLBACK_ERROR_UNINITIALIZED; + errorMessage = ConstantsHelper.CALLBACK_ERROR_MESSAGE_UNINITIALIZED; + } else if (mNative == null) { + errorCode = ConstantsHelper.CALLBACK_ERROR_LOAD_IN_PROGRESS; + errorMessage = ConstantsHelper.CALLBACK_ERROR_MESSAGE_LOAD_IN_PROGRESS; + } else { + errorCode = ConstantsHelper.CALLBACK_ERROR_NONE; + errorMessage = ConstantsHelper.CALLBACK_ERROR_MESSAGE_NONE; + mNative.performClick(payload); + } + completeNativeAdFutureCallback(callbackDataPtr, errorCode, errorMessage); + } + } + }); + } + /** Loads an ad for the underlying NativeAd object. */ public void loadAd(long callbackDataPtr, String adUnitId, final AdRequest request) { if (mActivity == null) { From 3873c973b0d3e490f492fed049265ec5135eed53 Mon Sep 17 00:00:00 2001 From: "nakirekommula@google.com" Date: Mon, 12 Jun 2023 15:28:20 -0700 Subject: [PATCH 2/6] Fix lint warnings. --- gma/src/ios/GADNativeAdCpp.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/gma/src/ios/GADNativeAdCpp.h b/gma/src/ios/GADNativeAdCpp.h index e6d09d646f..c1e769c725 100644 --- a/gma/src/ios/GADNativeAdCpp.h +++ b/gma/src/ios/GADNativeAdCpp.h @@ -18,7 +18,7 @@ #import #import -@interface GADNativeAd () +@interface GADNativeAd() /// Used only by allowlisted ad units. Provide a dictionary containing click data. - (void)performClickWithData:(nonnull NSDictionary *)clickData; @@ -27,4 +27,5 @@ /// if the impression is successfully recorded, otherwise returns NO. - (BOOL)recordImpressionWithData:(nonnull NSDictionary *)impressionData; -@end \ No newline at end of file +@end + From 7f24dd1353218652320bc683aceb364037359057 Mon Sep 17 00:00:00 2001 From: "nakirekommula@google.com" Date: Mon, 12 Jun 2023 16:15:12 -0700 Subject: [PATCH 3/6] Fix desktop build errors. --- gma/src/stub/native_ad_internal_stub.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/gma/src/stub/native_ad_internal_stub.h b/gma/src/stub/native_ad_internal_stub.h index 32b43bc28e..4aa7c92b7b 100644 --- a/gma/src/stub/native_ad_internal_stub.h +++ b/gma/src/stub/native_ad_internal_stub.h @@ -41,6 +41,14 @@ class NativeAdInternalStub : public NativeAdInternal { return CreateAndCompleteAdResultFutureStub(kNativeAdFnLoadAd); } + Future RecordImpression(const Variant& impression_data) override { + return CreateAndCompleteFutureStub(kNativeAdFnRecordImpression); + } + + Future PerformClick(const Variant& click_data) override { + return CreateAndCompleteFutureStub(kNativeAdFnPerformClick); + } + bool is_initialized() const override { return true; } private: From 16fa7705ac6820fbaf9a4a817c906193d12b510c Mon Sep 17 00:00:00 2001 From: "nakirekommula@google.com" Date: Tue, 13 Jun 2023 13:55:15 -0700 Subject: [PATCH 4/6] Addressing review comments. --- gma/integration_test/src/integration_test.cc | 64 +++++++++++++++----- 1 file changed, 50 insertions(+), 14 deletions(-) diff --git a/gma/integration_test/src/integration_test.cc b/gma/integration_test/src/integration_test.cc index ff75dc110d..161ed382b6 100644 --- a/gma/integration_test/src/integration_test.cc +++ b/gma/integration_test/src/integration_test.cc @@ -159,6 +159,7 @@ class FirebaseGmaTest : public FirebaseTest { protected: firebase::gma::AdRequest GetAdRequest(); + firebase::Variant GetVariantMap(); static firebase::App* shared_app_; }; @@ -285,6 +286,20 @@ firebase::gma::AdRequest FirebaseGmaTest::GetAdRequest() { return request; } +firebase::Variant FirebaseGmaTest::GetVariantMap() { + firebase::Variant in_key = firebase::Variant::FromMutableString("inner_key"); + firebase::Variant in_val = firebase::Variant::FromMutableString("inner_val"); + firebase::Variant out_key = firebase::Variant::FromMutableString("outer_key"); + + firebase::Variant out_val = firebase::Variant::EmptyMap(); + out_val.map()[in_key] = in_val; + + firebase::Variant variant_map = firebase::Variant::EmptyMap(); + variant_map.map()[out_key] = out_val; + + return variant_map; +} + FirebaseGmaUITest::FirebaseGmaUITest() {} FirebaseGmaUITest::~FirebaseGmaUITest() {} @@ -399,6 +414,8 @@ TEST_F(FirebaseGmaTest, TestSetAppKeyEnabled) { TEST_F(FirebaseGmaTest, TestGetAdRequest) { GetAdRequest(); } +TEST_F(FirebaseGmaTest, TestGetVariantMap) { GetVariantMap(); } + TEST_F(FirebaseGmaTest, TestGetAdRequestValues) { SKIP_TEST_ON_DESKTOP; @@ -2058,7 +2075,7 @@ TEST_F(FirebaseGmaTest, TestNativeAdLoadEmptyRequest) { delete native_ad; } -TEST_F(FirebaseGmaTest, TestNativeRecordImpressionAndClick) { +TEST_F(FirebaseGmaTest, TestNativeRecordImpression) { SKIP_TEST_ON_DESKTOP; SKIP_TEST_ON_SIMULATOR; @@ -2078,15 +2095,7 @@ TEST_F(FirebaseGmaTest, TestNativeRecordImpressionAndClick) { load_ad_future.Release(); - firebase::Variant in_key = firebase::Variant::FromMutableString("inner_key"); - firebase::Variant in_val = firebase::Variant::FromMutableString("inner_val"); - firebase::Variant out_key = firebase::Variant::FromMutableString("outer_key"); - - firebase::Variant out_val = firebase::Variant::EmptyMap(); - out_val.map()[in_key] = in_val; - - firebase::Variant impression_payload = firebase::Variant::EmptyMap(); - impression_payload.map()[out_key] = out_val; + firebase::Variant impression_payload = GetVariantMap(); #if defined(ANDROID) // Android doesn't have a return type for this API. @@ -2100,14 +2109,41 @@ TEST_F(FirebaseGmaTest, TestNativeRecordImpressionAndClick) { firebase::gma::kAdErrorCodeInvalidRequest); #endif - WaitForCompletion(native_ad->RecordImpression(in_key), "RecordImpression 2", + firebase::Variant str_variant = firebase::Variant::FromMutableString("test"); + WaitForCompletion(native_ad->RecordImpression(str_variant), + "RecordImpression 2", firebase::gma::kAdErrorCodeInvalidArgument); + delete native_ad; +} + +TEST_F(FirebaseGmaTest, TestNativePerformClick) { + SKIP_TEST_ON_DESKTOP; + SKIP_TEST_ON_SIMULATOR; + + firebase::gma::NativeAd* native_ad = new firebase::gma::NativeAd(); + + WaitForCompletion(native_ad->Initialize(app_framework::GetWindowContext()), + "Initialize"); + + // When the NativeAd is initialized, load an ad. + firebase::Future load_ad_future = + native_ad->LoadAd(kNativeAdUnit, GetAdRequest()); + + WaitForCompletion(load_ad_future, "LoadAd"); + const firebase::gma::AdResult* result_ptr = load_ad_future.result(); + ASSERT_NE(result_ptr, nullptr); + EXPECT_TRUE(result_ptr->is_successful()); + + load_ad_future.Release(); + + firebase::Variant click_payload = GetVariantMap(); + // Android and iOS doesn't have a return type for this API. - WaitForCompletion(native_ad->PerformClick(impression_payload), - "PerformClick"); + WaitForCompletion(native_ad->PerformClick(click_payload), "PerformClick"); - WaitForCompletion(native_ad->PerformClick(in_key), "PerformClick 2", + firebase::Variant str_variant = firebase::Variant::FromMutableString("test"); + WaitForCompletion(native_ad->PerformClick(str_variant), "PerformClick 2", firebase::gma::kAdErrorCodeInvalidArgument); delete native_ad; From 5b6fb8db6c9cc26b52aa709e87877d862e444295 Mon Sep 17 00:00:00 2001 From: "nakirekommula@google.com" Date: Fri, 23 Jun 2023 08:03:38 -0700 Subject: [PATCH 5/6] Address review comments. --- gma/src/android/native_ad_internal_android.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gma/src/android/native_ad_internal_android.cc b/gma/src/android/native_ad_internal_android.cc index b1caf92298..ede111a8ca 100644 --- a/gma/src/android/native_ad_internal_android.cc +++ b/gma/src/android/native_ad_internal_android.cc @@ -253,6 +253,8 @@ jobject NativeAdInternalAndroid::variantmap_to_bundle( env->DeleteLocalRef(val_bundle); } else { // Unsupported value type. + util::CheckAndClearJniExceptions(env); + env->DeleteLocalRef(key_str); return nullptr; } From 49b90300ac8223091f4407a24f7e37e993e13112 Mon Sep 17 00:00:00 2001 From: "nakirekommula@google.com" Date: Fri, 23 Jun 2023 08:30:47 -0700 Subject: [PATCH 6/6] Resolve review comments. --- gma/src/android/native_ad_internal_android.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gma/src/android/native_ad_internal_android.cc b/gma/src/android/native_ad_internal_android.cc index ede111a8ca..b99e1d643b 100644 --- a/gma/src/android/native_ad_internal_android.cc +++ b/gma/src/android/native_ad_internal_android.cc @@ -228,6 +228,7 @@ jobject NativeAdInternalAndroid::variantmap_to_bundle( return nullptr; } jstring key_str = env->NewStringUTF(key.string_value()); + util::CheckAndClearJniExceptions(env); if (value.is_int64()) { jlong val_long = (jlong)value.int64_value(); @@ -253,7 +254,6 @@ jobject NativeAdInternalAndroid::variantmap_to_bundle( env->DeleteLocalRef(val_bundle); } else { // Unsupported value type. - util::CheckAndClearJniExceptions(env); env->DeleteLocalRef(key_str); return nullptr; }