From 652bb7ec26c8c280e31ccc8390d35586e267a8ea Mon Sep 17 00:00:00 2001 From: Jon Simantov Date: Thu, 3 Aug 2023 12:48:35 -0700 Subject: [PATCH 01/26] Add iOS files, copies of stubs for now. --- gma/CMakeLists.txt | 2 +- gma/src/ios/ump/consent_info_internal_ios.h | 64 ++++++++ gma/src/ios/ump/consent_info_internal_ios.mm | 164 +++++++++++++++++++ 3 files changed, 229 insertions(+), 1 deletion(-) create mode 100644 gma/src/ios/ump/consent_info_internal_ios.h create mode 100644 gma/src/ios/ump/consent_info_internal_ios.mm diff --git a/gma/CMakeLists.txt b/gma/CMakeLists.txt index 440d0db7f0..844f061d09 100644 --- a/gma/CMakeLists.txt +++ b/gma/CMakeLists.txt @@ -54,7 +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/ump/consent_info_internal_ios.mm src/ios/FADAdSize.mm src/ios/FADAdView.mm src/ios/FADInterstitialDelegate.mm diff --git a/gma/src/ios/ump/consent_info_internal_ios.h b/gma/src/ios/ump/consent_info_internal_ios.h new file mode 100644 index 0000000000..f35fda694c --- /dev/null +++ b/gma/src/ios/ump/consent_info_internal_ios.h @@ -0,0 +1,64 @@ +/* + * 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_IOS_UMP_CONSENT_INFO_INTERNAL_IOS_H_ +#define FIREBASE_GMA_SRC_IOS_UMP_CONSENT_INFO_INTERNAL_IOS_H_ + +#include "gma/src/common/ump/consent_info_internal.h" + +namespace firebase { +namespace gma { +namespace ump { +namespace internal { + + class ConsentInfoInternalIos : public ConsentInfoInternal { + public: + ConsentInfoInternalIos(); + ~ConsentInfoInternalIos() 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_IOS_UMP_CONSENT_INFO_INTERNAL_IOS_H_ diff --git a/gma/src/ios/ump/consent_info_internal_ios.mm b/gma/src/ios/ump/consent_info_internal_ios.mm new file mode 100644 index 0000000000..cf8829b8c1 --- /dev/null +++ b/gma/src/ios/ump/consent_info_internal_ios.mm @@ -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/ios/ump/consent_info_internal_ios.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 ConsentInfoInternalIos(); +} + +ConsentInfoInternalIos::ConsentInfoInternalIos() + : consent_status_(kConsentStatusUnknown), + consent_form_status_(kConsentFormStatusUnknown), + privacy_options_requirement_status_( + kPrivacyOptionsRequirementStatusUnknown), + under_age_of_consent_(false), + debug_geo_(kConsentDebugGeographyDisabled) {} + +ConsentInfoInternalIos::~ConsentInfoInternalIos() {} + +Future ConsentInfoInternalIos::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 ConsentInfoInternalIos::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 ConsentInfoInternalIos::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 ConsentInfoInternalIos::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 +ConsentInfoInternalIos::GetPrivacyOptionsRequirementStatus() { + return privacy_options_requirement_status_; +} + +Future ConsentInfoInternalIos::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 ConsentInfoInternalIos::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 ConsentInfoInternalIos::Reset() { + consent_status_ = kConsentStatusUnknown; + consent_form_status_ = kConsentFormStatusUnknown; +} + +} // namespace internal +} // namespace ump +} // namespace gma +} // namespace firebase From 246ab64ac49c4fe01c44e90de71f6c1576fa3910 Mon Sep 17 00:00:00 2001 From: Jon Simantov Date: Thu, 3 Aug 2023 14:12:45 -0700 Subject: [PATCH 02/26] Format code. --- gma/src/ios/ump/consent_info_internal_ios.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gma/src/ios/ump/consent_info_internal_ios.h b/gma/src/ios/ump/consent_info_internal_ios.h index f35fda694c..6099ddb7b3 100644 --- a/gma/src/ios/ump/consent_info_internal_ios.h +++ b/gma/src/ios/ump/consent_info_internal_ios.h @@ -24,7 +24,7 @@ namespace gma { namespace ump { namespace internal { - class ConsentInfoInternalIos : public ConsentInfoInternal { +class ConsentInfoInternalIos : public ConsentInfoInternal { public: ConsentInfoInternalIos(); ~ConsentInfoInternalIos() override; From 8f54fad4b405d96054312bbfcc4828ecc37c074c Mon Sep 17 00:00:00 2001 From: Jon Simantov Date: Thu, 3 Aug 2023 15:03:54 -0700 Subject: [PATCH 03/26] Update cocoapod versions and add UMP pod. --- gma/integration_test/Podfile | 3 ++- ios_pod/Podfile | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/gma/integration_test/Podfile b/gma/integration_test/Podfile index 3943eeadd6..277693dfdd 100644 --- a/gma/integration_test/Podfile +++ b/gma/integration_test/Podfile @@ -5,7 +5,8 @@ use_frameworks! :linkage => :static target 'integration_test' do pod 'Firebase/CoreOnly', '10.12.0' - pod 'Google-Mobile-Ads-SDK', '10.8.0' + pod 'Google-Mobile-Ads-SDK', '10.9.0' + pod 'GoogleUserMessagingPlatform', '2.1.0' end post_install do |installer| diff --git a/ios_pod/Podfile b/ios_pod/Podfile index f45fd75556..9ba8fa1f07 100644 --- a/ios_pod/Podfile +++ b/ios_pod/Podfile @@ -5,7 +5,8 @@ use_frameworks! target 'GetPods' do pod 'Firebase/Core', '10.12.0' - pod 'Google-Mobile-Ads-SDK', '10.8.0' + pod 'Google-Mobile-Ads-SDK', '10.9.0' + pod 'GoogleUserMessagingPlatform', '2.1.0' pod 'Firebase/Analytics', '10.12.0' pod 'Firebase/AppCheck', '10.12.0' pod 'Firebase/Auth', '10.12.0' From 77384f5ed5546b717643e8676e8f2ea2d538157f Mon Sep 17 00:00:00 2001 From: Jon Simantov Date: Wed, 9 Aug 2023 15:16:39 -0700 Subject: [PATCH 04/26] Add iOS basics, also rename two enums. --- gma/CMakeLists.txt | 2 + gma/src/common/ump/consent_info_internal.cc | 6 +- gma/src/include/firebase/gma/ump/types.h | 4 +- gma/src/ios/ump/consent_info_internal_ios.h | 16 +- gma/src/ios/ump/consent_info_internal_ios.mm | 237 +++++++++++++------ 5 files changed, 174 insertions(+), 91 deletions(-) diff --git a/gma/CMakeLists.txt b/gma/CMakeLists.txt index 844f061d09..887139f1db 100644 --- a/gma/CMakeLists.txt +++ b/gma/CMakeLists.txt @@ -133,11 +133,13 @@ elseif(IOS) firebase_gma POD_NAMES Google-Mobile-Ads-SDK + GoogleUserMessagingPlatform ) # GMA expects the header files to be in a subfolder, so set up a symlink to # accomplish that. symlink_pod_headers(firebase_gma Google-Mobile-Ads-SDK GoogleMobileAds) + symlink_pod_headers(firebase_gma GoogleUserMessagingPlatform UserMessagingPlatform) if (FIREBASE_XCODE_TARGET_FORMAT STREQUAL "frameworks") set_target_properties(firebase_gma PROPERTIES diff --git a/gma/src/common/ump/consent_info_internal.cc b/gma/src/common/ump/consent_info_internal.cc index ef58873575..3a3e9cd4b8 100644 --- a/gma/src/common/ump/consent_info_internal.cc +++ b/gma/src/common/ump/consent_info_internal.cc @@ -45,7 +45,7 @@ const char* ConsentInfoInternal::GetConsentRequestErrorMessage( return "Network error"; case kConsentRequestErrorInternal: return "Internal error"; - case kConsentRequestErrorCodeMisconfiguration: + case kConsentRequestErrorMisconfiguration: return "A misconfiguration exists in the UI"; case kConsentRequestErrorUnknown: return "Unknown error"; @@ -72,8 +72,8 @@ const char* ConsentInfoInternal::GetConsentFormErrorMessage( return "Internal error"; case kConsentFormErrorUnknown: return "Unknown error"; - case kConsentFormErrorCodeAlreadyUsed: - return "Code already used"; + case kConsentFormErrorAlreadyUsed: + return "The form was already used"; case kConsentFormErrorInvalidOperation: return "Invalid operation"; case kConsentFormErrorOperationInProgress: diff --git a/gma/src/include/firebase/gma/ump/types.h b/gma/src/include/firebase/gma/ump/types.h index bf380d16be..af29182d16 100644 --- a/gma/src/include/firebase/gma/ump/types.h +++ b/gma/src/include/firebase/gma/ump/types.h @@ -114,7 +114,7 @@ enum ConsentRequestError { /// An internal error occurred. kConsentRequestErrorInternal, /// A misconfiguration exists in the UI. - kConsentRequestErrorCodeMisconfiguration, + kConsentRequestErrorMisconfiguration, /// An unknown error occurred. kConsentRequestErrorUnknown, /// An invalid operation occurred. Try again. @@ -151,7 +151,7 @@ enum ConsentFormError { /// The form is unavailable. kConsentFormErrorUnavailable, /// This form was already used. - kConsentFormErrorCodeAlreadyUsed, + kConsentFormErrorAlreadyUsed, /// An invalid operation occurred. Try again. kConsentFormErrorInvalidOperation, /// The operation is already in progress. Call diff --git a/gma/src/ios/ump/consent_info_internal_ios.h b/gma/src/ios/ump/consent_info_internal_ios.h index 6099ddb7b3..385df82f34 100644 --- a/gma/src/ios/ump/consent_info_internal_ios.h +++ b/gma/src/ios/ump/consent_info_internal_ios.h @@ -17,6 +17,8 @@ #ifndef FIREBASE_GMA_SRC_IOS_UMP_CONSENT_INFO_INTERNAL_IOS_H_ #define FIREBASE_GMA_SRC_IOS_UMP_CONSENT_INFO_INTERNAL_IOS_H_ +#include + #include "gma/src/common/ump/consent_info_internal.h" namespace firebase { @@ -29,13 +31,11 @@ class ConsentInfoInternalIos : public ConsentInfoInternal { ConsentInfoInternalIos(); ~ConsentInfoInternalIos() override; - ConsentStatus GetConsentStatus() const override { return consent_status_; } - ConsentFormStatus GetConsentFormStatus() const override { - return consent_form_status_; - } - + ConsentStatus GetConsentStatus() override; Future RequestConsentInfoUpdate( const ConsentRequestParameters& params) override; + + ConsentFormStatus GetConsentFormStatus() override; Future LoadConsentForm() override; Future ShowConsentForm(FormParent parent) override; @@ -49,11 +49,7 @@ class ConsentInfoInternalIos : public ConsentInfoInternal { void Reset() override; private: - ConsentStatus consent_status_; - ConsentFormStatus consent_form_status_; - PrivacyOptionsRequirementStatus privacy_options_requirement_status_; - ConsentDebugGeography debug_geo_; - bool under_age_of_consent_; + UMPConsentForm *loaded_form_; }; } // namespace internal diff --git a/gma/src/ios/ump/consent_info_internal_ios.mm b/gma/src/ios/ump/consent_info_internal_ios.mm index cf8829b8c1..07228ccebb 100644 --- a/gma/src/ios/ump/consent_info_internal_ios.mm +++ b/gma/src/ios/ump/consent_info_internal_ios.mm @@ -17,6 +17,7 @@ #include "gma/src/ios/ump/consent_info_internal_ios.h" #include "app/src/thread.h" +#include "app/src/util_ios.h" namespace firebase { namespace gma { @@ -30,72 +31,155 @@ } ConsentInfoInternalIos::ConsentInfoInternalIos() - : consent_status_(kConsentStatusUnknown), - consent_form_status_(kConsentFormStatusUnknown), - privacy_options_requirement_status_( - kPrivacyOptionsRequirementStatusUnknown), - under_age_of_consent_(false), - debug_geo_(kConsentDebugGeographyDisabled) {} + : loaded_form_(nil) {} ConsentInfoInternalIos::~ConsentInfoInternalIos() {} +static ConsentRequestError CppRequestErrorFromIosRequestError(UMPRequestErrorCode code) { + switch(code) { + case UMPRequestErrorCodeInternal: + return kConsentRequestErrorInternal; + case UMPRequestErrorCodeInvalidAppID: + return kConsentRequestErrorInvalidAppId; + case UMPRequestErrorCodeMisconfiguration: + return kConsentRequestErrorMisconfiguration; + case UMPRequestErrorCodeNetwork: + return kConsentRequestErrorNetwork; + default: + LogWarning("GMA: Unknown UMPRequestErrorCode returned by UMP iOS SDK: %d", + (int)(ios_status)); + return kConsentRequestErrorUnknown; + } +} + +static ConsentFormError CppFormErrorFromIosFormError(UMPFormErrorCode code) { + switch(code) { + case UMPFormErrorCodeInternal: + return kConsentFormErrorInternal; + case UMPFormErrorCodeAlreadyUsed: + return kConsentFormErrorAlreadyUsed; + case UMPFormErrorCodeUnavailable: + return kConsentFormErrorUnavailable; + case UMPFormErrorCodeTimeout: + return kConsentFormErrorTimeout; + case UMPFormErrorCodeInvalidViewController: + return kConsentFormErrorInvalidOperation; + default: + LogWarning("GMA: Unknown UMPFormErrorCode returned by UMP iOS SDK: %d", + (int)(ios_status)); + return kConsentFormErrorUnknown; + } +} + Future ConsentInfoInternalIos::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; + UMPRequestParameters *ios_parameters = [[UMPRequestParameters alloc] init]; + ios_parameters.tagForUnderAgeOfConsent = params.tag_for_under_age_of_consent ? YES : NO; + UMPDebugSettings *ios_debug_settings = [[UMPDebugSettings alloc] init]; + + switch(params.debug_settings.debug_geography) { + case kConsentDebugGeographyEEA: + ios_debug_settings.geography = UMPDebugGeographyEEA; + break; + case kConsentDebugGeographyNonEEA: + ios_debug_settings.geography = UMPDebugGeographyNonEEA; + break; + case kConsentDebugGeographyDisabled: + ios_debug_settings.geography = UMPDebugGeographyDisabled; + break; } + if (params.debug_settings.debug_device_ids.size() > 0) { + ios_debug_settings.testDeviceIdentifiers = + firebase::util::StringVectorToNSMutableArray(params.debug_settings.debug_device_ids); + } + ios_parameters.debugSettings = ios_debug_settings; + + [UMPConsentInformation.sharedInstance + requestConsentInfoUpdateWithParameters:ios_parameters + completionHandler:^(NSError *_Nullable error){ + if (!error) { + CompleteFuture(handle, kConsentRequestSuccess); + } else { + CompleteFuture(handle, CppRequestErrorFromIosRequestError(error.code),, error.message); + } + }]; - 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); } +ConsentStatus ConsentInfoInternalIos::GetConsentStatus() { + UMPConsentStatus ios_status = + UMPConsentInformation.sharedInstance.consentStatus; + switch(ios_status) { + case UMPConsentStatusNotRequired: + return kConsentStatusNotRequired; + case UMPConsentStatusRequired: + return kConsentStatusRequired; + case UMPConsentStatusObtained: + return kConsentStatusObtained; + case UMPConsentStatusUnknown: + return kConsentStatusUnknown; + default: + LogWarning("GMA: Unknown UMPConsentStatus returned by UMP iOS SDK: %d", + (int)(ios_status)); + return kConsentStatusUnknown; + } +} + + +ConsentFormStatus ConsentInfoInternalIos::GetConsentFormStatus() { + UMPFormStatus ios_status = + UMPConsentInformation.sharedInstance.formStatus; + switch(ios_status) { + case UMPFormStatusAvailable: + return kConsentFormStatusAvailable; + case UMPFormStatusUnavailable: + return kConsentFormStatusUnavailable; + case UMPFormStatusUnknown: + return kConsentFormStatusUnknown; + default: + LogWarning("GMA: Unknown UMPFormConsentStatus returned by UMP iOS SDK: %d", + (int)(ios_status)); + return kConsentFormStatusUnknown; + } +} + Future ConsentInfoInternalIos::LoadConsentForm() { SafeFutureHandle handle = CreateFuture(kConsentInfoFnLoadConsentForm); - if (consent_form_status_ != kConsentFormStatusAvailable) { - CompleteFuture(handle, kConsentFormErrorUnavailable); - return MakeFuture(futures(), handle); - } - CompleteFuture(handle, kConsentFormSuccess); + [UMPConsentForm + loadWithCompletionHandler:^(UMPConsentForm *_Nullable form, NSError *_Nullable error){ + if (form) { + loaded_form_ = form; + CompleteFuture(handle, kConsentFormSuccess, "Success"); + } else if (error) { + CompleteFuture(handle, CppFormErrorFromIosFormError(error.code), error.message); + } else { + CompleteFuture(handle, kConsentFormErrorUnknown, "An unknown error occurred."); + } + }]; return MakeFuture(futures(), handle); } Future ConsentInfoInternalIos::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; + if (!loaded_form_) { + CompleteFuture(handle, kConsentFormErrorInvalidOperation, + "You must call LoadConsentForm() prior to calling ShowConsentForm()."); + } else { + [loaded_form presentFromViewController:parent + completionHandler:^(NSError *_Nullable error){ + if (!error) { + CompleteFuture(handle, kConsentRequestSuccess); + } else { + CompleteFuture(handle, CppFormErrorFromIosFormError(error.code),, error.message); + } + }]; } - - CompleteFuture(handle, kConsentRequestSuccess); return MakeFuture(futures(), handle); } @@ -104,58 +188,59 @@ SafeFutureHandle handle = CreateFuture(kConsentInfoFnLoadAndShowConsentFormIfRequired); - if (consent_form_status_ != kConsentFormStatusAvailable) { - CompleteFuture(handle, kConsentFormErrorUnavailable); - return MakeFuture(futures(), handle); - } +Future ConsentInfoInternalIos::ShowConsentForm(FormParent parent) { + SafeFutureHandle handle = CreateFuture(kConsentInfoFnShowConsentForm); - 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); + [UMPConsentForm loadAndPresentFromViewControllerIfRequired:parent + completionHandler:^(NSError *_Nullable error){ + if (!error) { + CompleteFuture(handle, kConsentRequestSuccess); + } else { + CompleteFuture(handle, CppFormErrorFromIosFormError(error.code),, error.message); + } + }]; return MakeFuture(futures(), handle); } PrivacyOptionsRequirementStatus ConsentInfoInternalIos::GetPrivacyOptionsRequirementStatus() { - return privacy_options_requirement_status_; + UMPPrivacyOptionsRequirementStatus ios_status = + UMPConsentInformation.sharedInstance.privacyOptionsRequirementStatus; + switch(ios_status) { + case UMPPrivacyOptionsRequirementStatusRequired: + return kPrivacyOptionsRequirementStatusRequired; + case UMPPrivacyOptionsRequirementStatusNotRequired: + return kPrivacyOptionsRequirementStatusNotRequired; + case UMPPrivacyOptionsRequirementStatusUnknown: + return kPrivacyOptionsRequirementStatusUnknown; + default: + LogWarning("GMA: Unknown UMPPrivacyOptionsRequirementStatus returned by UMP iOS SDK: %d", + (int)(ios_status)); + return kPrivacyOptionsRequirementStatusUnknown; + } } Future ConsentInfoInternalIos::ShowPrivacyOptionsForm( FormParent parent) { SafeFutureHandle handle = CreateFuture(kConsentInfoFnShowPrivacyOptionsForm); - - if (consent_status_ == kConsentStatusObtained) { - consent_status_ = kConsentStatusRequired; - privacy_options_requirement_status_ = - kPrivacyOptionsRequirementStatusNotRequired; - } - CompleteFuture(handle, kConsentRequestSuccess); + [UMPConsentInformation presentPrivacyOptionsFromViewController:parent + completionHandler:^(NSError *_Nullable error){ + if (!error) { + CompleteFuture(handle, kConsentRequestSuccess); + } else { + CompleteFuture(handle, CppFormErrorFromIosFormError(error.code),, error.message); + } + }]; return MakeFuture(futures(), handle); } bool ConsentInfoInternalIos::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; + return (UMPConsentInformation.sharedInstance.canRequestAds == YES ? true : false); } void ConsentInfoInternalIos::Reset() { - consent_status_ = kConsentStatusUnknown; - consent_form_status_ = kConsentFormStatusUnknown; + [UMPConsentInformation.sharedInstance reset] } } // namespace internal From 6be9f7c49facc218c8e77209d6a11589eb07cdf0 Mon Sep 17 00:00:00 2001 From: Jon Simantov Date: Wed, 9 Aug 2023 15:27:55 -0700 Subject: [PATCH 05/26] Fixes. --- gma/src/common/ump/consent_info_internal.h | 4 +-- gma/src/ios/ump/consent_info_internal_ios.mm | 28 +++++++++---------- gma/src/stub/ump/consent_info_internal_stub.h | 4 +-- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/gma/src/common/ump/consent_info_internal.h b/gma/src/common/ump/consent_info_internal.h index 3438526ef0..88160a341c 100644 --- a/gma/src/common/ump/consent_info_internal.h +++ b/gma/src/common/ump/consent_info_internal.h @@ -46,8 +46,8 @@ class ConsentInfoInternal { // platform-specific subclass. static ConsentInfoInternal* CreateInstance(); - virtual ConsentStatus GetConsentStatus() const = 0; - virtual ConsentFormStatus GetConsentFormStatus() const = 0; + virtual ConsentStatus GetConsentStatus() = 0; + virtual ConsentFormStatus GetConsentFormStatus() = 0; virtual Future RequestConsentInfoUpdate( const ConsentRequestParameters& params) = 0; diff --git a/gma/src/ios/ump/consent_info_internal_ios.mm b/gma/src/ios/ump/consent_info_internal_ios.mm index 07228ccebb..60fe3eb652 100644 --- a/gma/src/ios/ump/consent_info_internal_ios.mm +++ b/gma/src/ios/ump/consent_info_internal_ios.mm @@ -35,7 +35,7 @@ ConsentInfoInternalIos::~ConsentInfoInternalIos() {} -static ConsentRequestError CppRequestErrorFromIosRequestError(UMPRequestErrorCode code) { +static ConsentRequestError CppRequestErrorFromIosRequestError(NSInteger code) { switch(code) { case UMPRequestErrorCodeInternal: return kConsentRequestErrorInternal; @@ -47,12 +47,12 @@ static ConsentRequestError CppRequestErrorFromIosRequestError(UMPRequestErrorCod return kConsentRequestErrorNetwork; default: LogWarning("GMA: Unknown UMPRequestErrorCode returned by UMP iOS SDK: %d", - (int)(ios_status)); + (int)code); return kConsentRequestErrorUnknown; } } -static ConsentFormError CppFormErrorFromIosFormError(UMPFormErrorCode code) { +static ConsentFormError CppFormErrorFromIosFormError(NSInteger code) { switch(code) { case UMPFormErrorCodeInternal: return kConsentFormErrorInternal; @@ -66,7 +66,7 @@ static ConsentFormError CppFormErrorFromIosFormError(UMPFormErrorCode code) { return kConsentFormErrorInvalidOperation; default: LogWarning("GMA: Unknown UMPFormErrorCode returned by UMP iOS SDK: %d", - (int)(ios_status)); + (int)code); return kConsentFormErrorUnknown; } } @@ -85,7 +85,7 @@ static ConsentFormError CppFormErrorFromIosFormError(UMPFormErrorCode code) { ios_debug_settings.geography = UMPDebugGeographyEEA; break; case kConsentDebugGeographyNonEEA: - ios_debug_settings.geography = UMPDebugGeographyNonEEA; + ios_debug_settings.geography = UMPDebugGeographyNotEEA; break; case kConsentDebugGeographyDisabled: ios_debug_settings.geography = UMPDebugGeographyDisabled; @@ -103,7 +103,7 @@ static ConsentFormError CppFormErrorFromIosFormError(UMPFormErrorCode code) { if (!error) { CompleteFuture(handle, kConsentRequestSuccess); } else { - CompleteFuture(handle, CppRequestErrorFromIosRequestError(error.code),, error.message); + CompleteFuture(handle, CppRequestErrorFromIosRequestError(error.code), error.localizedDescription); } }]; @@ -124,7 +124,7 @@ static ConsentFormError CppFormErrorFromIosFormError(UMPFormErrorCode code) { return kConsentStatusUnknown; default: LogWarning("GMA: Unknown UMPConsentStatus returned by UMP iOS SDK: %d", - (int)(ios_status)); + (int)ios_status); return kConsentStatusUnknown; } } @@ -142,7 +142,7 @@ static ConsentFormError CppFormErrorFromIosFormError(UMPFormErrorCode code) { return kConsentFormStatusUnknown; default: LogWarning("GMA: Unknown UMPFormConsentStatus returned by UMP iOS SDK: %d", - (int)(ios_status)); + (int)ios_status); return kConsentFormStatusUnknown; } } @@ -156,7 +156,7 @@ static ConsentFormError CppFormErrorFromIosFormError(UMPFormErrorCode code) { loaded_form_ = form; CompleteFuture(handle, kConsentFormSuccess, "Success"); } else if (error) { - CompleteFuture(handle, CppFormErrorFromIosFormError(error.code), error.message); + CompleteFuture(handle, CppFormErrorFromIosFormError(error.code), error.localizedDescription); } else { CompleteFuture(handle, kConsentFormErrorUnknown, "An unknown error occurred."); } @@ -171,12 +171,12 @@ static ConsentFormError CppFormErrorFromIosFormError(UMPFormErrorCode code) { CompleteFuture(handle, kConsentFormErrorInvalidOperation, "You must call LoadConsentForm() prior to calling ShowConsentForm()."); } else { - [loaded_form presentFromViewController:parent + [loaded_form_ presentFromViewController:parent completionHandler:^(NSError *_Nullable error){ if (!error) { CompleteFuture(handle, kConsentRequestSuccess); } else { - CompleteFuture(handle, CppFormErrorFromIosFormError(error.code),, error.message); + CompleteFuture(handle, CppFormErrorFromIosFormError(error.code), error.localizedDescription); } }]; } @@ -196,7 +196,7 @@ static ConsentFormError CppFormErrorFromIosFormError(UMPFormErrorCode code) { if (!error) { CompleteFuture(handle, kConsentRequestSuccess); } else { - CompleteFuture(handle, CppFormErrorFromIosFormError(error.code),, error.message); + CompleteFuture(handle, CppFormErrorFromIosFormError(error.code), error.localizedDescription); } }]; return MakeFuture(futures(), handle); @@ -215,7 +215,7 @@ static ConsentFormError CppFormErrorFromIosFormError(UMPFormErrorCode code) { return kPrivacyOptionsRequirementStatusUnknown; default: LogWarning("GMA: Unknown UMPPrivacyOptionsRequirementStatus returned by UMP iOS SDK: %d", - (int)(ios_status)); + (int)ios_status); return kPrivacyOptionsRequirementStatusUnknown; } } @@ -229,7 +229,7 @@ static ConsentFormError CppFormErrorFromIosFormError(UMPFormErrorCode code) { if (!error) { CompleteFuture(handle, kConsentRequestSuccess); } else { - CompleteFuture(handle, CppFormErrorFromIosFormError(error.code),, error.message); + CompleteFuture(handle, CppFormErrorFromIosFormError(error.code), error.localizedDescription); } }]; return MakeFuture(futures(), handle); diff --git a/gma/src/stub/ump/consent_info_internal_stub.h b/gma/src/stub/ump/consent_info_internal_stub.h index 23d44dfbaa..83368de284 100644 --- a/gma/src/stub/ump/consent_info_internal_stub.h +++ b/gma/src/stub/ump/consent_info_internal_stub.h @@ -51,8 +51,8 @@ class ConsentInfoInternalStub : public ConsentInfoInternal { ConsentInfoInternalStub(); ~ConsentInfoInternalStub() override; - ConsentStatus GetConsentStatus() const override { return consent_status_; } - ConsentFormStatus GetConsentFormStatus() const override { + ConsentStatus GetConsentStatus() override { return consent_status_; } + ConsentFormStatus GetConsentFormStatus() override { return consent_form_status_; } From 37b9d327c003e96bb708d4a229176bffaaacb5f0 Mon Sep 17 00:00:00 2001 From: Jon Simantov Date: Thu, 10 Aug 2023 12:12:26 -0700 Subject: [PATCH 06/26] Format code. --- gma/src/ios/ump/consent_info_internal_ios.mm | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/gma/src/ios/ump/consent_info_internal_ios.mm b/gma/src/ios/ump/consent_info_internal_ios.mm index cf8829b8c1..40a817322f 100644 --- a/gma/src/ios/ump/consent_info_internal_ios.mm +++ b/gma/src/ios/ump/consent_info_internal_ios.mm @@ -14,9 +14,8 @@ * limitations under the License. */ -#include "gma/src/ios/ump/consent_info_internal_ios.h" - #include "app/src/thread.h" +#include "gma/src/ios/ump/consent_info_internal_ios.h" namespace firebase { namespace gma { @@ -131,8 +130,7 @@ return privacy_options_requirement_status_; } -Future ConsentInfoInternalIos::ShowPrivacyOptionsForm( - FormParent parent) { +Future ConsentInfoInternalIos::ShowPrivacyOptionsForm(FormParent parent) { SafeFutureHandle handle = CreateFuture(kConsentInfoFnShowPrivacyOptionsForm); From c6d98d33c7a74f4ae328a5258672b97cc989497f Mon Sep 17 00:00:00 2001 From: Jon Simantov Date: Thu, 10 Aug 2023 12:14:18 -0700 Subject: [PATCH 07/26] Fix compiler errors. --- gma/src/ios/ump/consent_info_internal_ios.mm | 23 +++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/gma/src/ios/ump/consent_info_internal_ios.mm b/gma/src/ios/ump/consent_info_internal_ios.mm index a1fdd85a8f..cf6e9a7728 100644 --- a/gma/src/ios/ump/consent_info_internal_ios.mm +++ b/gma/src/ios/ump/consent_info_internal_ios.mm @@ -103,7 +103,7 @@ static ConsentFormError CppFormErrorFromIosFormError(NSInteger code) { if (!error) { CompleteFuture(handle, kConsentRequestSuccess); } else { - CompleteFuture(handle, CppRequestErrorFromIosRequestError(error.code), error.localizedDescription); + CompleteFuture(handle, CppRequestErrorFromIosRequestError(error.code), error.localizedDescription.UTF8String); } }]; @@ -156,7 +156,7 @@ static ConsentFormError CppFormErrorFromIosFormError(NSInteger code) { loaded_form_ = form; CompleteFuture(handle, kConsentFormSuccess, "Success"); } else if (error) { - CompleteFuture(handle, CppFormErrorFromIosFormError(error.code), error.localizedDescription); + CompleteFuture(handle, CppFormErrorFromIosFormError(error.code), error.localizedDescription.UTF8String); } else { CompleteFuture(handle, kConsentFormErrorUnknown, "An unknown error occurred."); } @@ -172,11 +172,11 @@ static ConsentFormError CppFormErrorFromIosFormError(NSInteger code) { "You must call LoadConsentForm() prior to calling ShowConsentForm()."); } else { [loaded_form_ presentFromViewController:parent - completionHandler:^(NSError *_Nullable error){ + completionHandler:^(NSError *_Nullable error){ if (!error) { CompleteFuture(handle, kConsentRequestSuccess); } else { - CompleteFuture(handle, CppFormErrorFromIosFormError(error.code), error.localizedDescription); + CompleteFuture(handle, CppFormErrorFromIosFormError(error.code), error.localizedDescription.UTF8String); } }]; } @@ -188,15 +188,12 @@ static ConsentFormError CppFormErrorFromIosFormError(NSInteger code) { SafeFutureHandle handle = CreateFuture(kConsentInfoFnLoadAndShowConsentFormIfRequired); -Future ConsentInfoInternalIos::ShowConsentForm(FormParent parent) { - SafeFutureHandle handle = CreateFuture(kConsentInfoFnShowConsentForm); - - [UMPConsentForm loadAndPresentFromViewControllerIfRequired:parent + [UMPConsentForm loadAndPresentIfRequiredFromViewController:parent completionHandler:^(NSError *_Nullable error){ if (!error) { CompleteFuture(handle, kConsentRequestSuccess); } else { - CompleteFuture(handle, CppFormErrorFromIosFormError(error.code), error.localizedDescription); + CompleteFuture(handle, CppFormErrorFromIosFormError(error.code), error.localizedDescription.UTF8String); } }]; return MakeFuture(futures(), handle); @@ -223,12 +220,12 @@ static ConsentFormError CppFormErrorFromIosFormError(NSInteger code) { Future ConsentInfoInternalIos::ShowPrivacyOptionsForm(FormParent parent) { SafeFutureHandle handle = CreateFuture(kConsentInfoFnShowPrivacyOptionsForm); - [UMPConsentInformation presentPrivacyOptionsFromViewController:parent - completionHandler:^(NSError *_Nullable error){ + [UMPConsentForm presentPrivacyOptionsFormFromViewController:parent + completionHandler:^(NSError *_Nullable error){ if (!error) { CompleteFuture(handle, kConsentRequestSuccess); } else { - CompleteFuture(handle, CppFormErrorFromIosFormError(error.code), error.localizedDescription); + CompleteFuture(handle, CppFormErrorFromIosFormError(error.code), error.localizedDescription.UTF8String); } }]; return MakeFuture(futures(), handle); @@ -239,7 +236,7 @@ static ConsentFormError CppFormErrorFromIosFormError(NSInteger code) { } void ConsentInfoInternalIos::Reset() { - [UMPConsentInformation.sharedInstance reset] + [UMPConsentInformation.sharedInstance reset]; } } // namespace internal From 063d678e5c8aa92ef7cf0e265534f4956269607a Mon Sep 17 00:00:00 2001 From: Jon Simantov Date: Fri, 11 Aug 2023 11:07:47 -0700 Subject: [PATCH 08/26] Add test flag for skipping on hardware. --- .../src/firebase_test_framework.h | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/testing/test_framework/src/firebase_test_framework.h b/testing/test_framework/src/firebase_test_framework.h index cff9ef5aec..2376e1731e 100644 --- a/testing/test_framework/src/firebase_test_framework.h +++ b/testing/test_framework/src/firebase_test_framework.h @@ -192,6 +192,23 @@ namespace firebase_test_framework { #define SKIP_TEST_ON_ANDROID ((void)0) #endif // defined(ANDROID) +// Skip on physical mobile device. +#if !defined(ANDROID) && !(defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) +// Allow desktop. +#define SKIP_TEST_ON_MOBILE_HARDWARE ((void)0) +#else +// Android needs to determine emulator at runtime, so we can't just use #ifdef. +#define SKIP_TEST_ON_MOBILE_HARDWARE \ + { \ + if (!IsRunningOnEmulator()) { \ + app_framework::LogInfo("Skipping %s on mobile hardware.", \ + test_info_->name()); \ + GTEST_SKIP(); \ + return; \ + } \ + } +#endif // !defined(ANDROID) && !(defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) + // Android needs to determine emulator at runtime, so we can't just use #ifdef. #define SKIP_TEST_ON_SIMULATOR \ { \ From 7ea9f6406ebc5ab78970beef25017e746cafac9e Mon Sep 17 00:00:00 2001 From: Jon Simantov Date: Mon, 14 Aug 2023 19:04:10 -0700 Subject: [PATCH 09/26] Fix iOS implementation and tests to match actual results. Add a test skip macro for skipping on physical hardware. --- gma/integration_test/src/integration_test.cc | 137 +++++++++++++++--- gma/src/ios/ump/consent_info_internal_ios.mm | 103 +++++++------ .../src/firebase_test_framework.h | 16 +- 3 files changed, 181 insertions(+), 75 deletions(-) diff --git a/gma/integration_test/src/integration_test.cc b/gma/integration_test/src/integration_test.cc index 7143237873..7b8ae2ba25 100644 --- a/gma/integration_test/src/integration_test.cc +++ b/gma/integration_test/src/integration_test.cc @@ -98,6 +98,8 @@ const char* kErrorDomain = "com.google.admob"; #endif // Sample test device IDs to use in making the request. +// You can replace these with actual device IDs for certain tests (e.g. UMP) +// to work on hardware devices. const std::vector kTestDeviceIDs = { "2077ef9a63d2b398840261c8221a0c9b", "098fe087d987c9a878965454a65654d7"}; @@ -136,6 +138,7 @@ static const std::vector kNeighboringContentURLs = { "test_url1", "test_url2", "test_url3"}; using app_framework::LogDebug; +using app_framework::LogInfo; using app_framework::LogWarning; using app_framework::ProcessEvents; @@ -370,7 +373,7 @@ TEST_F(FirebaseGmaMinimalTest, TestInitializeGmaWithoutFirebase) { LogDebug("Shutdown GMA."); firebase::gma::Terminate(); } - +#if 0 // jsimantov TEST_F(FirebaseGmaPreInitializationTests, TestDisableMediationInitialization) { // Note: This test should be disabled or put in an entirely different test // binrary if we ever wish to test mediation in this application. @@ -2470,6 +2473,7 @@ TEST_F(FirebaseGmaTest, TestAdViewMultithreadDeletion) { #endif // #if defined(ANDROID) || (defined(TARGET_OS_IPHONE) && // TARGET_OS_IPHONE) +#endif // jsimantov class FirebaseGmaUmpTest : public FirebaseGmaTest { public: FirebaseGmaUmpTest() : consent_info_(nullptr) {} @@ -2498,6 +2502,7 @@ void FirebaseGmaUmpTest::InitializeUmp(ResetOption reset) { void FirebaseGmaUmpTest::TerminateUmp() { if (consent_info_) { + consent_info_->Reset(); delete consent_info_; consent_info_ = nullptr; } @@ -2583,6 +2588,12 @@ TEST_F(FirebaseGmaUmpTest, TestUmpRequestConsentInfoUpdate) { } TEST_F(FirebaseGmaUmpTest, TestUmpRequestConsentInfoUpdateDebugEEA) { + // For debug options to work, you need to be on simulator OR enter + // the proper debug device ID below (which requires manual testing). + if (!ShouldRunUITests()) { + SKIP_TEST_ON_MOBILE_HARDWARE; + } + using firebase::gma::ump::ConsentDebugSettings; using firebase::gma::ump::ConsentRequestParameters; using firebase::gma::ump::ConsentStatus; @@ -2591,6 +2602,7 @@ TEST_F(FirebaseGmaUmpTest, TestUmpRequestConsentInfoUpdateDebugEEA) { params.tag_for_under_age_of_consent = false; params.debug_settings.debug_geography = firebase::gma::ump::kConsentDebugGeographyEEA; + params.debug_settings.debug_device_ids = kTestDeviceIDs; firebase::Future future = consent_info_->RequestConsentInfoUpdate(params); @@ -2602,6 +2614,12 @@ TEST_F(FirebaseGmaUmpTest, TestUmpRequestConsentInfoUpdateDebugEEA) { } TEST_F(FirebaseGmaUmpTest, TestUmpRequestConsentInfoUpdateDebugNonEEA) { + // For debug options to work, you need to be on simulator OR enter + // the proper debug device ID below (which requires manual testing). + if (!ShouldRunUITests()) { + SKIP_TEST_ON_MOBILE_HARDWARE; + } + using firebase::gma::ump::ConsentDebugSettings; using firebase::gma::ump::ConsentRequestParameters; using firebase::gma::ump::ConsentStatus; @@ -2610,6 +2628,7 @@ TEST_F(FirebaseGmaUmpTest, TestUmpRequestConsentInfoUpdateDebugNonEEA) { params.tag_for_under_age_of_consent = false; params.debug_settings.debug_geography = firebase::gma::ump::kConsentDebugGeographyNonEEA; + params.debug_settings.debug_device_ids = kTestDeviceIDs; firebase::Future future = consent_info_->RequestConsentInfoUpdate(params); @@ -2630,6 +2649,7 @@ TEST_F(FirebaseGmaUmpTest, TestUmpLoadForm) { params.tag_for_under_age_of_consent = false; params.debug_settings.debug_geography = firebase::gma::ump::kConsentDebugGeographyEEA; + params.debug_settings.debug_device_ids = kTestDeviceIDs; WaitForCompletion(consent_info_->RequestConsentInfoUpdate(params), "RequestConsentInfoUpdate"); @@ -2663,22 +2683,32 @@ TEST_F(FirebaseGmaUmpTest, TestUmpShowForm) { params.tag_for_under_age_of_consent = false; params.debug_settings.debug_geography = firebase::gma::ump::kConsentDebugGeographyEEA; + params.debug_settings.debug_device_ids = kTestDeviceIDs; WaitForCompletion(consent_info_->RequestConsentInfoUpdate(params), "RequestConsentInfoUpdate"); + ProcessEvents(1000); + EXPECT_EQ(consent_info_->GetConsentStatus(), firebase::gma::ump::kConsentStatusRequired); EXPECT_EQ(consent_info_->GetConsentFormStatus(), firebase::gma::ump::kConsentFormStatusAvailable); + ProcessEvents(1000); + + LogInfo("About to load consent form"); + + ProcessEvents(1000); + WaitForCompletion(consent_info_->LoadConsentForm(), "LoadConsentForm"); EXPECT_EQ(consent_info_->GetConsentFormStatus(), firebase::gma::ump::kConsentFormStatusAvailable); - firebase::Future future = consent_info_->ShowConsentForm(nullptr); + firebase::Future future = + consent_info_->ShowConsentForm(app_framework::GetWindowController()); EXPECT_TRUE(future == consent_info_->ShowConsentFormLastResult()); @@ -2689,7 +2719,11 @@ TEST_F(FirebaseGmaUmpTest, TestUmpShowForm) { } TEST_F(FirebaseGmaUmpTest, TestUmpLoadFormUnavailableDueUnderAgeOfConsent) { - TEST_REQUIRES_USER_INTERACTION; + // For debug options to work, you need to be on simulator OR enter + // the proper debug device ID below (which requires manual testing). + if (!ShouldRunUITests()) { + SKIP_TEST_ON_MOBILE_HARDWARE; + } using firebase::gma::ump::ConsentDebugSettings; using firebase::gma::ump::ConsentFormStatus; @@ -2700,24 +2734,74 @@ TEST_F(FirebaseGmaUmpTest, TestUmpLoadFormUnavailableDueUnderAgeOfConsent) { params.tag_for_under_age_of_consent = true; params.debug_settings.debug_geography = firebase::gma::ump::kConsentDebugGeographyEEA; + params.debug_settings.debug_device_ids = kTestDeviceIDs; WaitForCompletion(consent_info_->RequestConsentInfoUpdate(params), "RequestConsentInfoUpdate"); - EXPECT_EQ(consent_info_->GetConsentStatus(), - firebase::gma::ump::kConsentStatusRequired); + WaitForCompletion(consent_info_->LoadConsentForm(), "LoadConsentForm", + firebase::gma::ump::kConsentFormErrorUnavailable); +} - EXPECT_EQ(consent_info_->GetConsentFormStatus(), - firebase::gma::ump::kConsentFormStatusUnavailable); +TEST_F(FirebaseGmaUmpTest, TestUmpLoadFormUnavailableDebugNonEEA) { + // For debug options to work, you need to be on simulator OR enter + // the proper debug device ID below (which requires manual testing). + if (!ShouldRunUITests()) { + SKIP_TEST_ON_MOBILE_HARDWARE; + } + + 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::kConsentDebugGeographyNonEEA; + params.debug_settings.debug_device_ids = kTestDeviceIDs; + + WaitForCompletion(consent_info_->RequestConsentInfoUpdate(params), + "RequestConsentInfoUpdate"); WaitForCompletion(consent_info_->LoadConsentForm(), "LoadConsentForm", firebase::gma::ump::kConsentFormErrorUnavailable); +} - EXPECT_EQ(consent_info_->GetConsentFormStatus(), - firebase::gma::ump::kConsentFormStatusUnavailable); +TEST_F(FirebaseGmaUmpTest, TestUmpLoadAndShowIfRequiredDebugNonEEA) { + using firebase::gma::ump::ConsentDebugSettings; + using firebase::gma::ump::ConsentRequestParameters; + using firebase::gma::ump::ConsentStatus; + + // For debug options to work, you need to be on simulator OR enter + // the proper debug device ID below (which requires manual testing). + if (!ShouldRunUITests()) { + SKIP_TEST_ON_MOBILE_HARDWARE; + } + + ConsentRequestParameters params; + params.tag_for_under_age_of_consent = false; + params.debug_settings.debug_geography = + firebase::gma::ump::kConsentDebugGeographyNonEEA; + params.debug_settings.debug_device_ids = kTestDeviceIDs; + + WaitForCompletion(consent_info_->RequestConsentInfoUpdate(params), + "RequestConsentInfoUpdate"); + + EXPECT_EQ(consent_info_->GetConsentStatus(), + firebase::gma::ump::kConsentStatusNotRequired); + + firebase::Future future = + consent_info_->LoadAndShowConsentFormIfRequired( + app_framework::GetWindowController()); + + EXPECT_TRUE(future == + consent_info_->LoadAndShowConsentFormIfRequiredLastResult()); + + WaitForCompletion(future, "LoadAndShowConsentFormIfRequired"); } -TEST_F(FirebaseGmaUmpTest, TestUmpLoadAndShowIfRequired) { +TEST_F(FirebaseGmaUmpTest, TestUmpLoadAndShowIfRequiredDebugEEA) { using firebase::gma::ump::ConsentDebugSettings; using firebase::gma::ump::ConsentRequestParameters; using firebase::gma::ump::ConsentStatus; @@ -2728,6 +2812,7 @@ TEST_F(FirebaseGmaUmpTest, TestUmpLoadAndShowIfRequired) { params.tag_for_under_age_of_consent = false; params.debug_settings.debug_geography = firebase::gma::ump::kConsentDebugGeographyEEA; + params.debug_settings.debug_device_ids = kTestDeviceIDs; WaitForCompletion(consent_info_->RequestConsentInfoUpdate(params), "RequestConsentInfoUpdate"); @@ -2736,7 +2821,8 @@ TEST_F(FirebaseGmaUmpTest, TestUmpLoadAndShowIfRequired) { firebase::gma::ump::kConsentStatusRequired); firebase::Future future = - consent_info_->LoadAndShowConsentFormIfRequired(nullptr); + consent_info_->LoadAndShowConsentFormIfRequired( + app_framework::GetWindowController()); EXPECT_TRUE(future == consent_info_->LoadAndShowConsentFormIfRequiredLastResult()); @@ -2759,6 +2845,7 @@ TEST_F(FirebaseGmaUmpTest, TestUmpPrivacyOptions) { params.tag_for_under_age_of_consent = false; params.debug_settings.debug_geography = firebase::gma::ump::kConsentDebugGeographyEEA; + params.debug_settings.debug_device_ids = kTestDeviceIDs; WaitForCompletion(consent_info_->RequestConsentInfoUpdate(params), "RequestConsentInfoUpdate"); @@ -2768,28 +2855,30 @@ TEST_F(FirebaseGmaUmpTest, TestUmpPrivacyOptions) { EXPECT_FALSE(consent_info_->CanRequestAds()); - WaitForCompletion(consent_info_->LoadAndShowConsentFormIfRequired(nullptr), + WaitForCompletion(consent_info_->LoadAndShowConsentFormIfRequired( + app_framework::GetWindowController()), "LoadAndShowConsentFormIfRequired"); EXPECT_EQ(consent_info_->GetConsentStatus(), firebase::gma::ump::kConsentStatusObtained); - EXPECT_TRUE(consent_info_->CanRequestAds()); + EXPECT_TRUE(consent_info_->CanRequestAds()) << "After consent obtained"; + + LogInfo( + "******** On the Privacy Options screen that is about to appear, please " + "select DO NOT CONSENT."); + + ProcessEvents(5000); EXPECT_EQ(consent_info_->GetPrivacyOptionsRequirementStatus(), firebase::gma::ump::kPrivacyOptionsRequirementStatusRequired); - firebase::Future future = - consent_info_->ShowPrivacyOptionsForm(nullptr); + firebase::Future future = consent_info_->ShowPrivacyOptionsForm( + app_framework::GetWindowController()); 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) { @@ -2803,6 +2892,7 @@ TEST_F(FirebaseGmaUmpTest, TestCanRequestAdsNonEEA) { params.tag_for_under_age_of_consent = false; params.debug_settings.debug_geography = firebase::gma::ump::kConsentDebugGeographyNonEEA; + params.debug_settings.debug_device_ids = kTestDeviceIDs; WaitForCompletion(consent_info_->RequestConsentInfoUpdate(params), "RequestConsentInfoUpdate"); @@ -2824,6 +2914,7 @@ TEST_F(FirebaseGmaUmpTest, TestCanRequestAdsEEA) { params.tag_for_under_age_of_consent = false; params.debug_settings.debug_geography = firebase::gma::ump::kConsentDebugGeographyEEA; + params.debug_settings.debug_device_ids = kTestDeviceIDs; WaitForCompletion(consent_info_->RequestConsentInfoUpdate(params), "RequestConsentInfoUpdate"); @@ -2844,7 +2935,11 @@ TEST_F(FirebaseGmaUmpTest, TestUmpCleanup) { firebase::Future future_request = consent_info_->RequestConsentInfoUpdate(params); firebase::Future future_load = consent_info_->LoadConsentForm(); - firebase::Future future_show = consent_info_->ShowConsentForm(nullptr); + firebase::Future future_show = + consent_info_->ShowConsentForm(app_framework::GetWindowController()); + + // TODO(jsimantov): Remove this delay after race conditions are addressed. + ProcessEvents(5000); delete consent_info_; consent_info_ = nullptr; diff --git a/gma/src/ios/ump/consent_info_internal_ios.mm b/gma/src/ios/ump/consent_info_internal_ios.mm index cf6e9a7728..cb6a312646 100644 --- a/gma/src/ios/ump/consent_info_internal_ios.mm +++ b/gma/src/ios/ump/consent_info_internal_ios.mm @@ -79,7 +79,7 @@ static ConsentFormError CppFormErrorFromIosFormError(NSInteger code) { UMPRequestParameters *ios_parameters = [[UMPRequestParameters alloc] init]; ios_parameters.tagForUnderAgeOfConsent = params.tag_for_under_age_of_consent ? YES : NO; UMPDebugSettings *ios_debug_settings = [[UMPDebugSettings alloc] init]; - + switch(params.debug_settings.debug_geography) { case kConsentDebugGeographyEEA: ios_debug_settings.geography = UMPDebugGeographyEEA; @@ -97,15 +97,17 @@ static ConsentFormError CppFormErrorFromIosFormError(NSInteger code) { } ios_parameters.debugSettings = ios_debug_settings; - [UMPConsentInformation.sharedInstance - requestConsentInfoUpdateWithParameters:ios_parameters - completionHandler:^(NSError *_Nullable error){ - if (!error) { - CompleteFuture(handle, kConsentRequestSuccess); - } else { - CompleteFuture(handle, CppRequestErrorFromIosRequestError(error.code), error.localizedDescription.UTF8String); - } - }]; + util::DispatchAsyncSafeMainQueue(^{ + [UMPConsentInformation.sharedInstance + requestConsentInfoUpdateWithParameters:ios_parameters + completionHandler:^(NSError *_Nullable error){ + if (!error) { + CompleteFuture(handle, kConsentRequestSuccess); + } else { + CompleteFuture(handle, CppRequestErrorFromIosRequestError(error.code), error.localizedDescription.UTF8String); + } + }]; + }); return MakeFuture(futures(), handle); } @@ -149,18 +151,21 @@ static ConsentFormError CppFormErrorFromIosFormError(NSInteger code) { Future ConsentInfoInternalIos::LoadConsentForm() { SafeFutureHandle handle = CreateFuture(kConsentInfoFnLoadConsentForm); + loaded_form_ = nil; - [UMPConsentForm - loadWithCompletionHandler:^(UMPConsentForm *_Nullable form, NSError *_Nullable error){ - if (form) { - loaded_form_ = form; - CompleteFuture(handle, kConsentFormSuccess, "Success"); - } else if (error) { - CompleteFuture(handle, CppFormErrorFromIosFormError(error.code), error.localizedDescription.UTF8String); - } else { - CompleteFuture(handle, kConsentFormErrorUnknown, "An unknown error occurred."); - } - }]; + util::DispatchAsyncSafeMainQueue(^{ + [UMPConsentForm + loadWithCompletionHandler:^(UMPConsentForm *_Nullable form, NSError *_Nullable error){ + if (form) { + loaded_form_ = form; + CompleteFuture(handle, kConsentFormSuccess, "Success"); + } else if (error) { + CompleteFuture(handle, CppFormErrorFromIosFormError(error.code), error.localizedDescription.UTF8String); + } else { + CompleteFuture(handle, kConsentFormErrorUnknown, "An unknown error occurred."); + } + }]; + }); return MakeFuture(futures(), handle); } @@ -171,14 +176,16 @@ static ConsentFormError CppFormErrorFromIosFormError(NSInteger code) { CompleteFuture(handle, kConsentFormErrorInvalidOperation, "You must call LoadConsentForm() prior to calling ShowConsentForm()."); } else { - [loaded_form_ presentFromViewController:parent - completionHandler:^(NSError *_Nullable error){ - if (!error) { - CompleteFuture(handle, kConsentRequestSuccess); - } else { - CompleteFuture(handle, CppFormErrorFromIosFormError(error.code), error.localizedDescription.UTF8String); - } - }]; + util::DispatchAsyncSafeMainQueue(^{ + [loaded_form_ presentFromViewController:parent + completionHandler:^(NSError *_Nullable error){ + if (!error) { + CompleteFuture(handle, kConsentRequestSuccess); + } else { + CompleteFuture(handle, CppFormErrorFromIosFormError(error.code), error.localizedDescription.UTF8String); + } + }]; + }); } return MakeFuture(futures(), handle); } @@ -188,14 +195,16 @@ static ConsentFormError CppFormErrorFromIosFormError(NSInteger code) { SafeFutureHandle handle = CreateFuture(kConsentInfoFnLoadAndShowConsentFormIfRequired); - [UMPConsentForm loadAndPresentIfRequiredFromViewController:parent - completionHandler:^(NSError *_Nullable error){ - if (!error) { - CompleteFuture(handle, kConsentRequestSuccess); - } else { - CompleteFuture(handle, CppFormErrorFromIosFormError(error.code), error.localizedDescription.UTF8String); - } - }]; + util::DispatchAsyncSafeMainQueue(^{ + [UMPConsentForm loadAndPresentIfRequiredFromViewController:parent + completionHandler:^(NSError *_Nullable error){ + if (!error) { + CompleteFuture(handle, kConsentRequestSuccess); + } else { + CompleteFuture(handle, CppFormErrorFromIosFormError(error.code), error.localizedDescription.UTF8String); + } + }]; + }); return MakeFuture(futures(), handle); } @@ -220,17 +229,19 @@ static ConsentFormError CppFormErrorFromIosFormError(NSInteger code) { Future ConsentInfoInternalIos::ShowPrivacyOptionsForm(FormParent parent) { SafeFutureHandle handle = CreateFuture(kConsentInfoFnShowPrivacyOptionsForm); - [UMPConsentForm presentPrivacyOptionsFormFromViewController:parent - completionHandler:^(NSError *_Nullable error){ - if (!error) { - CompleteFuture(handle, kConsentRequestSuccess); - } else { - CompleteFuture(handle, CppFormErrorFromIosFormError(error.code), error.localizedDescription.UTF8String); - } - }]; + util::DispatchAsyncSafeMainQueue(^{ + [UMPConsentForm presentPrivacyOptionsFormFromViewController:parent + completionHandler:^(NSError *_Nullable error){ + if (!error) { + CompleteFuture(handle, kConsentRequestSuccess); + } else { + CompleteFuture(handle, CppFormErrorFromIosFormError(error.code), error.localizedDescription.UTF8String); + } + }]; + }); return MakeFuture(futures(), handle); } - + bool ConsentInfoInternalIos::CanRequestAds() { return (UMPConsentInformation.sharedInstance.canRequestAds == YES ? true : false); } diff --git a/testing/test_framework/src/firebase_test_framework.h b/testing/test_framework/src/firebase_test_framework.h index 2376e1731e..d260c91e69 100644 --- a/testing/test_framework/src/firebase_test_framework.h +++ b/testing/test_framework/src/firebase_test_framework.h @@ -198,14 +198,14 @@ namespace firebase_test_framework { #define SKIP_TEST_ON_MOBILE_HARDWARE ((void)0) #else // Android needs to determine emulator at runtime, so we can't just use #ifdef. -#define SKIP_TEST_ON_MOBILE_HARDWARE \ - { \ - if (!IsRunningOnEmulator()) { \ - app_framework::LogInfo("Skipping %s on mobile hardware.", \ - test_info_->name()); \ - GTEST_SKIP(); \ - return; \ - } \ +#define SKIP_TEST_ON_MOBILE_HARDWARE \ + { \ + if (!IsRunningOnEmulator()) { \ + app_framework::LogInfo("Skipping %s on mobile hardware.", \ + test_info_->name()); \ + GTEST_SKIP(); \ + return; \ + } \ } #endif // !defined(ANDROID) && !(defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) From 68a1698ee16996b16206474538a3b8ccc02600d2 Mon Sep 17 00:00:00 2001 From: Jon Simantov Date: Mon, 14 Aug 2023 19:05:18 -0700 Subject: [PATCH 10/26] Format code and add general GMA tests back in. --- gma/integration_test/src/integration_test.cc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/gma/integration_test/src/integration_test.cc b/gma/integration_test/src/integration_test.cc index 7b8ae2ba25..e1622d0a24 100644 --- a/gma/integration_test/src/integration_test.cc +++ b/gma/integration_test/src/integration_test.cc @@ -373,7 +373,7 @@ TEST_F(FirebaseGmaMinimalTest, TestInitializeGmaWithoutFirebase) { LogDebug("Shutdown GMA."); firebase::gma::Terminate(); } -#if 0 // jsimantov + TEST_F(FirebaseGmaPreInitializationTests, TestDisableMediationInitialization) { // Note: This test should be disabled or put in an entirely different test // binrary if we ever wish to test mediation in this application. @@ -2473,7 +2473,6 @@ TEST_F(FirebaseGmaTest, TestAdViewMultithreadDeletion) { #endif // #if defined(ANDROID) || (defined(TARGET_OS_IPHONE) && // TARGET_OS_IPHONE) -#endif // jsimantov class FirebaseGmaUmpTest : public FirebaseGmaTest { public: FirebaseGmaUmpTest() : consent_info_(nullptr) {} From de1f488e2f9efe84d23e9167dc780108aa62d95b Mon Sep 17 00:00:00 2001 From: Jon Simantov Date: Mon, 14 Aug 2023 19:14:53 -0700 Subject: [PATCH 11/26] Enable App Transparency for iOS app. --- gma/integration_test/Info.plist | 2 ++ .../integration_test.xcodeproj/project.pbxproj | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/gma/integration_test/Info.plist b/gma/integration_test/Info.plist index d2403b9c2c..953571e326 100644 --- a/gma/integration_test/Info.plist +++ b/gma/integration_test/Info.plist @@ -24,6 +24,8 @@ UILaunchStoryboardName LaunchScreen + NSUserTrackingUsageDescription + This identifier will be used to deliver personalized ads to you. CFBundleURLTypes diff --git a/gma/integration_test/integration_test.xcodeproj/project.pbxproj b/gma/integration_test/integration_test.xcodeproj/project.pbxproj index 54ac389b36..35b4a36051 100644 --- a/gma/integration_test/integration_test.xcodeproj/project.pbxproj +++ b/gma/integration_test/integration_test.xcodeproj/project.pbxproj @@ -17,6 +17,7 @@ D640F3172819C85800AC956E /* empty.swift in Sources */ = {isa = PBXBuildFile; fileRef = D640F3162819C85800AC956E /* empty.swift */; }; D66B16871CE46E8900E5638A /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D66B16861CE46E8900E5638A /* LaunchScreen.storyboard */; }; D67D355822BABD2200292C1D /* gtest-all.cc in Sources */ = {isa = PBXBuildFile; fileRef = D67D355622BABD2100292C1D /* gtest-all.cc */; }; + D686A3292A8B16F20034845A /* AppTrackingTransparency.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D686A3282A8B16F20034845A /* AppTrackingTransparency.framework */; }; D6C179E922CB322900C2651A /* ios_app_framework.mm in Sources */ = {isa = PBXBuildFile; fileRef = D6C179E722CB322900C2651A /* ios_app_framework.mm */; }; D6C179EA22CB322900C2651A /* ios_firebase_test_framework.mm in Sources */ = {isa = PBXBuildFile; fileRef = D6C179E822CB322900C2651A /* ios_firebase_test_framework.mm */; }; D6C179EE22CB323300C2651A /* firebase_test_framework.cc in Sources */ = {isa = PBXBuildFile; fileRef = D6C179EC22CB323300C2651A /* firebase_test_framework.cc */; }; @@ -39,6 +40,7 @@ D66B16861CE46E8900E5638A /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; D67D355622BABD2100292C1D /* gtest-all.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = "gtest-all.cc"; path = "external/googletest/src/googletest/src/gtest-all.cc"; sourceTree = ""; }; D67D355722BABD2100292C1D /* gtest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = gtest.h; path = external/googletest/src/googletest/include/gtest/gtest.h; sourceTree = ""; }; + D686A3282A8B16F20034845A /* AppTrackingTransparency.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppTrackingTransparency.framework; path = System/Library/Frameworks/AppTrackingTransparency.framework; sourceTree = SDKROOT; }; D6C179E722CB322900C2651A /* ios_app_framework.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ios_app_framework.mm; path = src/ios/ios_app_framework.mm; sourceTree = ""; }; D6C179E822CB322900C2651A /* ios_firebase_test_framework.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ios_firebase_test_framework.mm; path = src/ios/ios_firebase_test_framework.mm; sourceTree = ""; }; D6C179EB22CB323300C2651A /* firebase_test_framework.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = firebase_test_framework.h; path = src/firebase_test_framework.h; sourceTree = ""; }; @@ -53,6 +55,7 @@ buildActionMask = 2147483647; files = ( 529226D81C85F68000C89379 /* CoreGraphics.framework in Frameworks */, + D686A3292A8B16F20034845A /* AppTrackingTransparency.framework in Frameworks */, 529226DA1C85F68000C89379 /* UIKit.framework in Frameworks */, 529226D61C85F68000C89379 /* Foundation.framework in Frameworks */, ); @@ -85,6 +88,7 @@ 529226D41C85F68000C89379 /* Frameworks */ = { isa = PBXGroup; children = ( + D686A3282A8B16F20034845A /* AppTrackingTransparency.framework */, 529226D51C85F68000C89379 /* Foundation.framework */, 529226D71C85F68000C89379 /* CoreGraphics.framework */, 529226D91C85F68000C89379 /* UIKit.framework */, From a730f831f0bd11de6e9c9f2571081b941975d537 Mon Sep 17 00:00:00 2001 From: Jon Simantov Date: Wed, 16 Aug 2023 10:25:36 -0700 Subject: [PATCH 12/26] Add debug device ID to test framework on iOS. --- .../src/android/android_firebase_test_framework.cc | 5 +++++ .../src/desktop/desktop_firebase_test_framework.cc | 4 ++++ testing/test_framework/src/firebase_test_framework.h | 2 ++ .../test_framework/src/ios/ios_firebase_test_framework.mm | 4 ++++ 4 files changed, 15 insertions(+) diff --git a/testing/test_framework/src/android/android_firebase_test_framework.cc b/testing/test_framework/src/android/android_firebase_test_framework.cc index d1289d0750..03513c0924 100644 --- a/testing/test_framework/src/android/android_firebase_test_framework.cc +++ b/testing/test_framework/src/android/android_firebase_test_framework.cc @@ -267,4 +267,9 @@ int FirebaseTest::GetGooglePlayServicesVersion() { return static_cast(result); } +std::string FirebaseTest::GetDebugDeviceId() { + // TODO(jsimantov): Add this for Android. + return "placeholder-device-id"; +} + } // namespace firebase_test_framework diff --git a/testing/test_framework/src/desktop/desktop_firebase_test_framework.cc b/testing/test_framework/src/desktop/desktop_firebase_test_framework.cc index 31fc42184d..835a667b8a 100644 --- a/testing/test_framework/src/desktop/desktop_firebase_test_framework.cc +++ b/testing/test_framework/src/desktop/desktop_firebase_test_framework.cc @@ -59,4 +59,8 @@ int FirebaseTest::GetGooglePlayServicesVersion() { return 0; } +std::string FirebaseTest::GetDebugDeviceId() { + return "placeholder-device-id"; +} + } // namespace firebase_test_framework diff --git a/testing/test_framework/src/firebase_test_framework.h b/testing/test_framework/src/firebase_test_framework.h index d260c91e69..f7b0cd2fff 100644 --- a/testing/test_framework/src/firebase_test_framework.h +++ b/testing/test_framework/src/firebase_test_framework.h @@ -555,6 +555,8 @@ class FirebaseTest : public testing::Test { // false if it failed. static bool Base64Decode(const std::string& input, std::string* output); + static std::string GetDebugDeviceId(); + firebase::App* app_; static int argc_; static char** argv_; diff --git a/testing/test_framework/src/ios/ios_firebase_test_framework.mm b/testing/test_framework/src/ios/ios_firebase_test_framework.mm index 36295d0b76..0a14eb4f46 100644 --- a/testing/test_framework/src/ios/ios_firebase_test_framework.mm +++ b/testing/test_framework/src/ios/ios_firebase_test_framework.mm @@ -205,4 +205,8 @@ static bool SendHttpRequest(const char* method, const char* url, return 0; } +std::string FirebaseTest::GetDebugDeviceId() { + return UIDevice.currentDevice.identifierForVendor.UUIDString.UTF8String; +} + } // namespace firebase_test_framework From 2438a12cf3fcaeea568c4f505cdcc9150e8cf873 Mon Sep 17 00:00:00 2001 From: Jon Simantov Date: Wed, 16 Aug 2023 10:26:02 -0700 Subject: [PATCH 13/26] Change tests that use debug IDs to work differently. --- gma/integration_test/src/integration_test.cc | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/gma/integration_test/src/integration_test.cc b/gma/integration_test/src/integration_test.cc index e1622d0a24..abeb7a6985 100644 --- a/gma/integration_test/src/integration_test.cc +++ b/gma/integration_test/src/integration_test.cc @@ -2589,8 +2589,8 @@ TEST_F(FirebaseGmaUmpTest, TestUmpRequestConsentInfoUpdate) { TEST_F(FirebaseGmaUmpTest, TestUmpRequestConsentInfoUpdateDebugEEA) { // For debug options to work, you need to be on simulator OR enter // the proper debug device ID below (which requires manual testing). - if (!ShouldRunUITests()) { - SKIP_TEST_ON_MOBILE_HARDWARE; + if (!IsRunningOnEmulator()) { + TEST_REQUIRES_USER_INTERACTION; } using firebase::gma::ump::ConsentDebugSettings; @@ -2615,8 +2615,8 @@ TEST_F(FirebaseGmaUmpTest, TestUmpRequestConsentInfoUpdateDebugEEA) { TEST_F(FirebaseGmaUmpTest, TestUmpRequestConsentInfoUpdateDebugNonEEA) { // For debug options to work, you need to be on simulator OR enter // the proper debug device ID below (which requires manual testing). - if (!ShouldRunUITests()) { - SKIP_TEST_ON_MOBILE_HARDWARE; + if (!IsRunningOnEmulator()) { + TEST_REQUIRES_USER_INTERACTION; } using firebase::gma::ump::ConsentDebugSettings; @@ -2720,8 +2720,8 @@ TEST_F(FirebaseGmaUmpTest, TestUmpShowForm) { TEST_F(FirebaseGmaUmpTest, TestUmpLoadFormUnavailableDueUnderAgeOfConsent) { // For debug options to work, you need to be on simulator OR enter // the proper debug device ID below (which requires manual testing). - if (!ShouldRunUITests()) { - SKIP_TEST_ON_MOBILE_HARDWARE; + if (!IsRunningOnEmulator()) { + TEST_REQUIRES_USER_INTERACTION; } using firebase::gma::ump::ConsentDebugSettings; @@ -2745,8 +2745,8 @@ TEST_F(FirebaseGmaUmpTest, TestUmpLoadFormUnavailableDueUnderAgeOfConsent) { TEST_F(FirebaseGmaUmpTest, TestUmpLoadFormUnavailableDebugNonEEA) { // For debug options to work, you need to be on simulator OR enter // the proper debug device ID below (which requires manual testing). - if (!ShouldRunUITests()) { - SKIP_TEST_ON_MOBILE_HARDWARE; + if (!IsRunningOnEmulator()) { + TEST_REQUIRES_USER_INTERACTION; } using firebase::gma::ump::ConsentDebugSettings; @@ -2774,8 +2774,8 @@ TEST_F(FirebaseGmaUmpTest, TestUmpLoadAndShowIfRequiredDebugNonEEA) { // For debug options to work, you need to be on simulator OR enter // the proper debug device ID below (which requires manual testing). - if (!ShouldRunUITests()) { - SKIP_TEST_ON_MOBILE_HARDWARE; + if (!IsRunningOnEmulator()) { + TEST_REQUIRES_USER_INTERACTION; } ConsentRequestParameters params; From 2cba298bb7a9c6d48ee2b3e4952d4af1dd404cbe Mon Sep 17 00:00:00 2001 From: Jon Simantov Date: Wed, 16 Aug 2023 10:34:08 -0700 Subject: [PATCH 14/26] Add debug ID to certain tests. --- gma/integration_test/src/integration_test.cc | 48 ++++++++------------ 1 file changed, 18 insertions(+), 30 deletions(-) diff --git a/gma/integration_test/src/integration_test.cc b/gma/integration_test/src/integration_test.cc index abeb7a6985..448d5f4ca9 100644 --- a/gma/integration_test/src/integration_test.cc +++ b/gma/integration_test/src/integration_test.cc @@ -103,6 +103,9 @@ const char* kErrorDomain = "com.google.admob"; const std::vector kTestDeviceIDs = { "2077ef9a63d2b398840261c8221a0c9b", "098fe087d987c9a878965454a65654d7"}; +// TODO(jsimantov): Remove after UMP development. +#define DEBUG_ONLY_TEST_UMP + // Sample keywords to use in making the request. static const std::vector kKeywords({"GMA", "C++", "Fun"}); @@ -352,6 +355,8 @@ void FirebaseGmaPreInitializationTests::SetUpTestSuite() { #endif // defined(ANDROID) } +#ifndef DEBUG_ONLY_TEST_UMP + // Test cases below. TEST_F(FirebaseGmaMinimalTest, TestInitializeGmaWithoutFirebase) { @@ -2473,6 +2478,8 @@ TEST_F(FirebaseGmaTest, TestAdViewMultithreadDeletion) { #endif // #if defined(ANDROID) || (defined(TARGET_OS_IPHONE) && // TARGET_OS_IPHONE) +#endif // DEBUG_ONLY_TEST_UMP + class FirebaseGmaUmpTest : public FirebaseGmaTest { public: FirebaseGmaUmpTest() : consent_info_(nullptr) {} @@ -2587,12 +2594,6 @@ TEST_F(FirebaseGmaUmpTest, TestUmpRequestConsentInfoUpdate) { } TEST_F(FirebaseGmaUmpTest, TestUmpRequestConsentInfoUpdateDebugEEA) { - // For debug options to work, you need to be on simulator OR enter - // the proper debug device ID below (which requires manual testing). - if (!IsRunningOnEmulator()) { - TEST_REQUIRES_USER_INTERACTION; - } - using firebase::gma::ump::ConsentDebugSettings; using firebase::gma::ump::ConsentRequestParameters; using firebase::gma::ump::ConsentStatus; @@ -2602,6 +2603,7 @@ TEST_F(FirebaseGmaUmpTest, TestUmpRequestConsentInfoUpdateDebugEEA) { params.debug_settings.debug_geography = firebase::gma::ump::kConsentDebugGeographyEEA; params.debug_settings.debug_device_ids = kTestDeviceIDs; + params.debug_settings.debug_device_ids.push_back(GetDebugDeviceId()); firebase::Future future = consent_info_->RequestConsentInfoUpdate(params); @@ -2613,12 +2615,6 @@ TEST_F(FirebaseGmaUmpTest, TestUmpRequestConsentInfoUpdateDebugEEA) { } TEST_F(FirebaseGmaUmpTest, TestUmpRequestConsentInfoUpdateDebugNonEEA) { - // For debug options to work, you need to be on simulator OR enter - // the proper debug device ID below (which requires manual testing). - if (!IsRunningOnEmulator()) { - TEST_REQUIRES_USER_INTERACTION; - } - using firebase::gma::ump::ConsentDebugSettings; using firebase::gma::ump::ConsentRequestParameters; using firebase::gma::ump::ConsentStatus; @@ -2628,6 +2624,7 @@ TEST_F(FirebaseGmaUmpTest, TestUmpRequestConsentInfoUpdateDebugNonEEA) { params.debug_settings.debug_geography = firebase::gma::ump::kConsentDebugGeographyNonEEA; params.debug_settings.debug_device_ids = kTestDeviceIDs; + params.debug_settings.debug_device_ids.push_back(GetDebugDeviceId()); firebase::Future future = consent_info_->RequestConsentInfoUpdate(params); @@ -2649,6 +2646,7 @@ TEST_F(FirebaseGmaUmpTest, TestUmpLoadForm) { params.debug_settings.debug_geography = firebase::gma::ump::kConsentDebugGeographyEEA; params.debug_settings.debug_device_ids = kTestDeviceIDs; + params.debug_settings.debug_device_ids.push_back(GetDebugDeviceId()); WaitForCompletion(consent_info_->RequestConsentInfoUpdate(params), "RequestConsentInfoUpdate"); @@ -2683,6 +2681,7 @@ TEST_F(FirebaseGmaUmpTest, TestUmpShowForm) { params.debug_settings.debug_geography = firebase::gma::ump::kConsentDebugGeographyEEA; params.debug_settings.debug_device_ids = kTestDeviceIDs; + params.debug_settings.debug_device_ids.push_back(GetDebugDeviceId()); WaitForCompletion(consent_info_->RequestConsentInfoUpdate(params), "RequestConsentInfoUpdate"); @@ -2718,12 +2717,6 @@ TEST_F(FirebaseGmaUmpTest, TestUmpShowForm) { } TEST_F(FirebaseGmaUmpTest, TestUmpLoadFormUnavailableDueUnderAgeOfConsent) { - // For debug options to work, you need to be on simulator OR enter - // the proper debug device ID below (which requires manual testing). - if (!IsRunningOnEmulator()) { - TEST_REQUIRES_USER_INTERACTION; - } - using firebase::gma::ump::ConsentDebugSettings; using firebase::gma::ump::ConsentFormStatus; using firebase::gma::ump::ConsentRequestParameters; @@ -2734,6 +2727,7 @@ TEST_F(FirebaseGmaUmpTest, TestUmpLoadFormUnavailableDueUnderAgeOfConsent) { params.debug_settings.debug_geography = firebase::gma::ump::kConsentDebugGeographyEEA; params.debug_settings.debug_device_ids = kTestDeviceIDs; + params.debug_settings.debug_device_ids.push_back(GetDebugDeviceId()); WaitForCompletion(consent_info_->RequestConsentInfoUpdate(params), "RequestConsentInfoUpdate"); @@ -2743,12 +2737,6 @@ TEST_F(FirebaseGmaUmpTest, TestUmpLoadFormUnavailableDueUnderAgeOfConsent) { } TEST_F(FirebaseGmaUmpTest, TestUmpLoadFormUnavailableDebugNonEEA) { - // For debug options to work, you need to be on simulator OR enter - // the proper debug device ID below (which requires manual testing). - if (!IsRunningOnEmulator()) { - TEST_REQUIRES_USER_INTERACTION; - } - using firebase::gma::ump::ConsentDebugSettings; using firebase::gma::ump::ConsentFormStatus; using firebase::gma::ump::ConsentRequestParameters; @@ -2759,6 +2747,7 @@ TEST_F(FirebaseGmaUmpTest, TestUmpLoadFormUnavailableDebugNonEEA) { params.debug_settings.debug_geography = firebase::gma::ump::kConsentDebugGeographyNonEEA; params.debug_settings.debug_device_ids = kTestDeviceIDs; + params.debug_settings.debug_device_ids.push_back(GetDebugDeviceId()); WaitForCompletion(consent_info_->RequestConsentInfoUpdate(params), "RequestConsentInfoUpdate"); @@ -2772,17 +2761,12 @@ TEST_F(FirebaseGmaUmpTest, TestUmpLoadAndShowIfRequiredDebugNonEEA) { using firebase::gma::ump::ConsentRequestParameters; using firebase::gma::ump::ConsentStatus; - // For debug options to work, you need to be on simulator OR enter - // the proper debug device ID below (which requires manual testing). - if (!IsRunningOnEmulator()) { - TEST_REQUIRES_USER_INTERACTION; - } - ConsentRequestParameters params; params.tag_for_under_age_of_consent = false; params.debug_settings.debug_geography = firebase::gma::ump::kConsentDebugGeographyNonEEA; params.debug_settings.debug_device_ids = kTestDeviceIDs; + params.debug_settings.debug_device_ids.push_back(GetDebugDeviceId()); WaitForCompletion(consent_info_->RequestConsentInfoUpdate(params), "RequestConsentInfoUpdate"); @@ -2812,6 +2796,7 @@ TEST_F(FirebaseGmaUmpTest, TestUmpLoadAndShowIfRequiredDebugEEA) { params.debug_settings.debug_geography = firebase::gma::ump::kConsentDebugGeographyEEA; params.debug_settings.debug_device_ids = kTestDeviceIDs; + params.debug_settings.debug_device_ids.push_back(GetDebugDeviceId()); WaitForCompletion(consent_info_->RequestConsentInfoUpdate(params), "RequestConsentInfoUpdate"); @@ -2845,6 +2830,7 @@ TEST_F(FirebaseGmaUmpTest, TestUmpPrivacyOptions) { params.debug_settings.debug_geography = firebase::gma::ump::kConsentDebugGeographyEEA; params.debug_settings.debug_device_ids = kTestDeviceIDs; + params.debug_settings.debug_device_ids.push_back(GetDebugDeviceId()); WaitForCompletion(consent_info_->RequestConsentInfoUpdate(params), "RequestConsentInfoUpdate"); @@ -2892,6 +2878,7 @@ TEST_F(FirebaseGmaUmpTest, TestCanRequestAdsNonEEA) { params.debug_settings.debug_geography = firebase::gma::ump::kConsentDebugGeographyNonEEA; params.debug_settings.debug_device_ids = kTestDeviceIDs; + params.debug_settings.debug_device_ids.push_back(GetDebugDeviceId()); WaitForCompletion(consent_info_->RequestConsentInfoUpdate(params), "RequestConsentInfoUpdate"); @@ -2914,6 +2901,7 @@ TEST_F(FirebaseGmaUmpTest, TestCanRequestAdsEEA) { params.debug_settings.debug_geography = firebase::gma::ump::kConsentDebugGeographyEEA; params.debug_settings.debug_device_ids = kTestDeviceIDs; + params.debug_settings.debug_device_ids.push_back(GetDebugDeviceId()); WaitForCompletion(consent_info_->RequestConsentInfoUpdate(params), "RequestConsentInfoUpdate"); From a705ec540a0ecedac95b11ffe873483199a15baf Mon Sep 17 00:00:00 2001 From: Jon Simantov Date: Wed, 16 Aug 2023 10:39:49 -0700 Subject: [PATCH 15/26] Format code. --- .../src/desktop/desktop_firebase_test_framework.cc | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/testing/test_framework/src/desktop/desktop_firebase_test_framework.cc b/testing/test_framework/src/desktop/desktop_firebase_test_framework.cc index 835a667b8a..01e4a4e7c9 100644 --- a/testing/test_framework/src/desktop/desktop_firebase_test_framework.cc +++ b/testing/test_framework/src/desktop/desktop_firebase_test_framework.cc @@ -59,8 +59,6 @@ int FirebaseTest::GetGooglePlayServicesVersion() { return 0; } -std::string FirebaseTest::GetDebugDeviceId() { - return "placeholder-device-id"; -} +std::string FirebaseTest::GetDebugDeviceId() { return "placeholder-device-id"; } } // namespace firebase_test_framework From d1698837dd17feb22d292d487fca6179b1d31f2c Mon Sep 17 00:00:00 2001 From: Jon Simantov Date: Wed, 16 Aug 2023 10:39:57 -0700 Subject: [PATCH 16/26] Format code. --- gma/integration_test/src/integration_test.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gma/integration_test/src/integration_test.cc b/gma/integration_test/src/integration_test.cc index 448d5f4ca9..9bf1e7dbe6 100644 --- a/gma/integration_test/src/integration_test.cc +++ b/gma/integration_test/src/integration_test.cc @@ -2478,7 +2478,7 @@ TEST_F(FirebaseGmaTest, TestAdViewMultithreadDeletion) { #endif // #if defined(ANDROID) || (defined(TARGET_OS_IPHONE) && // TARGET_OS_IPHONE) -#endif // DEBUG_ONLY_TEST_UMP +#endif // DEBUG_ONLY_TEST_UMP class FirebaseGmaUmpTest : public FirebaseGmaTest { public: From 454bd7e4cd7b38dfa44f8241e8be69715d3dd1ae Mon Sep 17 00:00:00 2001 From: Jon Simantov Date: Wed, 16 Aug 2023 11:02:08 -0700 Subject: [PATCH 17/26] Add a mutex to prevent race condition. --- gma/src/ios/ump/consent_info_internal_ios.h | 8 +++ gma/src/ios/ump/consent_info_internal_ios.mm | 69 +++++++++++++++----- 2 files changed, 61 insertions(+), 16 deletions(-) diff --git a/gma/src/ios/ump/consent_info_internal_ios.h b/gma/src/ios/ump/consent_info_internal_ios.h index 385df82f34..5472c91d4a 100644 --- a/gma/src/ios/ump/consent_info_internal_ios.h +++ b/gma/src/ios/ump/consent_info_internal_ios.h @@ -19,6 +19,7 @@ #include +#include "firebase/internal/mutex.h" #include "gma/src/common/ump/consent_info_internal.h" namespace firebase { @@ -49,6 +50,13 @@ class ConsentInfoInternalIos : public ConsentInfoInternal { void Reset() override; private: + static ConsentInfoInternalIos* s_instance; + static firebase::Mutex s_instance_mutex; + + void SetLoadedForm(UMPConsentForm *form) { + loaded_form_ = form; + } + UMPConsentForm *loaded_form_; }; diff --git a/gma/src/ios/ump/consent_info_internal_ios.mm b/gma/src/ios/ump/consent_info_internal_ios.mm index cb6a312646..530cf4946e 100644 --- a/gma/src/ios/ump/consent_info_internal_ios.mm +++ b/gma/src/ios/ump/consent_info_internal_ios.mm @@ -24,6 +24,10 @@ namespace ump { namespace internal { +ConsentInfoInternalIos* ConsentInfoInternalIos::s_instance = nullptr; +firebase::Mutex ConsentInfoInternalIos::s_instance_mutex; + + // This explicitly implements the constructor for the outer class, // ConsentInfoInternal. ConsentInfoInternal* ConsentInfoInternal::CreateInstance() { @@ -31,9 +35,15 @@ } ConsentInfoInternalIos::ConsentInfoInternalIos() - : loaded_form_(nil) {} + : loaded_form_(nil) { + MutexLock lock(s_instance_mutex); + s_instance = this; +} -ConsentInfoInternalIos::~ConsentInfoInternalIos() {} +ConsentInfoInternalIos::~ConsentInfoInternalIos() { + MutexLock lock(s_instance_mutex); + s_instance = nullptr; +} static ConsentRequestError CppRequestErrorFromIosRequestError(NSInteger code) { switch(code) { @@ -70,7 +80,7 @@ static ConsentFormError CppFormErrorFromIosFormError(NSInteger code) { return kConsentFormErrorUnknown; } } - + Future ConsentInfoInternalIos::RequestConsentInfoUpdate( const ConsentRequestParameters& params) { SafeFutureHandle handle = @@ -79,7 +89,7 @@ static ConsentFormError CppFormErrorFromIosFormError(NSInteger code) { UMPRequestParameters *ios_parameters = [[UMPRequestParameters alloc] init]; ios_parameters.tagForUnderAgeOfConsent = params.tag_for_under_age_of_consent ? YES : NO; UMPDebugSettings *ios_debug_settings = [[UMPDebugSettings alloc] init]; - + switch(params.debug_settings.debug_geography) { case kConsentDebugGeographyEEA: ios_debug_settings.geography = UMPDebugGeographyEEA; @@ -154,15 +164,24 @@ static ConsentFormError CppFormErrorFromIosFormError(NSInteger code) { loaded_form_ = nil; util::DispatchAsyncSafeMainQueue(^{ - [UMPConsentForm + [UMPConsentForm loadWithCompletionHandler:^(UMPConsentForm *_Nullable form, NSError *_Nullable error){ if (form) { - loaded_form_ = form; - CompleteFuture(handle, kConsentFormSuccess, "Success"); + MutexLock lock(s_instance_mutex); + if (s_instance) { + SetLoadedForm(form); + CompleteFuture(handle, kConsentFormSuccess, "Success"); + } } else if (error) { - CompleteFuture(handle, CppFormErrorFromIosFormError(error.code), error.localizedDescription.UTF8String); + MutexLock lock(s_instance_mutex); + if (s_instance) { + CompleteFuture(handle, CppFormErrorFromIosFormError(error.code), error.localizedDescription.UTF8String); + } } else { - CompleteFuture(handle, kConsentFormErrorUnknown, "An unknown error occurred."); + MutexLock lock(s_instance_mutex); + if (s_instance) { + CompleteFuture(handle, kConsentFormErrorUnknown, "An unknown error occurred."); + } } }]; }); @@ -180,9 +199,15 @@ static ConsentFormError CppFormErrorFromIosFormError(NSInteger code) { [loaded_form_ presentFromViewController:parent completionHandler:^(NSError *_Nullable error){ if (!error) { - CompleteFuture(handle, kConsentRequestSuccess); + MutexLock lock(s_instance_mutex); + if (s_instance) { + CompleteFuture(handle, kConsentRequestSuccess); + } } else { - CompleteFuture(handle, CppFormErrorFromIosFormError(error.code), error.localizedDescription.UTF8String); + MutexLock lock(s_instance_mutex); + if (s_instance) { + CompleteFuture(handle, CppFormErrorFromIosFormError(error.code), error.localizedDescription.UTF8String); + } } }]; }); @@ -199,9 +224,15 @@ static ConsentFormError CppFormErrorFromIosFormError(NSInteger code) { [UMPConsentForm loadAndPresentIfRequiredFromViewController:parent completionHandler:^(NSError *_Nullable error){ if (!error) { - CompleteFuture(handle, kConsentRequestSuccess); + MutexLock lock(s_instance_mutex); + if (s_instance) { + CompleteFuture(handle, kConsentRequestSuccess); + } } else { - CompleteFuture(handle, CppFormErrorFromIosFormError(error.code), error.localizedDescription.UTF8String); + MutexLock lock(s_instance_mutex); + if (s_instance) { + CompleteFuture(handle, CppFormErrorFromIosFormError(error.code), error.localizedDescription.UTF8String); + } } }]; }); @@ -233,15 +264,21 @@ static ConsentFormError CppFormErrorFromIosFormError(NSInteger code) { [UMPConsentForm presentPrivacyOptionsFormFromViewController:parent completionHandler:^(NSError *_Nullable error){ if (!error) { - CompleteFuture(handle, kConsentRequestSuccess); + MutexLock lock(s_instance_mutex); + if (s_instance) { + CompleteFuture(handle, kConsentRequestSuccess); + } } else { - CompleteFuture(handle, CppFormErrorFromIosFormError(error.code), error.localizedDescription.UTF8String); + MutexLock lock(s_instance_mutex); + if (s_instance) { + CompleteFuture(handle, CppFormErrorFromIosFormError(error.code), error.localizedDescription.UTF8String); + } } }]; }); return MakeFuture(futures(), handle); } - + bool ConsentInfoInternalIos::CanRequestAds() { return (UMPConsentInformation.sharedInstance.canRequestAds == YES ? true : false); } From 47408f65b8c69d824ff6a6b421a1d78d80dffba9 Mon Sep 17 00:00:00 2001 From: Jon Simantov Date: Wed, 16 Aug 2023 11:03:32 -0700 Subject: [PATCH 18/26] Update test to check race condition. --- gma/integration_test/src/integration_test.cc | 24 ++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/gma/integration_test/src/integration_test.cc b/gma/integration_test/src/integration_test.cc index 9bf1e7dbe6..c54c20dd6a 100644 --- a/gma/integration_test/src/integration_test.cc +++ b/gma/integration_test/src/integration_test.cc @@ -2912,7 +2912,7 @@ TEST_F(FirebaseGmaUmpTest, TestCanRequestAdsEEA) { EXPECT_FALSE(consent_info_->CanRequestAds()); } -TEST_F(FirebaseGmaUmpTest, TestUmpCleanup) { +TEST_F(FirebaseGmaUmpTest, TestUmpCleanupWithDelay) { using firebase::gma::ump::ConsentFormStatus; using firebase::gma::ump::ConsentRequestParameters; using firebase::gma::ump::ConsentStatus; @@ -2925,7 +2925,6 @@ TEST_F(FirebaseGmaUmpTest, TestUmpCleanup) { firebase::Future future_show = consent_info_->ShowConsentForm(app_framework::GetWindowController()); - // TODO(jsimantov): Remove this delay after race conditions are addressed. ProcessEvents(5000); delete consent_info_; @@ -2936,4 +2935,25 @@ TEST_F(FirebaseGmaUmpTest, TestUmpCleanup) { EXPECT_EQ(future_show.status(), firebase::kFutureStatusInvalid); } +TEST_F(FirebaseGmaUmpTest, TestUmpCleanupRaceCondition) { + 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(app_framework::GetWindowController()); + + 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 From bdd4c5785df5b95833d24d1b50b3053fb81c0af6 Mon Sep 17 00:00:00 2001 From: Jon Simantov Date: Wed, 16 Aug 2023 11:09:22 -0700 Subject: [PATCH 19/26] Add include for lint --- testing/test_framework/src/firebase_test_framework.h | 1 + 1 file changed, 1 insertion(+) diff --git a/testing/test_framework/src/firebase_test_framework.h b/testing/test_framework/src/firebase_test_framework.h index f7b0cd2fff..8a786cdf3d 100644 --- a/testing/test_framework/src/firebase_test_framework.h +++ b/testing/test_framework/src/firebase_test_framework.h @@ -17,6 +17,7 @@ #include #include +#include #include #include From da701217d2ee7a83ba78969b1271a61fb4fd4123 Mon Sep 17 00:00:00 2001 From: Jon Simantov Date: Wed, 16 Aug 2023 11:26:23 -0700 Subject: [PATCH 20/26] Update test name. --- gma/integration_test/src/integration_test.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gma/integration_test/src/integration_test.cc b/gma/integration_test/src/integration_test.cc index c54c20dd6a..f78ed95836 100644 --- a/gma/integration_test/src/integration_test.cc +++ b/gma/integration_test/src/integration_test.cc @@ -2716,7 +2716,7 @@ TEST_F(FirebaseGmaUmpTest, TestUmpShowForm) { firebase::gma::ump::kConsentStatusObtained); } -TEST_F(FirebaseGmaUmpTest, TestUmpLoadFormUnavailableDueUnderAgeOfConsent) { +TEST_F(FirebaseGmaUmpTest, TestUmpLoadFormUnavailableDueToUnderAgeOfConsent) { using firebase::gma::ump::ConsentDebugSettings; using firebase::gma::ump::ConsentFormStatus; using firebase::gma::ump::ConsentRequestParameters; From b85feb3030608e0cbc76f1d9335524c0230ea95b Mon Sep 17 00:00:00 2001 From: Jon Simantov Date: Wed, 16 Aug 2023 12:42:03 -0700 Subject: [PATCH 21/26] Remove user interaction flag from debug test. --- gma/integration_test/src/integration_test.cc | 4 ---- 1 file changed, 4 deletions(-) diff --git a/gma/integration_test/src/integration_test.cc b/gma/integration_test/src/integration_test.cc index f78ed95836..e753c31300 100644 --- a/gma/integration_test/src/integration_test.cc +++ b/gma/integration_test/src/integration_test.cc @@ -2871,8 +2871,6 @@ TEST_F(FirebaseGmaUmpTest, TestCanRequestAdsNonEEA) { 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 = @@ -2894,8 +2892,6 @@ TEST_F(FirebaseGmaUmpTest, TestCanRequestAdsEEA) { 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 = From 453d3f8eca538eb880b75e26ec9436483cad7b36 Mon Sep 17 00:00:00 2001 From: Jon Simantov Date: Wed, 16 Aug 2023 12:50:37 -0700 Subject: [PATCH 22/26] Format code. --- testing/test_framework/src/firebase_test_framework.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/test_framework/src/firebase_test_framework.h b/testing/test_framework/src/firebase_test_framework.h index 8a786cdf3d..4d03fe317f 100644 --- a/testing/test_framework/src/firebase_test_framework.h +++ b/testing/test_framework/src/firebase_test_framework.h @@ -17,8 +17,8 @@ #include #include -#include #include +#include #include #include "app_framework.h" // NOLINT From d7ee1e1c6bb57e413e06d1a0549832dc0a1572fe Mon Sep 17 00:00:00 2001 From: Jon Simantov Date: Wed, 16 Aug 2023 12:51:19 -0700 Subject: [PATCH 23/26] Remove UMP-only test mode. --- gma/integration_test/src/integration_test.cc | 3 --- 1 file changed, 3 deletions(-) diff --git a/gma/integration_test/src/integration_test.cc b/gma/integration_test/src/integration_test.cc index e753c31300..2b6b249b8e 100644 --- a/gma/integration_test/src/integration_test.cc +++ b/gma/integration_test/src/integration_test.cc @@ -103,9 +103,6 @@ const char* kErrorDomain = "com.google.admob"; const std::vector kTestDeviceIDs = { "2077ef9a63d2b398840261c8221a0c9b", "098fe087d987c9a878965454a65654d7"}; -// TODO(jsimantov): Remove after UMP development. -#define DEBUG_ONLY_TEST_UMP - // Sample keywords to use in making the request. static const std::vector kKeywords({"GMA", "C++", "Fun"}); From a99c76232fb13bb503c0ed3aad6426478487afeb Mon Sep 17 00:00:00 2001 From: Jon Simantov Date: Wed, 16 Aug 2023 19:55:50 -0700 Subject: [PATCH 24/26] Remove unnecessary test stuff. Return existing instance rather than creating new instance of internal class. --- gma/integration_test/src/integration_test.cc | 12 ------------ gma/src/ios/ump/consent_info_internal_ios.mm | 4 +++- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/gma/integration_test/src/integration_test.cc b/gma/integration_test/src/integration_test.cc index 2b6b249b8e..da158fa0ea 100644 --- a/gma/integration_test/src/integration_test.cc +++ b/gma/integration_test/src/integration_test.cc @@ -352,8 +352,6 @@ void FirebaseGmaPreInitializationTests::SetUpTestSuite() { #endif // defined(ANDROID) } -#ifndef DEBUG_ONLY_TEST_UMP - // Test cases below. TEST_F(FirebaseGmaMinimalTest, TestInitializeGmaWithoutFirebase) { @@ -2475,8 +2473,6 @@ TEST_F(FirebaseGmaTest, TestAdViewMultithreadDeletion) { #endif // #if defined(ANDROID) || (defined(TARGET_OS_IPHONE) && // TARGET_OS_IPHONE) -#endif // DEBUG_ONLY_TEST_UMP - class FirebaseGmaUmpTest : public FirebaseGmaTest { public: FirebaseGmaUmpTest() : consent_info_(nullptr) {} @@ -2683,20 +2679,12 @@ TEST_F(FirebaseGmaUmpTest, TestUmpShowForm) { WaitForCompletion(consent_info_->RequestConsentInfoUpdate(params), "RequestConsentInfoUpdate"); - ProcessEvents(1000); - EXPECT_EQ(consent_info_->GetConsentStatus(), firebase::gma::ump::kConsentStatusRequired); EXPECT_EQ(consent_info_->GetConsentFormStatus(), firebase::gma::ump::kConsentFormStatusAvailable); - ProcessEvents(1000); - - LogInfo("About to load consent form"); - - ProcessEvents(1000); - WaitForCompletion(consent_info_->LoadConsentForm(), "LoadConsentForm"); EXPECT_EQ(consent_info_->GetConsentFormStatus(), diff --git a/gma/src/ios/ump/consent_info_internal_ios.mm b/gma/src/ios/ump/consent_info_internal_ios.mm index 530cf4946e..12c3f9c3c8 100644 --- a/gma/src/ios/ump/consent_info_internal_ios.mm +++ b/gma/src/ios/ump/consent_info_internal_ios.mm @@ -16,6 +16,7 @@ #include "gma/src/ios/ump/consent_info_internal_ios.h" +#include "app/src/assert.h" #include "app/src/thread.h" #include "app/src/util_ios.h" @@ -31,7 +32,8 @@ // This explicitly implements the constructor for the outer class, // ConsentInfoInternal. ConsentInfoInternal* ConsentInfoInternal::CreateInstance() { - return new ConsentInfoInternalIos(); + MutexLock lock(s_instance_mutex); + return s_instance ? s_instance : new ConsentInfoInternalIos(); } ConsentInfoInternalIos::ConsentInfoInternalIos() From e147305d2fca5c094c6e3c1f73edf1fc14b27e47 Mon Sep 17 00:00:00 2001 From: Jon Simantov Date: Wed, 16 Aug 2023 20:04:18 -0700 Subject: [PATCH 25/26] Fix assert. --- gma/src/ios/ump/consent_info_internal_ios.mm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gma/src/ios/ump/consent_info_internal_ios.mm b/gma/src/ios/ump/consent_info_internal_ios.mm index 12c3f9c3c8..08d2a0d004 100644 --- a/gma/src/ios/ump/consent_info_internal_ios.mm +++ b/gma/src/ios/ump/consent_info_internal_ios.mm @@ -32,13 +32,13 @@ // This explicitly implements the constructor for the outer class, // ConsentInfoInternal. ConsentInfoInternal* ConsentInfoInternal::CreateInstance() { - MutexLock lock(s_instance_mutex); - return s_instance ? s_instance : new ConsentInfoInternalIos(); + return new ConsentInfoInternalIos(); } ConsentInfoInternalIos::ConsentInfoInternalIos() : loaded_form_(nil) { MutexLock lock(s_instance_mutex); + FIREBASE_ASSERT(s_instance == nullptr); s_instance = this; } From 7a03dd97180701acd70347aed2a8bbd9e50a6c40 Mon Sep 17 00:00:00 2001 From: Jon Simantov Date: Wed, 16 Aug 2023 20:40:20 -0700 Subject: [PATCH 26/26] Fix stub to match iOS flow. --- gma/src/stub/ump/consent_info_internal_stub.cc | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/gma/src/stub/ump/consent_info_internal_stub.cc b/gma/src/stub/ump/consent_info_internal_stub.cc index 8f4d033896..6bc92ec08c 100644 --- a/gma/src/stub/ump/consent_info_internal_stub.cc +++ b/gma/src/stub/ump/consent_info_internal_stub.cc @@ -58,8 +58,10 @@ Future ConsentInfoInternalStub::RequestConsentInfoUpdate( 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; + consent_form_status_ = + (under_age_of_consent_ || consent_status_ != kConsentStatusRequired) + ? kConsentFormStatusUnavailable + : kConsentFormStatusAvailable; debug_geo_ = params.debug_settings.debug_geography; privacy_options_requirement_status_ = kPrivacyOptionsRequirementStatusNotRequired; @@ -104,7 +106,8 @@ Future ConsentInfoInternalStub::LoadAndShowConsentFormIfRequired( SafeFutureHandle handle = CreateFuture(kConsentInfoFnLoadAndShowConsentFormIfRequired); - if (consent_form_status_ != kConsentFormStatusAvailable) { + if (consent_status_ == kConsentStatusRequired && + consent_form_status_ != kConsentFormStatusAvailable) { CompleteFuture(handle, kConsentFormErrorUnavailable); return MakeFuture(futures(), handle); }