Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions app/src/util_android.h
Original file line number Diff line number Diff line change
Expand Up @@ -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, "<init>", "()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, "<init>", "()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)

Expand Down Expand Up @@ -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
Expand Down
97 changes: 97 additions & 0 deletions gma/integration_test/src/integration_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ class FirebaseGmaTest : public FirebaseTest {

protected:
firebase::gma::AdRequest GetAdRequest();
firebase::Variant GetVariantMap();

static firebase::App* shared_app_;
};
Expand Down Expand Up @@ -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() {}
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -2058,6 +2075,80 @@ TEST_F(FirebaseGmaTest, TestNativeAdLoadEmptyRequest) {
delete native_ad;
}

TEST_F(FirebaseGmaTest, TestNativeRecordImpression) {
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<firebase::gma::AdResult> 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 impression_payload = GetVariantMap();

#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

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<firebase::gma::AdResult> 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(click_payload), "PerformClick");

firebase::Variant str_variant = firebase::Variant::FromMutableString("test");
WaitForCompletion(native_ad->PerformClick(str_variant), "PerformClick 2",
firebase::gma::kAdErrorCodeInvalidArgument);

delete native_ad;
}

TEST_F(FirebaseGmaTest, TestNativeAdErrorNotInitialized) {
SKIP_TEST_ON_DESKTOP;

Expand All @@ -2067,6 +2158,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;
}

Expand Down
127 changes: 127 additions & 0 deletions gma/src/android/native_ad_internal_android.cc
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,133 @@ Future<AdResult> NativeAdInternalAndroid::LoadAd(const char* ad_unit_id,
return future;
}

Future<void> 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<void>* callback_data =
CreateVoidFutureCallbackData(kNativeAdFnRecordImpression, &future_data_);
Future<void> future =
MakeFuture(&future_data_.future_impl, callback_data->future_handle);
env->CallVoidMethod(
helper_,
native_ad_helper::GetMethodId(native_ad_helper::kRecordImpression),
reinterpret_cast<jlong>(callback_data), impression_bundle);

util::CheckAndClearJniExceptions(env);

return future;
}

Future<void> 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<void>* callback_data =
CreateVoidFutureCallbackData(kNativeAdFnPerformClick, &future_data_);
Future<void> future =
MakeFuture(&future_data_.future_impl, callback_data->future_handle);
env->CallVoidMethod(
helper_, native_ad_helper::GetMethodId(native_ad_helper::kPerformClick),
reinterpret_cast<jlong>(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());
util::CheckAndClearJniExceptions(env);

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.
env->DeleteLocalRef(key_str);
return nullptr;
}

util::CheckAndClearJniExceptions(env);
env->DeleteLocalRef(key_str);
}

return variant_bundle;
}

} // namespace internal
} // namespace gma
} // namespace firebase
13 changes: 9 additions & 4 deletions gma/src/android/native_ad_internal_android.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,12 @@ namespace gma {
// time spent looking up methods by string.
// clang-format off
#define NATIVEADHELPER_METHODS(X) \
X(Constructor, "<init>", "(J)V"), \
X(Initialize, "initialize", "(JLandroid/app/Activity;)V"), \
X(LoadAd, "loadAd", \
"(JLjava/lang/String;Lcom/google/android/gms/ads/AdRequest;)V"), \
X(Constructor, "<init>", "(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

Expand All @@ -48,6 +50,9 @@ class NativeAdInternalAndroid : public NativeAdInternal {
Future<AdResult> LoadAd(const char* ad_unit_id,
const AdRequest& request) override;
bool is_initialized() const override { return initialized_; }
Future<void> RecordImpression(const Variant& impression_data) override;
Future<void> 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
Expand Down
3 changes: 3 additions & 0 deletions gma/src/common/gma_common.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
2 changes: 2 additions & 0 deletions gma/src/common/gma_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
31 changes: 31 additions & 0 deletions gma/src/common/native_ad.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -84,5 +85,35 @@ const std::vector<NativeAdImage>& NativeAd::images() const {
return internal_->images();
}

Future<void> 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<void> NativeAd::RecordImpressionLastResult() const {
return internal_->GetLastResult(
firebase::gma::internal::kNativeAdFnRecordImpression);
}

Future<void> 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<void> NativeAd::PerformClickLastResult() const {
return internal_->GetLastResult(
firebase::gma::internal::kNativeAdFnPerformClick);
}

} // namespace gma
} // namespace firebase
Loading