Skip to content

Commit 54271d8

Browse files
authored
Feature: Analytics SetConsent function. (#1161)
* Add SetConsent function to Analytics. * Add stub. * Add integration test for SetConsent * Format code. * Add iOS implementation. * Add Android implementation. * Format code and add JNI SetConsent method. * Compiler errors. * Fix iOS build. * Fix iOS constants * Fix JNI specifier. * Lint * Fix iOS. * Use CacheFieldIds, oops. * Include Consent-specific header on iOS * Readme note. * Format code. * Fix JNI specifier
1 parent 0cac295 commit 54271d8

File tree

6 files changed

+205
-1
lines changed

6 files changed

+205
-1
lines changed

analytics/integration_test/src/integration_test.cc

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,30 @@ TEST_F(FirebaseAnalyticsTest, TestSetCollectionEnabled) {
8989
firebase::analytics::SetAnalyticsCollectionEnabled(true);
9090
}
9191

92+
TEST_F(FirebaseAnalyticsTest, TestSetConsent) {
93+
// Can't confirm that these do anything but just run them all to ensure the
94+
// app doesn't crash.
95+
std::map<firebase::analytics::ConsentType, firebase::analytics::ConsentStatus>
96+
consent_settings_allow = {
97+
{firebase::analytics::kConsentTypeAnalyticsStorage,
98+
firebase::analytics::kConsentStatusGranted},
99+
{firebase::analytics::kConsentTypeAdStorage,
100+
firebase::analytics::kConsentStatusGranted}};
101+
std::map<firebase::analytics::ConsentType, firebase::analytics::ConsentStatus>
102+
consent_settings_deny = {
103+
{firebase::analytics::kConsentTypeAnalyticsStorage,
104+
firebase::analytics::kConsentStatusDenied},
105+
{firebase::analytics::kConsentTypeAdStorage,
106+
firebase::analytics::kConsentStatusDenied}};
107+
std::map<firebase::analytics::ConsentType, firebase::analytics::ConsentStatus>
108+
consent_settings_empty;
109+
firebase::analytics::SetConsent(consent_settings_empty);
110+
firebase::analytics::SetConsent(consent_settings_deny);
111+
firebase::analytics::SetConsent(consent_settings_empty);
112+
firebase::analytics::SetConsent(consent_settings_allow);
113+
firebase::analytics::SetConsent(consent_settings_empty);
114+
}
115+
92116
TEST_F(FirebaseAnalyticsTest, TestSetSessionTimeoutDuraction) {
93117
firebase::analytics::SetSessionTimeoutDuration(1000 * 60 * 5);
94118
firebase::analytics::SetSessionTimeoutDuration(1000 * 60 * 15);

analytics/src/analytics_android.cc

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ static const ::firebase::App* g_app = nullptr;
4747
// clang-format off
4848
#define ANALYTICS_METHODS(X) \
4949
X(SetEnabled, "setAnalyticsCollectionEnabled", "(Z)V"), \
50+
X(SetConsent, "setConsent", "(Ljava/util/Map;)V"), \
5051
X(LogEvent, "logEvent", "(Ljava/lang/String;Landroid/os/Bundle;)V"), \
5152
X(SetUserProperty, "setUserProperty", \
5253
"(Ljava/lang/String;Ljava/lang/String;)V"), \
@@ -60,12 +61,46 @@ static const ::firebase::App* g_app = nullptr;
6061
firebase::util::kMethodTypeStatic)
6162
// clang-format on
6263

64+
// clang-format off
65+
#define ANALYTICS_CONSENT_TYPE_FIELDS(X) \
66+
X(AnalyticsStorage, "ANALYTICS_STORAGE", \
67+
"Lcom/google/firebase/analytics/FirebaseAnalytics$ConsentType;", \
68+
util::kFieldTypeStatic), \
69+
X(AdStorage, "AD_STORAGE", \
70+
"Lcom/google/firebase/analytics/FirebaseAnalytics$ConsentType;", \
71+
util::kFieldTypeStatic)
72+
73+
#define ANALYTICS_CONSENT_STATUS_FIELDS(X) \
74+
X(Granted, "GRANTED", \
75+
"Lcom/google/firebase/analytics/FirebaseAnalytics$ConsentStatus;", \
76+
util::kFieldTypeStatic), \
77+
X(Denied, "DENIED", \
78+
"Lcom/google/firebase/analytics/FirebaseAnalytics$ConsentStatus;", \
79+
util::kFieldTypeStatic)
80+
// clang-format on
81+
6382
METHOD_LOOKUP_DECLARATION(analytics, ANALYTICS_METHODS)
6483
METHOD_LOOKUP_DEFINITION(analytics,
6584
PROGUARD_KEEP_CLASS
6685
"com/google/firebase/analytics/FirebaseAnalytics",
6786
ANALYTICS_METHODS)
6887

88+
METHOD_LOOKUP_DECLARATION(analytics_consent_type, METHOD_LOOKUP_NONE,
89+
ANALYTICS_CONSENT_TYPE_FIELDS)
90+
METHOD_LOOKUP_DEFINITION(
91+
analytics_consent_type,
92+
PROGUARD_KEEP_CLASS
93+
"com/google/firebase/analytics/FirebaseAnalytics$ConsentType",
94+
METHOD_LOOKUP_NONE, ANALYTICS_CONSENT_TYPE_FIELDS)
95+
96+
METHOD_LOOKUP_DECLARATION(analytics_consent_status, METHOD_LOOKUP_NONE,
97+
ANALYTICS_CONSENT_STATUS_FIELDS)
98+
METHOD_LOOKUP_DEFINITION(
99+
analytics_consent_status,
100+
PROGUARD_KEEP_CLASS
101+
"com/google/firebase/analytics/FirebaseAnalytics$ConsentStatus",
102+
METHOD_LOOKUP_NONE, ANALYTICS_CONSENT_STATUS_FIELDS)
103+
69104
// Initialize the Analytics API.
70105
void Initialize(const ::firebase::App& app) {
71106
if (g_app) {
@@ -85,6 +120,17 @@ void Initialize(const ::firebase::App& app) {
85120
util::Terminate(env);
86121
return;
87122
}
123+
if (!analytics_consent_type::CacheFieldIds(env, app.activity())) {
124+
analytics::ReleaseClass(env);
125+
util::Terminate(env);
126+
return;
127+
}
128+
if (!analytics_consent_status::CacheFieldIds(env, app.activity())) {
129+
analytics_consent_type::ReleaseClass(env);
130+
analytics::ReleaseClass(env);
131+
util::Terminate(env);
132+
return;
133+
}
88134

89135
internal::FutureData::Create();
90136
g_app = &app;
@@ -123,6 +169,8 @@ void Terminate() {
123169
g_app = nullptr;
124170
env->DeleteGlobalRef(g_analytics_class_instance);
125171
g_analytics_class_instance = nullptr;
172+
analytics_consent_status::ReleaseClass(env);
173+
analytics_consent_type::ReleaseClass(env);
126174
analytics::ReleaseClass(env);
127175
util::Terminate(env);
128176
}
@@ -138,6 +186,74 @@ void SetAnalyticsCollectionEnabled(bool enabled) {
138186
util::CheckAndClearJniExceptions(env);
139187
}
140188

189+
void SetConsent(const std::map<ConsentType, ConsentStatus>& consent_settings) {
190+
FIREBASE_ASSERT_RETURN_VOID(internal::IsInitialized());
191+
JNIEnv* env = g_app->GetJNIEnv();
192+
193+
jobject consent_map =
194+
env->NewObject(util::hash_map::GetClass(),
195+
util::hash_map::GetMethodId(util::hash_map::kConstructor));
196+
util::CheckAndClearJniExceptions(env);
197+
jmethodID put_method = util::map::GetMethodId(util::map::kPut);
198+
for (auto it = consent_settings.begin(); it != consent_settings.end(); ++it) {
199+
jobject consent_type;
200+
switch (it->first) {
201+
case kConsentTypeAdStorage:
202+
consent_type =
203+
env->GetStaticObjectField(analytics_consent_type::GetClass(),
204+
analytics_consent_type::GetFieldId(
205+
analytics_consent_type::kAdStorage));
206+
util::CheckAndClearJniExceptions(env);
207+
break;
208+
case kConsentTypeAnalyticsStorage:
209+
consent_type = env->GetStaticObjectField(
210+
analytics_consent_type::GetClass(),
211+
analytics_consent_type::GetFieldId(
212+
analytics_consent_type::kAnalyticsStorage));
213+
214+
util::CheckAndClearJniExceptions(env);
215+
break;
216+
default:
217+
LogError("Unknown ConsentType value: %d", it->first);
218+
env->DeleteLocalRef(consent_map);
219+
return;
220+
}
221+
jobject consent_status;
222+
switch (it->second) {
223+
case kConsentStatusGranted:
224+
consent_status =
225+
env->GetStaticObjectField(analytics_consent_status::GetClass(),
226+
analytics_consent_status::GetFieldId(
227+
analytics_consent_status::kGranted));
228+
util::CheckAndClearJniExceptions(env);
229+
break;
230+
case kConsentStatusDenied:
231+
consent_status =
232+
env->GetStaticObjectField(analytics_consent_status::GetClass(),
233+
analytics_consent_status::GetFieldId(
234+
analytics_consent_status::kDenied));
235+
util::CheckAndClearJniExceptions(env);
236+
break;
237+
default:
238+
LogError("Unknown ConsentStatus value: %d", it->second);
239+
env->DeleteLocalRef(consent_map);
240+
env->DeleteLocalRef(consent_type);
241+
return;
242+
}
243+
jobject previous = env->CallObjectMethod(consent_map, put_method,
244+
consent_type, consent_status);
245+
util::CheckAndClearJniExceptions(env);
246+
if (previous) env->DeleteLocalRef(previous);
247+
env->DeleteLocalRef(consent_type);
248+
env->DeleteLocalRef(consent_status);
249+
}
250+
env->CallVoidMethod(g_analytics_class_instance,
251+
analytics::GetMethodId(analytics::kSetConsent),
252+
consent_map);
253+
util::CheckAndClearJniExceptions(env);
254+
env->DeleteLocalRef(consent_map);
255+
}
256+
141257
// Build an event bundle using build_bundle and log it.
142258
template <typename BuildBundleFunction>
143259
void LogEvent(JNIEnv* env, const char* name, BuildBundleFunction build_bundle) {

analytics/src/analytics_ios.mm

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
#import <Foundation/Foundation.h>
1818

19+
#import "FIRAnalytics+Consent.h"
1920
#import "FIRAnalytics+OnDevice.h"
2021
#import "FIRAnalytics.h"
2122

@@ -150,6 +151,40 @@ void SetAnalyticsCollectionEnabled(bool enabled) {
150151
[FIRAnalytics setAnalyticsCollectionEnabled:enabled];
151152
}
152153

154+
void SetConsent(const std::map<ConsentType, ConsentStatus>& consent_settings) {
155+
FIREBASE_ASSERT_RETURN_VOID(internal::IsInitialized());
156+
NSMutableDictionary* consent_settings_dict =
157+
[[NSMutableDictionary alloc] initWithCapacity:consent_settings.size()];
158+
for (auto it = consent_settings.begin(); it != consent_settings.end(); ++it) {
159+
FIRConsentType consent_type;
160+
switch (it->first) {
161+
case kConsentTypeAdStorage:
162+
consent_type = FIRConsentTypeAdStorage;
163+
break;
164+
case kConsentTypeAnalyticsStorage:
165+
consent_type = FIRConsentTypeAnalyticsStorage;
166+
break;
167+
default:
168+
LogError("Unknown ConsentType value: %d", it->first);
169+
return;
170+
};
171+
FIRConsentStatus consent_status;
172+
switch (it->second) {
173+
case kConsentStatusGranted:
174+
consent_status = FIRConsentStatusGranted;
175+
break;
176+
case kConsentStatusDenied:
177+
consent_status = FIRConsentStatusDenied;
178+
break;
179+
default:
180+
LogError("Unknown ConsentStatus value: %d", it->second);
181+
return;
182+
};
183+
[consent_settings_dict setObject:consent_status forKey:consent_type];
184+
}
185+
[FIRAnalytics setConsent:consent_settings_dict];
186+
}
187+
153188
// Due to overloads of LogEvent(), it's possible for users to call variants that require a
154189
// string with a nullptr. To prevent a crash and instead yield a warning, pass an empty string
155190
// to logEventWithName: methods instead. See b/30061553 for the background.
@@ -293,5 +328,5 @@ Thread get_id_thread(
293328
internal::FutureData::Get()->api()->LastResult(internal::kAnalyticsFnGetAnalyticsInstanceId));
294329
}
295330

296-
} // namespace measurement
331+
} // namespace analytics
297332
} // namespace firebase

