diff --git a/gma/CMakeLists.txt b/gma/CMakeLists.txt
index 440d0db7f0..887139f1db 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
@@ -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/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/Podfile b/gma/integration_test/Podfile
index 8f9a305cdf..b347ca3b39 100644
--- a/gma/integration_test/Podfile
+++ b/gma/integration_test/Podfile
@@ -6,6 +6,7 @@ use_frameworks! :linkage => :static
target 'integration_test' do
pod 'Firebase/CoreOnly', '10.13.0'
pod 'Google-Mobile-Ads-SDK', '10.9.0'
+ pod 'GoogleUserMessagingPlatform', '2.1.0'
end
post_install do |installer|
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 */,
diff --git a/gma/integration_test/src/integration_test.cc b/gma/integration_test/src/integration_test.cc
index 7143237873..da158fa0ea 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;
@@ -2498,6 +2501,7 @@ void FirebaseGmaUmpTest::InitializeUmp(ResetOption reset) {
void FirebaseGmaUmpTest::TerminateUmp() {
if (consent_info_) {
+ consent_info_->Reset();
delete consent_info_;
consent_info_ = nullptr;
}
@@ -2591,6 +2595,8 @@ 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;
+ params.debug_settings.debug_device_ids.push_back(GetDebugDeviceId());
firebase::Future future =
consent_info_->RequestConsentInfoUpdate(params);
@@ -2610,6 +2616,8 @@ 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;
+ params.debug_settings.debug_device_ids.push_back(GetDebugDeviceId());
firebase::Future future =
consent_info_->RequestConsentInfoUpdate(params);
@@ -2630,6 +2638,8 @@ 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;
+ params.debug_settings.debug_device_ids.push_back(GetDebugDeviceId());
WaitForCompletion(consent_info_->RequestConsentInfoUpdate(params),
"RequestConsentInfoUpdate");
@@ -2663,6 +2673,8 @@ 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;
+ params.debug_settings.debug_device_ids.push_back(GetDebugDeviceId());
WaitForCompletion(consent_info_->RequestConsentInfoUpdate(params),
"RequestConsentInfoUpdate");
@@ -2678,7 +2690,8 @@ TEST_F(FirebaseGmaUmpTest, TestUmpShowForm) {
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());
@@ -2688,9 +2701,7 @@ TEST_F(FirebaseGmaUmpTest, TestUmpShowForm) {
firebase::gma::ump::kConsentStatusObtained);
}
-TEST_F(FirebaseGmaUmpTest, TestUmpLoadFormUnavailableDueUnderAgeOfConsent) {
- TEST_REQUIRES_USER_INTERACTION;
-
+TEST_F(FirebaseGmaUmpTest, TestUmpLoadFormUnavailableDueToUnderAgeOfConsent) {
using firebase::gma::ump::ConsentDebugSettings;
using firebase::gma::ump::ConsentFormStatus;
using firebase::gma::ump::ConsentRequestParameters;
@@ -2700,24 +2711,65 @@ 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;
+ params.debug_settings.debug_device_ids.push_back(GetDebugDeviceId());
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) {
+ 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;
+ params.debug_settings.debug_device_ids.push_back(GetDebugDeviceId());
+
+ 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;
+
+ 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");
+
+ 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 +2780,8 @@ 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;
+ params.debug_settings.debug_device_ids.push_back(GetDebugDeviceId());
WaitForCompletion(consent_info_->RequestConsentInfoUpdate(params),
"RequestConsentInfoUpdate");
@@ -2736,7 +2790,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 +2814,8 @@ 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;
+ params.debug_settings.debug_device_ids.push_back(GetDebugDeviceId());
WaitForCompletion(consent_info_->RequestConsentInfoUpdate(params),
"RequestConsentInfoUpdate");
@@ -2768,28 +2825,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) {
@@ -2797,12 +2856,12 @@ 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 =
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");
@@ -2818,12 +2877,12 @@ 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 =
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");
@@ -2834,7 +2893,30 @@ 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;
+
+ 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());
+
+ ProcessEvents(5000);
+
+ 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);
+}
+
+TEST_F(FirebaseGmaUmpTest, TestUmpCleanupRaceCondition) {
using firebase::gma::ump::ConsentFormStatus;
using firebase::gma::ump::ConsentRequestParameters;
using firebase::gma::ump::ConsentStatus;
@@ -2844,7 +2926,8 @@ 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());
delete consent_info_;
consent_info_ = nullptr;
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/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/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
new file mode 100644
index 0000000000..5472c91d4a
--- /dev/null
+++ b/gma/src/ios/ump/consent_info_internal_ios.h
@@ -0,0 +1,68 @@
+/*
+ * 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
+
+#include "firebase/internal/mutex.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() override;
+ Future RequestConsentInfoUpdate(
+ const ConsentRequestParameters& params) override;
+
+ ConsentFormStatus GetConsentFormStatus() 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:
+ static ConsentInfoInternalIos* s_instance;
+ static firebase::Mutex s_instance_mutex;
+
+ void SetLoadedForm(UMPConsentForm *form) {
+ loaded_form_ = form;
+ }
+
+ UMPConsentForm *loaded_form_;
+};
+
+} // 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..08d2a0d004
--- /dev/null
+++ b/gma/src/ios/ump/consent_info_internal_ios.mm
@@ -0,0 +1,295 @@
+/*
+ * 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/assert.h"
+#include "app/src/thread.h"
+#include "app/src/util_ios.h"
+
+namespace firebase {
+namespace gma {
+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() {
+ return new ConsentInfoInternalIos();
+}
+
+ConsentInfoInternalIos::ConsentInfoInternalIos()
+ : loaded_form_(nil) {
+ MutexLock lock(s_instance_mutex);
+ FIREBASE_ASSERT(s_instance == nullptr);
+ s_instance = this;
+}
+
+ConsentInfoInternalIos::~ConsentInfoInternalIos() {
+ MutexLock lock(s_instance_mutex);
+ s_instance = nullptr;
+}
+
+static ConsentRequestError CppRequestErrorFromIosRequestError(NSInteger 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)code);
+ return kConsentRequestErrorUnknown;
+ }
+}
+
+static ConsentFormError CppFormErrorFromIosFormError(NSInteger 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)code);
+ return kConsentFormErrorUnknown;
+ }
+}
+
+Future ConsentInfoInternalIos::RequestConsentInfoUpdate(
+ const ConsentRequestParameters& params) {
+ SafeFutureHandle handle =
+ CreateFuture(kConsentInfoFnRequestConsentInfoUpdate);
+
+ 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 = UMPDebugGeographyNotEEA;
+ 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;
+
+ 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);
+}
+
+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);
+ loaded_form_ = nil;
+
+ util::DispatchAsyncSafeMainQueue(^{
+ [UMPConsentForm
+ loadWithCompletionHandler:^(UMPConsentForm *_Nullable form, NSError *_Nullable error){
+ if (form) {
+ MutexLock lock(s_instance_mutex);
+ if (s_instance) {
+ SetLoadedForm(form);
+ CompleteFuture(handle, kConsentFormSuccess, "Success");
+ }
+ } else if (error) {
+ MutexLock lock(s_instance_mutex);
+ if (s_instance) {
+ CompleteFuture(handle, CppFormErrorFromIosFormError(error.code), error.localizedDescription.UTF8String);
+ }
+ } else {
+ MutexLock lock(s_instance_mutex);
+ if (s_instance) {
+ CompleteFuture(handle, kConsentFormErrorUnknown, "An unknown error occurred.");
+ }
+ }
+ }];
+ });
+ return MakeFuture(futures(), handle);
+}
+
+Future ConsentInfoInternalIos::ShowConsentForm(FormParent parent) {
+ SafeFutureHandle handle = CreateFuture(kConsentInfoFnShowConsentForm);
+
+ if (!loaded_form_) {
+ CompleteFuture(handle, kConsentFormErrorInvalidOperation,
+ "You must call LoadConsentForm() prior to calling ShowConsentForm().");
+ } else {
+ util::DispatchAsyncSafeMainQueue(^{
+ [loaded_form_ presentFromViewController:parent
+ completionHandler:^(NSError *_Nullable error){
+ if (!error) {
+ MutexLock lock(s_instance_mutex);
+ if (s_instance) {
+ CompleteFuture(handle, kConsentRequestSuccess);
+ }
+ } else {
+ MutexLock lock(s_instance_mutex);
+ if (s_instance) {
+ CompleteFuture(handle, CppFormErrorFromIosFormError(error.code), error.localizedDescription.UTF8String);
+ }
+ }
+ }];
+ });
+ }
+ return MakeFuture(futures(), handle);
+}
+
+Future ConsentInfoInternalIos::LoadAndShowConsentFormIfRequired(
+ FormParent parent) {
+ SafeFutureHandle handle =
+ CreateFuture(kConsentInfoFnLoadAndShowConsentFormIfRequired);
+
+ util::DispatchAsyncSafeMainQueue(^{
+ [UMPConsentForm loadAndPresentIfRequiredFromViewController:parent
+ completionHandler:^(NSError *_Nullable error){
+ if (!error) {
+ MutexLock lock(s_instance_mutex);
+ if (s_instance) {
+ CompleteFuture(handle, kConsentRequestSuccess);
+ }
+ } else {
+ MutexLock lock(s_instance_mutex);
+ if (s_instance) {
+ CompleteFuture(handle, CppFormErrorFromIosFormError(error.code), error.localizedDescription.UTF8String);
+ }
+ }
+ }];
+ });
+ return MakeFuture(futures(), handle);
+}
+
+PrivacyOptionsRequirementStatus
+ConsentInfoInternalIos::GetPrivacyOptionsRequirementStatus() {
+ 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);
+ util::DispatchAsyncSafeMainQueue(^{
+ [UMPConsentForm presentPrivacyOptionsFormFromViewController:parent
+ completionHandler:^(NSError *_Nullable error){
+ if (!error) {
+ MutexLock lock(s_instance_mutex);
+ if (s_instance) {
+ CompleteFuture(handle, kConsentRequestSuccess);
+ }
+ } else {
+ 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);
+}
+
+void ConsentInfoInternalIos::Reset() {
+ [UMPConsentInformation.sharedInstance reset];
+}
+
+} // namespace internal
+} // namespace ump
+} // namespace gma
+} // namespace firebase
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);
}
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_;
}
diff --git a/ios_pod/Podfile b/ios_pod/Podfile
index 85d0daf610..fc5a040227 100644
--- a/ios_pod/Podfile
+++ b/ios_pod/Podfile
@@ -6,6 +6,7 @@ target 'GetPods' do
pod 'Firebase/Core', '10.13.0'
pod 'Google-Mobile-Ads-SDK', '10.9.0'
+ pod 'GoogleUserMessagingPlatform', '2.1.0'
pod 'Firebase/Analytics', '10.13.0'
pod 'Firebase/AppCheck', '10.13.0'
pod 'Firebase/Auth', '10.13.0'
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..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,4 +59,6 @@ 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 cff9ef5aec..4d03fe317f 100644
--- a/testing/test_framework/src/firebase_test_framework.h
+++ b/testing/test_framework/src/firebase_test_framework.h
@@ -18,6 +18,7 @@
#include
#include
#include
+#include
#include
#include "app_framework.h" // NOLINT
@@ -192,6 +193,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 \
{ \
@@ -538,6 +556,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