From 1fc6b994fe798c20fc2444334a69c18ffa47f4a8 Mon Sep 17 00:00:00 2001 From: a-maurice Date: Thu, 17 Oct 2024 13:27:15 -0700 Subject: [PATCH] Refactor Analytics swig logic (#1108) * Refactor Analytics swig logic * Update CMakeLists.txt * Add a deprecated ParameterGroupId * Add generated headers to the doc only logic * Update generate_constants.py --- CMakeLists.txt | 2 +- analytics/CMakeLists.txt | 47 +++ analytics/generate_constants.py | 83 +++++ analytics/src/Consent.cs | 38 ++ analytics/src/FirebaseAnalytics.cs | 301 +++++++++++++++ analytics/src/Parameter.cs | 108 ++++++ analytics/src/swig/analytics.i | 352 +++--------------- .../Firebase/Sample/Analytics/UIHandler.cs | 2 +- docs/readme.md | 8 + 9 files changed, 642 insertions(+), 299 deletions(-) create mode 100644 analytics/generate_constants.py create mode 100644 analytics/src/Consent.cs create mode 100644 analytics/src/FirebaseAnalytics.cs create mode 100644 analytics/src/Parameter.cs diff --git a/CMakeLists.txt b/CMakeLists.txt index e7a049782..a0d0dee02 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -273,7 +273,7 @@ endif() if (FIREBASE_INCLUDE_ANALYTICS) add_subdirectory(analytics) list(APPEND TARGET_LINK_LIB_NAMES "firebase_analytics" "firebase_analytics_swig") - list(APPEND DOCUMENTATION_ONLY_LIB_NAMES "firebase_analytics_swig") + list(APPEND DOCUMENTATION_ONLY_LIB_NAMES "firebase_analytics_swig" FIREBASE_UNITY_ANALYTICS_GENERATED_FILES) list(APPEND PROJECT_LIST_HEADER " X(Analytics)") endif() if (FIREBASE_INCLUDE_APP_CHECK) diff --git a/analytics/CMakeLists.txt b/analytics/CMakeLists.txt index 99c10f239..46affb27d 100644 --- a/analytics/CMakeLists.txt +++ b/analytics/CMakeLists.txt @@ -21,8 +21,54 @@ set(firebase_analytics_swig src/swig/analytics.i ) +# Generate CSharp files for the various Analytics constants +set(analytics_cpp_generated_headers_dir + "${CMAKE_BINARY_DIR}/generated/analytics/src/include/firebase/analytics") +set(analytics_csharp_generated_dir + "${CMAKE_BINARY_DIR}/analytics/generated") +file(MAKE_DIRECTORY ${analytics_csharp_generated_dir}) + +# Generate the header file by invoking the generate_constants python script. +function(generate_analytics_unity_file CPP_FILE CSHARP_FILE) + add_custom_command( + OUTPUT ${CSHARP_FILE} + COMMAND ${FIREBASE_PYTHON_EXECUTABLE} "${CMAKE_CURRENT_LIST_DIR}/generate_constants.py" + "--cpp_header=${CPP_FILE}" + "--csharp_file=${CSHARP_FILE}" + DEPENDS FIREBASE_ANALYTICS_GENERATED_HEADERS + ${CPP_FILE} + COMMENT "Generating ${CSHARP_FILE}" + ) +endfunction() + +# Call the above function for all of the files to generate. +generate_analytics_unity_file( + "${analytics_cpp_generated_headers_dir}/event_names.h" + "${analytics_csharp_generated_dir}/EventNames.cs" +) +generate_analytics_unity_file( + "${analytics_cpp_generated_headers_dir}/parameter_names.h" + "${analytics_csharp_generated_dir}/ParameterNames.cs" +) +generate_analytics_unity_file( + "${analytics_cpp_generated_headers_dir}/user_property_names.h" + "${analytics_csharp_generated_dir}/UserPropertyNames.cs" +) +add_custom_target(FIREBASE_UNITY_ANALYTICS_GENERATED_FILES + DEPENDS + ${analytics_csharp_generated_dir}/EventNames.cs + ${analytics_csharp_generated_dir}/ParameterNames.cs + ${analytics_csharp_generated_dir}/UserPropertyNames.cs +) + # Firebase Analytics CSharp files set(firebase_analytics_src + src/Consent.cs + src/FirebaseAnalytics.cs + src/Parameter.cs + ${analytics_csharp_generated_dir}/EventNames.cs + ${analytics_csharp_generated_dir}/ParameterNames.cs + ${analytics_csharp_generated_dir}/UserPropertyNames.cs ) firebase_swig_add_library(firebase_analytics_swig @@ -59,6 +105,7 @@ mono_add_library(firebase_analytics_cs ${FIREBASE_PLATFORM_REF} DEPENDS firebase_analytics_swig + FIREBASE_UNITY_ANALYTICS_GENERATED_FILES ) if(FIREBASE_IOS_BUILD) diff --git a/analytics/generate_constants.py b/analytics/generate_constants.py new file mode 100644 index 000000000..5c561f7a6 --- /dev/null +++ b/analytics/generate_constants.py @@ -0,0 +1,83 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Convert C++ headers of Analytics constants to C# files.""" + +import datetime +import os +import re +import subprocess + +from absl import app +from absl import flags + +FLAGS = flags.FLAGS + +# Required args +flags.DEFINE_string('cpp_header', '', 'C++ header file containing a ' + 'set of constants to convert to C#.') +flags.register_validator( + 'cpp_header', + lambda x: x and os.path.exists(x), + message=('Must reference an existing C++ header file')) +flags.DEFINE_string('csharp_file', '', 'Full path of the C# file to write ' + 'out.') +flags.register_validator( + 'csharp_file', lambda x: x, message='Output C# file must be specified') + +CPP_NAMESPACE = 'namespace analytics' + +DOC_REPLACEMENTS = [ + ('static const char\*const k', 'public static string ') +] + + +def main(unused_argv): + """Convert the C++ file into C#, going line-by-line to edit it.""" + with open(FLAGS.cpp_header) as input_file: + with open(FLAGS.csharp_file, 'w') as output_file: + # Write the initial lines at the top + output_file.write('// Copyright %s Google LLC\n\n' % + str(datetime.date.today().year)) + output_file.write('namespace Firebase.Analytics {\n\n') + output_file.write('public static partial class FirebaseAnalytics {\n\n') + + found_namespace = False + for line in input_file: + line = line.rstrip() + + # Ignore everything in the C++ file until inside the namespaces + if not found_namespace: + if re.search(CPP_NAMESPACE, line): + found_namespace = True + continue + # Stop copying when finding the next namespace (we assume it is closing) + if re.search(CPP_NAMESPACE, line): + break + + for replace_from, replace_to in DOC_REPLACEMENTS: + if (re.search(replace_from, line)): + line = re.sub(replace_from, replace_to, line) + output_file.write(line + '\n') + + # Write the lines at the end + # Close the class + output_file.write('}\n\n') + # close the namespace + output_file.write('}\n') + + return 0 + +if __name__ == '__main__': + app.run(main) diff --git a/analytics/src/Consent.cs b/analytics/src/Consent.cs new file mode 100644 index 000000000..966f95008 --- /dev/null +++ b/analytics/src/Consent.cs @@ -0,0 +1,38 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Firebase.Analytics { + +/// @brief The type of consent to set. +/// +/// Supported consent types are mapped to corresponding constants in the Android +/// and iOS SDKs. Omitting a type retains its previous status. +public enum ConsentType { + AdStorage = 0, + AnalyticsStorage, + AdUserData, + AdPersonalization +} + +/// @brief The status value of the consent type. +/// +/// Supported statuses are ConsentStatus.Granted and ConsentStatus.Denied. +public enum ConsentStatus { + Granted = 0, + Denied +} + +} diff --git a/analytics/src/FirebaseAnalytics.cs b/analytics/src/FirebaseAnalytics.cs new file mode 100644 index 000000000..d3c600db9 --- /dev/null +++ b/analytics/src/FirebaseAnalytics.cs @@ -0,0 +1,301 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Threading.Tasks; + +namespace Firebase.Analytics { + +public static partial class FirebaseAnalytics { + + /// Get the instance ID from the analytics service. + /// + /// @returns A Task with the Analytics instance ID. + public static System.Threading.Tasks.Task GetAnalyticsInstanceIdAsync() { + return FirebaseAnalyticsInternal.GetAnalyticsInstanceIdAsync(); + } + + /// Asynchronously retrieves the identifier of the current app + /// session. + /// + /// The session ID retrieval could fail due to Analytics collection + /// disabled, or if the app session was expired. + /// + /// @returns A Task with the identifier of the current app session. + public static System.Threading.Tasks.Task GetSessionIdAsync() { + return FirebaseAnalyticsInternal.GetSessionIdAsync(); + } + + /// Initiates on-device conversion measurement given a user email address on iOS + /// and tvOS (no-op on Android). On iOS and tvOS, this method requires the + /// dependency GoogleAppMeasurementOnDeviceConversion to be linked in, + /// otherwise the invocation results in a no-op. + /// + /// @param emailAddress User email address. Include a domain name for all + /// email addresses (e.g. gmail.com or hotmail.co.jp). + public static void InitiateOnDeviceConversionMeasurementWithEmailAddress(string emailAddress) { + FirebaseAnalyticsInternal.InitiateOnDeviceConversionMeasurementWithEmailAddress(emailAddress); + } + + /// Initiates on-device conversion measurement given a sha256-hashed user email address. + /// Requires dependency GoogleAppMeasurementOnDeviceConversion to be linked in, otherwise it is + /// a no-op. + /// + /// @param hashedEmailAddress User email address as a UTF8-encoded string normalized and + /// hashed according to the instructions at + /// https://firebase.google.com/docs/tutorials/ads-ios-on-device-measurement/step-3. + public static void InitiateOnDeviceConversionMeasurementWithHashedEmailAddress(byte[] hashedEmailAddress) { + FirebaseAnalyticsInternal.InitiateOnDeviceConversionMeasurementWithHashedEmailAddress(new CharVector(hashedEmailAddress)); + } + + /// Initiates on-device conversion measurement given a sha256-hashed phone number in E.164 + /// format. Requires dependency GoogleAppMeasurementOnDeviceConversion to be linked in, + /// otherwise it is a no-op. + /// + /// @param hashedPhoneNumber UTF8-encoded user phone number in E.164 format and then hashed + /// according to the instructions at + /// https://firebase.google.com/docs/tutorials/ads-ios-on-device-measurement/step-3. + public static void InitiateOnDeviceConversionMeasurementWithHashedPhoneNumber(byte[] hashedPhoneNumber) { + FirebaseAnalyticsInternal.InitiateOnDeviceConversionMeasurementWithHashedPhoneNumber(new CharVector(hashedPhoneNumber)); + } + + /// Initiates on-device conversion measurement given a phone number in E.164 + /// format on iOS (no-op on Android). On iOS, requires dependency + /// GoogleAppMeasurementOnDeviceConversion to be linked in, otherwise it is a + /// no-op. + /// + /// @param phoneNumber User phone number. Must be in E.164 format, which means + /// it must be + /// limited to a maximum of 15 digits and must include a plus sign (+) prefix + /// and country code with no dashes, parentheses, or spaces. + public static void InitiateOnDeviceConversionMeasurementWithPhoneNumber(string phoneNumber) { + FirebaseAnalyticsInternal.InitiateOnDeviceConversionMeasurementWithPhoneNumber(phoneNumber); + } + + /// @brief Log an event with one string parameter. + /// + /// @param[in] name Name of the event to log. Should contain 1 to 40 + /// alphanumeric characters or underscores. The name must start with an + /// alphabetic character. Some event names are reserved. + /// See the FirebaseAnalytics.Event properties for the list of reserved event + /// names. + /// The "firebase_" prefix is reserved and should not be used. Note that event + /// names are case-sensitive and that logging two events whose names differ + /// only in case will result in two distinct events. + /// + /// @param[in] parameterName Name of the parameter to log. + /// For more information, see @ref Parameter. + /// + /// @param[in] parameterValue Value of the parameter to log. + /// + /// @see LogEvent(string, Parameter[]) + public static void LogEvent(string name, string parameterName, string parameterValue) { + FirebaseAnalyticsInternal.LogEvent(name, parameterName, parameterValue); + } + + /// @brief Log an event with one float parameter. + /// + /// @param[in] name Name of the event to log. Should contain 1 to 40 + /// alphanumeric characters or underscores. The name must start with an + /// alphabetic character. Some event names are reserved. + /// See the FirebaseAnalytics.Event properties for the list of reserved event + /// names. + /// The "firebase_" prefix is reserved and should not be used. Note that event + /// names are case-sensitive and that logging two events whose names differ + /// only in case will result in two distinct events. + /// + /// @param[in] parameterName Name of the parameter to log. + /// For more information, see @ref Parameter. + /// + /// @param[in] parameterValue Value of the parameter to log. + /// + /// @see LogEvent(string, Parameter[]) + public static void LogEvent(string name, string parameterName, double parameterValue) { + FirebaseAnalyticsInternal.LogEvent(name, parameterName, parameterValue); + } + + /// @brief Log an event with one 64-bit integer parameter. + /// + /// @param[in] name Name of the event to log. Should contain 1 to 40 + /// alphanumeric characters or underscores. The name must start with an + /// alphabetic character. Some event names are reserved. + /// See the FirebaseAnalytics.Event properties for the list of reserved event + /// names. + /// The "firebase_" prefix is reserved and should not be used. Note that event + /// names are case-sensitive and that logging two events whose names differ + /// only in case will result in two distinct events. + /// + /// @param[in] parameterName Name of the parameter to log. + /// For more information, see @ref Parameter. + /// + /// @param[in] parameterValue Value of the parameter to log. + /// + /// @see LogEvent(string, Parameter[]) + public static void LogEvent(string name, string parameterName, long parameterValue) { + FirebaseAnalyticsInternal.LogEvent(name, parameterName, parameterValue); + } + + /// @brief Log an event with one integer parameter + /// (stored as a 64-bit integer). + /// + /// @param[in] name Name of the event to log. Should contain 1 to 40 + /// alphanumeric characters or underscores. The name must start with an + /// alphabetic character. Some event names are reserved. + /// See the FirebaseAnalytics.Event properties for the list of reserved event + /// names. + /// The "firebase_" prefix is reserved and should not be used. Note that event + /// names are case-sensitive and that logging two events whose names differ + /// only in case will result in two distinct events. + /// + /// @param[in] parameterName Name of the parameter to log. + /// For more information, see @ref Parameter. + /// + /// @param[in] parameterValue Value of the parameter to log. + /// + /// @see LogEvent(string, Parameter[]) + public static void LogEvent(string name, string parameterName, int parameterValue) { + FirebaseAnalyticsInternal.LogEvent(name, parameterName, parameterValue); + } + + /// @brief Log an event with no parameters. + /// + /// @param[in] name Name of the event to log. Should contain 1 to 40 + /// alphanumeric characters or underscores. The name must start with an + /// alphabetic character. Some event names are reserved. + /// See the FirebaseAnalytics.Event properties for the list of reserved event + /// names. + /// The "firebase_" prefix is reserved and should not be used. Note that event + /// names are case-sensitive and that logging two events whose names differ + /// only in case will result in two distinct events. + /// + /// @see LogEvent(string, Parameter[]) + public static void LogEvent(string name) { + FirebaseAnalyticsInternal.LogEvent(name); + } + + /// @brief Log an event with associated parameters. + /// + /// An Event is an important occurrence in your app that you want to measure. + /// You can report up to 500 different types of events per app and you can + /// associate up to 25 unique parameters with each Event type. + /// + /// Some common events are in the reference guide via the + /// FirebaseAnalytics.Event* constants, but you may also choose to specify + /// custom event types that are associated with your specific app. + /// + /// @param[in] name Name of the event to log. Should contain 1 to 40 + /// alphanumeric characters or underscores. The name must start with an + /// alphabetic character. Some event names are reserved. + /// See the FirebaseAnalytics.Event properties for the list of reserved event + /// names. + /// The "firebase_" prefix is reserved and should not be used. Note that event + /// names are case-sensitive and that logging two events whose names differ + /// only in case will result in two distinct events. + /// + /// @param[in] parameters A parameter array of `Parameter` instances. + public static void LogEvent(string name, params Parameter[] parameters) { + // Convert the Parameter array into a StringList and VariantList to pass to C++ + StringList parameterNames = new StringList(); + VariantList parameterValues = new VariantList(); + + foreach (Parameter p in parameters) { + parameterNames.Add(p.Name); + parameterValues.Add(Firebase.Variant.FromObject(p.Value)); + } + + FirebaseAnalyticsInternal.LogEvent(name, parameterNames, parameterValues); + } + + /// Clears all analytics data for this app from the device and resets the app + /// instance id. + public static void ResetAnalyticsData() { + FirebaseAnalyticsInternal.ResetAnalyticsData(); + } + + /// @brief Sets whether analytics collection is enabled for this app on this + /// device. + /// + /// This setting is persisted across app sessions. By default it is enabled. + /// + /// @param[in] enabled true to enable analytics collection, false to disable. + public static void SetAnalyticsCollectionEnabled(bool enabled) { + FirebaseAnalyticsInternal.SetAnalyticsCollectionEnabled(enabled); + } + + /// @brief Sets the applicable end user consent state (e.g., for device + /// identifiers) for this app on this device. + /// + /// Use the consent map to specify individual consent type values. Settings are + /// persisted across app sessions. By default consent types are set to + /// "granted". + public static void SetConsent(System.Collections.Generic.IDictionary< ConsentType, ConsentStatus > consentSettings) { + IntIntMap consentSettingsMap = new IntIntMap(); + foreach (var kv in consentSettings) { + consentSettingsMap[(int)kv.Key] = (int)kv.Value; + } + FirebaseAnalyticsInternal.SetConsentWithInts(consentSettingsMap); + } + + /// @brief Sets the duration of inactivity that terminates the current session. + /// + /// @note The default value is 30 minutes. + /// + /// @param timeSpan The duration of inactivity that terminates the current + /// session. + public static void SetSessionTimeoutDuration(System.TimeSpan timeSpan) { + FirebaseAnalyticsInternal.SetSessionTimeoutDuration((long)timeSpan.TotalMilliseconds); + } + + /// @brief Sets the user ID property. + /// + /// This feature must be used in accordance with + /// Google's Privacy + /// Policy + /// + /// @param[in] userId The user ID associated with the user of this app on this + /// device. The user ID must be non-empty and no more than 256 characters long. + /// Setting userId to null removes the user ID. + public static void SetUserId(string userId) { + FirebaseAnalyticsInternal.SetUserId(userId); + } + + /// @brief Set a user property to the given value. + /// + /// Properties associated with a user allow a developer to segment users + /// into groups that are useful to their application. Up to 25 properties + /// can be associated with a user. + /// + /// Suggested property names are listed @ref user_property_names + /// (%user_property_names.h) but you're not limited to this set. For example, + /// the "gamertype" property could be used to store the type of player where + /// a range of values could be "casual", "mid_core", or "core". + /// + /// @param[in] name Name of the user property to set. This must be a + /// combination of letters and digits (matching the regular expression + /// [a-zA-Z0-9] between 1 and 40 characters long starting with a letter + /// [a-zA-Z] character. + /// @param[in] property Value to set the user property to. Set this + /// argument to NULL or nullptr to remove the user property. The value can be + /// between 1 to 100 characters long. + public static void SetUserProperty(string name, string property) { + FirebaseAnalyticsInternal.SetUserProperty(name, property); + } + + /// @deprecated Use ParameterGroupID instead + [System.Obsolete("Use ParameterGroupID instead.")] + public static string ParameterGroupId { get { return ParameterGroupID; } } +} + +} diff --git a/analytics/src/Parameter.cs b/analytics/src/Parameter.cs new file mode 100644 index 000000000..5b14f008f --- /dev/null +++ b/analytics/src/Parameter.cs @@ -0,0 +1,108 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Collections.Generic; + +namespace Firebase.Analytics { + +/// @brief Event parameter. +/// +/// Parameters supply information that contextualize events (see @ref LogEvent). +/// You can associate up to 25 unique Parameters with each event type (name). +/// +/// Common event types are provided as static properties of the +/// FirebaseAnalytics class (e.g FirebaseAnalytics.EventPostScore) where +/// parameters of these events are also provided in this FirebaseAnalytics +/// class (e.g FirebaseAnalytics.ParameterScore). +/// +/// You are not limited to the set of event types and parameter names +/// suggested in FirebaseAnalytics class properties. Additional Parameters can +/// be supplied for suggested event types or custom Parameters for custom event +/// types. +/// +/// Parameter names must be a combination of letters and digits +/// (matching the regular expression [a-zA-Z0-9]) between 1 and 40 characters +/// long starting with a letter [a-zA-Z] character. The "firebase_", +/// "google_" and "ga_" prefixes are reserved and should not be used. +/// +/// Parameter string values can be up to 100 characters long. +/// +/// An array of Parameter class instances can be passed to LogEvent in order +/// to associate parameters's of an event with values where each value can be +/// a double, 64-bit integer or string. +/// +/// For example, a game may log an achievement event along with the +/// character the player is using and the level they're currently on: +/// +/// @code{.cs} +/// using Firebase.Analytics; +/// +/// int currentLevel = GetCurrentLevel(); +/// Parameter[] AchievementParameters = { +/// new Parameter(FirebaseAnalytics.ParameterAchievementID, +/// "ultimate_wizard"), +/// new Parameter(FirebaseAnalytics.ParameterCharacter, "mysterion"), +/// new Parameter(FirebaseAnalytics.ParameterLevel, currentLevel), +/// }; +/// FirebaseAnalytics.LogEvent(FirebaseAnalytics.EventLevelUp, +/// AchievementParameters); +/// @endcode +/// +public class Parameter : System.IDisposable { + + internal string Name { get; set; } + + internal object Value { get; set; } + + public Parameter(string parameterName, string parameterValue) { + Name = parameterName; + Value = parameterValue; + } + + public Parameter(string parameterName, long parameterValue) { + Name = parameterName; + Value = parameterValue; + } + + public Parameter(string parameterName, double parameterValue) { + Name = parameterName; + Value = parameterValue; + } + + // TODO: Implement accepting maps and vectors in C++ Analytics before enabling these. + /* + public Parameter(string parameterName, IDictionary parameterValue) { + Name = parameterName; + Value = parameterValue; + } + + public Parameter(string parameterName, IEnumerable> parameterValue) { + Name = parameterName; + Value = parameterValue; + } + */ + + /// @deprecated No longer needed, will be removed in the future. + [System.Obsolete("No longer needed, will be removed in the future.")] + public void Dispose() {} + + /// @deprecated No longer needed, will be removed in the future. + [System.Obsolete("No longer needed, will be removed in the future.")] + public void Dispose(bool disposing) {} + +} + +} diff --git a/analytics/src/swig/analytics.i b/analytics/src/swig/analytics.i index 4329fa03b..866f6d4fb 100644 --- a/analytics/src/swig/analytics.i +++ b/analytics/src/swig/analytics.i @@ -8,10 +8,9 @@ // interface stores references, and the C# strings are marshalled as temporary // since they have to be converted from unicode strings anyway. // -// TODO(butterfield): Replace with %ignoreall/%unignoreall //swiglint: disable include-h-allglobals -%module FirebaseAnalytics +%module FirebaseAnalyticsInternal #ifdef USE_EXPORT_FIX // Generate a function that we can reference to force linker @@ -23,48 +22,24 @@ %include "std_map.i" -%pragma(csharp) moduleclassmodifiers="public sealed class" +%pragma(csharp) moduleclassmodifiers="internal static class" %pragma(csharp) modulecode=%{ // Hold a reference to the default app when methods from this module are // referenced. static Firebase.FirebaseApp app; - static FirebaseAnalytics() { app = Firebase.FirebaseApp.DefaultInstance; } + static FirebaseAnalyticsInternal() { app = Firebase.FirebaseApp.DefaultInstance; } /// Get the app used by this module. /// @return FirebaseApp instance referenced by this module. static Firebase.FirebaseApp App { get { return app; } } - - private FirebaseAnalytics() {} %} %feature("flatnested"); -// These are directly included in the generated code -// it's necessary to put them here so that the generated code knows what we're -// dealing with. -%{ -#include "analytics/src/include/firebase/analytics.h" -#include "analytics/src/include/firebase/analytics/event_names.h" -#include "analytics/src/include/firebase/analytics/parameter_names.h" -#include "analytics/src/include/firebase/analytics/user_property_names.h" -%} - -%rename(kConsentTypeAdStorage) firebase::analytics::kConsentTypeAdStorage; -%rename(kConsentTypeAnalyticsStorage) firebase::analytics::kConsentTypeAnalyticsStorage; -%rename(kConsentStatusGranted) firebase::analytics::kConsentStatusGranted; -%rename(kConsentStatusDenied) firebase::analytics::kConsentStatusDenied; - -// Constant renaming must happen before SWIG_CONSTANT_HEADERS is included. -%rename(kParameterAchievementId) firebase::analytics::kParameterAchievementID; -%rename(kParameterGroupId) firebase::analytics::kParameterGroupID; -%rename(kParameterItemId) firebase::analytics::kParameterItemID; -%rename(kParameterItemLocationId) firebase::analytics::kParameterItemLocationID; -%rename(kParameterTransactionId) firebase::analytics::kParameterTransactionID; +// Change the default class modifier to internal, so that new classes are not accidentally exposed +%typemap(csclassmodifiers) SWIGTYPE "internal class" %import "app/src/swig/app.i" %include "app/src/swig/null_check_this.i" %include "app/src/swig/serial_dispose.i" -%include "firebase/analytics/event_names.h" -%include "firebase/analytics/parameter_names.h" -%include "firebase/analytics/user_property_names.h" // Including cstdint before stdint.i ensures the int64_t typedef is correct, // otherwise on some platforms it is defined as "long long int" instead of @@ -72,296 +47,79 @@ #include %include "stdint.i" -namespace firebase { -namespace analytics { - -// ctype is equivalent to java's "jni" -// This is specific to C# - -// This is kind of hacky, but we can sneak in an extra argument while -// we're still on the C++ side -// The sneaky input is named with the $1 to allow this hack to hopefully work -// with multiple array inputs pairs if ever it were needed -%typemap(ctype) (const Parameter* parameters, size_t number_of_parameters) - "firebase::analytics::Parameter** $1_ptr_array, size_t" - -// to match the function prototype, we need to do the hack in the -// intermediary code as well. -%typemap(imtype) (const Parameter* parameters, size_t number_of_parameters) - "System.IntPtr arg, int" - -// In C# though, we're just Parameter[], as C# arrays have known .Length -%typemap(cstype) (const Parameter* parameters, size_t number_of_parameters) - "params Parameter[]" - -// We need to add code in C# to modify the Proxy array into an array of C ptrs -// before we pass it in. We can do that with the csin, pre attribute. -%typemap(csin, pre= -" // First copy the array of proxy classes containing C pointers - // to an array of c pointers - System.IntPtr[] swig_unwrap_$csinput = new System.IntPtr[$csinput.Length]; - for (int i = 0; i < $csinput.Length; ++i) { - swig_unwrap_$csinput[i] = (System.IntPtr)Parameter.getCPtr($csinput[i]); - } - fixed ( System.IntPtr* swig_ptrTo_$csinput = swig_unwrap_$csinput ) -") (const Parameter* parameters, size_t number_of_parameters) - "(System.IntPtr)swig_ptrTo_$csinput, $csinput.Length" - -%typemap(in) (const Parameter* parameters, size_t number_of_parameters) %{ - // The csin typemap above extracted the pointers to the C++ classes from the - // C# Parameter proxy classes. The array of pointers is provided here via: - // $1_ptr_array: Array of pointers to the C class - // $input: re-assigned to the size of the array of pointers - - // Copy into an array of Parameter structs prior to calling LogEvent. - // This array is deleted using the - // %typemap(freearg)(parameter, number_of_paramters) typemap below. - firebase::analytics::Parameter* $1_array = - new firebase::analytics::Parameter[$input]; - for (size_t i = 0; i < $input; ++i) { - ParameterCopy::AsParameterCopy($1_ptr_array[i])->CopyToParameter( - &$1_array[i]); - } - - $1 = $1_array; - $2 = $input; -%} - -// The 'in' typemap is really just code up to the point when the C++ function is -// invoked, so we actually need this to follow up and free the temporary memory. -// The short-lived nature of this is ok, because we know java's jni copies it by -// the time we get back from the C invocation -%typemap(freearg) (const Parameter* parameters, size_t number_of_parameters) %{ - if ($1_array) delete [] $1_array; -%} - +// Start of the code added to the C++ module file %{ -// Parameter which maintains a copy of strings provided on construction. -// This requires a copy of any strings construction as strings passed to the -// constructor are temporarily allocated by the SWIG binding code and -// deallocated after construction. -class ParameterCopy : private firebase::analytics::Parameter { - public: - ParameterCopy(const char *parameter_name, const char *parameter_value) : - Parameter(nullptr, 0) { - Initialize(parameter_name, parameter_value); - } - - ParameterCopy(const char *parameter_name, int64_t parameter_value) : - Parameter(nullptr, 0) { - Initialize(parameter_name, parameter_value); - } - - ParameterCopy(const char *parameter_name, double parameter_value) : - Parameter(nullptr, 0) { - Initialize(parameter_name, parameter_value); - } - - ~ParameterCopy() {} - - // Initialize this parameter with a new name and value. - void Initialize(const char *parameter_name, - firebase::Variant parameter_value) { - SetString(parameter_name, &name_copy, &name); - if (parameter_value.is_string()) { - const char* string_value = parameter_value.string_value(); - // Make `value` store its own bytes. - value = firebase::Variant::MutableStringFromStaticString( - string_value ? string_value : ""); - } else { - value = parameter_value; - } - } - - // Copy to a Parameter. - // IMPORTANT: Since the supplied parameter simply references pointers within - // this object, the lifetime of the parameter must exceed this instance. - void CopyToParameter(firebase::analytics::Parameter *parameter) const { - *parameter = *AsParameter(); - } - - // This is only non-const so the Parameter can be cast to ParameterCopy in - // order to delete the object. - // IMPORTANT: Do *not* mutate the returned Parameter use accessors on this - // object instead. - firebase::analytics::Parameter* AsParameter() { return this; } - const firebase::analytics::Parameter* AsParameter() const { return this; } +#include +#include "analytics/src/include/firebase/analytics.h" +#include "app/src/log.h" - // Convert a Parameter* (assuming it was allocated as a copy there is no - // checking here so be careful) to a ParameterCopy pointer. - static ParameterCopy* AsParameterCopy( - firebase::analytics::Parameter* parameter) { - return static_cast(parameter); - } +namespace firebase { +namespace analytics { - private: - // Copy the specified string value into string_storage with the C pointer - // to the string stored in output. - template - static void SetString(const char *value, std::string * const string_storage, - T *output) { - if (value) { - *string_storage = value; - } else { - string_storage->clear(); - } - *output = string_storage->c_str(); +// Internal version of LogEvent that takes in two vectors of known types, +// and converts them into C++ Parameters to pass into the public LogEvent instead. +void LogEvent(const char* name, std::vector parameter_names, + std::vector parameter_values) { + if (parameter_names.size() != parameter_values.size()) { + firebase::LogError("LogEvent for %s given different list sizes (%d, %d)", + name, parameter_names.size(), parameter_values.size()); + return; } - std::string name_copy; -}; -%} + size_t number_of_parameters = parameter_names.size(); + Parameter* parameters = new Parameter[number_of_parameters]; -%extend Parameter { - Parameter(const char *parameter_name, const char *parameter_value) { - return (new ParameterCopy(parameter_name, parameter_value))->AsParameter(); + for (size_t i = 0; i < number_of_parameters; ++i) { + parameters[i] = Parameter(parameter_names[i].c_str(), parameter_values[i]); } - Parameter(const char* parameter_name, int64_t parameter_value) { - return (new ParameterCopy(parameter_name, parameter_value))->AsParameter(); - } + LogEvent(name, parameters, number_of_parameters); - Parameter(const char* parameter_name, double parameter_value) { - return (new ParameterCopy(parameter_name, parameter_value))->AsParameter(); - } + delete[] parameters; +} - ~Parameter() { - delete ParameterCopy::AsParameterCopy($self); +// Converts from a generic int, int map to the C++ Consent enums +void SetConsentWithInts(const std::map& settings) { + std::map converted; + for (const auto& pair : settings) { + converted[static_cast(pair.first)] = static_cast(pair.second); } + SetConsent(converted); } -// Overridden in the class extension methods above. -%ignore Parameter::Parameter(const char* parameter_name, - const char* parameter_value); -%ignore Parameter::Parameter(const char* parameter_name, - int parameter_value); -%ignore Parameter::Parameter(const char* parameter_name, - int64_t parameter_value); -%ignore Parameter::Parameter(const char* parameter_name, - double parameter_value); -// Initialize / Terminate implicitly called when App is created / destroyed. -%ignore Initialize; -%ignore Terminate; -// SetConsent handled via SetConsentInternal below. -%ignore SetConsent; - -} // namespace analytics -} // namespace firebase - -%typemap(csclassmodifiers) firebase::analytics::Parameter "public sealed class"; -// This is a hack that overrides this specific log function, which is currently -// the only `unsafe` function that gets generated. -%csmethodmodifiers - firebase::analytics::LogEvent(const char *name, - const Parameter *parameters, - size_t number_of_parameters) - "/// @brief Log an event with associated parameters. - /// - /// An Event is an important occurrence in your app that you want to measure. - /// You can report up to 500 different types of events per app and you can - /// associate up to 25 unique parameters with each Event type. - /// - /// Some common events are in the reference guide via the - /// FirebaseAnalytics.Event* constants, but you may also choose to specify - /// custom event types that are associated with your specific app. - /// - /// @param[in] name Name of the event to log. Should contain 1 to 32 - /// alphanumeric characters or underscores. The name must start with an - /// alphabetic character. Some event names are reserved. See - /// `Analytics Events` for the list of reserved event - /// names. The \"firebase_\" prefix is reserved and should not be used. - /// Note that event names are case-sensitive and that logging two events - /// whose names differ only in case will result in two distinct events. - /// @param[in] parameters A parameter array of `Parameter` instances. - public unsafe"; +} // namespace analytics +} // namespace firebase -%rename(SetUserId) firebase::analytics::SetUserID; -%rename(SetSessionTimeoutDurationInternal) SetSessionTimeoutDuration; -%csmethodmodifiers firebase::analytics::SetSessionTimeoutDuration(int64_t milliseconds) "internal"; +%} // End of the code added to the C++ module file -%pragma(csharp) modulecode=%{ - /// @brief Sets the duration of inactivity that terminates the current session. - /// - /// @note The default value is 30 minutes. - /// - /// @param timeSpan The duration of inactivity that terminates the current - /// session. - public static void SetSessionTimeoutDuration(System.TimeSpan timeSpan) { - SetSessionTimeoutDurationInternal((long)timeSpan.TotalMilliseconds); - } -%} - -// Mark these as internal, so that we can do the conversion of types manually -%rename(InitiateOnDeviceConversionMeasurementWithHashedEmailAddressInternal) firebase::analytics::InitiateOnDeviceConversionMeasurementWithHashedEmailAddress; -%rename(InitiateOnDeviceConversionMeasurementWithHashedPhoneNumberInternal) firebase::analytics::InitiateOnDeviceConversionMeasurementWithHashedPhoneNumber; +// Initialize / Terminate implicitly called when App is created / destroyed. +%ignore firebase::analytics::Initialize; +%ignore firebase::analytics::Terminate; +// Ignore the SendEvent that takes a Parameter array, as we handle it +// with a custom version instead. +%ignore firebase::analytics::LogEvent(const char*, const Parameter*, size_t); +// Ignore SetConsent, in order to convert the types with our own function. +%ignore firebase::analytics::SetConsent; +// Ignore the Parameter class, as we don't want to expose that to C# at all. +%ignore firebase::analytics::Parameter; // GetSessionId returns Future in SWIG. %include "app/src/swig/future.i" %SWIG_FUTURE(Future_LongLong, long, internal, long long, FirebaseException) -%include "analytics/src/include/firebase/analytics.h" +// Ignore the Consent enums, so we can use commented ones in C# +%ignore firebase::analytics::ConsentType; +%ignore firebase::analytics::ConsentStatus; +%template(IntIntMap) std::map; -%rename(ConsentType) firebase::analytics::ConsentType; -%rename(ConsentStatus) firebase::analytics::ConsentStatus; -// Add a swig C++ function to call into the Analytics C++ implementation. -%{ -namespace firebase { -namespace analytics { - - void SetConsentInternal(std::map *ptr) { - firebase::analytics::SetConsent(*ptr); - } - -} // namespace analytics -} // namespace firebase -%} -// The definition on the C++ side, so that swig is aware of the function's existence. -void SetConsentInternal(std::map *ptr); - -%typemap(csclassmodifiers) firebase::analytics::ConsentType "enum"; -%typemap(csclassmodifiers) firebase::analytics::ConsentStatus "enum"; - -%typemap(csclassmodifiers) std::map "internal class" -%template(ConsentMap) std::map; +%include "analytics/src/include/firebase/analytics.h" +// Declare the new C++ functions we added in this file that we want +// to expose to C#. namespace firebase { namespace analytics { - -%pragma(csharp) modulecode=%{ - /// @brief Sets the applicable end user consent state (e.g., for device - /// identifiers) for this app on this device. - /// - /// Use the consent map to specify individual consent type values. Settings are - /// persisted across app sessions. By default consent types are set to - /// "granted". - public static void SetConsent(System.Collections.Generic.IDictionary consentSettings) { - ConsentMap consentSettingsMap = new ConsentMap(); - foreach(var kv in consentSettings) { - consentSettingsMap[kv.Key] = kv.Value; - } - SetConsentInternal(consentSettingsMap); - } - - /// Initiates on-device conversion measurement given a sha256-hashed user email address. - /// Requires dependency GoogleAppMeasurementOnDeviceConversion to be linked in, otherwise it is - /// a no-op. - /// @param hashedEmailAddress User email address as a UTF8-encoded string normalized and - /// hashed according to the instructions at - /// https://firebase.google.com/docs/tutorials/ads-ios-on-device-measurement/step-3. - public static void InitiateOnDeviceConversionMeasurementWithHashedEmailAddress(byte[] hashedEmailAddress) { - InitiateOnDeviceConversionMeasurementWithHashedEmailAddressInternal(new CharVector(hashedEmailAddress)); - } - - /// Initiates on-device conversion measurement given a sha256-hashed phone number in E.164 - /// format. Requires dependency GoogleAppMeasurementOnDeviceConversion to be linked in, - /// otherwise it is a no-op. - /// @param hashedPhoneNumber UTF8-encoded user phone number in E.164 format and then hashed - /// according to the instructions at - /// https://firebase.google.com/docs/tutorials/ads-ios-on-device-measurement/step-3. - public static void InitiateOnDeviceConversionMeasurementWithHashedPhoneNumber(byte[] hashedPhoneNumber) { - InitiateOnDeviceConversionMeasurementWithHashedPhoneNumberInternal(new CharVector(hashedPhoneNumber)); - } -%} - +void LogEvent(const char* name, std::vector parameter_names, + std::vector parameter_values); +void SetConsentWithInts(const std::map& settings); } // namespace analytics } // namespace firebase diff --git a/analytics/testapp/Assets/Firebase/Sample/Analytics/UIHandler.cs b/analytics/testapp/Assets/Firebase/Sample/Analytics/UIHandler.cs index c598806f0..25cb7dfd1 100644 --- a/analytics/testapp/Assets/Firebase/Sample/Analytics/UIHandler.cs +++ b/analytics/testapp/Assets/Firebase/Sample/Analytics/UIHandler.cs @@ -104,7 +104,7 @@ public void AnalyticsScore() { public void AnalyticsGroupJoin() { // Log an event with a string parameter. DebugLog("Logging a group join event."); - FirebaseAnalytics.LogEvent(FirebaseAnalytics.EventJoinGroup, FirebaseAnalytics.ParameterGroupId, + FirebaseAnalytics.LogEvent(FirebaseAnalytics.EventJoinGroup, FirebaseAnalytics.ParameterGroupID, "spoon_welders"); } diff --git a/docs/readme.md b/docs/readme.md index 6ff0ab0d5..f2d2c8d31 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -71,6 +71,14 @@ Support Release Notes ------------- +### Upcoming +- Changes + - Analytics: Renamed ParameterGroupId to ParameterGroupID, to be + consistent with other similarly named variables. ParameterGroupId + is considered deprecated, and will be removed in the future. + - Analytics: Deprecated the Dispose functions, as they are no longer + necessary for cleaning up memory. + ### 12.3.0 - Changes - General: Update to Firebase C++ SDK version 12.3.0.