diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index b008b7de78..ac52251529 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -546,7 +546,10 @@ if (IOS) ${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/rewarded_ad.h - ${FIREBASE_SOURCE_DIR}/gma/src/include/firebase/gma/types.h) + ${FIREBASE_SOURCE_DIR}/gma/src/include/firebase/gma/types.h + ${FIREBASE_SOURCE_DIR}/gma/src/include/firebase/gma/ump.h + ${FIREBASE_SOURCE_DIR}/gma/src/include/firebase/gma/ump/consent_info.h + ${FIREBASE_SOURCE_DIR}/gma/src/include/firebase/gma/ump/types.h) set(installations_HDRS ${FIREBASE_SOURCE_DIR}/installations/src/include/firebase/installations.h) set(messaging_HDRS diff --git a/gma/CMakeLists.txt b/gma/CMakeLists.txt index caf0a32508..440d0db7f0 100644 --- a/gma/CMakeLists.txt +++ b/gma/CMakeLists.txt @@ -16,6 +16,8 @@ # Common source files used by all platforms set(common_SRCS + src/common/ump/consent_info.cc + src/common/ump/consent_info_internal.cc src/common/gma_common.cc src/common/ad_view.cc src/common/ad_view_internal.cc @@ -38,6 +40,7 @@ binary_to_array("gma_resources" # Source files used by the Android implementation. set(android_SRCS ${gma_resources_source} + src/stub/ump/consent_info_internal_stub.cc src/android/ad_request_converter.cc src/android/ad_error_android.cc src/android/adapter_response_info_android.cc @@ -51,6 +54,7 @@ set(android_SRCS # Source files used by the iOS implementation. set(ios_SRCS + src/stub/ump/consent_info_internal_stub.cc src/ios/FADAdSize.mm src/ios/FADAdView.mm src/ios/FADInterstitialDelegate.mm @@ -69,6 +73,7 @@ set(ios_SRCS # Source files used by the stub implementation. set(stub_SRCS + src/stub/ump/consent_info_internal_stub.cc src/stub/ad_error_stub.cc src/stub/adapter_response_info_stub.cc src/stub/gma_stub.cc diff --git a/gma/integration_test/src/integration_test.cc b/gma/integration_test/src/integration_test.cc index 2a48d0066f..c98bbb748f 100644 --- a/gma/integration_test/src/integration_test.cc +++ b/gma/integration_test/src/integration_test.cc @@ -26,6 +26,7 @@ #include "firebase/app.h" #include "firebase/gma.h" #include "firebase/gma/types.h" +#include "firebase/gma/ump.h" #include "firebase/util.h" #include "firebase_test_framework.h" // NOLINT @@ -2439,4 +2440,388 @@ TEST_F(FirebaseGmaTest, TestAdViewMultithreadDeletion) { #endif // #if defined(ANDROID) || (defined(TARGET_OS_IPHONE) && // TARGET_OS_IPHONE) +class FirebaseGmaUmpTest : public FirebaseGmaTest { + public: + FirebaseGmaUmpTest() : consent_info_(nullptr) {} + + // Whether to call ConsentInfo::Reset() upon initialization, which + // resets UMP's consent state to as if the app was first installed. + enum ResetOption { kReset, kNoReset }; + + void InitializeUmp(ResetOption reset = kReset); + void TerminateUmp(); + + void SetUp() override; + void TearDown() override; + + protected: + firebase::gma::ump::ConsentInfo* consent_info_; +}; + +void FirebaseGmaUmpTest::InitializeUmp(ResetOption reset) { + using firebase::gma::ump::ConsentInfo; + consent_info_ = ConsentInfo::GetInstance(*shared_app_); + if (consent_info_ != nullptr && reset == kReset) { + consent_info_->Reset(); + } +} + +void FirebaseGmaUmpTest::TerminateUmp() { + if (consent_info_) { + delete consent_info_; + consent_info_ = nullptr; + } +} + +void FirebaseGmaUmpTest::SetUp() { + FirebaseGmaTest::SetUp(); + InitializeUmp(); + ASSERT_NE(consent_info_, nullptr); +} + +void FirebaseGmaUmpTest::TearDown() { + TerminateUmp(); + FirebaseGmaTest::TearDown(); +} + +// Tests for User Messaging Platform +TEST_F(FirebaseGmaUmpTest, TestUmpInitialization) { + // Initialize handled automatically in test setup. + EXPECT_NE(consent_info_, nullptr); + // Terminate handled automatically in test teardown. +} + +// Tests for User Messaging Platform +TEST_F(FirebaseGmaUmpTest, TestUmpDefaultsToUnknownStatus) { + EXPECT_EQ(consent_info_->GetConsentStatus(), + firebase::gma::ump::kConsentStatusUnknown); + EXPECT_EQ(consent_info_->GetConsentFormStatus(), + firebase::gma::ump::kConsentFormStatusUnknown); + EXPECT_EQ(consent_info_->GetPrivacyOptionsRequirementStatus(), + firebase::gma::ump::kPrivacyOptionsRequirementStatusUnknown); + EXPECT_FALSE(consent_info_->CanRequestAds()); +} + +// Tests for User Messaging Platform +TEST_F(FirebaseGmaUmpTest, TestUmpGetInstanceIsAlwaysEqual) { + using firebase::gma::ump::ConsentInfo; + + EXPECT_NE(consent_info_, nullptr); + + // Ensure that GetInstance() with any options is always equal. + EXPECT_EQ(consent_info_, ConsentInfo::GetInstance()); + EXPECT_EQ(consent_info_, ConsentInfo::GetInstance(*shared_app_)); + +#if defined(ANDROID) + EXPECT_EQ(consent_info_, + ConsentInfo::GetInstance(app_framework::GetJniEnv(), + app_framework::GetActivity())); + + firebase::App* second_app = firebase::App::Create( + firebase::AppOptions(), "2ndApp", app_framework::GetJniEnv(), + app_framework::GetActivity()); +#else + firebase::App* second_app = + firebase::App::Create(firebase::AppOptions(), "2ndApp"); +#endif // defined(ANDROID) + + EXPECT_EQ(consent_info_, ConsentInfo::GetInstance(*second_app)); + + delete second_app; +} + +TEST_F(FirebaseGmaUmpTest, TestUmpRequestConsentInfoUpdate) { + using firebase::gma::ump::ConsentRequestParameters; + using firebase::gma::ump::ConsentStatus; + + ConsentRequestParameters params; + params.tag_for_under_age_of_consent = false; + + firebase::Future future = + consent_info_->RequestConsentInfoUpdate(params); + + EXPECT_TRUE(future == consent_info_->RequestConsentInfoUpdateLastResult()); + + WaitForCompletion(future, "RequestConsentInfoUpdate"); + + EXPECT_NE(consent_info_->GetConsentStatus(), + firebase::gma::ump::kConsentStatusUnknown); + EXPECT_NE(consent_info_->GetConsentFormStatus(), + firebase::gma::ump::kConsentFormStatusUnknown); + EXPECT_NE(consent_info_->GetPrivacyOptionsRequirementStatus(), + firebase::gma::ump::kPrivacyOptionsRequirementStatusUnknown); +} + +TEST_F(FirebaseGmaUmpTest, TestUmpRequestConsentInfoUpdateDebugEEA) { + using firebase::gma::ump::ConsentDebugSettings; + using firebase::gma::ump::ConsentRequestParameters; + using firebase::gma::ump::ConsentStatus; + + ConsentRequestParameters params; + params.tag_for_under_age_of_consent = false; + params.debug_settings.debug_geography = + firebase::gma::ump::kConsentDebugGeographyEEA; + + firebase::Future future = + consent_info_->RequestConsentInfoUpdate(params); + + WaitForCompletion(future, "RequestConsentInfoUpdate"); + + EXPECT_EQ(consent_info_->GetConsentStatus(), + firebase::gma::ump::kConsentStatusRequired); +} + +TEST_F(FirebaseGmaUmpTest, TestUmpRequestConsentInfoUpdateDebugNonEEA) { + using firebase::gma::ump::ConsentDebugSettings; + using firebase::gma::ump::ConsentRequestParameters; + using firebase::gma::ump::ConsentStatus; + + ConsentRequestParameters params; + params.tag_for_under_age_of_consent = false; + params.debug_settings.debug_geography = + firebase::gma::ump::kConsentDebugGeographyNonEEA; + + firebase::Future future = + consent_info_->RequestConsentInfoUpdate(params); + + WaitForCompletion(future, "RequestConsentInfoUpdate"); + + EXPECT_EQ(consent_info_->GetConsentStatus(), + firebase::gma::ump::kConsentStatusNotRequired); +} + +TEST_F(FirebaseGmaUmpTest, TestUmpLoadForm) { + using firebase::gma::ump::ConsentDebugSettings; + using firebase::gma::ump::ConsentFormStatus; + using firebase::gma::ump::ConsentRequestParameters; + using firebase::gma::ump::ConsentStatus; + + ConsentRequestParameters params; + params.tag_for_under_age_of_consent = false; + params.debug_settings.debug_geography = + firebase::gma::ump::kConsentDebugGeographyEEA; + + WaitForCompletion(consent_info_->RequestConsentInfoUpdate(params), + "RequestConsentInfoUpdate"); + + EXPECT_EQ(consent_info_->GetConsentStatus(), + firebase::gma::ump::kConsentStatusRequired); + + EXPECT_EQ(consent_info_->GetConsentFormStatus(), + firebase::gma::ump::kConsentFormStatusAvailable); + + // Load the form. + firebase::Future future = consent_info_->LoadConsentForm(); + + EXPECT_TRUE(future == consent_info_->LoadConsentFormLastResult()); + + WaitForCompletion(future, "LoadConsentForm"); + + EXPECT_EQ(consent_info_->GetConsentFormStatus(), + firebase::gma::ump::kConsentFormStatusAvailable); +} + +TEST_F(FirebaseGmaUmpTest, TestUmpShowForm) { + TEST_REQUIRES_USER_INTERACTION; + + using firebase::gma::ump::ConsentDebugSettings; + using firebase::gma::ump::ConsentFormStatus; + using firebase::gma::ump::ConsentRequestParameters; + using firebase::gma::ump::ConsentStatus; + + ConsentRequestParameters params; + params.tag_for_under_age_of_consent = false; + params.debug_settings.debug_geography = + firebase::gma::ump::kConsentDebugGeographyEEA; + + WaitForCompletion(consent_info_->RequestConsentInfoUpdate(params), + "RequestConsentInfoUpdate"); + + EXPECT_EQ(consent_info_->GetConsentStatus(), + firebase::gma::ump::kConsentStatusRequired); + + EXPECT_EQ(consent_info_->GetConsentFormStatus(), + firebase::gma::ump::kConsentFormStatusAvailable); + + WaitForCompletion(consent_info_->LoadConsentForm(), "LoadConsentForm"); + + EXPECT_EQ(consent_info_->GetConsentFormStatus(), + firebase::gma::ump::kConsentFormStatusAvailable); + + firebase::Future future = consent_info_->ShowConsentForm(nullptr); + + EXPECT_TRUE(future == consent_info_->ShowConsentFormLastResult()); + + WaitForCompletion(future, "ShowConsentForm"); + + EXPECT_EQ(consent_info_->GetConsentStatus(), + firebase::gma::ump::kConsentStatusObtained); +} + +TEST_F(FirebaseGmaUmpTest, TestUmpLoadFormUnavailableDueUnderAgeOfConsent) { + TEST_REQUIRES_USER_INTERACTION; + + using firebase::gma::ump::ConsentDebugSettings; + using firebase::gma::ump::ConsentFormStatus; + using firebase::gma::ump::ConsentRequestParameters; + using firebase::gma::ump::ConsentStatus; + + ConsentRequestParameters params; + params.tag_for_under_age_of_consent = true; + params.debug_settings.debug_geography = + firebase::gma::ump::kConsentDebugGeographyEEA; + + WaitForCompletion(consent_info_->RequestConsentInfoUpdate(params), + "RequestConsentInfoUpdate"); + + EXPECT_EQ(consent_info_->GetConsentStatus(), + firebase::gma::ump::kConsentStatusRequired); + + EXPECT_EQ(consent_info_->GetConsentFormStatus(), + firebase::gma::ump::kConsentFormStatusUnavailable); + + WaitForCompletion(consent_info_->LoadConsentForm(), "LoadConsentForm", + firebase::gma::ump::kConsentFormErrorUnavailable); + + EXPECT_EQ(consent_info_->GetConsentFormStatus(), + firebase::gma::ump::kConsentFormStatusUnavailable); +} + +TEST_F(FirebaseGmaUmpTest, TestUmpLoadAndShowIfRequired) { + using firebase::gma::ump::ConsentDebugSettings; + using firebase::gma::ump::ConsentRequestParameters; + using firebase::gma::ump::ConsentStatus; + + TEST_REQUIRES_USER_INTERACTION; + + ConsentRequestParameters params; + params.tag_for_under_age_of_consent = false; + params.debug_settings.debug_geography = + firebase::gma::ump::kConsentDebugGeographyEEA; + + WaitForCompletion(consent_info_->RequestConsentInfoUpdate(params), + "RequestConsentInfoUpdate"); + + EXPECT_EQ(consent_info_->GetConsentStatus(), + firebase::gma::ump::kConsentStatusRequired); + + firebase::Future future = + consent_info_->LoadAndShowConsentFormIfRequired(nullptr); + + EXPECT_TRUE(future == + consent_info_->LoadAndShowConsentFormIfRequiredLastResult()); + + WaitForCompletion(future, "LoadAndShowConsentFormIfRequired"); + + EXPECT_EQ(consent_info_->GetConsentStatus(), + firebase::gma::ump::kConsentStatusObtained); +} + +TEST_F(FirebaseGmaUmpTest, TestUmpPrivacyOptions) { + using firebase::gma::ump::ConsentDebugSettings; + using firebase::gma::ump::ConsentRequestParameters; + using firebase::gma::ump::ConsentStatus; + using firebase::gma::ump::PrivacyOptionsRequirementStatus; + + TEST_REQUIRES_USER_INTERACTION; + + ConsentRequestParameters params; + params.tag_for_under_age_of_consent = false; + params.debug_settings.debug_geography = + firebase::gma::ump::kConsentDebugGeographyEEA; + + WaitForCompletion(consent_info_->RequestConsentInfoUpdate(params), + "RequestConsentInfoUpdate"); + + EXPECT_EQ(consent_info_->GetConsentStatus(), + firebase::gma::ump::kConsentStatusRequired); + + EXPECT_FALSE(consent_info_->CanRequestAds()); + + WaitForCompletion(consent_info_->LoadAndShowConsentFormIfRequired(nullptr), + "LoadAndShowConsentFormIfRequired"); + + EXPECT_EQ(consent_info_->GetConsentStatus(), + firebase::gma::ump::kConsentStatusObtained); + + EXPECT_TRUE(consent_info_->CanRequestAds()); + + EXPECT_EQ(consent_info_->GetPrivacyOptionsRequirementStatus(), + firebase::gma::ump::kPrivacyOptionsRequirementStatusRequired); + + firebase::Future future = + consent_info_->ShowPrivacyOptionsForm(nullptr); + + EXPECT_TRUE(future == consent_info_->ShowPrivacyOptionsFormLastResult()); + + WaitForCompletion(future, "ShowPrivacyOptionsForm"); + + EXPECT_EQ(consent_info_->GetConsentStatus(), + firebase::gma::ump::kConsentStatusRequired); + + EXPECT_FALSE(consent_info_->CanRequestAds()); +} + +TEST_F(FirebaseGmaUmpTest, TestCanRequestAdsNonEEA) { + using firebase::gma::ump::ConsentDebugSettings; + using firebase::gma::ump::ConsentRequestParameters; + using firebase::gma::ump::ConsentStatus; + + TEST_REQUIRES_USER_INTERACTION; + + ConsentRequestParameters params; + params.tag_for_under_age_of_consent = false; + params.debug_settings.debug_geography = + firebase::gma::ump::kConsentDebugGeographyNonEEA; + + WaitForCompletion(consent_info_->RequestConsentInfoUpdate(params), + "RequestConsentInfoUpdate"); + + EXPECT_EQ(consent_info_->GetConsentStatus(), + firebase::gma::ump::kConsentStatusNotRequired); + + EXPECT_TRUE(consent_info_->CanRequestAds()); +} + +TEST_F(FirebaseGmaUmpTest, TestCanRequestAdsEEA) { + using firebase::gma::ump::ConsentDebugSettings; + using firebase::gma::ump::ConsentRequestParameters; + using firebase::gma::ump::ConsentStatus; + + TEST_REQUIRES_USER_INTERACTION; + + ConsentRequestParameters params; + params.tag_for_under_age_of_consent = false; + params.debug_settings.debug_geography = + firebase::gma::ump::kConsentDebugGeographyEEA; + + WaitForCompletion(consent_info_->RequestConsentInfoUpdate(params), + "RequestConsentInfoUpdate"); + + EXPECT_EQ(consent_info_->GetConsentStatus(), + firebase::gma::ump::kConsentStatusRequired); + + EXPECT_FALSE(consent_info_->CanRequestAds()); +} + +TEST_F(FirebaseGmaUmpTest, TestUmpCleanup) { + using firebase::gma::ump::ConsentFormStatus; + using firebase::gma::ump::ConsentRequestParameters; + using firebase::gma::ump::ConsentStatus; + + ConsentRequestParameters params; + params.tag_for_under_age_of_consent = false; + firebase::Future future_request = + consent_info_->RequestConsentInfoUpdate(params); + firebase::Future future_load = consent_info_->LoadConsentForm(); + firebase::Future future_show = consent_info_->ShowConsentForm(nullptr); + + delete consent_info_; + consent_info_ = nullptr; + + EXPECT_EQ(future_request.status(), firebase::kFutureStatusInvalid); + EXPECT_EQ(future_load.status(), firebase::kFutureStatusInvalid); + EXPECT_EQ(future_show.status(), firebase::kFutureStatusInvalid); +} + } // namespace firebase_testapp_automated diff --git a/gma/src/common/ump/consent_info.cc b/gma/src/common/ump/consent_info.cc new file mode 100644 index 0000000000..89f2ed0410 --- /dev/null +++ b/gma/src/common/ump/consent_info.cc @@ -0,0 +1,177 @@ +/* + * 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. + */ + +#include "firebase/gma/ump/consent_info.h" + +#include "app/src/assert.h" +#include "firebase/app.h" +#include "firebase/gma/ump.h" +#include "firebase/internal/platform.h" +#include "gma/src/common/ump/consent_info_internal.h" + +namespace firebase { +namespace gma { +namespace ump { + +ConsentInfo* ConsentInfo::s_instance_ = nullptr; + +ConsentInfo* ConsentInfo::GetInstance(const ::firebase::App& app, + ::firebase::InitResult* init_result_out) { + if (s_instance_) { + if (init_result_out) *init_result_out = kInitResultSuccess; + return s_instance_; + } +#if FIREBASE_PLATFORM_ANDROID + return GetInstance(app.GetJNIEnv(), app.activity(), init_result_out); +#else // !FIREBASE_PLATFORM_ANDROID + return GetInstance(init_result_out); +#endif // FIREBASE_PLATFORM_ANDROID +} + +#if FIREBASE_PLATFORM_ANDROID +ConsentInfo* ConsentInfo::GetInstance() { return s_instance_; } + +ConsentInfo* ConsentInfo::GetInstance(JNIEnv* jni_env, jobject activity, + ::firebase::InitResult* init_result_out) { +#else // !FIREBASE_PLATFORM_ANDROID +ConsentInfo* ConsentInfo::GetInstance(::firebase::InitResult* init_result_out) { +#endif + if (s_instance_) { + if (init_result_out) *init_result_out = kInitResultSuccess; + return s_instance_; + } + + ConsentInfo* consent_info = new ConsentInfo(); +#if FIREBASE_PLATFORM_ANDROID + InitResult result = + consent_info->Initialize(/* jni_env, activity */); // TODO(b/291622888) +#else + InitResult result = consent_info->Initialize(); +#endif + if (result != kInitResultSuccess) { + if (init_result_out) *init_result_out = result; + delete consent_info; + return nullptr; + } + return consent_info; +} + +ConsentInfo::ConsentInfo() { + internal_ = nullptr; +#if FIREBASE_PLATFORM_ANDROID + java_vm_ = nullptr; +#endif + s_instance_ = this; +} + +ConsentInfo::~ConsentInfo() { + if (internal_) { + delete internal_; + internal_ = nullptr; + } + s_instance_ = nullptr; +} + +InitResult ConsentInfo::Initialize() { + FIREBASE_ASSERT(!internal_); + internal_ = internal::ConsentInfoInternal::CreateInstance(); + return kInitResultSuccess; +} + +// Below this, everything is a passthrough to ConsentInfoInternal. If there is +// no internal_ pointer (e.g. it's been cleaned up), return default values and +// invalid futures. + +ConsentStatus ConsentInfo::GetConsentStatus() { + if (!internal_) return kConsentStatusUnknown; + return internal_->GetConsentStatus(); +} + +ConsentFormStatus ConsentInfo::GetConsentFormStatus() { + if (!internal_) return kConsentFormStatusUnknown; + return internal_->GetConsentFormStatus(); +} + +Future ConsentInfo::RequestConsentInfoUpdate( + const ConsentRequestParameters& params) { + if (!internal_) return Future(); + return internal_->RequestConsentInfoUpdate(params); +} + +Future ConsentInfo::RequestConsentInfoUpdateLastResult() { + if (!internal_) return Future(); + return internal_->RequestConsentInfoUpdateLastResult(); +} + +Future ConsentInfo::LoadConsentForm() { + if (!internal_) return Future(); + return internal_->LoadConsentForm(); +} + +Future ConsentInfo::LoadConsentFormLastResult() { + if (!internal_) return Future(); + return internal_->LoadConsentFormLastResult(); +} + +Future ConsentInfo::ShowConsentForm(FormParent parent) { + if (!internal_) return Future(); + return internal_->ShowConsentForm(parent); +} + +Future ConsentInfo::ShowConsentFormLastResult() { + if (!internal_) return Future(); + return internal_->ShowConsentFormLastResult(); +} + +Future ConsentInfo::LoadAndShowConsentFormIfRequired(FormParent parent) { + if (!internal_) return Future(); + return internal_->LoadAndShowConsentFormIfRequired(parent); +} + +Future ConsentInfo::LoadAndShowConsentFormIfRequiredLastResult() { + if (!internal_) return Future(); + return internal_->LoadAndShowConsentFormIfRequiredLastResult(); +} + +PrivacyOptionsRequirementStatus +ConsentInfo::GetPrivacyOptionsRequirementStatus() { + if (!internal_) return kPrivacyOptionsRequirementStatusUnknown; + return internal_->GetPrivacyOptionsRequirementStatus(); +} + +Future ConsentInfo::ShowPrivacyOptionsForm(FormParent parent) { + if (!internal_) return Future(); + return internal_->ShowPrivacyOptionsForm(parent); +} + +Future ConsentInfo::ShowPrivacyOptionsFormLastResult() { + if (!internal_) return Future(); + return internal_->ShowPrivacyOptionsFormLastResult(); +} + +bool ConsentInfo::CanRequestAds() { + if (!internal_) return false; + return internal_->CanRequestAds(); +} + +void ConsentInfo::Reset() { + if (!internal_) return; + internal_->Reset(); +} + +} // namespace ump +} // namespace gma +} // namespace firebase diff --git a/gma/src/common/ump/consent_info_internal.cc b/gma/src/common/ump/consent_info_internal.cc new file mode 100644 index 0000000000..ef58873575 --- /dev/null +++ b/gma/src/common/ump/consent_info_internal.cc @@ -0,0 +1,90 @@ +/* + * 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. + */ + +#include "gma/src/common/ump/consent_info_internal.h" + +#include "app/src/include/firebase/internal/platform.h" + +namespace firebase { +namespace gma { +namespace ump { +namespace internal { + +ConsentInfoInternal::ConsentInfoInternal() : futures_(kConsentInfoFnCount) {} + +ConsentInfoInternal::~ConsentInfoInternal() {} + +const char* ConsentInfoInternal::GetConsentRequestErrorMessage( + ConsentRequestError error_code) { + switch (error_code) { + case kConsentRequestSuccess: + return "Success"; + case kConsentRequestErrorInvalidAppId: +#if FIREBASE_PLATFORM_ANDROID + return "Missing or invalid com.google.android.gms.ads.APPLICATION_ID in " + "AndroidManifest.xml"; +#elif FIREBASE_PLATFORM_IOS + return "Missing or invalid GADApplicationidentifier in Info.plist"; +#else + return "Missing or invalid App ID"; +#endif + case kConsentRequestErrorNetwork: + return "Network error"; + case kConsentRequestErrorInternal: + return "Internal error"; + case kConsentRequestErrorCodeMisconfiguration: + return "A misconfiguration exists in the UI"; + case kConsentRequestErrorUnknown: + return "Unknown error"; + case kConsentRequestErrorInvalidOperation: + return "Invalid operation"; + case kConsentRequestErrorOperationInProgress: + return "Operation already in progress. Please wait for it to finish by " + "checking RequestConsentInfoUpdateLastResult()."; + default: + return "Bad error code"; + } +} + +const char* ConsentInfoInternal::GetConsentFormErrorMessage( + ConsentFormError error_code) { + switch (error_code) { + case kConsentFormSuccess: + return "Success"; + case kConsentFormErrorTimeout: + return "Timed out"; + case kConsentFormErrorUnavailable: + return "The form is unavailable."; + case kConsentFormErrorInternal: + return "Internal error"; + case kConsentFormErrorUnknown: + return "Unknown error"; + case kConsentFormErrorCodeAlreadyUsed: + return "Code already used"; + case kConsentFormErrorInvalidOperation: + return "Invalid operation"; + case kConsentFormErrorOperationInProgress: + return "Operation already in progress. Please wait for it to finish by " + "checking LoadFormLastResult() or ShowFormLastResult()."; + default: + return "Bad error code"; + } +} + +} // namespace internal +} // namespace ump +} // namespace gma +} // namespace firebase diff --git a/gma/src/common/ump/consent_info_internal.h b/gma/src/common/ump/consent_info_internal.h new file mode 100644 index 0000000000..3438526ef0 --- /dev/null +++ b/gma/src/common/ump/consent_info_internal.h @@ -0,0 +1,133 @@ +/* + * 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. + */ + +#ifndef FIREBASE_GMA_SRC_COMMON_UMP_CONSENT_INFO_INTERNAL_H_ +#define FIREBASE_GMA_SRC_COMMON_UMP_CONSENT_INFO_INTERNAL_H_ + +#include "app/src/cleanup_notifier.h" +#include "app/src/reference_counted_future_impl.h" +#include "firebase/future.h" +#include "firebase/gma/ump.h" +#include "firebase/gma/ump/types.h" + +namespace firebase { +namespace gma { +namespace ump { +namespace internal { + +// Constants representing each ConsentInfo function that returns a Future. +enum ConsentInfoFn { + kConsentInfoFnRequestConsentInfoUpdate, + kConsentInfoFnLoadConsentForm, + kConsentInfoFnShowConsentForm, + kConsentInfoFnLoadAndShowConsentFormIfRequired, + kConsentInfoFnShowPrivacyOptionsForm, + kConsentInfoFnCount +}; + +class ConsentInfoInternal { + public: + virtual ~ConsentInfoInternal(); + + // Implemented in platform-specific code to instantiate a + // platform-specific subclass. + static ConsentInfoInternal* CreateInstance(); + + virtual ConsentStatus GetConsentStatus() const = 0; + virtual ConsentFormStatus GetConsentFormStatus() const = 0; + + virtual Future RequestConsentInfoUpdate( + const ConsentRequestParameters& params) = 0; + Future RequestConsentInfoUpdateLastResult() { + return static_cast&>( + futures()->LastResult(kConsentInfoFnRequestConsentInfoUpdate)); + } + virtual Future LoadConsentForm() = 0; + + Future LoadConsentFormLastResult() { + return static_cast&>( + futures()->LastResult(kConsentInfoFnLoadConsentForm)); + } + + virtual Future ShowConsentForm(FormParent parent) = 0; + + Future ShowConsentFormLastResult() { + return static_cast&>( + futures()->LastResult(kConsentInfoFnShowConsentForm)); + } + + virtual Future LoadAndShowConsentFormIfRequired(FormParent parent) = 0; + + Future LoadAndShowConsentFormIfRequiredLastResult() { + return static_cast&>( + futures()->LastResult(kConsentInfoFnLoadAndShowConsentFormIfRequired)); + } + + virtual PrivacyOptionsRequirementStatus + GetPrivacyOptionsRequirementStatus() = 0; + + virtual Future ShowPrivacyOptionsForm(FormParent parent) = 0; + + Future ShowPrivacyOptionsFormLastResult() { + return static_cast&>( + futures()->LastResult(kConsentInfoFnShowPrivacyOptionsForm)); + } + + virtual bool CanRequestAds() = 0; + + virtual void Reset() = 0; + + protected: + ConsentInfoInternal(); + + static const char* GetConsentRequestErrorMessage( + ConsentRequestError error_code); + + static const char* GetConsentFormErrorMessage(ConsentFormError error_code); + + SafeFutureHandle CreateFuture() { return futures()->SafeAlloc(); } + SafeFutureHandle CreateFuture(ConsentInfoFn fn_idx) { + return futures()->SafeAlloc(fn_idx); + } + + // Complete a Future with the given error code. + void CompleteFuture(SafeFutureHandle handle, ConsentRequestError error, + const char* message = nullptr) { + return futures()->Complete( + handle, error, + message ? message : GetConsentRequestErrorMessage(error)); + } + // Complete the future with the given error code. + void CompleteFuture(SafeFutureHandle handle, ConsentFormError error, + const char* message = nullptr) { + return futures()->Complete( + handle, error, message ? message : GetConsentFormErrorMessage(error)); + } + + ReferenceCountedFutureImpl* futures() { return &futures_; } + CleanupNotifier* cleanup() { return &cleanup_; } + + private: + ReferenceCountedFutureImpl futures_; + CleanupNotifier cleanup_; +}; + +} // namespace internal +} // namespace ump +} // namespace gma +} // namespace firebase + +#endif // FIREBASE_GMA_SRC_COMMON_UMP_CONSENT_INFO_INTERNAL_H_ diff --git a/gma/src/include/firebase/gma/ump.h b/gma/src/include/firebase/gma/ump.h new file mode 100644 index 0000000000..26e90d630b --- /dev/null +++ b/gma/src/include/firebase/gma/ump.h @@ -0,0 +1,23 @@ +/* + * 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. + */ + +#ifndef FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_UMP_H_ +#define FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_UMP_H_ + +#include "firebase/gma/ump/consent_info.h" +#include "firebase/gma/ump/types.h" + +#endif // FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_UMP_H_ diff --git a/gma/src/include/firebase/gma/ump/consent_info.h b/gma/src/include/firebase/gma/ump/consent_info.h new file mode 100644 index 0000000000..32abf089f8 --- /dev/null +++ b/gma/src/include/firebase/gma/ump/consent_info.h @@ -0,0 +1,228 @@ +/* + * 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. + */ + +#ifndef FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_UMP_CONSENT_INFO_H_ +#define FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_UMP_CONSENT_INFO_H_ + +#include "firebase/app.h" +#include "firebase/future.h" +#include "firebase/gma/ump/types.h" +#include "firebase/internal/platform.h" + +#if FIREBASE_PLATFORM_ANDROID +#include +#endif // FIREBASE_PLATFORM_ANDROID + +namespace firebase { +namespace gma { +/// @brief API for User Messaging Platform. +/// +/// The User Messaging Platform (UMP) SDK is Google’s option to handle user +/// privacy and consent in mobile apps. +namespace ump { + +namespace internal { +// Forward declaration for platform-specific data, implemented in each library. +class ConsentInfoInternal; +} // namespace internal + +/// This class contains all of the methods necessary for obtaining +/// consent from the user. +class ConsentInfo { + public: + /// Shut down the User Messaging Platform Consent SDK + ~ConsentInfo(); + + /// Initializes the User Messaging Platform Consent SDK. + /// + /// @param[in] app Any Firebase App instance. + /// + /// @param[out] init_result_out Optional: If provided, write the basic init + /// result here. kInitResultSuccess if initialization succeeded, or + /// kInitResultFailedMissingDependency on Android if there are Android + /// dependencies missing. + /// + /// @return A pointer to the ConsentInfo instance if UMP was successfully + /// initialized, nullptr otherwise. Each call to GetInstance() will return the + /// same pointer; when you are finished using the SDK, you can delete the + /// pointer and the UMP SDK will shut down. + static ConsentInfo* GetInstance(const ::firebase::App& app, + InitResult* init_result_out = nullptr); + +#if FIREBASE_PLATFORM_ANDROID || defined(DOXYGEN) + /// Initializes the User Messaging Platform Consent SDK without Firebase for + /// Android. + /// + /// The arguments to GetInstance() are platform-specific so the caller must + /// do something like this: + /// @code + /// #if defined(__ANDROID__) + /// consent_info = firebase::gma::ump::ConsentInfo::GetInstance(jni_env, + /// activity); #else consent_info = firebase::gma::ump::GetInstance(); #endif + /// @endcode + /// + /// @param[in] jni_env JNIEnv pointer. + /// @param[in] activity Activity used to start the application. + /// @param[out] init_result_out Optional: If provided, write the basic init + /// result here. kInitResultSuccess if initialization succeeded, or + /// kInitResultFailedMissingDependency on Android if there are Android + /// dependencies missing. + /// + /// @return A pointer to the ConsentInfo instance if UMP was successfully + /// initialized, nullptr otherwise. Each call to GetInstance() will return the + /// same pointer; when you are finished using the SDK, you can delete the + /// pointer and the UMP SDK will shut down. + static ConsentInfo* GetInstance(JNIEnv* jni_env, jobject activity, + InitResult* init_result_out = nullptr); + +#if !defined(DOXYGEN) + // On Android, this convenience function exists so you can easily get the + // existing ConsentInfo instance after it's first initialized. Returns nullptr + // if no instance has been created yet; make sure you have called + // GetInstance(JNIEnv*, jobject) first. + static ConsentInfo* GetInstance(); +#endif // defined(DOXYGEN) +#endif // FIREBASE_PLATFORM_ANDROID || defined(DOXYGEN) + +#if !FIREBASE_PLATFORM_ANDROID || defined(DOXYGEN) + /// Initializes User Messaging Platform for iOS without Firebase. + /// + /// @param[out] init_result_out Optional: If provided, write the basic init + /// result here. kInitResultSuccess if initialization succeeded, or + /// kInitResultFailedMissingDependency if a dependency is missing. On iOS, + /// this will always return kInitResultSuccess, as missing dependencies would + /// have caused a linker error at build time. + /// + /// @return A pointer to the ConsentInfo instance. Each call to GetInstance() + /// will return the same pointer; when you are finished using the SDK, you can + /// delete the pointer, and the UMP SDK will shut down. + static ConsentInfo* GetInstance(InitResult* init_result_out = nullptr); +#endif // !defined(__ANDROID__) || defined(DOXYGEN) + + /// The user’s consent status. This value defaults to kConsentStatusUnknown + /// until RequestConsentInfoUpdate() is called, and defaults to the previous + /// session’s value until RequestConsentInfoUpdate() completes. + ConsentStatus GetConsentStatus(); + + /// Requests consent information update. Must be called in every app session + /// before checking the user’s consent status or loading a consent form. After + /// calling this method, GetConsentStatus() will be updated immediately to + /// hold the consent state from the previous app session, if one + /// exists. GetConsentStatus() may be updated again immediately before the + /// returned future is completed. + Future RequestConsentInfoUpdate(const ConsentRequestParameters& params); + + /// Get the Future from the most recent call to RequestConsentInfoUpdate(). + Future RequestConsentInfoUpdateLastResult(); + + /// Consent form status. This value defaults to kConsentFormStatusUnknown and + /// requires a call to RequestConsentInfoUpdate() to update. + ConsentFormStatus GetConsentFormStatus(); + + /// Loads a consent form. Returns an error if the consent form is unavailable + /// or cannot be loaded. + Future LoadConsentForm(); + + /// Get the Future from the most recent call to LoadConsentForm(). + Future LoadConsentFormLastResult(); + + /// Presents the full screen consent form using the given FormParent, which is + /// defined as an Activity on Android and a UIViewController on iOS. The form + /// will be dismissed and the Future will be completed after the user selects + /// an option. + /// + /// GetConsentStatus() is updated when the returned Future is completed. + /// + /// @note You must call LoadConsentForm() and wait for it to complete before + /// calling this method. + Future ShowConsentForm(FormParent parent); + + /// Get the Future from the most recent call to ShowConsentForm(). + Future ShowConsentFormLastResult(); + + /// Loads a consent form and immediately presents it using the given + /// FormParent, if ConsentStatus is kConsentStatusRequired. The FormParent is + /// defined as an Activity on Android and a UIViewController on iOS. The + /// Future will be completed successfully after the user selects an option + /// (and the form is dismissed), or if the form is not required. The Future + /// will be completed with an error if the form fails to load or show. + /// + /// GetConsentStatus() will be updated prior to the Future being completed. + Future LoadAndShowConsentFormIfRequired(FormParent parent); + + /// Get the Future from the most recent call to + /// LoadAndShowConsentFormIfRequired(). + Future LoadAndShowConsentFormIfRequiredLastResult(); + + /// Check whether the privacy options form needs to be displayed. + /// This is updated by RequestConsentInfoUpdate(). + PrivacyOptionsRequirementStatus GetPrivacyOptionsRequirementStatus(); + + /// If GetPrivacyOptionsRequirementStatus() is + /// kPrivacyOptionsRequirementStatusRequired, presents a privacy options form + /// from the provided FormParent, which is defined as an Activity on Android + /// and a UIViewController on iOS. + /// + /// This method should only be called in response to a user input to request a + /// privacy options form to be shown. + /// + /// The future completes when the user selects an option and dismisses the + /// form or is completed immediately with an error code if no form is + /// presented. The privacy options form is preloaded by the SDK automatically + /// when a form becomes available. If no form has been preloaded, the SDK will + /// try to load one asynchronously. + Future ShowPrivacyOptionsForm(FormParent parent); + + /// Get the Future from the most recent call to ShowPrivacyOptionsForm(). + Future ShowPrivacyOptionsFormLastResult(); + + /// Indicates whether the app has completed the necessary steps for gathering + /// updated user consent. Returns true if RequestConsentInfoUpdate() has been + /// called and GetConsentStatus returns either kConsentStatusNotRequired or + /// kConsentStatusObtained. + bool CanRequestAds(); + + /// Clears all consent state from persistent storage. This can be used in + /// development to simulate a new installation. + void Reset(); + + private: + ConsentInfo(); +#if FIREBASE_PLATFORM_ANDROID + // TODO(b/291622888) Implement Android-specific Initialize.. + InitResult Initialize(/* JNIEnv* jni_env, jobject activity */); +#else + InitResult Initialize(); +#endif // FIREBASE_PLATFORM_ANDROID + void Terminate(); + + static ConsentInfo* s_instance_; + +#if FIREBASE_PLATFORM_ANDROID + JavaVM* java_vm() { return java_vm_; } + JavaVM* java_vm_; +#endif + + // An internal, platform-specific implementation object that this class uses + // to interact with the User Messaging Platform SDKs for iOS and Android. + internal::ConsentInfoInternal* internal_; +}; + +} // namespace ump +} // namespace gma +} // namespace firebase + +#endif // FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_UMP_CONSENT_INFO_H_ diff --git a/gma/src/include/firebase/gma/ump/types.h b/gma/src/include/firebase/gma/ump/types.h new file mode 100644 index 0000000000..bf380d16be --- /dev/null +++ b/gma/src/include/firebase/gma/ump/types.h @@ -0,0 +1,179 @@ +/* + * 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. + */ + +#ifndef FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_UMP_TYPES_H_ +#define FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_UMP_TYPES_H_ + +#include +#include +#include + +#include "firebase/internal/platform.h" + +#if FIREBASE_PLATFORM_ANDROID +#include +#elif FIREBASE_PLATFORM_IOS || FIREBASE_PLATFORM_TVOS +extern "C" { +#include +} // extern "C" +#endif // FIREBASE_PLATFORM_ANDROID, FIREBASE_PLATFORM_IOS, + // FIREBASE_PLATFORM_TVOS + +namespace firebase { +namespace gma { +namespace ump { + +/// Debug values for testing geography. +enum ConsentDebugGeography { + /// Disable geography debugging. + kConsentDebugGeographyDisabled = 0, + /// Geography appears as in EEA (European Economic Area) for debug devices. + kConsentDebugGeographyEEA, + /// Geography appears as not in EEA for debug devices. + kConsentDebugGeographyNonEEA +}; + +/// Debug settings for `ConsentInfo::RequestConsentInfoUpdate()`. These let you +/// force a speific geographic location. Be sure to include debug device IDs to +/// enable this on hardware. Debug features are always enabled for simulators. +struct ConsentDebugSettings { + /// Create a default debug setting, with debugging disabled. + ConsentDebugSettings() : debug_geography(kConsentDebugGeographyDisabled) {} + + /// The geographical location, for debugging. + ConsentDebugGeography debug_geography; + /// A list of all device IDs that are allowed to use debug settings. You can + /// obtain this from the device log after running with debug settings enabled. + std::vector debug_device_ids; +}; + +/// Parameters for the `ConsentInfo::RequestConsentInfoUpdate()` operation. +struct ConsentRequestParameters { + ConsentRequestParameters() : tag_for_under_age_of_consent(false) {} + + /// Debug settings for the consent request. + ConsentDebugSettings debug_settings; + + /// Whether the user is under the age of consent. + bool tag_for_under_age_of_consent; +}; + +/// This is a platform specific datatype that is required to show a consent form +/// on screen. +/// +/// The following defines the datatype on each platform: +///
    +///
  • Android: A `jobject` which references an Android Activity.
  • +///
  • iOS: An `id` which references an iOS UIViewController.
  • +///
+#if FIREBASE_PLATFORM_ANDROID +/// An Android Activity from Java. +typedef jobject FormParent; +#elif FIREBASE_PLATFORM_IOS || FIREBASE_PLATFORM_TVOS +/// A pointer to an iOS UIViewController. +typedef id FormParent; +#else +/// A void pointer for stub classes. +typedef void* FormParent; +#endif // FIREBASE_PLATFORM_ANDROID, FIREBASE_PLATFORM_IOS, + // FIREBASE_PLATFORM_TVOS + +/// Consent status values. +enum ConsentStatus { + /// Unknown status, e.g. prior to calling Request, or if the request fails. + kConsentStatusUnknown = 0, + /// Consent is required, but not obtained + kConsentStatusRequired, + /// Consent is not required + kConsentStatusNotRequired, + /// Consent was required, and has been obtained + kConsentStatusObtained +}; + +/// Errors that can occur during a RequestConsentInfoUpdate operation. +enum ConsentRequestError { + /// The operation succeeded. + kConsentRequestSuccess = 0, + /// Invalid GMA App ID specified in AndroidManifest.xml or Info.plist. + kConsentRequestErrorInvalidAppId, + /// A network error occurred. + kConsentRequestErrorNetwork, + /// An internal error occurred. + kConsentRequestErrorInternal, + /// A misconfiguration exists in the UI. + kConsentRequestErrorCodeMisconfiguration, + /// An unknown error occurred. + kConsentRequestErrorUnknown, + /// An invalid operation occurred. Try again. + kConsentRequestErrorInvalidOperation, + /// The operation is already in progress. Use + /// `ConsentInfo::RequestConsentInfoUpdateLastResult()` + /// to get the status. + kConsentRequestErrorOperationInProgress +}; + +/// Status of the consent form, whether it is available to show or not. +enum ConsentFormStatus { + /// Status is unknown. Call `ConsentInfo::RequestConsentInfoUpdate()` to + /// update this. + kConsentFormStatusUnknown = 0, + /// The consent form is unavailable. Call `ConsentInfo::LoadConsentForm()` to + /// load it. + kConsentFormStatusUnavailable, + /// The consent form is available. Call `ConsentInfo::ShowConsentForm()` to + /// display it. + kConsentFormStatusAvailable, +}; + +/// Errors when loading or showing the consent form. +enum ConsentFormError { + /// The operation succeeded. + kConsentFormSuccess = 0, + /// The load request timed out. Try again. + kConsentFormErrorTimeout, + /// An internal error occurred. + kConsentFormErrorInternal, + /// An unknown error occurred. + kConsentFormErrorUnknown, + /// The form is unavailable. + kConsentFormErrorUnavailable, + /// This form was already used. + kConsentFormErrorCodeAlreadyUsed, + /// An invalid operation occurred. Try again. + kConsentFormErrorInvalidOperation, + /// The operation is already in progress. Call + /// `ConsentInfo::LoadConsentFormLastResult()` or + /// `ConsentInfo::ShowConsentFormLastResult()` to get the status. + kConsentFormErrorOperationInProgress +}; + +/// Whether the privacy options need to be displayed. +enum PrivacyOptionsRequirementStatus { + /// Privacy options requirement status is unknown. Call + /// `ConsentInfo::RequestConsentInfoUpdate()` to update. + kPrivacyOptionsRequirementStatusUnknown = 0, + /// Privacy options are not required to be shown. + kPrivacyOptionsRequirementStatusNotRequired, + /// Privacy options must be shown. Call + /// `ConsentInfo::ShowPrivacyOptionsForm()` to fulfil this requirement. + kPrivacyOptionsRequirementStatusRequired +}; + +} // namespace ump +} // namespace gma +} // namespace firebase + +#endif // FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_UMP_TYPES_H_ diff --git a/gma/src/stub/ump/consent_info_internal_stub.cc b/gma/src/stub/ump/consent_info_internal_stub.cc new file mode 100644 index 0000000000..8f4d033896 --- /dev/null +++ b/gma/src/stub/ump/consent_info_internal_stub.cc @@ -0,0 +1,164 @@ +/* + * 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. + */ + +#include "gma/src/stub/ump/consent_info_internal_stub.h" + +#include "app/src/thread.h" + +namespace firebase { +namespace gma { +namespace ump { +namespace internal { + +// This explicitly implements the constructor for the outer class, +// ConsentInfoInternal. +ConsentInfoInternal* ConsentInfoInternal::CreateInstance() { + return new ConsentInfoInternalStub(); +} + +ConsentInfoInternalStub::ConsentInfoInternalStub() + : consent_status_(kConsentStatusUnknown), + consent_form_status_(kConsentFormStatusUnknown), + privacy_options_requirement_status_( + kPrivacyOptionsRequirementStatusUnknown), + under_age_of_consent_(false), + debug_geo_(kConsentDebugGeographyDisabled) {} + +ConsentInfoInternalStub::~ConsentInfoInternalStub() {} + +Future ConsentInfoInternalStub::RequestConsentInfoUpdate( + const ConsentRequestParameters& params) { + SafeFutureHandle handle = + CreateFuture(kConsentInfoFnRequestConsentInfoUpdate); + + // See the header file for an explanation of these default settings. + ConsentStatus new_consent_status = kConsentStatusObtained; + PrivacyOptionsRequirementStatus new_privacy_req = + kPrivacyOptionsRequirementStatusNotRequired; + // Simulate consent status based on debug geo. + if (params.debug_settings.debug_geography == kConsentDebugGeographyEEA) { + new_consent_status = kConsentStatusRequired; + } else if (params.debug_settings.debug_geography == + kConsentDebugGeographyNonEEA) { + new_consent_status = kConsentStatusNotRequired; + } + + consent_status_ = new_consent_status; + under_age_of_consent_ = params.tag_for_under_age_of_consent; + consent_form_status_ = under_age_of_consent_ ? kConsentFormStatusUnavailable + : kConsentFormStatusAvailable; + debug_geo_ = params.debug_settings.debug_geography; + privacy_options_requirement_status_ = + kPrivacyOptionsRequirementStatusNotRequired; + + CompleteFuture(handle, kConsentRequestSuccess); + return MakeFuture(futures(), handle); +} + +Future ConsentInfoInternalStub::LoadConsentForm() { + SafeFutureHandle handle = CreateFuture(kConsentInfoFnLoadConsentForm); + + if (consent_form_status_ != kConsentFormStatusAvailable) { + CompleteFuture(handle, kConsentFormErrorUnavailable); + return MakeFuture(futures(), handle); + } + CompleteFuture(handle, kConsentFormSuccess); + return MakeFuture(futures(), handle); +} + +Future ConsentInfoInternalStub::ShowConsentForm(FormParent parent) { + SafeFutureHandle handle = CreateFuture(kConsentInfoFnShowConsentForm); + + consent_status_ = kConsentStatusObtained; + + if (debug_geo_ == kConsentDebugGeographyEEA) { + privacy_options_requirement_status_ = + kPrivacyOptionsRequirementStatusRequired; + } else if (debug_geo_ == kConsentDebugGeographyNonEEA) { + privacy_options_requirement_status_ = + kPrivacyOptionsRequirementStatusNotRequired; + } else { // no debug option + privacy_options_requirement_status_ = + kPrivacyOptionsRequirementStatusNotRequired; + } + + CompleteFuture(handle, kConsentRequestSuccess); + return MakeFuture(futures(), handle); +} + +Future ConsentInfoInternalStub::LoadAndShowConsentFormIfRequired( + FormParent parent) { + SafeFutureHandle handle = + CreateFuture(kConsentInfoFnLoadAndShowConsentFormIfRequired); + + if (consent_form_status_ != kConsentFormStatusAvailable) { + CompleteFuture(handle, kConsentFormErrorUnavailable); + return MakeFuture(futures(), handle); + } + + if (consent_status_ == kConsentStatusRequired) { + consent_status_ = kConsentStatusObtained; + if (debug_geo_ == kConsentDebugGeographyEEA) { + privacy_options_requirement_status_ = + kPrivacyOptionsRequirementStatusRequired; + } else if (debug_geo_ == kConsentDebugGeographyNonEEA) { + privacy_options_requirement_status_ = + kPrivacyOptionsRequirementStatusNotRequired; + } else { // no debug option + privacy_options_requirement_status_ = + kPrivacyOptionsRequirementStatusNotRequired; + } + } + CompleteFuture(handle, kConsentRequestSuccess); + return MakeFuture(futures(), handle); +} + +PrivacyOptionsRequirementStatus +ConsentInfoInternalStub::GetPrivacyOptionsRequirementStatus() { + return privacy_options_requirement_status_; +} + +Future ConsentInfoInternalStub::ShowPrivacyOptionsForm( + FormParent parent) { + SafeFutureHandle handle = + CreateFuture(kConsentInfoFnShowPrivacyOptionsForm); + + if (consent_status_ == kConsentStatusObtained) { + consent_status_ = kConsentStatusRequired; + privacy_options_requirement_status_ = + kPrivacyOptionsRequirementStatusNotRequired; + } + CompleteFuture(handle, kConsentRequestSuccess); + return MakeFuture(futures(), handle); +} + +bool ConsentInfoInternalStub::CanRequestAds() { + bool consent_status_ok = (consent_status_ == kConsentStatusObtained || + consent_status_ == kConsentStatusNotRequired); + bool privacy_options_ok = (privacy_options_requirement_status_ != + kPrivacyOptionsRequirementStatusUnknown); + return consent_status_ok && privacy_options_ok; +} + +void ConsentInfoInternalStub::Reset() { + consent_status_ = kConsentStatusUnknown; + consent_form_status_ = kConsentFormStatusUnknown; +} + +} // namespace internal +} // namespace ump +} // namespace gma +} // namespace firebase diff --git a/gma/src/stub/ump/consent_info_internal_stub.h b/gma/src/stub/ump/consent_info_internal_stub.h new file mode 100644 index 0000000000..23d44dfbaa --- /dev/null +++ b/gma/src/stub/ump/consent_info_internal_stub.h @@ -0,0 +1,86 @@ +/* + * 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. + */ + +#ifndef FIREBASE_GMA_SRC_STUB_UMP_CONSENT_INFO_INTERNAL_STUB_H_ +#define FIREBASE_GMA_SRC_STUB_UMP_CONSENT_INFO_INTERNAL_STUB_H_ + +#include "gma/src/common/ump/consent_info_internal.h" + +namespace firebase { +namespace gma { +namespace ump { +namespace internal { + +// The stub interface implements a few specific workflows, for testing: +// +// Before requesting: consent and privacy options requirement will be Unknown. +// +// After requesting: +// +// If debug_geography == EEA, consent will be Required, privacy options +// NotRequired. After calling ShowConsentForm() or +// LoadAndShowConsentFormIfRequired(), it will change to change to Obtained and +// privacy options will become Required, and when the privacy options form is +// shown, consent will go back to Required. +// +// If debug_geography == NonEEA, consent will be NotRequired. No privacy options +// form is required. +// +// If debug_geography == Disabled, consent will be Obtained and privacy options +// will be NotRequired. +// +// If tag_for_under_age_of_consent = true, LoadConsentForm and +// LoadAndShowConsentFormIfRequired will fail with kConsentFormErrorUnavailable. +// +// CanRequestAds returns true if consent is NotRequired or Obtained. +class ConsentInfoInternalStub : public ConsentInfoInternal { + public: + ConsentInfoInternalStub(); + ~ConsentInfoInternalStub() override; + + ConsentStatus GetConsentStatus() const override { return consent_status_; } + ConsentFormStatus GetConsentFormStatus() const override { + return consent_form_status_; + } + + Future RequestConsentInfoUpdate( + const ConsentRequestParameters& params) override; + Future LoadConsentForm() override; + Future ShowConsentForm(FormParent parent) override; + + Future LoadAndShowConsentFormIfRequired(FormParent parent) override; + + PrivacyOptionsRequirementStatus GetPrivacyOptionsRequirementStatus() override; + Future ShowPrivacyOptionsForm(FormParent parent) override; + + bool CanRequestAds() override; + + void Reset() override; + + private: + ConsentStatus consent_status_; + ConsentFormStatus consent_form_status_; + PrivacyOptionsRequirementStatus privacy_options_requirement_status_; + ConsentDebugGeography debug_geo_; + bool under_age_of_consent_; +}; + +} // namespace internal +} // namespace ump +} // namespace gma +} // namespace firebase + +#endif // FIREBASE_GMA_SRC_STUB_UMP_CONSENT_INFO_INTERNAL_STUB_H_