diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 22718a7d51..4aefc3a36c 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -546,6 +546,7 @@ if (IOS) ${FIREBASE_SOURCE_DIR}/gma/src/include/firebase/gma/ad_view.h ${FIREBASE_SOURCE_DIR}/gma/src/include/firebase/gma/interstitial_ad.h ${FIREBASE_SOURCE_DIR}/gma/src/include/firebase/gma/internal/native_ad.h + ${FIREBASE_SOURCE_DIR}/gma/src/include/firebase/gma/internal/query_info.h ${FIREBASE_SOURCE_DIR}/gma/src/include/firebase/gma/rewarded_ad.h ${FIREBASE_SOURCE_DIR}/gma/src/include/firebase/gma/types.h ${FIREBASE_SOURCE_DIR}/gma/src/include/firebase/gma/ump.h diff --git a/gma/CMakeLists.txt b/gma/CMakeLists.txt index a58354f7f9..2b0b225740 100644 --- a/gma/CMakeLists.txt +++ b/gma/CMakeLists.txt @@ -26,6 +26,8 @@ set(common_SRCS src/common/full_screen_ad_event_listener.cc src/common/native_ad.cc src/common/native_ad_internal.cc + src/common/query_info.cc + src/common/query_info_internal.cc src/common/rewarded_ad.cc src/common/rewarded_ad_internal.cc) @@ -49,6 +51,7 @@ set(android_SRCS src/android/interstitial_ad_internal_android.cc src/android/native_ad_image_android.cc src/android/native_ad_internal_android.cc + src/android/query_info_internal_android.cc src/android/response_info_android.cc src/android/rewarded_ad_internal_android.cc) @@ -68,6 +71,7 @@ set(ios_SRCS src/ios/interstitial_ad_internal_ios.mm src/ios/native_ad_image_ios.mm src/ios/native_ad_internal_ios.mm + src/ios/query_info_internal_ios.mm src/ios/response_info_ios.mm src/ios/rewarded_ad_internal_ios.mm) diff --git a/gma/integration_test/src/integration_test.cc b/gma/integration_test/src/integration_test.cc index 025a521969..e26beb6ff3 100644 --- a/gma/integration_test/src/integration_test.cc +++ b/gma/integration_test/src/integration_test.cc @@ -993,6 +993,72 @@ TEST_F(FirebaseGmaTest, TestNativeAdLoad) { delete native_ad; } +TEST_F(FirebaseGmaTest, TestCreateQueryInfo) { + SKIP_TEST_ON_DESKTOP; + SKIP_TEST_ON_SIMULATOR; + + firebase::gma::QueryInfo* query_info = new firebase::gma::QueryInfo(); + + WaitForCompletion(query_info->Initialize(app_framework::GetWindowContext()), + "Initialize"); + + firebase::gma::AdRequest request = GetAdRequest(); + // Set the requester type to 8. QueryInfo gets generated without a + // query_info_type set, but throws a warning that it is missing. + request.add_extra(kAdNetworkExtrasClassName, "query_info_type", + "requester_type_8"); + // When the QueryInfo is initialized, generate a query info string. + firebase::Future create_query_info_future = + query_info->CreateQueryInfo(firebase::gma::kAdFormatNative, request); + + WaitForCompletion(create_query_info_future, "CreateQueryInfo"); + + if (create_query_info_future.error() == firebase::gma::kAdErrorCodeNone) { + const firebase::gma::QueryInfoResult* result_ptr = + create_query_info_future.result(); + ASSERT_NE(result_ptr, nullptr); + EXPECT_TRUE(result_ptr->is_successful()); + EXPECT_FALSE(result_ptr->query_info().empty()); + } + + create_query_info_future.Release(); + delete query_info; +} + +TEST_F(FirebaseGmaTest, TestCreateQueryInfoWithAdUnit) { + SKIP_TEST_ON_DESKTOP; + SKIP_TEST_ON_SIMULATOR; + + firebase::gma::QueryInfo* query_info = new firebase::gma::QueryInfo(); + + WaitForCompletion(query_info->Initialize(app_framework::GetWindowContext()), + "Initialize"); + + firebase::gma::AdRequest request = GetAdRequest(); + // Set the requester type to 8. QueryInfo gets generated without a + // query_info_type set, but throws a warning that it is missing. + request.add_extra(kAdNetworkExtrasClassName, "query_info_type", + "requester_type_8"); + // When the QueryInfo is initialized, generate a query info string. + // Providing a bad/empty ad unit does not affect the query info generation. + firebase::Future create_query_info_future = + query_info->CreateQueryInfoWithAdUnit(firebase::gma::kAdFormatNative, + request, kNativeAdUnit); + + WaitForCompletion(create_query_info_future, "CreateQueryInfoWithAdUnit"); + + if (create_query_info_future.error() == firebase::gma::kAdErrorCodeNone) { + const firebase::gma::QueryInfoResult* result_ptr = + create_query_info_future.result(); + ASSERT_NE(result_ptr, nullptr); + EXPECT_TRUE(result_ptr->is_successful()); + EXPECT_FALSE(result_ptr->query_info().empty()); + } + + create_query_info_future.Release(); + delete query_info; +} + // Interactive test section. These have been placed up front so that the // tester doesn't get bored waiting for them. TEST_F(FirebaseGmaUITest, TestAdViewAdOpenedAdClosed) { diff --git a/gma/src/android/gma_android.cc b/gma/src/android/gma_android.cc index 62a53575e9..9fc2400892 100644 --- a/gma/src/android/gma_android.cc +++ b/gma/src/android/gma_android.cc @@ -40,6 +40,7 @@ #include "gma/src/android/interstitial_ad_internal_android.h" #include "gma/src/android/native_ad_image_android.h" #include "gma/src/android/native_ad_internal_android.h" +#include "gma/src/android/query_info_internal_android.h" #include "gma/src/android/response_info_android.h" #include "gma/src/android/rewarded_ad_internal_android.h" #include "gma/src/common/gma_common.h" @@ -363,6 +364,9 @@ Future Initialize(JNIEnv* env, jobject activity, download_helper::CacheClassFromFiles(env, activity, &embedded_files) != nullptr && download_helper::CacheMethodIds(env, activity) && + query_info_helper::CacheClassFromFiles(env, activity, + &embedded_files) != nullptr && + query_info_helper::CacheMethodIds(env, activity) && rewarded_ad_helper::CacheClassFromFiles(env, activity, &embedded_files) != nullptr && rewarded_ad_helper::CacheMethodIds(env, activity) && @@ -705,6 +709,7 @@ void ReleaseClasses(JNIEnv* env) { native_ad_helper::ReleaseClass(env); native_image::ReleaseClass(env); download_helper::ReleaseClass(env); + query_info_helper::ReleaseClass(env); rewarded_ad_helper::ReleaseClass(env); load_ad_error::ReleaseClass(env); } @@ -917,6 +922,35 @@ void JNI_completeLoadedAd(JNIEnv* env, jclass clazz, jlong data_ptr, env->DeleteLocalRef(j_response_info); } +void JNI_completeCreateQueryInfoSuccess(JNIEnv* env, jclass clazz, + jlong data_ptr, jstring j_query_info) { + FIREBASE_ASSERT(env); + FIREBASE_ASSERT(data_ptr); + FIREBASE_ASSERT(j_query_info); + + std::string query_info = util::JStringToString(env, j_query_info); + FutureCallbackData* callback_data = + reinterpret_cast*>(data_ptr); + GmaInternal::CompleteCreateQueryInfoFutureSuccess(callback_data, query_info); + env->DeleteLocalRef(j_query_info); +} + +void JNI_completeQueryInfoError(JNIEnv* env, jclass clazz, jlong data_ptr, + jint j_error_code, jstring j_error_message) { + FIREBASE_ASSERT(env); + FIREBASE_ASSERT(data_ptr); + FIREBASE_ASSERT(j_error_message); + + // QueryInfo errors return only internal AdErrorCode values. + const AdErrorCode error_code = static_cast(j_error_code); + std::string error_message = util::JStringToString(env, j_error_message); + + FutureCallbackData* callback_data = + reinterpret_cast*>(data_ptr); + GmaInternal::CompleteCreateQueryInfoFutureFailure(callback_data, error_code, + error_message); +} + void JNI_NativeAd_completeLoadedAd(JNIEnv* env, jclass clazz, jlong data_ptr, jlong native_internal_data_ptr, jobject j_icon, jobjectArray j_images, @@ -1307,6 +1341,15 @@ bool RegisterNatives() { reinterpret_cast(&JNI_NativeAd_notifyAdOpened)}, }; + static const JNINativeMethod kQueryInfoMethods[] = { + {"completeQueryInfoFutureCallback", "(JILjava/lang/String;)V", + reinterpret_cast(&JNI_completeAdFutureCallback)}, + {"completeCreateQueryInfoSuccess", "(JLjava/lang/String;)V", + reinterpret_cast(&JNI_completeCreateQueryInfoSuccess)}, + {"completeCreateQueryInfoError", "(JILjava/lang/String;)V", + reinterpret_cast(&JNI_completeQueryInfoError)}, + }; + static const JNINativeMethod kNativeImageMethods[] = { {"completeNativeImageFutureCallback", "(JILjava/lang/String;)V", reinterpret_cast(&JNI_completeAdFutureCallback)}, @@ -1370,6 +1413,8 @@ bool RegisterNatives() { download_helper::RegisterNatives( env, kNativeImageMethods, FIREBASE_ARRAYSIZE(kNativeImageMethods)) && + query_info_helper::RegisterNatives( + env, kQueryInfoMethods, FIREBASE_ARRAYSIZE(kQueryInfoMethods)) && rewarded_ad_helper::RegisterNatives( env, kRewardedAdMethods, FIREBASE_ARRAYSIZE(kRewardedAdMethods)) && gma_initialization_helper::RegisterNatives( diff --git a/gma/src/android/query_info_internal_android.cc b/gma/src/android/query_info_internal_android.cc new file mode 100644 index 0000000000..6321021adb --- /dev/null +++ b/gma/src/android/query_info_internal_android.cc @@ -0,0 +1,192 @@ +/* + * Copyright 2024 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. + */ + +#include "gma/src/android/query_info_internal_android.h" + +#include +#include + +#include +#include + +#include "app/src/assert.h" +#include "app/src/util_android.h" +#include "gma/src/android/ad_request_converter.h" +#include "gma/src/android/gma_android.h" +#include "gma/src/common/gma_common.h" +#include "gma/src/include/firebase/gma.h" +#include "gma/src/include/firebase/gma/internal/query_info.h" +#include "gma/src/include/firebase/gma/types.h" + +namespace firebase { +namespace gma { + +METHOD_LOOKUP_DEFINITION(query_info_helper, + "com/google/firebase/gma/internal/cpp/QueryInfoHelper", + QUERYINFOHELPER_METHODS); + +namespace internal { + +QueryInfoInternalAndroid::QueryInfoInternalAndroid(QueryInfo* base) + : QueryInfoInternal(base), helper_(nullptr), initialized_(false) { + firebase::MutexLock lock(mutex_); + JNIEnv* env = ::firebase::gma::GetJNI(); + jobject helper_ref = env->NewObject( + query_info_helper::GetClass(), + query_info_helper::GetMethodId(query_info_helper::kConstructor), + reinterpret_cast(this)); + util::CheckAndClearJniExceptions(env); + + FIREBASE_ASSERT(helper_ref); + helper_ = env->NewGlobalRef(helper_ref); + FIREBASE_ASSERT(helper_); + env->DeleteLocalRef(helper_ref); +} + +QueryInfoInternalAndroid::~QueryInfoInternalAndroid() { + firebase::MutexLock lock(mutex_); + JNIEnv* env = ::firebase::gma::GetJNI(); + + env->CallVoidMethod( + helper_, query_info_helper::GetMethodId(query_info_helper::kDisconnect)); + util::CheckAndClearJniExceptions(env); + env->DeleteGlobalRef(helper_); + helper_ = nullptr; +} + +Future QueryInfoInternalAndroid::Initialize(AdParent parent) { + firebase::MutexLock lock(mutex_); + + if (initialized_) { + const SafeFutureHandle future_handle = + future_data_.future_impl.SafeAlloc(kQueryInfoFnInitialize); + Future future = MakeFuture(&future_data_.future_impl, future_handle); + CompleteFuture(kAdErrorCodeAlreadyInitialized, + kAdAlreadyInitializedErrorMessage, future_handle, + &future_data_); + return future; + } + + initialized_ = true; + JNIEnv* env = ::firebase::gma::GetJNI(); + FIREBASE_ASSERT(env); + + FutureCallbackData* callback_data = + CreateVoidFutureCallbackData(kQueryInfoFnInitialize, &future_data_); + Future future = + MakeFuture(&future_data_.future_impl, callback_data->future_handle); + env->CallVoidMethod( + helper_, query_info_helper::GetMethodId(query_info_helper::kInitialize), + reinterpret_cast(callback_data), parent); + util::CheckAndClearJniExceptions(env); + return future; +} + +Future QueryInfoInternalAndroid::CreateQueryInfo( + AdFormat format, const AdRequest& request) { + firebase::MutexLock lock(mutex_); + + if (!initialized_) { + SafeFutureHandle future_handle = + CreateFuture(kQueryInfoFnCreateQueryInfo, + &future_data_); + Future future = + MakeFuture(&future_data_.future_impl, future_handle); + CompleteFuture(kAdErrorCodeUninitialized, kAdUninitializedErrorMessage, + future_handle, &future_data_, QueryInfoResult()); + return future; + } + + gma::AdErrorCode error = kAdErrorCodeNone; + jobject j_request = GetJavaAdRequestFromCPPAdRequest(request, &error); + if (j_request == nullptr) { + if (error == kAdErrorCodeNone) { + error = kAdErrorCodeInternalError; + } + return CreateAndCompleteFutureWithQueryInfoResult( + kQueryInfoFnCreateQueryInfo, error, + kAdCouldNotParseAdRequestErrorMessage, &future_data_, + QueryInfoResult()); + } + JNIEnv* env = GetJNI(); + FIREBASE_ASSERT(env); + FutureCallbackData* callback_data = + CreateQueryInfoResultFutureCallbackData(kQueryInfoFnCreateQueryInfo, + &future_data_); + Future future = + MakeFuture(&future_data_.future_impl, callback_data->future_handle); + + jstring j_ad_unit_str = env->NewStringUTF(""); + ::firebase::gma::GetJNI()->CallVoidMethod( + helper_, + query_info_helper::GetMethodId(query_info_helper::kCreateQueryInfo), + reinterpret_cast(callback_data), static_cast(format), + j_ad_unit_str, j_request); + util::CheckAndClearJniExceptions(env); + env->DeleteLocalRef(j_ad_unit_str); + env->DeleteLocalRef(j_request); + return future; +} + +Future QueryInfoInternalAndroid::CreateQueryInfoWithAdUnit( + AdFormat format, const AdRequest& request, const char* ad_unit_id) { + firebase::MutexLock lock(mutex_); + + if (!initialized_) { + SafeFutureHandle future_handle = + CreateFuture(kQueryInfoFnCreateQueryInfoWithAdUnit, + &future_data_); + Future future = + MakeFuture(&future_data_.future_impl, future_handle); + CompleteFuture(kAdErrorCodeUninitialized, kAdUninitializedErrorMessage, + future_handle, &future_data_, QueryInfoResult()); + return future; + } + + gma::AdErrorCode error = kAdErrorCodeNone; + jobject j_request = GetJavaAdRequestFromCPPAdRequest(request, &error); + if (j_request == nullptr) { + if (error == kAdErrorCodeNone) { + error = kAdErrorCodeInternalError; + } + return CreateAndCompleteFutureWithQueryInfoResult( + kQueryInfoFnCreateQueryInfoWithAdUnit, error, + kAdCouldNotParseAdRequestErrorMessage, &future_data_, + QueryInfoResult()); + } + JNIEnv* env = GetJNI(); + FIREBASE_ASSERT(env); + FutureCallbackData* callback_data = + CreateQueryInfoResultFutureCallbackData( + kQueryInfoFnCreateQueryInfoWithAdUnit, &future_data_); + Future future = + MakeFuture(&future_data_.future_impl, callback_data->future_handle); + + jstring j_ad_unit_str = env->NewStringUTF(ad_unit_id); + ::firebase::gma::GetJNI()->CallVoidMethod( + helper_, + query_info_helper::GetMethodId(query_info_helper::kCreateQueryInfo), + reinterpret_cast(callback_data), static_cast(format), + j_ad_unit_str, j_request); + util::CheckAndClearJniExceptions(env); + env->DeleteLocalRef(j_ad_unit_str); + env->DeleteLocalRef(j_request); + return future; +} + +} // namespace internal +} // namespace gma +} // namespace firebase diff --git a/gma/src/android/query_info_internal_android.h b/gma/src/android/query_info_internal_android.h new file mode 100644 index 0000000000..e3ee51a6c9 --- /dev/null +++ b/gma/src/android/query_info_internal_android.h @@ -0,0 +1,70 @@ +/* + * Copyright 2024 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. + */ + +#ifndef FIREBASE_GMA_SRC_ANDROID_QUERY_INFO_INTERNAL_ANDROID_H_ +#define FIREBASE_GMA_SRC_ANDROID_QUERY_INFO_INTERNAL_ANDROID_H_ + +#include "app/src/include/firebase/internal/mutex.h" +#include "app/src/util_android.h" +#include "gma/src/common/query_info_internal.h" + +namespace firebase { +namespace gma { + +// Used to set up the cache of QueryInfoHelper class method IDs to reduce +// time spent looking up methods by string. +// clang-format off +#define QUERYINFOHELPER_METHODS(X) \ + X(Constructor, "", "(J)V"), \ + X(Initialize, "initialize", "(JLandroid/app/Activity;)V"), \ + X(CreateQueryInfo, "createQueryInfo", \ + "(JILjava/lang/String;Lcom/google/android/gms/ads/AdRequest;)V"), \ + X(Disconnect, "disconnect", "()V") +// clang-format on + +METHOD_LOOKUP_DECLARATION(query_info_helper, QUERYINFOHELPER_METHODS); + +namespace internal { +class QueryInfoInternalAndroid : public QueryInfoInternal { + public: + explicit QueryInfoInternalAndroid(QueryInfo* base); + ~QueryInfoInternalAndroid() override; + + Future Initialize(AdParent parent) override; + Future CreateQueryInfo(AdFormat format, + const AdRequest& request) override; + Future CreateQueryInfoWithAdUnit( + AdFormat format, const AdRequest& request, + const char* ad_unit_id) override; + bool is_initialized() const override { return initialized_; } + + private: + // Reference to the Java helper object used to interact with the Mobile Ads + // SDK. + jobject helper_; + + // Tracks if this QueryInfo has been initialized. + bool initialized_; + + // Mutex to guard against concurrent operations; + Mutex mutex_; +}; + +} // namespace internal +} // namespace gma +} // namespace firebase + +#endif // FIREBASE_GMA_SRC_ANDROID_QUERY_INFO_INTERNAL_ANDROID_H_ diff --git a/gma/src/common/gma_common.cc b/gma/src/common/gma_common.cc index 891086de17..777d099030 100644 --- a/gma/src/common/gma_common.cc +++ b/gma/src/common/gma_common.cc @@ -29,6 +29,7 @@ #include "gma/src/include/firebase/gma.h" #include "gma/src/include/firebase/gma/ad_view.h" #include "gma/src/include/firebase/gma/internal/native_ad.h" +#include "gma/src/include/firebase/gma/internal/query_info.h" #include "gma/src/include/firebase/gma/interstitial_ad.h" #include "gma/src/include/firebase/gma/rewarded_ad.h" #include "gma/src/include/firebase/gma/types.h" @@ -111,6 +112,25 @@ void GmaInternal::CompleteLoadImageFutureFailure( delete callback_data; } +void GmaInternal::CompleteCreateQueryInfoFutureSuccess( + FutureCallbackData* callback_data, + const std::string& query_info_data) { + callback_data->future_data->future_impl.CompleteWithResult( + callback_data->future_handle, static_cast(kAdErrorCodeNone), "", + QueryInfoResult(query_info_data)); + delete callback_data; +} + +void GmaInternal::CompleteCreateQueryInfoFutureFailure( + FutureCallbackData* callback_data, int error_code, + const std::string& error_message) { + callback_data->future_data->future_impl.CompleteWithResult( + callback_data->future_handle, static_cast(error_code), + error_message.c_str(), QueryInfoResult()); + // This method is responsible for disposing of the callback data struct. + delete callback_data; +} + AdError GmaInternal::CreateAdError(const AdErrorInternal& ad_error_internal) { return AdError(ad_error_internal); } @@ -160,6 +180,15 @@ const std::vector& ImageResult::image() const { return image_info_; } +// QueryInfoResult +QueryInfoResult::QueryInfoResult() : is_successful_(false) {} +QueryInfoResult::QueryInfoResult(const std::string& query_info) + : is_successful_(true), query_info_(query_info) {} + +QueryInfoResult::~QueryInfoResult() {} +bool QueryInfoResult::is_successful() const { return is_successful_; } +const std::string& QueryInfoResult::query_info() const { return query_info_; } + // AdSize // Hardcoded values are from publicly available documentation: // https://developers.google.com/android/reference/com/google/android/gms/ads/AdSize @@ -367,6 +396,15 @@ Future CreateAndCompleteFutureWithImageResult( return MakeFuture(&future_data->future_impl, handle); } +Future CreateAndCompleteFutureWithQueryInfoResult( + int fn_idx, int error, const char* error_msg, FutureData* future_data, + const QueryInfoResult& result) { + SafeFutureHandle handle = + CreateFuture(fn_idx, future_data); + CompleteFuture(error, error_msg, handle, future_data, result); + return MakeFuture(&future_data->future_impl, handle); +} + FutureCallbackData* CreateVoidFutureCallbackData( int fn_idx, FutureData* future_data) { return new FutureCallbackData{ @@ -387,5 +425,12 @@ FutureCallbackData* CreateImageResultFutureCallbackData( future_data->future_impl.SafeAlloc(fn_idx, ImageResult())}; } +FutureCallbackData* CreateQueryInfoResultFutureCallbackData( + int fn_idx, FutureData* future_data) { + return new FutureCallbackData{ + future_data, future_data->future_impl.SafeAlloc( + fn_idx, QueryInfoResult())}; +} + } // namespace gma } // namespace firebase diff --git a/gma/src/common/gma_common.h b/gma/src/common/gma_common.h index ad1343ed41..8d2619152d 100644 --- a/gma/src/common/gma_common.h +++ b/gma/src/common/gma_common.h @@ -27,6 +27,7 @@ #include "app/src/cleanup_notifier.h" #include "app/src/reference_counted_future_impl.h" #include "gma/src/include/firebase/gma/internal/native_ad.h" +#include "gma/src/include/firebase/gma/internal/query_info.h" #include "gma/src/include/firebase/gma/types.h" namespace firebase { @@ -109,6 +110,12 @@ Future CreateAndCompleteFutureWithImageResult( int fn_idx, int error, const char* error_msg, FutureData* future_data, const ImageResult& result); +// For calls that aren't asynchronous, create and complete a future with a +// result at the same time. +Future CreateAndCompleteFutureWithQueryInfoResult( + int fn_idx, int error, const char* error_msg, FutureData* future_data, + const QueryInfoResult& result); + template struct FutureCallbackData { FutureData* future_data; @@ -120,16 +127,21 @@ struct FutureCallbackData { FutureCallbackData* CreateVoidFutureCallbackData(int fn_idx, FutureData* future_data); -// Constructs a FutureCallbackData instance to handle results from LoadAd. +// Constructs a FutureCallbackData instance to handle results from LoadAd // requests. FutureCallbackData* CreateAdResultFutureCallbackData( int fn_idx, FutureData* future_data); -// Constructs a FutureCallbackData instance to handle results from LoadImage. +// Constructs a FutureCallbackData instance to handle results from LoadImage // requests. FutureCallbackData* CreateImageResultFutureCallbackData( int fn_idx, FutureData* future_data); +// Constructs a FutureCallbackData instance to handle results from +// createQueryInfo requests. +FutureCallbackData* CreateQueryInfoResultFutureCallbackData( + int fn_idx, FutureData* future_data); + // Template function implementations. template SafeFutureHandle CreateFuture(int fn_idx, FutureData* future_data) { @@ -167,6 +179,16 @@ class GmaInternal { FutureCallbackData* callback_data, int error_code, const std::string& error_message); + // Completes an QueryInfoResult future with a successful result. + static void CompleteCreateQueryInfoFutureSuccess( + FutureCallbackData* callback_data, + const std::string& query_info_data); + + // Completes an QueryInfoResult future as an error. + static void CompleteCreateQueryInfoFutureFailure( + FutureCallbackData* callback_data, int error_code, + const std::string& error_message); + // Constructs and returns an AdError object given an AdErrorInternal object. static AdError CreateAdError(const AdErrorInternal& ad_error_internal); diff --git a/gma/src/common/query_info.cc b/gma/src/common/query_info.cc new file mode 100644 index 0000000000..e5ce519c3f --- /dev/null +++ b/gma/src/common/query_info.cc @@ -0,0 +1,109 @@ +/* + * Copyright 2024 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. + */ + +#include "gma/src/include/firebase/gma/internal/query_info.h" + +#include "app/src/assert.h" +#include "app/src/include/firebase/future.h" +#include "gma/src/common/gma_common.h" +#include "gma/src/common/query_info_internal.h" +#include "gma/src/include/firebase/gma/types.h" + +namespace firebase { +namespace gma { + +QueryInfo::QueryInfo() { + FIREBASE_ASSERT(gma::IsInitialized()); + internal_ = internal::QueryInfoInternal::CreateInstance(this); + + GetOrCreateCleanupNotifier()->RegisterObject(this, [](void* object) { + LogWarning("QueryInfo must be deleted before gma::Terminate."); + QueryInfo* query_info = reinterpret_cast(object); + delete query_info->internal_; + query_info->internal_ = nullptr; + }); +} + +QueryInfo::~QueryInfo() { + FIREBASE_ASSERT(internal_); + + GetOrCreateCleanupNotifier()->UnregisterObject(this); + delete internal_; +} + +// 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(internal::QueryInfoInternal* internal) { + FIREBASE_ASSERT(internal); + return internal->is_initialized(); +} + +Future QueryInfo::Initialize(AdParent parent) { + return internal_->Initialize(parent); +} + +Future QueryInfo::InitializeLastResult() const { + return internal_->GetInitializeLastResult(); +} + +Future QueryInfo::CreateQueryInfo(AdFormat format, + const AdRequest& request) { + if (!CheckIsInitialized(internal_)) { + return CreateAndCompleteFutureWithQueryInfoResult( + firebase::gma::internal::kQueryInfoFnCreateQueryInfo, + kAdErrorCodeUninitialized, kAdUninitializedErrorMessage, + &internal_->future_data_, QueryInfoResult()); + } + + return internal_->CreateQueryInfo(format, request); +} + +Future QueryInfo::CreateQueryInfoLastResult() const { + if (!CheckIsInitialized(internal_)) { + return CreateAndCompleteFutureWithQueryInfoResult( + firebase::gma::internal::kQueryInfoFnCreateQueryInfo, + kAdErrorCodeUninitialized, kAdUninitializedErrorMessage, + &internal_->future_data_, QueryInfoResult()); + } + return internal_->CreateQueryInfoLastResult( + internal::kQueryInfoFnCreateQueryInfo); +} + +Future QueryInfo::CreateQueryInfoWithAdUnit( + AdFormat format, const AdRequest& request, const char* ad_unit_id) { + if (!CheckIsInitialized(internal_)) { + return CreateAndCompleteFutureWithQueryInfoResult( + firebase::gma::internal::kQueryInfoFnCreateQueryInfoWithAdUnit, + kAdErrorCodeUninitialized, kAdUninitializedErrorMessage, + &internal_->future_data_, QueryInfoResult()); + } + + return internal_->CreateQueryInfoWithAdUnit(format, request, ad_unit_id); +} + +Future QueryInfo::CreateQueryInfoWithAdUnitLastResult() const { + if (!CheckIsInitialized(internal_)) { + return CreateAndCompleteFutureWithQueryInfoResult( + firebase::gma::internal::kQueryInfoFnCreateQueryInfoWithAdUnit, + kAdErrorCodeUninitialized, kAdUninitializedErrorMessage, + &internal_->future_data_, QueryInfoResult()); + } + return internal_->CreateQueryInfoLastResult( + internal::kQueryInfoFnCreateQueryInfoWithAdUnit); +} + +} // namespace gma +} // namespace firebase diff --git a/gma/src/common/query_info_internal.cc b/gma/src/common/query_info_internal.cc new file mode 100644 index 0000000000..ef5d0fa6ec --- /dev/null +++ b/gma/src/common/query_info_internal.cc @@ -0,0 +1,68 @@ +/* + * Copyright 2024 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. + */ + +#include "gma/src/common/query_info_internal.h" + +#include + +#include "app/src/include/firebase/future.h" +#include "app/src/include/firebase/internal/mutex.h" +#include "app/src/include/firebase/internal/platform.h" +#include "app/src/reference_counted_future_impl.h" +#include "gma/src/include/firebase/gma/internal/query_info.h" +#include "gma/src/include/firebase/gma/types.h" + +#if FIREBASE_PLATFORM_ANDROID +#include "gma/src/android/query_info_internal_android.h" +#elif FIREBASE_PLATFORM_IOS || FIREBASE_PLATFORM_TVOS +#include "gma/src/ios/query_info_internal_ios.h" +#else +#include "gma/src/stub/query_info_internal_stub.h" +#endif // FIREBASE_PLATFORM_ANDROID, FIREBASE_PLATFORM_IOS, + // FIREBASE_PLATFORM_TVOS + +namespace firebase { +namespace gma { +namespace internal { + +QueryInfoInternal::QueryInfoInternal(QueryInfo* base) + : base_(base), future_data_(kQueryInfoFnCount) {} + +QueryInfoInternal* QueryInfoInternal::CreateInstance(QueryInfo* base) { +#if FIREBASE_PLATFORM_ANDROID + return new QueryInfoInternalAndroid(base); +#elif FIREBASE_PLATFORM_IOS || FIREBASE_PLATFORM_TVOS + return new QueryInfoInternalIOS(base); +#else + return new QueryInfoInternalStub(base); +#endif // FIREBASE_PLATFORM_ANDROID, FIREBASE_PLATFORM_IOS, + // FIREBASE_PLATFORM_TVOS +} + +Future QueryInfoInternal::GetInitializeLastResult() { + return static_cast&>( + future_data_.future_impl.LastResult(kQueryInfoFnInitialize)); +} + +Future QueryInfoInternal::CreateQueryInfoLastResult( + QueryInfoFn fn) { + return static_cast&>( + future_data_.future_impl.LastResult(fn)); +} + +} // namespace internal +} // namespace gma +} // namespace firebase diff --git a/gma/src/common/query_info_internal.h b/gma/src/common/query_info_internal.h new file mode 100644 index 0000000000..828be152cc --- /dev/null +++ b/gma/src/common/query_info_internal.h @@ -0,0 +1,87 @@ +/* + * Copyright 2024 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. + */ + +#ifndef FIREBASE_GMA_SRC_COMMON_QUERY_INFO_INTERNAL_H_ +#define FIREBASE_GMA_SRC_COMMON_QUERY_INFO_INTERNAL_H_ + +#include + +#include "app/src/include/firebase/future.h" +#include "app/src/include/firebase/internal/mutex.h" +#include "gma/src/common/gma_common.h" +#include "gma/src/include/firebase/gma/internal/query_info.h" + +namespace firebase { +namespace gma { +namespace internal { + +// Constants representing each NativeAd function that returns a Future. +enum QueryInfoFn { + kQueryInfoFnInitialize, + kQueryInfoFnCreateQueryInfo, + kQueryInfoFnCreateQueryInfoWithAdUnit, + kQueryInfoFnCount +}; + +class QueryInfoInternal { + public: + // Create an instance of whichever subclass of QueryInfoInternal is + // appropriate for the current platform. + static QueryInfoInternal* CreateInstance(QueryInfo* base); + + // Virtual destructor is required. + virtual ~QueryInfoInternal() = default; + + // Initializes this object and any platform-specific helpers that it uses. + virtual Future Initialize(AdParent parent) = 0; + + // Retrieves the most recent Future for Initialize(). + Future GetInitializeLastResult(); + + // Initiates queryInfo creation for the given ad format and request. + virtual Future CreateQueryInfo(AdFormat format, + const AdRequest& request) = 0; + + // Initiates queryInfo creation for the given ad format, request and ad unit. + virtual Future CreateQueryInfoWithAdUnit( + AdFormat format, const AdRequest& request, const char* ad_unit_id) = 0; + + // Retrieves the most recent QueryInfo future for the given create function. + Future CreateQueryInfoLastResult(QueryInfoFn fn); + + // Returns true if the QueryInfo has been initialized. + virtual bool is_initialized() const = 0; + + protected: + friend class firebase::gma::QueryInfo; + friend class firebase::gma::GmaInternal; + + // Used by CreateInstance() to create an appropriate one for the current + // platform. + explicit QueryInfoInternal(QueryInfo* base); + + // A pointer back to the QueryInfo class that created us. + QueryInfo* base_; + + // Future data used to synchronize asynchronous calls. + FutureData future_data_; +}; + +} // namespace internal +} // namespace gma +} // namespace firebase + +#endif // FIREBASE_GMA_SRC_COMMON_QUERY_INFO_INTERNAL_H_ diff --git a/gma/src/include/firebase/gma.h b/gma/src/include/firebase/gma.h index 9ff5714a65..eca1666a6a 100644 --- a/gma/src/include/firebase/gma.h +++ b/gma/src/include/firebase/gma.h @@ -28,6 +28,7 @@ #include "firebase/app.h" #include "firebase/gma/ad_view.h" #include "firebase/gma/internal/native_ad.h" +#include "firebase/gma/internal/query_info.h" #include "firebase/gma/interstitial_ad.h" #include "firebase/gma/rewarded_ad.h" #include "firebase/gma/types.h" diff --git a/gma/src/include/firebase/gma/internal/query_info.h b/gma/src/include/firebase/gma/internal/query_info.h new file mode 100644 index 0000000000..b2d77959dc --- /dev/null +++ b/gma/src/include/firebase/gma/internal/query_info.h @@ -0,0 +1,120 @@ +/* + * Copyright 2024 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. + */ + +#ifndef FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_INTERNAL_QUERY_INFO_H_ +#define FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_INTERNAL_QUERY_INFO_H_ + +#include + +#include "firebase/future.h" +#include "firebase/gma/types.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. +#ifndef DOXYGEN + +namespace firebase { +namespace gma { + +namespace internal { +// Forward declaration for platform-specific data, implemented in each library. +class QueryInfoInternal; +} // namespace internal + +class GmaInternal; +class QueryInfoResult; + +class QueryInfo { + public: + QueryInfo(); + ~QueryInfo(); + + /// Initialize the QueryInfo object. + /// parent: The platform-specific application context. + Future Initialize(AdParent parent); + + /// Returns a Future containing the status of the last call to + /// Initialize. + Future InitializeLastResult() const; + + /// Begins an asynchronous request for creating a query info string. + /// + /// format: The format of the ad for which the query info is being created. + /// request: An AdRequest struct with information about the request + /// to be made (such as targeting info). + Future CreateQueryInfo(AdFormat format, + const AdRequest& request); + + /// Returns a Future containing the status of the last call to + /// CreateQueryInfo. + Future CreateQueryInfoLastResult() const; + + /// Begins an asynchronous request for creating a query info string. + /// + /// format: The format of the ad for which the query info is being created. + /// request: An AdRequest struct with information about the request + /// to be made (such as targeting info). + /// ad_unit_id: The ad unit ID to use in loading the ad. + Future CreateQueryInfoWithAdUnit(AdFormat format, + const AdRequest& request, + const char* ad_unit_id); + + /// Returns a Future containing the status of the last call to + /// CreateQueryInfoWithAdUnit. + Future CreateQueryInfoWithAdUnitLastResult() const; + + private: + // An internal, platform-specific implementation object that this class uses + // to interact with the Google Mobile Ads SDKs for iOS and Android. + internal::QueryInfoInternal* internal_; +}; + +/// Information about the result of a create queryInfo operation. +class QueryInfoResult { + public: + /// Default Constructor. + QueryInfoResult(); + + /// Destructor. + virtual ~QueryInfoResult(); + + /// Returns true if the operation was successful. + bool is_successful() const; + + /// If the QueryInfoResult::is_successful() returned false, then the + /// string returned via this method will contain no contextual + /// information. + const std::string& query_info() const; + + private: + friend class GmaInternal; + + /// Constructor invoked upon successful query info generation. + explicit QueryInfoResult(const std::string& query_info); + + /// Denotes if the QueryInfoResult represents a success or an error. + bool is_successful_; + + /// Contains the full query info string. + std::string query_info_; +}; + +} // namespace gma +} // namespace firebase + +#endif // DOXYGEN + +#endif // FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_INTERNAL_QUERY_INFO_H_ diff --git a/gma/src/include/firebase/gma/types.h b/gma/src/include/firebase/gma/types.h index a44421ef54..be568f2146 100644 --- a/gma/src/include/firebase/gma/types.h +++ b/gma/src/include/firebase/gma/types.h @@ -153,6 +153,24 @@ enum AdErrorCode { kAdErrorCodeUnknown, }; +#if !defined(DOXYGEN) +/// Format of the ad being requested. Currently used only for internal updates. +enum AdFormat { + /// App open ad format. + kAdFormatAppOpen, + /// Banner ad format. + kAdFormatBanner, + /// Interstitial ad format. + kAdFormatInterstitial, + /// Native ad format. + kAdFormatNative, + /// Rewarded ad format. + kAdFormatRewarded, + /// Rewarded interstitial ad format. + kAdFormatRewardedInterstitial, +}; +#endif // !defined(DOXYGEN) + /// A listener for receiving notifications during the lifecycle of a BannerAd. class AdListener { public: diff --git a/gma/src/ios/FADRequest.h b/gma/src/ios/FADRequest.h index b0f275705b..4a2e7c4b7f 100644 --- a/gma/src/ios/FADRequest.h +++ b/gma/src/ios/FADRequest.h @@ -51,6 +51,10 @@ AdErrorCode MapAdRequestErrorCodeToCPPErrorCode(GADErrorCode error_code); AdErrorCode MapFullScreenContentErrorCodeToCPPErrorCode( GADPresentationErrorCode error_code); +// Converts the platform independent ad format defined in AdFormat to the iOS +// ad format. +GADAdFormat MapCPPAdFormatToGADAdformat(AdFormat format); + } // namespace gma } // namespace firebase diff --git a/gma/src/ios/FADRequest.mm b/gma/src/ios/FADRequest.mm index 0c533681ee..c2393ef710 100644 --- a/gma/src/ios/FADRequest.mm +++ b/gma/src/ios/FADRequest.mm @@ -164,5 +164,24 @@ AdErrorCode MapFullScreenContentErrorCodeToCPPErrorCode(GADPresentationErrorCode } } +GADAdFormat MapCPPAdFormatToGADAdformat(AdFormat format) { + switch (format) { + case kAdFormatBanner: + return GADAdFormatBanner; + case kAdFormatInterstitial: + return GADAdFormatInterstitial; + case kAdFormatRewarded: + return GADAdFormatRewarded; + case kAdFormatRewardedInterstitial: + return GADAdFormatRewardedInterstitial; + case kAdFormatNative: + return GADAdFormatNative; + case kAdFormatAppOpen: + return GADAdFormatAppOpen; + default: + return GADAdFormatBanner; + } +} + } // namespace gma } // namespace firebase diff --git a/gma/src/ios/gma_ios.h b/gma/src/ios/gma_ios.h index 46e71099b0..2c32aca310 100644 --- a/gma/src/ios/gma_ios.h +++ b/gma/src/ios/gma_ios.h @@ -47,6 +47,17 @@ void CompleteLoadImageInternalSuccess( FutureCallbackData* callback_data, const std::vector& img_data); +// Pipes query info generation errors that exist in the C++ SDK. +void CompleteQueryInfoInternalError( + FutureCallbackData* callback_data, + int error_code, + const char* error_message); + +// Completes QueryInfoResult futures for successful query info generation. +void CompleteQueryInfoInternalSuccess( + FutureCallbackData* callback_data, + NSString* query_info); + // Resolves LoadImage errors that exist in the C++ SDK before initiating image // loads. void CompleteLoadImageInternalError( diff --git a/gma/src/ios/gma_ios.mm b/gma/src/ios/gma_ios.mm index b47eee884a..47b185d228 100644 --- a/gma/src/ios/gma_ios.mm +++ b/gma/src/ios/gma_ios.mm @@ -357,6 +357,23 @@ void CompleteLoadImageInternalError(FutureCallbackData* callback_da GmaInternal::CompleteLoadImageFutureFailure(callback_data, error_code, std::string(error_message)); } +void CompleteQueryInfoInternalSuccess(FutureCallbackData* callback_data, + NSString* query_info) { + FIREBASE_ASSERT(callback_data); + + GmaInternal::CompleteCreateQueryInfoFutureSuccess(callback_data, + util::NSStringToString(query_info)); +} + +void CompleteQueryInfoInternalError(FutureCallbackData* callback_data, + int error_code, const char* error_message) { + FIREBASE_ASSERT(callback_data); + FIREBASE_ASSERT(error_message); + + GmaInternal::CompleteCreateQueryInfoFutureFailure(callback_data, error_code, + std::string(error_message)); +} + void CompleteAdResultError(FutureCallbackData* callback_data, NSError* gad_error, bool is_load_ad_error) { FIREBASE_ASSERT(callback_data); diff --git a/gma/src/ios/query_info_internal_ios.h b/gma/src/ios/query_info_internal_ios.h new file mode 100644 index 0000000000..3b32e2b4ef --- /dev/null +++ b/gma/src/ios/query_info_internal_ios.h @@ -0,0 +1,75 @@ +/* + * Copyright 2024 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. + */ + +#ifndef FIREBASE_GMA_SRC_IOS_QUERY_INFO_INTERNAL_IOS_H_ +#define FIREBASE_GMA_SRC_IOS_QUERY_INFO_INTERNAL_IOS_H_ + +#ifdef __OBJC__ +#import +#import +#endif // __OBJC__ + +extern "C" { +#include +} // extern "C" + +#include "app/src/include/firebase/internal/mutex.h" +#include "gma/src/common/query_info_internal.h" + +namespace firebase { +namespace gma { +namespace internal { + +class QueryInfoInternalIOS : public QueryInfoInternal { + public: + explicit QueryInfoInternalIOS(QueryInfo* base); + ~QueryInfoInternalIOS(); + + Future Initialize(AdParent parent) override; + Future CreateQueryInfo(AdFormat format, + const AdRequest& request) override; + Future CreateQueryInfoWithAdUnit( + AdFormat format, const AdRequest& request, + const char* ad_unit_id) override; + bool is_initialized() const override { return initialized_; } + +#ifdef __OBJC__ + void CreateQueryInfoSucceeded(GADQueryInfo *query_info); + void CreateQueryInfoFailedWithError(NSError *gad_error); +#endif // __OBJC__ + + private: + /// Prevents duplicate invocations of initialize on the Native Ad. + bool initialized_; + + /// Contains information to asynchronously complete the createQueryInfo + /// Future. + FutureCallbackData* query_info_callback_data_; + + /// The publisher-provided view (UIView) that's the parent view of the ad. + /// Declared as an "id" type to avoid referencing an Objective-C class in this + /// header. + id parent_view_; + + // Mutex to guard against concurrent operations; + Mutex mutex_; +}; + +} // namespace internal +} // namespace gma +} // namespace firebase + +#endif // FIREBASE_GMA_SRC_IOS_QUERY_INFO_INTERNAL_IOS_H_ diff --git a/gma/src/ios/query_info_internal_ios.mm b/gma/src/ios/query_info_internal_ios.mm new file mode 100644 index 0000000000..c9385b1ca1 --- /dev/null +++ b/gma/src/ios/query_info_internal_ios.mm @@ -0,0 +1,197 @@ +/* + * Copyright 2024 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. + */ + +extern "C" { +#include +} // extern "C" + +#include "gma/src/ios/query_info_internal_ios.h" + +#import "gma/src/ios/FADRequest.h" +#import "gma/src/ios/gma_ios.h" + +#include "app/src/util_ios.h" +#include "gma/src/ios/response_info_ios.h" + +namespace firebase { +namespace gma { +namespace internal { + +QueryInfoInternalIOS::QueryInfoInternalIOS(QueryInfo *base) + : QueryInfoInternal(base), + initialized_(false), + query_info_callback_data_(nil), + parent_view_(nil) {} + +QueryInfoInternalIOS::~QueryInfoInternalIOS() { + firebase::MutexLock lock(mutex_); + if (query_info_callback_data_ != nil) { + delete query_info_callback_data_; + query_info_callback_data_ = nil; + } +} + +Future QueryInfoInternalIOS::Initialize(AdParent parent) { + firebase::MutexLock lock(mutex_); + const SafeFutureHandle future_handle = + future_data_.future_impl.SafeAlloc(kQueryInfoFnInitialize); + Future future = MakeFuture(&future_data_.future_impl, future_handle); + + if (initialized_) { + CompleteFuture(kAdErrorCodeAlreadyInitialized, kAdAlreadyInitializedErrorMessage, future_handle, + &future_data_); + } else { + initialized_ = true; + parent_view_ = (UIView *)parent; + CompleteFuture(kAdErrorCodeNone, nullptr, future_handle, &future_data_); + } + return future; +} + +Future QueryInfoInternalIOS::CreateQueryInfo(AdFormat format, + const AdRequest &request) { + firebase::MutexLock lock(mutex_); + FutureCallbackData *callback_data = + CreateQueryInfoResultFutureCallbackData(kQueryInfoFnCreateQueryInfo, &future_data_); + Future future = + MakeFuture(&future_data_.future_impl, callback_data->future_handle); + + if (query_info_callback_data_ != nil) { + CompleteQueryInfoInternalError(callback_data, kAdErrorCodeLoadInProgress, + kAdLoadInProgressErrorMessage); + return future; + } + + // Persist a pointer to the callback data so that we may use it after the iOS + // SDK returns the QueryInfo. + query_info_callback_data_ = callback_data; + + // Guard against parameter object destruction before the async operation + // executes (below). + AdRequest local_ad_request = request; + + dispatch_async(dispatch_get_main_queue(), ^{ + // Create a GADRequest from a gma::AdRequest. + AdErrorCode error_code = kAdErrorCodeNone; + std::string error_message; + GADRequest *ad_request = + GADRequestFromCppAdRequest(local_ad_request, &error_code, &error_message); + if (ad_request == nullptr) { + if (error_code == kAdErrorCodeNone) { + error_code = kAdErrorCodeInternalError; + error_message = kAdCouldNotParseAdRequestErrorMessage; + } + CompleteQueryInfoInternalError(query_info_callback_data_, error_code, error_message.c_str()); + query_info_callback_data_ = nil; + } else { + // Make the create query info request. + [GADQueryInfo + createQueryInfoWithRequest:ad_request + adFormat:MapCPPAdFormatToGADAdformat(format) + completionHandler:^(GADQueryInfo *query_info, NSError *error) // NO LINT + { + if (error) { + CreateQueryInfoFailedWithError(error); + } else { + CreateQueryInfoSucceeded(query_info); + } + }]; + } + }); + + return future; +} + +Future QueryInfoInternalIOS::CreateQueryInfoWithAdUnit(AdFormat format, + const AdRequest &request, + const char *ad_unit_id) { + firebase::MutexLock lock(mutex_); + FutureCallbackData *callback_data = + CreateQueryInfoResultFutureCallbackData(kQueryInfoFnCreateQueryInfoWithAdUnit, &future_data_); + Future future = + MakeFuture(&future_data_.future_impl, callback_data->future_handle); + + if (query_info_callback_data_ != nil) { + CompleteQueryInfoInternalError(callback_data, kAdErrorCodeLoadInProgress, + kAdLoadInProgressErrorMessage); + return future; + } + + // Persist a pointer to the callback data so that we may use it after the iOS + // SDK returns the QueryInfo. + query_info_callback_data_ = callback_data; + + // Guard against parameter object destruction before the async operation + // executes (below). + AdRequest local_ad_request = request; + NSString *local_ad_unit_id = @(ad_unit_id); + + dispatch_async(dispatch_get_main_queue(), ^{ + // Create a GADRequest from a gma::AdRequest. + AdErrorCode error_code = kAdErrorCodeNone; + std::string error_message; + GADRequest *ad_request = + GADRequestFromCppAdRequest(local_ad_request, &error_code, &error_message); + if (ad_request == nullptr) { + if (error_code == kAdErrorCodeNone) { + error_code = kAdErrorCodeInternalError; + error_message = kAdCouldNotParseAdRequestErrorMessage; + } + CompleteQueryInfoInternalError(query_info_callback_data_, error_code, error_message.c_str()); + query_info_callback_data_ = nil; + } else { + // Make the create query info request. + [GADQueryInfo + createQueryInfoWithRequest:ad_request + adFormat:MapCPPAdFormatToGADAdformat(format) + adUnitID:local_ad_unit_id + completionHandler:^(GADQueryInfo *query_info, NSError *error) // NO LINT + { + if (error) { + CreateQueryInfoFailedWithError(error); + } else { + CreateQueryInfoSucceeded(query_info); + } + }]; + } + }); + + return future; +} + +void QueryInfoInternalIOS::CreateQueryInfoSucceeded(GADQueryInfo *query_info) { + firebase::MutexLock lock(mutex_); + + if (query_info_callback_data_ != nil) { + CompleteQueryInfoInternalSuccess(query_info_callback_data_, query_info.query); + query_info_callback_data_ = nil; + } +} + +void QueryInfoInternalIOS::CreateQueryInfoFailedWithError(NSError *gad_error) { + firebase::MutexLock lock(mutex_); + FIREBASE_ASSERT(gad_error); + if (query_info_callback_data_ != nil) { + AdErrorCode error_code = MapAdRequestErrorCodeToCPPErrorCode((GADErrorCode)gad_error.code); + CompleteQueryInfoInternalError(query_info_callback_data_, error_code, + util::NSStringToString(gad_error.localizedDescription).c_str()); + query_info_callback_data_ = nil; + } +} + +} // namespace internal +} // namespace gma +} // namespace firebase diff --git a/gma/src/stub/query_info_internal_stub.h b/gma/src/stub/query_info_internal_stub.h new file mode 100644 index 0000000000..81c6669f36 --- /dev/null +++ b/gma/src/stub/query_info_internal_stub.h @@ -0,0 +1,69 @@ +/* + * Copyright 2024 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. + */ + +#ifndef FIREBASE_GMA_SRC_STUB_QUERY_INFO_INTERNAL_STUB_H_ +#define FIREBASE_GMA_SRC_STUB_QUERY_INFO_INTERNAL_STUB_H_ + +#include "gma/src/common/query_info_internal.h" + +namespace firebase { +namespace gma { +namespace internal { + +/// Stub version of QueryInfoInternal, for use on desktop platforms. GMA +/// is forbidden on desktop, so this version creates and immediately completes +/// the Future for each method. +class QueryInfoInternalStub : public QueryInfoInternal { + public: + explicit QueryInfoInternalStub(QueryInfo* base) : QueryInfoInternal(base) {} + + ~QueryInfoInternalStub() override {} + + Future Initialize(AdParent parent) override { + return CreateAndCompleteFutureStub(kQueryInfoFnInitialize); + } + + Future CreateQueryInfo(AdFormat format, + const AdRequest& request) override { + return CreateAndCompleteQueryInfoFutureStub(kQueryInfoFnCreateQueryInfo); + } + + Future CreateQueryInfoWithAdUnit( + AdFormat format, const AdRequest& request, + const char* ad_unit_id) override { + return CreateAndCompleteQueryInfoFutureStub( + kQueryInfoFnCreateQueryInfoWithAdUnit); + } + + bool is_initialized() const override { return true; } + + private: + Future CreateAndCompleteFutureStub(QueryInfoFn fn) { + CreateAndCompleteFuture(fn, kAdErrorCodeNone, nullptr, &future_data_); + return GetInitializeLastResult(); + } + + Future CreateAndCompleteQueryInfoFutureStub(QueryInfoFn fn) { + return CreateAndCompleteFutureWithQueryInfoResult( + fn, kAdErrorCodeNone, nullptr, &future_data_, QueryInfoResult()); + } +}; + +} // namespace internal +} // namespace gma +} // namespace firebase + +#endif // FIREBASE_GMA_SRC_STUB_QUERY_INFO_INTERNAL_STUB_H_ diff --git a/gma/src_java/com/google/firebase/gma/internal/cpp/ConstantsHelper.java b/gma/src_java/com/google/firebase/gma/internal/cpp/ConstantsHelper.java index 2056275293..71da84ff2b 100644 --- a/gma/src_java/com/google/firebase/gma/internal/cpp/ConstantsHelper.java +++ b/gma/src_java/com/google/firebase/gma/internal/cpp/ConstantsHelper.java @@ -84,4 +84,22 @@ public final class ConstantsHelper { public static final int AD_VIEW_POSITION_BOTTOM_LEFT = 4; public static final int AD_VIEW_POSITION_BOTTOM_RIGHT = 5; + + /** + * Ad formats (matches the firebase::gma::AdFormat and com.google.android.gms.ads.AdFormat + * enumerations ). + */ + public static final int AD_FORMAT_UNDEFINED = -1; + + public static final int AD_FORMAT_APP_OPEN_AD = 0; + + public static final int AD_FORMAT_BANNER = 1; + + public static final int AD_FORMAT_INTERSTITIAL = 2; + + public static final int AD_FORMAT_NATIVE = 3; + + public static final int AD_FORMAT_REWARDED = 4; + + public static final int AD_FORMAT_REWARDED_INTERSTITIAL = 5; } diff --git a/gma/src_java/com/google/firebase/gma/internal/cpp/QueryInfoHelper.java b/gma/src_java/com/google/firebase/gma/internal/cpp/QueryInfoHelper.java new file mode 100644 index 0000000000..644e905d05 --- /dev/null +++ b/gma/src_java/com/google/firebase/gma/internal/cpp/QueryInfoHelper.java @@ -0,0 +1,218 @@ +/* + * Copyright 2024 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. + */ + +package com.google.firebase.gma.internal.cpp; + +import android.app.Activity; +import com.google.android.gms.ads.AdFormat; +import com.google.android.gms.ads.AdRequest; +import com.google.android.gms.ads.query.QueryInfo; +import com.google.android.gms.ads.query.QueryInfoGenerationCallback; + +/** + * Helper class to make interactions between the GMA C++ wrapper and Java objects cleaner. It's + * designed to wrap and adapt a single instance of QueryInfo, translate calls coming from C++ into + * their (typically more complicated) Java equivalents. + */ +public class QueryInfoHelper { + // C++ nullptr for use with the callbacks. + private static final long CPP_NULLPTR = 0; + + // Pointer to the QueryInfoInternalAndroid object that created this + // object. + private long queryInfoInternalPtr; + + // The GMA SDK QueryInfo associated with this helper. + private QueryInfo gmaQueryInfo; + + // Synchronization object for thread safe access to: + // * queryInfoInternalPtr + // * createQueryInfoCallbackDataPtr + private final Object queryInfoLock; + + // The Activity this helper uses to generate the QueryInfo. + private Activity activity; + + // Pointer to a FutureCallbackData in the C++ wrapper that will be used to + // complete the Future associated with the latest call to CreateQueryInfo. + private long createQueryInfoCallbackDataPtr; + + /** Constructor. */ + public QueryInfoHelper(long queryInfoInternalPtr) { + this.queryInfoInternalPtr = queryInfoInternalPtr; + queryInfoLock = new Object(); + + // Test the callbacks and fail quickly if something's wrong. + completeQueryInfoFutureCallback(CPP_NULLPTR, 0, ConstantsHelper.CALLBACK_ERROR_MESSAGE_NONE); + } + + /** + * Initializes the QueryInfoHelper. This creates the corresponding GMA SDK NativeAd object and + * sets it up. + */ + public void initialize(final long callbackDataPtr, Activity activity) { + this.activity = activity; + + this.activity.runOnUiThread(new Runnable() { + @Override + public void run() { + int errorCode; + String errorMessage; + if (gmaQueryInfo == null) { + try { + errorCode = ConstantsHelper.CALLBACK_ERROR_NONE; + errorMessage = ConstantsHelper.CALLBACK_ERROR_MESSAGE_NONE; + } catch (IllegalStateException e) { + gmaQueryInfo = null; + errorCode = ConstantsHelper.CALLBACK_ERROR_ALREADY_INITIALIZED; + errorMessage = ConstantsHelper.CALLBACK_ERROR_MESSAGE_ALREADY_INITIALIZED; + } + } else { + errorCode = ConstantsHelper.CALLBACK_ERROR_ALREADY_INITIALIZED; + errorMessage = ConstantsHelper.CALLBACK_ERROR_MESSAGE_ALREADY_INITIALIZED; + } + completeQueryInfoFutureCallback(callbackDataPtr, errorCode, errorMessage); + } + }); + } + + /** Disconnect the helper from the query info. */ + public void disconnect() { + synchronized (queryInfoLock) { + queryInfoInternalPtr = CPP_NULLPTR; + createQueryInfoCallbackDataPtr = CPP_NULLPTR; + } + + if (activity == null) { + return; + } + + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + synchronized (queryInfoLock) { + if (gmaQueryInfo != null) { + gmaQueryInfo = null; + } + } + } + }); + } + + private AdFormat getAdFormat(int format) { + switch (format) { + case ConstantsHelper.AD_FORMAT_BANNER: + return AdFormat.BANNER; + case ConstantsHelper.AD_FORMAT_INTERSTITIAL: + return AdFormat.INTERSTITIAL; + case ConstantsHelper.AD_FORMAT_REWARDED: + return AdFormat.REWARDED; + case ConstantsHelper.AD_FORMAT_NATIVE: + return AdFormat.NATIVE; + case ConstantsHelper.AD_FORMAT_REWARDED_INTERSTITIAL: + return AdFormat.REWARDED_INTERSTITIAL; + default: + return AdFormat.APP_OPEN_AD; + } + } + + /** Creates a query info for the underlying QueryInfo object. */ + public void createQueryInfo( + long callbackDataPtr, int format, String adUnitId, final AdRequest request) { + if (activity == null) { + return; + } + synchronized (queryInfoLock) { + if (createQueryInfoCallbackDataPtr != CPP_NULLPTR) { + completeCreateQueryInfoError(callbackDataPtr, + ConstantsHelper.CALLBACK_ERROR_LOAD_IN_PROGRESS, + ConstantsHelper.CALLBACK_ERROR_MESSAGE_LOAD_IN_PROGRESS); + return; + } + createQueryInfoCallbackDataPtr = callbackDataPtr; + } + + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + if (activity == null) { + synchronized (queryInfoLock) { + completeCreateQueryInfoError(createQueryInfoCallbackDataPtr, + ConstantsHelper.CALLBACK_ERROR_UNINITIALIZED, + ConstantsHelper.CALLBACK_ERROR_MESSAGE_UNINITIALIZED); + createQueryInfoCallbackDataPtr = CPP_NULLPTR; + } + } else { + try { + AdFormat adFormat = getAdFormat(format); + if (adUnitId != null && !adUnitId.isEmpty()) { + QueryInfo.generate(activity, adFormat, request, adUnitId, new QueryInfoListener()); + } else { + QueryInfo.generate(activity, adFormat, request, new QueryInfoListener()); + } + } catch (IllegalStateException e) { + synchronized (queryInfoLock) { + completeCreateQueryInfoError(createQueryInfoCallbackDataPtr, + ConstantsHelper.CALLBACK_ERROR_UNINITIALIZED, + ConstantsHelper.CALLBACK_ERROR_MESSAGE_UNINITIALIZED); + createQueryInfoCallbackDataPtr = CPP_NULLPTR; + } + } + } + } + }); + } + + private class QueryInfoListener extends QueryInfoGenerationCallback { + @Override + public void onFailure(String errorMessage) { + synchronized (queryInfoLock) { + if (createQueryInfoCallbackDataPtr != CPP_NULLPTR) { + completeCreateQueryInfoError(createQueryInfoCallbackDataPtr, + ConstantsHelper.CALLBACK_ERROR_INVALID_REQUEST, errorMessage); + createQueryInfoCallbackDataPtr = CPP_NULLPTR; + } + } + } + + @Override + public void onSuccess(QueryInfo queryInfo) { + synchronized (queryInfoLock) { + gmaQueryInfo = queryInfo; + if (createQueryInfoCallbackDataPtr != CPP_NULLPTR) { + completeCreateQueryInfoSuccess(createQueryInfoCallbackDataPtr, queryInfo.getQuery()); + createQueryInfoCallbackDataPtr = CPP_NULLPTR; + } + } + } + } + + /** Native callback to instruct the C++ wrapper to complete the corresponding future. */ + public static native void completeQueryInfoFutureCallback( + long internalPtr, int errorCode, String errorMessage); + + /** Native callback invoked upon successfully generating a QueryInfo. */ + public static native void completeCreateQueryInfoSuccess( + long createQueryInfoInternalPtr, String query); + + /** + * Native callback invoked upon error generating a QueryInfo. Also used for wrapper/internal + * errors when processing a query info generation request. Returns integers representing + * firebase::gma::AdError codes. + */ + public static native void completeCreateQueryInfoError( + long createQueryInfoInternalPtr, int gmaErrorCode, String errorMessage); +}