analytics/src/analytics_stub.cc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@ void SetAnalyticsCollectionEnabled(bool /*enabled*/) {
5656
FIREBASE_ASSERT_RETURN_VOID(internal::IsInitialized());
5757
}
5858

59+
// Enable / disable measurement and reporting.
60+
void SetConsent(const std::map<ConsentType, ConsentStatus>& consent_settings) {
61+
FIREBASE_ASSERT_RETURN_VOID(internal::IsInitialized());
62+
}
63+
5964
// Log an event with one string parameter.
6065
void LogEvent(const char* /*name*/, const char* /*parameter_name*/,
6166
const char* /*parameter_value*/) {

analytics/src/include/firebase/analytics.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
#include <cstddef>
2121
#include <cstdint>
22+
#include <map>
2223
#include <string>
2324

2425
#include "firebase/app.h"
@@ -270,6 +271,25 @@ void Terminate();
270271
/// @param[in] enabled true to enable analytics collection, false to disable.
271272
void SetAnalyticsCollectionEnabled(bool enabled);
272273

274+
/// @brief The type of consent to set.
275+
///
276+
/// Supported consent types are kConsentTypeAdStorage and
277+
/// kConsentTypeAnalyticsStorage. Omitting a type retains its previous status.
278+
enum ConsentType { kConsentTypeAdStorage = 0, kConsentTypeAnalyticsStorage };
279+
280+
/// @brief The status value of the consent type.
281+
///
282+
/// Supported statuses are kConsentStatusGranted and kConsentStatusDenied.
283+
enum ConsentStatus { kConsentStatusGranted = 0, kConsentStatusDenied };
284+
285+
/// @brief Sets the applicable end user consent state (e.g., for device
286+
/// identifiers) for this app on this device.
287+
///
288+
/// Use the consent map to specify individual consent type values. Settings are
289+
/// persisted across app sessions. By default consent types are set to
290+
/// "granted".
291+
void SetConsent(const std::map<ConsentType, ConsentStatus>& consent_settings);
292+
273293
/// @brief Log an event with one string parameter.
274294
///
275295
/// @param[in] name Name of the event to log. Should contain 1 to 40

release_build_files/readme.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -642,6 +642,10 @@ workflow use only during the development of your app, not for publicly shipping
642642
code.
643643

644644
## Release Notes
645+
### Upcoming Release
646+
- Changes
647+
- Analytics: Add `analytics::SetConsent()` API.
648+
645649
### 10.3.0
646650
- Changes
647651
- General (Android): Update to Firebase Android BoM version 31.1.1.

0 commit comments

Comments
 (0)