diff --git a/analytics/CMakeLists.txt b/analytics/CMakeLists.txt index 84166eb772..63d33b125f 100644 --- a/analytics/CMakeLists.txt +++ b/analytics/CMakeLists.txt @@ -74,9 +74,15 @@ set(android_SRCS set(ios_SRCS src/analytics_ios.mm) -# Source files used by the stub implementation. -set(stub_SRCS - src/analytics_stub.cc) +# Source files used by the desktop / stub implementation. +set(desktop_SRCS + src/analytics_desktop.cc + src/analytics_desktop_dynamic.c) + +if(WIN32) + # Add Windows-specific sources for desktop builds. + list(APPEND desktop_SRCS src/analytics_windows.cc) +endif() if(ANDROID) set(analytics_platform_SRCS @@ -86,7 +92,7 @@ elseif(IOS) "${ios_SRCS}") else() set(analytics_platform_SRCS - "${stub_SRCS}") + "${desktop_SRCS}") endif() add_library(firebase_analytics STATIC @@ -98,6 +104,9 @@ set_property(TARGET firebase_analytics PROPERTY FOLDER "Firebase Cpp") # Set up the dependency on Firebase App. target_link_libraries(firebase_analytics PUBLIC firebase_app) +if(WIN32) + target_link_libraries(firebase_analytics PRIVATE Crypt32.lib) +endif() # Public headers all refer to each other relative to the src/include directory, # while private headers are relative to the entire C++ SDK directory. target_include_directories(firebase_analytics diff --git a/analytics/generate_windows_stubs.py b/analytics/generate_windows_stubs.py index 4ef2a76b64..0b7f7f0db3 100755 --- a/analytics/generate_windows_stubs.py +++ b/analytics/generate_windows_stubs.py @@ -16,12 +16,13 @@ """Generate stubs and function pointers for Windows SDK""" import argparse +import hashlib import os import re import sys HEADER_GUARD_PREFIX = "FIREBASE_ANALYTICS_SRC_WINDOWS_" -INCLUDE_PATH = "src/windows/" +INCLUDE_PATH = "src/" INCLUDE_PREFIX = "analytics/" + INCLUDE_PATH COPYRIGHT_NOTICE = """// Copyright 2025 Google LLC // @@ -39,17 +40,50 @@ """ -def generate_function_pointers(header_file_path, output_h_path, output_c_path): + +def hash_file(filename): + sha256_hash = hashlib.sha256() + with open(filename, "rb") as file: + while chunk := file.read(4096): + sha256_hash.update(chunk) + return sha256_hash.digest() + +def generate_function_pointers(dll_file_path, header_file_path, output_h_path, output_c_path): """ Parses a C header file to generate a self-contained header with typedefs, extern function pointer declarations, and a source file with stub functions, initialized pointers, and a dynamic loading function for Windows. Args: + dll_file_path (str): The path to the DLL file. header_file_path (str): The path to the input C header file. output_h_path (str): The path for the generated C header output file. output_c_path (str): The path for the generated C source output file. """ + print(f"Reading DLL file: {dll_file_path}") + dll_hash = hash_file(dll_file_path) # This is binary + + # --- Manage known hashes --- + hash_file_path = os.path.join(os.path.dirname(dll_file_path), "known_dll_hashes.txt") + known_hex_hashes = [] + try: + with open(hash_file_path, 'r') as f: + for line in f: + known_hex_hashes.append(line.strip()) + except FileNotFoundError: + print(f"Info: '{hash_file_path}' not found, will be created.") + pass # File doesn't exist, list remains empty + + current_dll_hex_hash = dll_hash.hex() + if current_dll_hex_hash not in known_hex_hashes: + known_hex_hashes.append(current_dll_hex_hash) + + with open(hash_file_path, 'w') as f: + for hex_hash in known_hex_hashes: + f.write(hex_hash + '\n') + print(f"Updated known hashes in: {hash_file_path}") + # --- End of manage known hashes --- + print(f"Reading header file: {header_file_path}") try: with open(header_file_path, 'r', encoding='utf-8') as f: @@ -64,7 +98,7 @@ def generate_function_pointers(header_file_path, output_h_path, output_c_path): includes = re.findall(r"#include\s+<.*?>", header_content) # Find all typedefs, including their documentation comments - typedefs = re.findall(r"/\*\*(?:[\s\S]*?)\*/\s*typedef[\s\S]*?;\s*", header_content) + typedefs = re.findall(r"(?:/\*\*(?:[\s\S]*?)\*/\s*)?typedef[\s\S]*?;\s*", header_content) # --- Extract function prototypes --- function_pattern = re.compile( @@ -83,7 +117,7 @@ def generate_function_pointers(header_file_path, output_h_path, output_c_path): return_type = match.group(1).strip() function_name = match.group(2).strip() params_str = match.group(3).strip() - + cleaned_params_for_decl = re.sub(r'\s+', ' ', params_str) if params_str else "" stub_name = f"Stub_{function_name}" @@ -91,10 +125,10 @@ def generate_function_pointers(header_file_path, output_h_path, output_c_path): if "void" in return_type: return_statement = " // No return value." elif "*" in return_type: - return_statement = f' return ({return_type})(&g_stub_memory);' + return_statement = f' return ({return_type})(&g_stub_memory[0]);' else: # bool, int64_t, etc. return_statement = " return 1;" - + stub_function = ( f"// Stub for {function_name}\n" f"static {return_type} {stub_name}({params_str}) {{\n" @@ -111,7 +145,7 @@ def generate_function_pointers(header_file_path, output_h_path, output_c_path): pointer_init = f"{return_type} (*ptr_{function_name})({cleaned_params_for_decl}) = &{stub_name};" pointer_initializations.append(pointer_init) - + function_details_for_loader.append((function_name, return_type, cleaned_params_for_decl)) print(f"Found {len(pointer_initializations)} functions. Generating output files...") @@ -123,13 +157,14 @@ def generate_function_pointers(header_file_path, output_h_path, output_c_path): f.write(f"// Generated from {os.path.basename(header_file_path)} by {os.path.basename(sys.argv[0])}\n\n") f.write(f"#ifndef {header_guard}\n") f.write(f"#define {header_guard}\n\n") + f.write(f"#define ANALYTICS_API // filter out from header copy\n\n") f.write("#include // needed for bool type in pure C\n\n") - + f.write("// --- Copied from original header ---\n") f.write("\n".join(includes) + "\n\n") f.write("".join(typedefs)) f.write("// --- End of copied section ---\n\n") - + f.write("#ifdef __cplusplus\n") f.write('extern "C" {\n') f.write("#endif\n\n") @@ -139,15 +174,21 @@ def generate_function_pointers(header_file_path, output_h_path, output_c_path): f.write("\n\n") f.write("\n".join(macro_definitions)) f.write("\n// clang-format on\n") - f.write("\n\n// --- Dynamic Loader Declaration for Windows ---\n") - f.write("#if defined(_WIN32)\n") - f.write('#include // For HMODULE\n') - f.write('// Load Google Analytics functions from the given DLL handle into function pointers.\n') - f.write(f'// Returns the number of functions successfully loaded (out of {len(function_details_for_loader)}).\n') - f.write("int FirebaseAnalytics_LoadAnalyticsFunctions(HMODULE dll_handle);\n\n") + f.write(f'\n// Number of Google Analytics functions expected to be loaded from the DLL.') + f.write('\nextern const int FirebaseAnalytics_DynamicFunctionCount;\n'); + f.write("\n// --- Dynamic Loader Declaration for Windows ---\n") + f.write("#if defined(_WIN32)\n\n") + f.write('#include \n') + f.write('\n// Array of known Google Analytics Windows DLL SHA256 hashes (hex strings).\n') + f.write('extern const char* FirebaseAnalytics_KnownWindowsDllHashes[];\n') + f.write('// Count of known Google Analytics Windows DLL SHA256 hashes.\n') + f.write('extern const int FirebaseAnalytics_KnownWindowsDllHashCount;\n\n') + f.write('// Load Analytics functions from the given DLL handle into function pointers.\n') + f.write(f'// Returns the number of functions successfully loaded.\n') + f.write("int FirebaseAnalytics_LoadDynamicFunctions(HMODULE dll_handle);\n\n") f.write('// Reset all function pointers back to stubs.\n') - f.write("void FirebaseAnalytics_UnloadAnalyticsFunctions(void);\n\n") - f.write("#endif // defined(_WIN32)\n") + f.write("void FirebaseAnalytics_UnloadDynamicFunctions(void);\n\n") + f.write("#endif // defined(_WIN32)\n") f.write("\n#ifdef __cplusplus\n") f.write("}\n") f.write("#endif\n\n") @@ -159,18 +200,34 @@ def generate_function_pointers(header_file_path, output_h_path, output_c_path): with open(output_c_path, 'w', encoding='utf-8') as f: f.write(f"{COPYRIGHT_NOTICE}") f.write(f"// Generated from {os.path.basename(header_file_path)} by {os.path.basename(sys.argv[0])}\n\n") - f.write(f'#include "{INCLUDE_PREFIX}{os.path.basename(output_h_path)}"\n') + f.write(f'#include "{INCLUDE_PREFIX}{os.path.basename(output_h_path)}"\n\n') f.write('#include \n\n') - f.write("// clang-format off\n\n") - f.write("static void* g_stub_memory = NULL;\n\n") - f.write("// --- Stub Function Definitions ---\n") + f.write("// A nice big chunk of stub memory that can be returned by stubbed Create methods.\n") + f.write("static char g_stub_memory[256] = {0};\n\n") + f.write("// clang-format off\n") + f.write(f'\n// Number of Google Analytics functions expected to be loaded from the DLL.') + f.write(f'\nconst int FirebaseAnalytics_DynamicFunctionCount = {len(function_details_for_loader)};\n\n'); + f.write("#if defined(_WIN32)\n") + f.write('// Array of known Google Analytics Windows DLL SHA256 hashes (hex strings).\n') + f.write('const char* FirebaseAnalytics_KnownWindowsDllHashes[] = {\n') + if known_hex_hashes: + for i, hex_hash in enumerate(known_hex_hashes): + f.write(f' "{hex_hash}"') + if i < len(known_hex_hashes) - 1: + f.write(',') + f.write('\n') + f.write('};\n\n') + f.write('// Count of known Google Analytics Windows DLL SHA256 hashes.\n') + f.write(f'const int FirebaseAnalytics_KnownWindowsDllHashCount = {len(known_hex_hashes)};\n') + f.write("#endif // defined(_WIN32)\n") + f.write("\n// --- Stub Function Definitions ---\n") f.write("\n\n".join(stub_functions)) f.write("\n\n\n// --- Function Pointer Initializations ---\n") f.write("\n".join(pointer_initializations)) f.write("\n\n// --- Dynamic Loader Function for Windows ---\n") loader_lines = [ '#if defined(_WIN32)', - 'int FirebaseAnalytics_LoadAnalyticsFunctions(HMODULE dll_handle) {', + 'int FirebaseAnalytics_LoadDynamicFunctions(HMODULE dll_handle) {', ' int count = 0;\n', ' if (!dll_handle) {', ' return count;', @@ -188,7 +245,7 @@ def generate_function_pointers(header_file_path, output_h_path, output_c_path): loader_lines.extend(proc_check) loader_lines.append('\n return count;') loader_lines.append('}\n') - loader_lines.append('void FirebaseAnalytics_UnloadAnalyticsFunctions(void) {') + loader_lines.append('void FirebaseAnalytics_UnloadDynamicFunctions(void) {') for name, ret_type, params in function_details_for_loader: loader_lines.append(f' ptr_{name} = &Stub_{name};'); loader_lines.append('}\n') @@ -203,29 +260,31 @@ def generate_function_pointers(header_file_path, output_h_path, output_c_path): parser = argparse.ArgumentParser( description="Generate C stubs and function pointers from a header file." ) + parser.add_argument( + "--windows_dll", + default = os.path.join(os.path.dirname(sys.argv[0]), "windows/analytics_win.dll"), + help="Path to the DLL file to calculate a hash." + ) parser.add_argument( "--windows_header", default = os.path.join(os.path.dirname(sys.argv[0]), "windows/include/public/c/analytics.h"), - #required=True, help="Path to the input C header file." ) parser.add_argument( "--output_header", - default = os.path.join(os.path.dirname(sys.argv[0]), INCLUDE_PATH, "analytics_dynamic.h"), - #required=True, + default = os.path.join(os.path.dirname(sys.argv[0]), INCLUDE_PATH, "analytics_desktop_dynamic.h"), help="Path for the generated output header file." ) parser.add_argument( "--output_source", - default = os.path.join(os.path.dirname(sys.argv[0]), INCLUDE_PATH, "analytics_dynamic.c"), - #required=True, + default = os.path.join(os.path.dirname(sys.argv[0]), INCLUDE_PATH, "analytics_desktop_dynamic.c"), help="Path for the generated output source file." ) - args = parser.parse_args() - + generate_function_pointers( - args.windows_header, - args.output_header, + args.windows_dll, + args.windows_header, + args.output_header, args.output_source ) diff --git a/analytics/integration_test/CMakeLists.txt b/analytics/integration_test/CMakeLists.txt index 4824536107..3a91d28c84 100644 --- a/analytics/integration_test/CMakeLists.txt +++ b/analytics/integration_test/CMakeLists.txt @@ -211,6 +211,16 @@ else() ) elseif(MSVC) set(ADDITIONAL_LIBS advapi32 ws2_32 crypt32) + set(ANALYTICS_WINDOWS_DLL "${FIREBASE_CPP_SDK_DIR}/analytics/windows/analytics_win.dll") + + # For Windows, check if the Analytics DLL exists, and copy it in if so. + if (EXISTS "${ANALYTICS_WINDOWS_DLL}") + add_custom_command( + TARGET ${integration_test_target_name} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + "${ANALYTICS_WINDOWS_DLL}" + $) + endif() else() set(ADDITIONAL_LIBS pthread) endif() diff --git a/analytics/src/analytics_desktop.cc b/analytics/src/analytics_desktop.cc new file mode 100644 index 0000000000..b484beee9a --- /dev/null +++ b/analytics/src/analytics_desktop.cc @@ -0,0 +1,580 @@ +// Copyright 2025 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 +#include +#include +#include + +#include "analytics/src/analytics_common.h" +#include "analytics/src/analytics_desktop_dynamic.h" +#include "analytics/src/include/firebase/analytics.h" +#include "app/src/future_manager.h" // For FutureData +#include "app/src/include/firebase/app.h" +#include "app/src/include/firebase/future.h" +#include "app/src/include/firebase/variant.h" +#include "app/src/log.h" + +#if defined(_WIN32) +#include + +#include "analytics_windows.h" +#endif // defined(_WIN32) + +namespace firebase { +namespace analytics { + +#if defined(_WIN32) +#define ANALYTICS_DLL_FILENAME L"analytics_win.dll" + +static HMODULE g_analytics_module = 0; +#endif // defined(_WIN32) + +// Future data for analytics. +// This is initialized in `Initialize()` and cleaned up in `Terminate()`. +static bool g_initialized = false; +static int g_fake_instance_id = 0; +static bool g_analytics_collection_enabled = true; + +// Initializes the Analytics desktop API. +// This function must be called before any other Analytics methods. +void Initialize(const App& app) { + g_initialized = true; + internal::RegisterTerminateOnDefaultAppDestroy(); + internal::FutureData::Create(); + g_fake_instance_id = 0; + +#if defined(_WIN32) + if (!g_analytics_module) { + std::vector allowed_hashes; + for (int i = 0; i < FirebaseAnalytics_KnownWindowsDllHashCount; i++) { + allowed_hashes.push_back( + std::string(FirebaseAnalytics_KnownWindowsDllHashes[i])); + } + + g_analytics_module = + firebase::analytics::internal::VerifyAndLoadAnalyticsLibrary( + ANALYTICS_DLL_FILENAME, allowed_hashes); + + if (g_analytics_module) { + int num_loaded = + FirebaseAnalytics_LoadDynamicFunctions(g_analytics_module); + if (num_loaded < FirebaseAnalytics_DynamicFunctionCount) { + LogWarning( + "Analytics: Failed to load functions from Google Analytics " + "module (%d out of %d loaded), reverting to stubs.", + num_loaded, FirebaseAnalytics_DynamicFunctionCount); + FirebaseAnalytics_UnloadDynamicFunctions(); + FreeLibrary(g_analytics_module); + g_analytics_module = 0; + // Do not proceed with C API initialization if functions didn't load + } else { + LogInfo("Analytics: Loaded Google Analytics module."); + + // Initialize Google Analytics C API + std::string current_app_id = app.options().app_id(); + std::string current_package_name = app.options().package_name(); + + GoogleAnalytics_Options* c_options = GoogleAnalytics_Options_Create(); + if (!c_options) { + LogError("Analytics: Failed to create GoogleAnalytics_Options."); + } else { + c_options->app_id = current_app_id.c_str(); + c_options->package_name = current_package_name.c_str(); + c_options->analytics_collection_enabled_at_first_launch = + g_analytics_collection_enabled; + + LogInfo( + "Analytics: Initializing Google Analytics C API with App ID: %s, " + "Package Name: %s", + c_options->app_id ? c_options->app_id : "null", + c_options->package_name ? c_options->package_name : "null"); + + if (!GoogleAnalytics_Initialize(c_options)) { + LogError("Analytics: Failed to initialize Google Analytics C API."); + // GoogleAnalytics_Initialize destroys c_options automatically if + // created by _Create + } else { + LogInfo( + "Analytics: Google Analytics C API initialized successfully."); + } + } + } + } else { + // LogWarning for g_analytics_module load failure is handled by + // VerifyAndLoadAnalyticsLibrary + g_analytics_module = 0; // Ensure it's null if loading failed + } + } +#endif // defined(_WIN32) +} + +namespace internal { + +// Determine whether the analytics module is initialized. +bool IsInitialized() { return g_initialized; } + +} // namespace internal + +// Terminates the Analytics desktop API. +// Call this function when Analytics is no longer needed to free up resources. +void Terminate() { +#if defined(_WIN32) + if (g_analytics_module) { + FirebaseAnalytics_UnloadDynamicFunctions(); + FreeLibrary(g_analytics_module); + g_analytics_module = 0; + } +#endif // defined(_WIN32) + + internal::FutureData::Destroy(); + internal::UnregisterTerminateOnDefaultAppDestroy(); + g_initialized = false; +} + +static void ConvertParametersToGAParams( + const Parameter* parameters, size_t number_of_parameters, + GoogleAnalytics_EventParameters* c_event_params) { + if (!parameters || number_of_parameters == 0 || !c_event_params) { + return; + } + + for (size_t i = 0; i < number_of_parameters; ++i) { + const Parameter& param = parameters[i]; + if (param.name == nullptr || param.name[0] == '\0') { + LogError("Analytics: Parameter name cannot be null or empty."); + continue; + } + + if (param.value.is_int64()) { + GoogleAnalytics_EventParameters_InsertInt(c_event_params, param.name, + param.value.int64_value()); + } else if (param.value.is_double()) { + GoogleAnalytics_EventParameters_InsertDouble(c_event_params, param.name, + param.value.double_value()); + } else if (param.value.is_string()) { + GoogleAnalytics_EventParameters_InsertString(c_event_params, param.name, + param.value.string_value()); + } else if (param.value.is_vector()) { + // Vector types for top-level event parameters are not supported on + // Desktop. Only specific complex types (like a map processed into an + // ItemVector) are handled. +#if defined(_WIN32) + if (g_analytics_module) { + // Only log this if we are not in stub mode. + LogError( + "Analytics: Parameter '%s' has type Vector, which is unsupported " + "for " + "event parameters on Desktop. Skipping.", + param.name); + } +#endif // defined(_WIN32) + continue; // Skip this parameter + } else if (param.value.is_map()) { + // This block handles parameters that are maps. + // Each key-value pair from the input map is converted into a distinct + // GoogleAnalytics_Item. In each such GoogleAnalytics_Item, the original + // key from the map is used directly as the property key, and the original + // value (which must be a primitive) is set as the property's value. All + // these GoogleAnalytics_Items are then bundled into a single + // GoogleAnalytics_ItemVector, which is associated with the original + // parameter's name. + const std::map& user_map = + param.value.map(); + if (user_map.empty()) { + LogWarning("Analytics: Parameter '%s' is an empty map. Skipping.", + param.name); + continue; // Skip this parameter + } + + GoogleAnalytics_ItemVector* c_item_vector = + GoogleAnalytics_ItemVector_Create(); + if (!c_item_vector) { + LogError( + "Analytics: Failed to create ItemVector for map parameter '%s'.", + param.name); + continue; // Skip this parameter + } + + bool item_vector_populated = false; + for (const auto& entry : user_map) { + const firebase::Variant& key_variant = entry.first; + if (!key_variant.is_string()) { + LogError("Analytics: Non-string map key found. Skipping."); + continue; + } + const std::string& key_from_map = key_variant.mutable_string(); + const firebase::Variant& value_from_map = entry.second; + + GoogleAnalytics_Item* c_item = GoogleAnalytics_Item_Create(); + if (!c_item) { + LogError( + "Analytics: Failed to create Item for key '%s' in map parameter " + "'%s'.", + key_from_map.c_str(), param.name); + continue; // Skip this key-value pair, try next one in map + } + + bool successfully_set_property = false; + if (value_from_map.is_int64()) { + GoogleAnalytics_Item_InsertInt(c_item, key_from_map.c_str(), + value_from_map.int64_value()); + successfully_set_property = true; + } else if (value_from_map.is_double()) { + GoogleAnalytics_Item_InsertDouble(c_item, key_from_map.c_str(), + value_from_map.double_value()); + successfully_set_property = true; + } else if (value_from_map.is_string()) { + GoogleAnalytics_Item_InsertString(c_item, key_from_map.c_str(), + value_from_map.string_value()); + successfully_set_property = true; + } else { + LogWarning( + "Analytics: Value for key '%s' in map parameter '%s' has an " + "unsupported Variant type. This key-value pair will be skipped.", + key_from_map.c_str(), param.name); + // successfully_set_property remains false + } + + if (successfully_set_property) { + GoogleAnalytics_ItemVector_InsertItem(c_item_vector, c_item); + // c_item is now owned by c_item_vector + item_vector_populated = + true; // Mark that the vector has at least one item + } else { + // If no property was set (e.g., value type was unsupported), destroy + // the created c_item. + GoogleAnalytics_Item_Destroy(c_item); + } + } + + if (item_vector_populated) { + GoogleAnalytics_EventParameters_InsertItemVector( + c_event_params, param.name, c_item_vector); + // c_item_vector is now owned by c_event_params + } else { + // If no items were successfully created and added (e.g., all values in + // map were unsupported types) + GoogleAnalytics_ItemVector_Destroy(c_item_vector); + LogWarning( + "Analytics: Map parameter '%s' resulted in an empty ItemVector; no " + "valid key-value pairs found or all values had unsupported types. " + "This map parameter was skipped.", + param.name); + } + } else { + LogWarning("Analytics: Unsupported variant type for parameter '%s'.", + param.name); + } + } +} + +// Logs an event with the given name and parameters. +void LogEvent(const char* name, const Parameter* parameters, + size_t number_of_parameters) { + FIREBASE_ASSERT_RETURN_VOID(internal::IsInitialized()); + + if (name == nullptr || name[0] == '\0') { + LogError("Analytics: Event name cannot be null or empty."); + return; + } + + GoogleAnalytics_EventParameters* c_event_params = nullptr; + if (parameters != nullptr && number_of_parameters > 0) { + c_event_params = GoogleAnalytics_EventParameters_Create(); + if (!c_event_params) { + LogError( + "Analytics: Failed to create event parameters map for event '%s'.", + name); + return; + } + ConvertParametersToGAParams(parameters, number_of_parameters, + c_event_params); + } + + GoogleAnalytics_LogEvent(name, c_event_params); + // GoogleAnalytics_LogEvent is expected to handle the lifecycle of + // c_event_params if non-null. +} + +// Sets a user property to the given value. +// +// Up to 25 user property names are supported. Once set, user property values +// persist throughout the app lifecycle and across sessions. +// +// @param[in] name The name of the user property to set. Should contain 1 to 24 +// alphanumeric characters or underscores, and must start with an alphabetic +// character. The "firebase_", "google_", and "ga_" prefixes are reserved and +// should not be used for user property names. Must be UTF-8 encoded. +// @param[in] value The value of the user property. Values can be up to 36 +// characters long. Setting the value to `nullptr` or an empty string will +// clear the user property. Must be UTF-8 encoded if not nullptr. +void SetUserProperty(const char* name, const char* property) { + FIREBASE_ASSERT_RETURN_VOID(internal::IsInitialized()); + + if (name == nullptr || name[0] == '\0') { + LogError("Analytics: User property name cannot be null or empty."); + return; + } + // The C API GoogleAnalytics_SetUserProperty allows value to be nullptr to + // remove the property. If value is an empty string, it might also be treated + // as clearing by some backends, or it might be an invalid value. The C API + // doc says: "Setting the value to `nullptr` remove the user property." For + // consistency, we pass it as is. + GoogleAnalytics_SetUserProperty(name, property); +} + +// Sets the user ID property. +// This feature must be used in accordance with Google's Privacy Policy. +// +// @param[in] user_id The user ID associated with the user of this app on this +// device. The user ID must be non-empty if not nullptr, and no more than 256 +// characters long, and UTF-8 encoded. Setting user_id to `nullptr` removes +// the user ID. +void SetUserId(const char* user_id) { + FIREBASE_ASSERT_RETURN_VOID(internal::IsInitialized()); + // The C API GoogleAnalytics_SetUserId allows user_id to be nullptr to clear + // the user ID. The C API documentation also mentions: "The user ID must be + // non-empty and no more than 256 characters long". We'll pass nullptr as is. + // If user_id is an empty string "", this might be an issue for the underlying + // C API or backend if it expects non-empty. However, the Firebase API + // typically allows passing "" to clear some fields, or it's treated as an + // invalid value. For SetUserId, `nullptr` is the standard clear mechanism. An + // empty string might be an invalid ID. For now, we are not adding extra + // validation for empty string beyond what C API does. Consider adding a check + // for empty string if Firebase spec requires it. e.g., if (user_id != nullptr + // && user_id[0] == '\0') { /* log error */ return; } + GoogleAnalytics_SetUserId(user_id); +} + +// 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 A flag that enables or disables Analytics collection. +void SetAnalyticsCollectionEnabled(bool enabled) { + g_analytics_collection_enabled = enabled; + + if (internal::IsInitialized()) { + GoogleAnalytics_SetAnalyticsCollectionEnabled(enabled); + } +} + +// Clears all analytics data for this app from the device and resets the app +// instance ID. +void ResetAnalyticsData() { + FIREBASE_ASSERT_RETURN_VOID(internal::IsInitialized()); + + GoogleAnalytics_ResetAnalyticsData(); + g_fake_instance_id++; +} + +// Overloaded versions of LogEvent for convenience. + +void LogEvent(const char* name) { + LogEvent(name, static_cast(nullptr), 0); +} + +void LogEvent(const char* name, const char* parameter_name, + const char* parameter_value) { + if (parameter_name == nullptr) { + LogEvent(name, static_cast(nullptr), 0); + return; + } + Parameter param(parameter_name, parameter_value); + LogEvent(name, ¶m, 1); +} + +void LogEvent(const char* name, const char* parameter_name, + const double parameter_value) { + if (parameter_name == nullptr) { + LogEvent(name, static_cast(nullptr), 0); + return; + } + Parameter param(parameter_name, parameter_value); + LogEvent(name, ¶m, 1); +} + +void LogEvent(const char* name, const char* parameter_name, + const int64_t parameter_value) { + if (parameter_name == nullptr) { + LogEvent(name, static_cast(nullptr), 0); + return; + } + Parameter param(parameter_name, parameter_value); + LogEvent(name, ¶m, 1); +} + +void LogEvent(const char* name, const char* parameter_name, + const int parameter_value) { + if (parameter_name == nullptr) { + LogEvent(name, static_cast(nullptr), 0); + return; + } + Parameter param(parameter_name, static_cast(parameter_value)); + LogEvent(name, ¶m, 1); +} + +// --- Stub Implementations for Unsupported Features --- + +void SetConsent(const std::map& consent_settings) { + FIREBASE_ASSERT_RETURN_VOID(internal::IsInitialized()); + + // Not supported by the Windows C API. + (void)consent_settings; // Mark as unused +#if defined(_WIN32) + if (g_analytics_module) { + // Only log this if we are not in stub mode. + LogWarning( + "Analytics: SetConsent() is not supported and has no effect on " + "Desktop."); + } +#endif // defined(_WIN32) +} + +void InitiateOnDeviceConversionMeasurementWithEmailAddress( + const char* email_address) { + FIREBASE_ASSERT_RETURN_VOID(internal::IsInitialized()); + (void)email_address; +#if defined(_WIN32) + if (g_analytics_module) { + // Only log this if we are not in stub mode. + LogWarning( + "Analytics: InitiateOnDeviceConversionMeasurementWithEmailAddress() is " + "not supported and has no effect on Desktop."); + } +#endif // defined(_WIN32) +} + +void InitiateOnDeviceConversionMeasurementWithPhoneNumber( + const char* phone_number) { + FIREBASE_ASSERT_RETURN_VOID(internal::IsInitialized()); + (void)phone_number; +#if defined(_WIN32) + if (g_analytics_module) { + // Only log this if we are not in stub mode. + LogWarning( + "Analytics: InitiateOnDeviceConversionMeasurementWithPhoneNumber() is " + "not supported and has no effect on Desktop."); + } +#endif // defined(_WIN32) +} + +void InitiateOnDeviceConversionMeasurementWithHashedEmailAddress( + std::vector hashed_email_address) { + FIREBASE_ASSERT_RETURN_VOID(internal::IsInitialized()); + (void)hashed_email_address; +#if defined(_WIN32) + if (g_analytics_module) { + // Only log this if we are not in stub mode. + LogWarning( + "Analytics: " + "InitiateOnDeviceConversionMeasurementWithHashedEmailAddress() is not " + "supported and has no effect on Desktop."); + } +#endif // defined(_WIN32) +} + +void InitiateOnDeviceConversionMeasurementWithHashedPhoneNumber( + std::vector hashed_phone_number) { + FIREBASE_ASSERT_RETURN_VOID(internal::IsInitialized()); + (void)hashed_phone_number; +#if defined(_WIN32) + if (g_analytics_module) { + // Only log this if we are not in stub mode. + LogWarning( + "Analytics: " + "InitiateOnDeviceConversionMeasurementWithHashedPhoneNumber() " + "is not supported and has no effect on Desktop."); + } +#endif // defined(_WIN32) +} + +void SetSessionTimeoutDuration(int64_t milliseconds) { + FIREBASE_ASSERT_RETURN_VOID(internal::IsInitialized()); + (void)milliseconds; +#if defined(_WIN32) + if (g_analytics_module) { + // Only log this if we are not in stub mode. + LogWarning( + "Analytics: SetSessionTimeoutDuration() is not supported and has no " + "effect on Desktop."); + } +#endif // defined(_WIN32) +} + +Future GetAnalyticsInstanceId() { + FIREBASE_ASSERT_RETURN(Future(), internal::IsInitialized()); + auto* api = internal::FutureData::Get()->api(); + const auto future_handle = + api->SafeAlloc(internal::kAnalyticsFnGetAnalyticsInstanceId); + std::string instance_id = std::string("FakeAnalyticsInstanceId"); + { + std::stringstream ss; + ss << g_fake_instance_id; + instance_id += ss.str(); + } + api->CompleteWithResult(future_handle, 0, "", instance_id); + +#if defined(_WIN32) + if (g_analytics_module) { + // Only log this if we are not in stub mode. + LogWarning( + "Analytics: GetAnalyticsInstanceId() is not supported on Desktop."); + } +#endif // defined(_WIN32) + return Future(api, future_handle.get()); +} + +Future GetAnalyticsInstanceIdLastResult() { + FIREBASE_ASSERT_RETURN(Future(), internal::IsInitialized()); + LogWarning( + "Analytics: GetAnalyticsInstanceIdLastResult() is not supported on " + "Desktop."); + return static_cast&>( + internal::FutureData::Get()->api()->LastResult( + internal::kAnalyticsFnGetAnalyticsInstanceId)); +} + +Future GetSessionId() { + FIREBASE_ASSERT_RETURN(Future(), internal::IsInitialized()); + auto* api = internal::FutureData::Get()->api(); + const auto future_handle = + api->SafeAlloc(internal::kAnalyticsFnGetSessionId); + int64_t session_id = 0x5E5510171D570BL; // "SESSIONIDSTUB", kinda + api->CompleteWithResult(future_handle, 0, "", session_id); +#if defined(_WIN32) + if (g_analytics_module) { + // Only log this if we are not in stub mode. + LogWarning("Analytics: GetSessionId() is not supported on Desktop."); + } +#endif // defined(_WIN32) + return Future(api, future_handle.get()); +} + +Future GetSessionIdLastResult() { + FIREBASE_ASSERT_RETURN(Future(), internal::IsInitialized()); +#if defined(_WIN32) + if (g_analytics_module) { + // Only log this if we are not in stub mode. + LogWarning( + "Analytics: GetSessionIdLastResult() is not supported on Desktop."); + } +#endif // defined(_WIN32) + return static_cast&>( + internal::FutureData::Get()->api()->LastResult( + internal::kAnalyticsFnGetSessionId)); +} + +} // namespace analytics +} // namespace firebase diff --git a/analytics/src/windows/analytics_dynamic.c b/analytics/src/analytics_desktop_dynamic.c similarity index 84% rename from analytics/src/windows/analytics_dynamic.c rename to analytics/src/analytics_desktop_dynamic.c index d7f483b72d..dec496ba40 100644 --- a/analytics/src/windows/analytics_dynamic.c +++ b/analytics/src/analytics_desktop_dynamic.c @@ -14,17 +14,44 @@ // Generated from analytics.h by generate_windows_stubs.py -#include "analytics/src/windows/analytics_dynamic.h" +#include "analytics/src/analytics_desktop_dynamic.h" + #include +// A nice big chunk of stub memory that can be returned by stubbed Create +// methods. +static char g_stub_memory[256] = {0}; + // clang-format off -static void* g_stub_memory = NULL; +// Number of Google Analytics functions expected to be loaded from the DLL. +const int FirebaseAnalytics_DynamicFunctionCount = 22; + +#if defined(_WIN32) +// Array of known Google Analytics Windows DLL SHA256 hashes (hex strings). +const char* FirebaseAnalytics_KnownWindowsDllHashes[] = { + "c1b9ff6e9119c30bbeb7472326dcde418f45682e6b822e25eed922fe6e3cc698", + "13ae5f9349b24186f1f3667b52832076e8d14ad9656c3546b1b7fca79ac8144b" +}; + +// Count of known Google Analytics Windows DLL SHA256 hashes. +const int FirebaseAnalytics_KnownWindowsDllHashCount = 2; +#endif // defined(_WIN32) // --- Stub Function Definitions --- +// Stub for GoogleAnalytics_Options_Create +static GoogleAnalytics_Options* Stub_GoogleAnalytics_Options_Create() { + return (GoogleAnalytics_Options*)(&g_stub_memory[0]); +} + +// Stub for GoogleAnalytics_Options_Destroy +static void Stub_GoogleAnalytics_Options_Destroy(GoogleAnalytics_Options* options) { + // No return value. +} + // Stub for GoogleAnalytics_Item_Create static GoogleAnalytics_Item* Stub_GoogleAnalytics_Item_Create() { - return (GoogleAnalytics_Item*)(&g_stub_memory); + return (GoogleAnalytics_Item*)(&g_stub_memory[0]); } // Stub for GoogleAnalytics_Item_InsertInt @@ -55,7 +82,7 @@ static void Stub_GoogleAnalytics_Item_Destroy(GoogleAnalytics_Item* item) { // Stub for GoogleAnalytics_ItemVector_Create static GoogleAnalytics_ItemVector* Stub_GoogleAnalytics_ItemVector_Create() { - return (GoogleAnalytics_ItemVector*)(&g_stub_memory); + return (GoogleAnalytics_ItemVector*)(&g_stub_memory[0]); } // Stub for GoogleAnalytics_ItemVector_InsertItem @@ -70,7 +97,7 @@ static void Stub_GoogleAnalytics_ItemVector_Destroy(GoogleAnalytics_ItemVector* // Stub for GoogleAnalytics_EventParameters_Create static GoogleAnalytics_EventParameters* Stub_GoogleAnalytics_EventParameters_Create() { - return (GoogleAnalytics_EventParameters*)(&g_stub_memory); + return (GoogleAnalytics_EventParameters*)(&g_stub_memory[0]); } // Stub for GoogleAnalytics_EventParameters_InsertInt @@ -102,6 +129,11 @@ static void Stub_GoogleAnalytics_EventParameters_Destroy(GoogleAnalytics_EventPa // No return value. } +// Stub for GoogleAnalytics_Initialize +static bool Stub_GoogleAnalytics_Initialize(const GoogleAnalytics_Options* options) { + return 1; +} + // Stub for GoogleAnalytics_LogEvent static void Stub_GoogleAnalytics_LogEvent(const char* name, GoogleAnalytics_EventParameters* parameters) { // No return value. @@ -130,6 +162,8 @@ static void Stub_GoogleAnalytics_SetAnalyticsCollectionEnabled(bool enabled) { // --- Function Pointer Initializations --- +GoogleAnalytics_Options* (*ptr_GoogleAnalytics_Options_Create)() = &Stub_GoogleAnalytics_Options_Create; +void (*ptr_GoogleAnalytics_Options_Destroy)(GoogleAnalytics_Options* options) = &Stub_GoogleAnalytics_Options_Destroy; GoogleAnalytics_Item* (*ptr_GoogleAnalytics_Item_Create)() = &Stub_GoogleAnalytics_Item_Create; void (*ptr_GoogleAnalytics_Item_InsertInt)(GoogleAnalytics_Item* item, const char* key, int64_t value) = &Stub_GoogleAnalytics_Item_InsertInt; void (*ptr_GoogleAnalytics_Item_InsertDouble)(GoogleAnalytics_Item* item, const char* key, double value) = &Stub_GoogleAnalytics_Item_InsertDouble; @@ -144,6 +178,7 @@ void (*ptr_GoogleAnalytics_EventParameters_InsertDouble)(GoogleAnalytics_EventPa void (*ptr_GoogleAnalytics_EventParameters_InsertString)(GoogleAnalytics_EventParameters* event_parameter_map, const char* key, const char* value) = &Stub_GoogleAnalytics_EventParameters_InsertString; void (*ptr_GoogleAnalytics_EventParameters_InsertItemVector)(GoogleAnalytics_EventParameters* event_parameter_map, const char* key, GoogleAnalytics_ItemVector* value) = &Stub_GoogleAnalytics_EventParameters_InsertItemVector; void (*ptr_GoogleAnalytics_EventParameters_Destroy)(GoogleAnalytics_EventParameters* event_parameter_map) = &Stub_GoogleAnalytics_EventParameters_Destroy; +bool (*ptr_GoogleAnalytics_Initialize)(const GoogleAnalytics_Options* options) = &Stub_GoogleAnalytics_Initialize; void (*ptr_GoogleAnalytics_LogEvent)(const char* name, GoogleAnalytics_EventParameters* parameters) = &Stub_GoogleAnalytics_LogEvent; void (*ptr_GoogleAnalytics_SetUserProperty)(const char* name, const char* value) = &Stub_GoogleAnalytics_SetUserProperty; void (*ptr_GoogleAnalytics_SetUserId)(const char* user_id) = &Stub_GoogleAnalytics_SetUserId; @@ -152,13 +187,23 @@ void (*ptr_GoogleAnalytics_SetAnalyticsCollectionEnabled)(bool enabled) = &Stub_ // --- Dynamic Loader Function for Windows --- #if defined(_WIN32) -int FirebaseAnalytics_LoadAnalyticsFunctions(HMODULE dll_handle) { +int FirebaseAnalytics_LoadDynamicFunctions(HMODULE dll_handle) { int count = 0; if (!dll_handle) { return count; } + FARPROC proc_GoogleAnalytics_Options_Create = GetProcAddress(dll_handle, "GoogleAnalytics_Options_Create"); + if (proc_GoogleAnalytics_Options_Create) { + ptr_GoogleAnalytics_Options_Create = (GoogleAnalytics_Options* (*)())proc_GoogleAnalytics_Options_Create; + count++; + } + FARPROC proc_GoogleAnalytics_Options_Destroy = GetProcAddress(dll_handle, "GoogleAnalytics_Options_Destroy"); + if (proc_GoogleAnalytics_Options_Destroy) { + ptr_GoogleAnalytics_Options_Destroy = (void (*)(GoogleAnalytics_Options* options))proc_GoogleAnalytics_Options_Destroy; + count++; + } FARPROC proc_GoogleAnalytics_Item_Create = GetProcAddress(dll_handle, "GoogleAnalytics_Item_Create"); if (proc_GoogleAnalytics_Item_Create) { ptr_GoogleAnalytics_Item_Create = (GoogleAnalytics_Item* (*)())proc_GoogleAnalytics_Item_Create; @@ -229,6 +274,11 @@ int FirebaseAnalytics_LoadAnalyticsFunctions(HMODULE dll_handle) { ptr_GoogleAnalytics_EventParameters_Destroy = (void (*)(GoogleAnalytics_EventParameters* event_parameter_map))proc_GoogleAnalytics_EventParameters_Destroy; count++; } + FARPROC proc_GoogleAnalytics_Initialize = GetProcAddress(dll_handle, "GoogleAnalytics_Initialize"); + if (proc_GoogleAnalytics_Initialize) { + ptr_GoogleAnalytics_Initialize = (bool (*)(const GoogleAnalytics_Options* options))proc_GoogleAnalytics_Initialize; + count++; + } FARPROC proc_GoogleAnalytics_LogEvent = GetProcAddress(dll_handle, "GoogleAnalytics_LogEvent"); if (proc_GoogleAnalytics_LogEvent) { ptr_GoogleAnalytics_LogEvent = (void (*)(const char* name, GoogleAnalytics_EventParameters* parameters))proc_GoogleAnalytics_LogEvent; @@ -258,7 +308,9 @@ int FirebaseAnalytics_LoadAnalyticsFunctions(HMODULE dll_handle) { return count; } -void FirebaseAnalytics_UnloadAnalyticsFunctions(void) { +void FirebaseAnalytics_UnloadDynamicFunctions(void) { + ptr_GoogleAnalytics_Options_Create = &Stub_GoogleAnalytics_Options_Create; + ptr_GoogleAnalytics_Options_Destroy = &Stub_GoogleAnalytics_Options_Destroy; ptr_GoogleAnalytics_Item_Create = &Stub_GoogleAnalytics_Item_Create; ptr_GoogleAnalytics_Item_InsertInt = &Stub_GoogleAnalytics_Item_InsertInt; ptr_GoogleAnalytics_Item_InsertDouble = &Stub_GoogleAnalytics_Item_InsertDouble; @@ -273,6 +325,7 @@ void FirebaseAnalytics_UnloadAnalyticsFunctions(void) { ptr_GoogleAnalytics_EventParameters_InsertString = &Stub_GoogleAnalytics_EventParameters_InsertString; ptr_GoogleAnalytics_EventParameters_InsertItemVector = &Stub_GoogleAnalytics_EventParameters_InsertItemVector; ptr_GoogleAnalytics_EventParameters_Destroy = &Stub_GoogleAnalytics_EventParameters_Destroy; + ptr_GoogleAnalytics_Initialize = &Stub_GoogleAnalytics_Initialize; ptr_GoogleAnalytics_LogEvent = &Stub_GoogleAnalytics_LogEvent; ptr_GoogleAnalytics_SetUserProperty = &Stub_GoogleAnalytics_SetUserProperty; ptr_GoogleAnalytics_SetUserId = &Stub_GoogleAnalytics_SetUserId; diff --git a/analytics/src/windows/analytics_dynamic.h b/analytics/src/analytics_desktop_dynamic.h similarity index 60% rename from analytics/src/windows/analytics_dynamic.h rename to analytics/src/analytics_desktop_dynamic.h index a35784e980..55567bd1ba 100644 --- a/analytics/src/windows/analytics_dynamic.h +++ b/analytics/src/analytics_desktop_dynamic.h @@ -14,14 +14,89 @@ // Generated from analytics.h by generate_windows_stubs.py -#ifndef FIREBASE_ANALYTICS_SRC_WINDOWS_ANALYTICS_DYNAMIC_H_ -#define FIREBASE_ANALYTICS_SRC_WINDOWS_ANALYTICS_DYNAMIC_H_ +#ifndef FIREBASE_ANALYTICS_SRC_WINDOWS_ANALYTICS_DESKTOP_DYNAMIC_H_ +#define FIREBASE_ANALYTICS_SRC_WINDOWS_ANALYTICS_DESKTOP_DYNAMIC_H_ + +#define ANALYTICS_API // filter out from header copy #include // needed for bool type in pure C // --- Copied from original header --- +#include #include +typedef struct GoogleAnalytics_Reserved_Opaque GoogleAnalytics_Reserved; + +/** + * @brief GoogleAnalytics_Options for initializing the Analytics SDK. + * GoogleAnalytics_Options_Create() must be used to create an instance of this + * struct with default values. If these options are created manually instead of + * using GoogleAnalytics_Options_Create(), initialization will fail, and the + * caller will be responsible for destroying the options. + */ +ANALYTICS_API typedef struct { + /** + * @brief The unique identifier for the Firebase app across all of Firebase + * with a platform-specific format. This is a required field, can not be null + * or empty, and must be UTF-8 encoded. + * + * The caller is responsible for allocating this memory, and deallocating it + * once the options instance has been destroyed. + * + * Example: 1:1234567890:android:321abc456def7890 + */ + const char* app_id; + + /** + * @brief Unique identifier for the application implementing the SDK. The + * format typically follows a reversed domain name convention. This is a + * required field, can not be null or empty, and must be UTF-8 encoded. + * + * The caller is responsible for allocating this memory, and deallocating it + * once the options instance has been destroyed. + * + * Example: com.google.analytics.AnalyticsApp + */ + const char* package_name; + + /** + * @brief Whether Analytics is enabled at the very first launch. + * This value is then persisted across app sessions, and from then on, takes + * precedence over the value of this field. + * GoogleAnalytics_SetAnalyticsCollectionEnabled() can be used to + * enable/disable after that point. + */ + bool analytics_collection_enabled_at_first_launch; + + /** + * @brief Reserved for internal use by the SDK. + */ + GoogleAnalytics_Reserved* reserved; +} GoogleAnalytics_Options; + +/** + * @brief Creates an instance of GoogleAnalytics_Options with default values. + * + * The caller is responsible for destroying the options using the + * GoogleAnalytics_Options_Destroy() function, unless it has been passed to the + * GoogleAnalytics_Initialize() function, in which case it will be destroyed + * automatically. + * + * @return A pointer to a newly allocated GoogleAnalytics_Options instance. + */ +ANALYTICS_API GoogleAnalytics_Options* GoogleAnalytics_Options_Create(); + +/** + * @brief Destroys the GoogleAnalytics_Options instance. Must not be called if + * the options were created with GoogleAnalytics_Options_Create() and passed to + * the GoogleAnalytics_Initialize() function, which would destroy them + * automatically. + * + * @param[in] options The GoogleAnalytics_Options instance to destroy. + */ +ANALYTICS_API void GoogleAnalytics_Options_Destroy( + GoogleAnalytics_Options* options); + /** * @brief Opaque type for an item. * @@ -71,6 +146,8 @@ extern "C" { // --- Function Pointer Declarations --- // clang-format off +extern GoogleAnalytics_Options* (*ptr_GoogleAnalytics_Options_Create)(); +extern void (*ptr_GoogleAnalytics_Options_Destroy)(GoogleAnalytics_Options* options); extern GoogleAnalytics_Item* (*ptr_GoogleAnalytics_Item_Create)(); extern void (*ptr_GoogleAnalytics_Item_InsertInt)(GoogleAnalytics_Item* item, const char* key, int64_t value); extern void (*ptr_GoogleAnalytics_Item_InsertDouble)(GoogleAnalytics_Item* item, const char* key, double value); @@ -85,12 +162,15 @@ extern void (*ptr_GoogleAnalytics_EventParameters_InsertDouble)(GoogleAnalytics_ extern void (*ptr_GoogleAnalytics_EventParameters_InsertString)(GoogleAnalytics_EventParameters* event_parameter_map, const char* key, const char* value); extern void (*ptr_GoogleAnalytics_EventParameters_InsertItemVector)(GoogleAnalytics_EventParameters* event_parameter_map, const char* key, GoogleAnalytics_ItemVector* value); extern void (*ptr_GoogleAnalytics_EventParameters_Destroy)(GoogleAnalytics_EventParameters* event_parameter_map); +extern bool (*ptr_GoogleAnalytics_Initialize)(const GoogleAnalytics_Options* options); extern void (*ptr_GoogleAnalytics_LogEvent)(const char* name, GoogleAnalytics_EventParameters* parameters); extern void (*ptr_GoogleAnalytics_SetUserProperty)(const char* name, const char* value); extern void (*ptr_GoogleAnalytics_SetUserId)(const char* user_id); extern void (*ptr_GoogleAnalytics_ResetAnalyticsData)(); extern void (*ptr_GoogleAnalytics_SetAnalyticsCollectionEnabled)(bool enabled); +#define GoogleAnalytics_Options_Create ptr_GoogleAnalytics_Options_Create +#define GoogleAnalytics_Options_Destroy ptr_GoogleAnalytics_Options_Destroy #define GoogleAnalytics_Item_Create ptr_GoogleAnalytics_Item_Create #define GoogleAnalytics_Item_InsertInt ptr_GoogleAnalytics_Item_InsertInt #define GoogleAnalytics_Item_InsertDouble ptr_GoogleAnalytics_Item_InsertDouble @@ -105,6 +185,7 @@ extern void (*ptr_GoogleAnalytics_SetAnalyticsCollectionEnabled)(bool enabled); #define GoogleAnalytics_EventParameters_InsertString ptr_GoogleAnalytics_EventParameters_InsertString #define GoogleAnalytics_EventParameters_InsertItemVector ptr_GoogleAnalytics_EventParameters_InsertItemVector #define GoogleAnalytics_EventParameters_Destroy ptr_GoogleAnalytics_EventParameters_Destroy +#define GoogleAnalytics_Initialize ptr_GoogleAnalytics_Initialize #define GoogleAnalytics_LogEvent ptr_GoogleAnalytics_LogEvent #define GoogleAnalytics_SetUserProperty ptr_GoogleAnalytics_SetUserProperty #define GoogleAnalytics_SetUserId ptr_GoogleAnalytics_SetUserId @@ -112,21 +193,30 @@ extern void (*ptr_GoogleAnalytics_SetAnalyticsCollectionEnabled)(bool enabled); #define GoogleAnalytics_SetAnalyticsCollectionEnabled ptr_GoogleAnalytics_SetAnalyticsCollectionEnabled // clang-format on +// Number of Google Analytics functions expected to be loaded from the DLL. +extern const int FirebaseAnalytics_DynamicFunctionCount; // --- Dynamic Loader Declaration for Windows --- #if defined(_WIN32) -#include // For HMODULE -// Load Google Analytics functions from the given DLL handle into function pointers. -// Returns the number of functions successfully loaded (out of 19). -int FirebaseAnalytics_LoadAnalyticsFunctions(HMODULE dll_handle); + +#include + +// Array of known Google Analytics Windows DLL SHA256 hashes (hex strings). +extern const char* FirebaseAnalytics_KnownWindowsDllHashes[]; +// Count of known Google Analytics Windows DLL SHA256 hashes. +extern const int FirebaseAnalytics_KnownWindowsDllHashCount; + +// Load Analytics functions from the given DLL handle into function pointers. +// Returns the number of functions successfully loaded. +int FirebaseAnalytics_LoadDynamicFunctions(HMODULE dll_handle); // Reset all function pointers back to stubs. -void FirebaseAnalytics_UnloadAnalyticsFunctions(void); +void FirebaseAnalytics_UnloadDynamicFunctions(void); -#endif // defined(_WIN32) +#endif // defined(_WIN32) #ifdef __cplusplus } #endif -#endif // FIREBASE_ANALYTICS_SRC_WINDOWS_ANALYTICS_DYNAMIC_H_ +#endif // FIREBASE_ANALYTICS_SRC_WINDOWS_ANALYTICS_DESKTOP_DYNAMIC_H_ diff --git a/analytics/src/analytics_stub.cc b/analytics/src/analytics_stub.cc deleted file mode 100644 index 37c4b3520b..0000000000 --- a/analytics/src/analytics_stub.cc +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Copyright 2016 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 - -#include "analytics/src/analytics_common.h" -#include "analytics/src/include/firebase/analytics.h" -#include "app/src/assert.h" -#include "app/src/include/firebase/version.h" - -namespace firebase { -namespace analytics { - -DEFINE_FIREBASE_VERSION_STRING(FirebaseAnalytics); - -static bool g_initialized = false; -static int g_fake_instance_id = 0; - -// Initialize the API. -void Initialize(const ::firebase::App& /*app*/) { - g_initialized = true; - internal::RegisterTerminateOnDefaultAppDestroy(); - internal::FutureData::Create(); - g_fake_instance_id = 0; -} - -namespace internal { - -// Determine whether the analytics module is initialized. -bool IsInitialized() { return g_initialized; } - -} // namespace internal - -// Terminate the API. -void Terminate() { - internal::FutureData::Destroy(); - internal::UnregisterTerminateOnDefaultAppDestroy(); - g_initialized = false; -} - -// Enable / disable measurement and reporting. -void SetAnalyticsCollectionEnabled(bool /*enabled*/) { - FIREBASE_ASSERT_RETURN_VOID(internal::IsInitialized()); -} - -// Enable / disable measurement and reporting. -void SetConsent(const std::map& consent_settings) { - FIREBASE_ASSERT_RETURN_VOID(internal::IsInitialized()); -} - -// Log an event with one string parameter. -void LogEvent(const char* /*name*/, const char* /*parameter_name*/, - const char* /*parameter_value*/) { - FIREBASE_ASSERT_RETURN_VOID(internal::IsInitialized()); -} - -// Log an event with one double parameter. -void LogEvent(const char* /*name*/, const char* /*parameter_name*/, - const double /*parameter_value*/) { - FIREBASE_ASSERT_RETURN_VOID(internal::IsInitialized()); -} - -// Log an event with one 64-bit integer parameter. -void LogEvent(const char* /*name*/, const char* /*parameter_name*/, - const int64_t /*parameter_value*/) { - FIREBASE_ASSERT_RETURN_VOID(internal::IsInitialized()); -} - -// Log an event with one integer parameter (stored as a 64-bit integer). -void LogEvent(const char* name, const char* parameter_name, - const int parameter_value) { - LogEvent(name, parameter_name, static_cast(parameter_value)); -} - -// Log an event with no parameters. -void LogEvent(const char* name) { - LogEvent(name, static_cast(nullptr), - static_cast(0)); -} - -// Log an event with associated parameters. -void LogEvent(const char* /*name*/, const Parameter* /*parameters*/, - size_t /*number_of_parameters*/) { - FIREBASE_ASSERT_RETURN_VOID(internal::IsInitialized()); -} - -/// Initiates on-device conversion measurement given a user email address on iOS -/// (no-op on Android). On iOS, requires dependency -/// GoogleAppMeasurementOnDeviceConversion to be linked in, otherwise it is a -/// no-op. -void InitiateOnDeviceConversionMeasurementWithEmailAddress( - const char* email_address) { - FIREBASE_ASSERT_RETURN_VOID(internal::IsInitialized()); -} - -void InitiateOnDeviceConversionMeasurementWithHashedEmailAddress( - std::vector email_address) { - FIREBASE_ASSERT_RETURN_VOID(internal::IsInitialized()); -} - -/// Initiates on-device conversion measurement given a phone number on iOS -/// (no-op on Android). On iOS, requires dependency -/// GoogleAppMeasurementOnDeviceConversion to be linked in, otherwise it is a -/// no-op. -void InitiateOnDeviceConversionMeasurementWithPhoneNumber( - const char* phone_number) { - FIREBASE_ASSERT_RETURN_VOID(internal::IsInitialized()); -} - -void InitiateOnDeviceConversionMeasurementWithHashedPhoneNumber( - std::vector phone_number_hash) { - FIREBASE_ASSERT_RETURN_VOID(internal::IsInitialized()); -} - -// Set a user property to the given value. -void SetUserProperty(const char* /*name*/, const char* /*value*/) { - FIREBASE_ASSERT_RETURN_VOID(internal::IsInitialized()); -} - -// Sets the user ID property. This feature must be used in accordance with -// -// Google's Privacy Policy -void SetUserId(const char* /*user_id*/) { - FIREBASE_ASSERT_RETURN_VOID(internal::IsInitialized()); -} - -// Sets the duration of inactivity that terminates the current session. -void SetSessionTimeoutDuration(int64_t /*milliseconds*/) { - FIREBASE_ASSERT_RETURN_VOID(internal::IsInitialized()); -} - -void ResetAnalyticsData() { - FIREBASE_ASSERT_RETURN_VOID(internal::IsInitialized()); - g_fake_instance_id++; -} - -Future GetAnalyticsInstanceId() { - FIREBASE_ASSERT_RETURN(Future(), internal::IsInitialized()); - auto* api = internal::FutureData::Get()->api(); - const auto future_handle = - api->SafeAlloc(internal::kAnalyticsFnGetAnalyticsInstanceId); - std::string instance_id = std::string("FakeAnalyticsInstanceId"); - { - std::stringstream ss; - ss << g_fake_instance_id; - instance_id += ss.str(); - } - api->CompleteWithResult(future_handle, 0, "", instance_id); - return Future(api, future_handle.get()); -} - -Future GetAnalyticsInstanceIdLastResult() { - FIREBASE_ASSERT_RETURN(Future(), internal::IsInitialized()); - return static_cast&>( - internal::FutureData::Get()->api()->LastResult( - internal::kAnalyticsFnGetAnalyticsInstanceId)); -} - -Future GetSessionId() { - FIREBASE_ASSERT_RETURN(Future(), internal::IsInitialized()); - auto* api = internal::FutureData::Get()->api(); - const auto future_handle = - api->SafeAlloc(internal::kAnalyticsFnGetSessionId); - int64_t session_id = 0x5E5510171D570BL; // "SESSIONIDSTUB", kinda - api->CompleteWithResult(future_handle, 0, "", session_id); - return Future(api, future_handle.get()); -} - -Future GetSessionIdLastResult() { - FIREBASE_ASSERT_RETURN(Future(), internal::IsInitialized()); - return static_cast&>( - internal::FutureData::Get()->api()->LastResult( - internal::kAnalyticsFnGetSessionId)); -} - -} // namespace analytics -} // namespace firebase diff --git a/analytics/src/analytics_windows.cc b/analytics/src/analytics_windows.cc new file mode 100644 index 0000000000..5398d8136c --- /dev/null +++ b/analytics/src/analytics_windows.cc @@ -0,0 +1,332 @@ +// Copyright 2025 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 "analytics/src/analytics_windows.h" + +#include +#include + +#include +#include +#include +#include + +#include "app/src/log.h" + +#define LOG_TAG "VerifyAndLoadAnalyticsLibrary: " + +namespace firebase { +namespace analytics { +namespace internal { + +// Helper function to retrieve the full path of the current executable. +// Returns an empty string on failure. +static std::wstring GetExecutablePath() { + std::vector buffer; + // First attempt with MAX_PATH + 1 + buffer.reserve(MAX_PATH + 1); + buffer.resize(MAX_PATH + 1); + + DWORD length = GetModuleFileNameW(NULL, buffer.data(), + static_cast(buffer.size())); + + if (length == 0) { + DWORD error_code = GetLastError(); + LogError(LOG_TAG "Failed to get executable path. Error: %u", error_code); + return std::wstring(); + } + + if (length < buffer.size()) { + // Executable path fit in our buffer, return it. + return std::wstring(buffer.data(), length); + } else { + DWORD error_code = GetLastError(); + if (error_code == ERROR_INSUFFICIENT_BUFFER) { + // Buffer was too small, try a larger one. + const size_t kLongPathMax = 65536 + 1; + + buffer.clear(); + buffer.reserve(kLongPathMax); + buffer.resize(kLongPathMax); + + DWORD length_large = GetModuleFileNameW( + NULL, buffer.data(), static_cast(buffer.size())); + + if (length_large == 0) { + DWORD error_code_large = GetLastError(); + LogError(LOG_TAG "Failed to get executable long path. Error: %u", + error_code_large); + return std::wstring(); + } + + if (length_large < buffer.size()) { + return std::wstring(buffer.data(), length_large); + } else { + // If length_large is still equal to or greater than buffer size, + // regardless of error, the path is too long for even the large buffer. + DWORD error_code_large = GetLastError(); + LogError(LOG_TAG "Executable path too long. Error: %u", + error_code_large); + return std::wstring(); + } + } else { + // length >= buffer.size() but not ERROR_INSUFFICIENT_BUFFER. + LogError(LOG_TAG "Failed to get executable path. Error: %u", error_code); + return std::wstring(); + } + } +} + +// Helper function to calculate the SHA256 hash of a file and return it as a hex +// string (upper-case). +static std::string CalculateFileSha256(HANDLE hFile) { + HCRYPTPROV hProv = 0; + HCRYPTHASH hHash = 0; + + // Ensure the file pointer is at the beginning of the file. + if (SetFilePointer(hFile, 0, NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER) { + DWORD dwError = GetLastError(); + LogError(LOG_TAG "CalculateFileSha256.SetFilePointer failed. Error: %u", + dwError); + return ""; // Return empty string on failure + } + + // Acquire Crypto Provider. + // Using CRYPT_VERIFYCONTEXT for operations that don't require private key + // access. + if (!CryptAcquireContextW(&hProv, NULL, NULL, PROV_RSA_AES, + CRYPT_VERIFYCONTEXT)) { + DWORD dwError = GetLastError(); + LogError(LOG_TAG + "CalculateFileSha256.CryptAcquireContextW failed. Error: %u", + dwError); + return ""; + } + + // Create a hash object. + if (!CryptCreateHash(hProv, CALG_SHA_256, 0, 0, &hHash)) { + DWORD dwError = GetLastError(); + LogError(LOG_TAG "CalculateFileSha256.CryptCreateHash failed. Error: %u", + dwError); + CryptReleaseContext(hProv, 0); + return ""; + } + + // Read the file in chunks and hash the data. + BYTE rgbFile[1024]; + DWORD cbRead = 0; + while (true) { + if (!ReadFile(hFile, rgbFile, sizeof(rgbFile), &cbRead, NULL)) { + DWORD dwError = GetLastError(); + LogError(LOG_TAG "CalculateFileSha256.ReadFile failed. Error: %u", + dwError); + CryptDestroyHash(hHash); + CryptReleaseContext(hProv, 0); + return ""; + } + // End of file + if (cbRead == 0) { + break; + } + // Add the chunk to the hash object. + if (!CryptHashData(hHash, rgbFile, cbRead, 0)) { + DWORD dwError = GetLastError(); + LogError(LOG_TAG "CalculateFileSha256.CryptHashData failed. Error: %u", + dwError); + CryptDestroyHash(hHash); + CryptReleaseContext(hProv, 0); + return ""; + } + } + + // --- Get the binary hash value --- + DWORD cbHashValue = 0; + DWORD dwCount = sizeof(DWORD); + if (!CryptGetHashParam(hHash, HP_HASHSIZE, (BYTE*)&cbHashValue, &dwCount, + 0)) { + DWORD dwError = GetLastError(); + LogError(LOG_TAG + "CalculateFileSha256.CryptGetHashParam (HP_HASHSIZE) failed. " + "Error: %u", + dwError); + CryptDestroyHash(hHash); + CryptReleaseContext(hProv, 0); + return ""; + } + + std::vector binary_hash_value(cbHashValue); + if (!CryptGetHashParam(hHash, HP_HASHVAL, binary_hash_value.data(), + &cbHashValue, 0)) { + DWORD dwError = GetLastError(); + LogError(LOG_TAG + "CalculateFileSha256.CryptGetHashParam (HP_HASHVAL) failed. " + "Error: %u", + dwError); + CryptDestroyHash(hHash); + CryptReleaseContext(hProv, 0); + return ""; + } + + // --- Convert the binary hash to a hex string --- + DWORD hex_string_size = 0; + if (!CryptBinaryToStringA(binary_hash_value.data(), binary_hash_value.size(), + CRYPT_STRING_HEXRAW | CRYPT_STRING_NOCRLF, NULL, + &hex_string_size)) { + DWORD dwError = GetLastError(); + LogError( + LOG_TAG + "CalculateFileSha256.CryptBinaryToStringA (size) failed. Error: %u", + dwError); + CryptDestroyHash(hHash); + CryptReleaseContext(hProv, 0); + return ""; + } + + std::string hex_hash_string(hex_string_size, '\0'); + if (!CryptBinaryToStringA(binary_hash_value.data(), binary_hash_value.size(), + CRYPT_STRING_HEXRAW | CRYPT_STRING_NOCRLF, + &hex_hash_string[0], &hex_string_size)) { + DWORD dwError = GetLastError(); + LogError(LOG_TAG + "CalculateFileSha256.CryptBinaryToStringA (conversion) failed. " + "Error: %u", + dwError); + CryptDestroyHash(hHash); + CryptReleaseContext(hProv, 0); + return ""; + } + + // Remove the null terminator from the string. + hex_hash_string.resize(hex_string_size); + + // --- Final Cleanup --- + CryptDestroyHash(hHash); + CryptReleaseContext(hProv, 0); + + return hex_hash_string; +} + +HMODULE VerifyAndLoadAnalyticsLibrary( + const wchar_t* library_filename, + const std::vector& allowed_hashes) { + if (library_filename == nullptr || library_filename[0] == L'\0') { + LogError(LOG_TAG "Invalid arguments."); + return nullptr; + } + if (allowed_hashes.empty()) { + // Don't check the hash, just load the library. + LogWarning(LOG_TAG "No hash provided, using unverified Analytics DLL."); + return LoadLibraryW(library_filename); + } + + std::wstring executable_path_str = GetExecutablePath(); + + if (executable_path_str.empty()) { + // GetExecutablePath() is expected to log specific errors. + // This log indicates the failure to proceed within this function. + LogError(LOG_TAG "Can't determine executable path."); + return nullptr; + } + + size_t last_slash_pos = executable_path_str.find_last_of(L"\\"); + if (last_slash_pos == std::wstring::npos) { + // Log message updated to avoid using %ls for executable_path_str + LogError(LOG_TAG "Could not determine executable directory."); + return nullptr; + } + + std::wstring full_dll_path_str = + executable_path_str.substr(0, last_slash_pos + 1); + full_dll_path_str += library_filename; // library_filename is the filename + + HANDLE hFile = + CreateFileW(full_dll_path_str.c_str(), GENERIC_READ, FILE_SHARE_READ, + NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (hFile == INVALID_HANDLE_VALUE) { + DWORD dwError = GetLastError(); + // If the DLL is simply not found, silently proceed to stub mode without + // logging an error. For other errors (e.g., access denied on an existing + // file), log them as it's an unexpected issue. + if (dwError != ERROR_FILE_NOT_FOUND && dwError != ERROR_PATH_NOT_FOUND) { + LogError(LOG_TAG "Failed to open Analytics DLL. Error: %u", dwError); + } + return nullptr; // In all CreateFileW failure cases, return nullptr to + // fall back to stub mode. + } + + OVERLAPPED overlapped = {0}; + // Attempt to lock the entire file exclusively (LOCKFILE_EXCLUSIVE_LOCK). + // This helps ensure no other process modifies the file while we are + // verifying and loading it. + BOOL bFileLocked = LockFileEx(hFile, LOCKFILE_EXCLUSIVE_LOCK, 0, 0xFFFFFFFF, + 0xFFFFFFFF, &overlapped); + if (!bFileLocked) { + DWORD dwError = GetLastError(); + LogError(LOG_TAG "Failed to lock Analytics DLL. Error: %u", dwError); + CloseHandle(hFile); + return nullptr; + } + + HMODULE hModule = nullptr; + + std::string calculated_hash = CalculateFileSha256(hFile); + + if (calculated_hash.length() == 0) { + LogError(LOG_TAG "Hash failed for Analytics DLL."); + } else { + bool hash_matched = false; + for (const auto& expected_hash : allowed_hashes) { + if (calculated_hash == expected_hash) { + hash_matched = true; + break; + } else { + LogDebug(LOG_TAG "Hash mismatch: got %s expected %s", + calculated_hash.c_str(), expected_hash.c_str()); + } + } + + if (hash_matched) { + LogDebug(LOG_TAG "Successfully verified Analytics DLL."); + // Load the library. When loading with a full path string, other + // directories are not searched. + hModule = LoadLibraryW(full_dll_path_str.c_str()); + if (hModule == NULL) { + DWORD dwError = GetLastError(); + LogError(LOG_TAG "Library load failed for Analytics DLL. Error: %u", + dwError); + } + } else { + LogError(LOG_TAG "Hash mismatch for Analytics DLL."); + } + } + + if (bFileLocked) { + if (!UnlockFileEx(hFile, 0, 0xFFFFFFFF, 0xFFFFFFFF, &overlapped)) { + DWORD dwError = GetLastError(); + LogError(LOG_TAG "Failed to unlock Analytics DLL. Error: %u", dwError); + } + } + + if (hFile != INVALID_HANDLE_VALUE) { + if (!CloseHandle(hFile)) { + DWORD dwError = GetLastError(); + LogError(LOG_TAG "Failed to close Analytics DLL. Error: %u", dwError); + } + } + return hModule; +} + +} // namespace internal +} // namespace analytics +} // namespace firebase diff --git a/analytics/src/analytics_windows.h b/analytics/src/analytics_windows.h new file mode 100644 index 0000000000..7d9e4489fe --- /dev/null +++ b/analytics/src/analytics_windows.h @@ -0,0 +1,35 @@ +// Copyright 2025 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_ANALYTICS_SRC_ANALYTICS_WINDOWS_H_ +#define FIREBASE_ANALYTICS_SRC_ANALYTICS_WINDOWS_H_ + +#include + +#include +#include + +namespace firebase { +namespace analytics { +namespace internal { + +HMODULE VerifyAndLoadAnalyticsLibrary( + const wchar_t* library_filename, + const std::vector& allowed_hashes); + +} // namespace internal +} // namespace analytics +} // namespace firebase + +#endif // FIREBASE_ANALYTICS_SRC_ANALYTICS_WINDOWS_H_ diff --git a/analytics/tests/CMakeLists.txt b/analytics/tests/CMakeLists.txt index 51e72f2490..ac2e828254 100644 --- a/analytics/tests/CMakeLists.txt +++ b/analytics/tests/CMakeLists.txt @@ -13,7 +13,11 @@ # limitations under the License. - +if(WIN32) + set(ANALYTICS_TEST_PLATFORM_DEPS Crypt32.lib) +else() + set(ANALYTICS_TEST_PLATFORM_DEPS "") +endif() firebase_cpp_cc_test( firebase_analytics_test @@ -23,6 +27,7 @@ firebase_cpp_cc_test( firebase_app_for_testing firebase_analytics firebase_testing + ${ANALYTICS_TEST_PLATFORM_DEPS} ) firebase_cpp_cc_test_on_ios( diff --git a/analytics/windows/analytics_win.dll b/analytics/windows/analytics_win.dll index f0c83825e3..bc2725ffb7 100755 Binary files a/analytics/windows/analytics_win.dll and b/analytics/windows/analytics_win.dll differ diff --git a/analytics/windows/include/public/analytics.h b/analytics/windows/include/public/analytics.h index d2dcc448ae..1781e6e83d 100644 --- a/analytics/windows/include/public/analytics.h +++ b/analytics/windows/include/public/analytics.h @@ -1,4 +1,5 @@ // Copyright 2025 Google LLC + #ifndef ANALYTICS_MOBILE_CONSOLE_MEASUREMENT_PUBLIC_ANALYTICS_H_ #define ANALYTICS_MOBILE_CONSOLE_MEASUREMENT_PUBLIC_ANALYTICS_H_ @@ -32,6 +33,38 @@ class Analytics { std::variant; using EventParameters = std::unordered_map; + /** + * @brief Options for initializing the Analytics SDK. + */ + struct Options { + /** + * @brief The unique identifier for the Firebase app across all of Firebase + * with a platform-specific format. This is a required field, can not be + * empty, and must be UTF-8 encoded. + * + * Example: 1:1234567890:android:321abc456def7890 + */ + std::string app_id; + + /** + * @brief Unique identifier for the application implementing the SDK. The + * format typically follows a reversed domain name convention. This is a + * required field, can not be empty, and must be UTF-8 encoded. + * + * Example: com.google.analytics.AnalyticsApp + */ + std::string package_name; + + /** + * @brief Whether Analytics is enabled at the very first launch. + * This value is then persisted across app sessions, and from then on, takes + * precedence over the value of this field. + * SetAnalyticsCollectionEnabled() can be used to enable/disable after that + * point. + */ + bool analytics_collection_enabled_at_first_launch = true; + }; + /** * @brief Returns the singleton instance of the Analytics class. */ @@ -46,6 +79,26 @@ class Analytics { Analytics(Analytics&&) = delete; Analytics& operator=(Analytics&&) = delete; + /** + * @brief Initializes the Analytics SDK with the given options. Until this is + * called, all analytics methods below will be no-ops. + * + * @param[in] options The options to initialize the Analytics SDK with. + * + * @return true if the Analytics SDK was successfully initialized, false + * otherwise. Also returns false if the Analytics SDK has already been + * initialized. + */ + bool Initialize(const Options& options) { + GoogleAnalytics_Options* google_analytics_options = + GoogleAnalytics_Options_Create(); + google_analytics_options->app_id = options.app_id.c_str(); + google_analytics_options->package_name = options.package_name.c_str(); + google_analytics_options->analytics_collection_enabled_at_first_launch = + options.analytics_collection_enabled_at_first_launch; + return GoogleAnalytics_Initialize(google_analytics_options); + } + /** * @brief Logs an app event. * diff --git a/analytics/windows/include/public/c/analytics.h b/analytics/windows/include/public/c/analytics.h index cb3047f310..dc6ddd3ceb 100644 --- a/analytics/windows/include/public/c/analytics.h +++ b/analytics/windows/include/public/c/analytics.h @@ -1,7 +1,9 @@ // Copyright 2025 Google LLC + #ifndef ANALYTICS_MOBILE_CONSOLE_MEASUREMENT_PUBLIC_C_ANALYTICS_H_ #define ANALYTICS_MOBILE_CONSOLE_MEASUREMENT_PUBLIC_C_ANALYTICS_H_ +#include #include #if defined(ANALYTICS_DLL) && defined(_WIN32) @@ -14,6 +16,78 @@ extern "C" { #endif +typedef struct GoogleAnalytics_Reserved_Opaque GoogleAnalytics_Reserved; + +/** + * @brief GoogleAnalytics_Options for initializing the Analytics SDK. + * GoogleAnalytics_Options_Create() must be used to create an instance of this + * struct with default values. If these options are created manually instead of + * using GoogleAnalytics_Options_Create(), initialization will fail, and the + * caller will be responsible for destroying the options. + */ +ANALYTICS_API typedef struct { + /** + * @brief The unique identifier for the Firebase app across all of Firebase + * with a platform-specific format. This is a required field, can not be null + * or empty, and must be UTF-8 encoded. + * + * The caller is responsible for allocating this memory, and deallocating it + * once the options instance has been destroyed. + * + * Example: 1:1234567890:android:321abc456def7890 + */ + const char* app_id; + + /** + * @brief Unique identifier for the application implementing the SDK. The + * format typically follows a reversed domain name convention. This is a + * required field, can not be null or empty, and must be UTF-8 encoded. + * + * The caller is responsible for allocating this memory, and deallocating it + * once the options instance has been destroyed. + * + * Example: com.google.analytics.AnalyticsApp + */ + const char* package_name; + + /** + * @brief Whether Analytics is enabled at the very first launch. + * This value is then persisted across app sessions, and from then on, takes + * precedence over the value of this field. + * GoogleAnalytics_SetAnalyticsCollectionEnabled() can be used to + * enable/disable after that point. + */ + bool analytics_collection_enabled_at_first_launch; + + /** + * @brief Reserved for internal use by the SDK. + */ + GoogleAnalytics_Reserved* reserved; +} GoogleAnalytics_Options; + +/** + * @brief Creates an instance of GoogleAnalytics_Options with default values. + * + * The caller is responsible for destroying the options using the + * GoogleAnalytics_Options_Destroy() function, unless it has been passed to the + * GoogleAnalytics_Initialize() function, in which case it will be destroyed + * automatically. + * + * @return A pointer to a newly allocated GoogleAnalytics_Options instance. + */ +ANALYTICS_API GoogleAnalytics_Options* GoogleAnalytics_Options_Create(); + +/** + * @brief Destroys the GoogleAnalytics_Options instance. Must not be called if + * the options were created with GoogleAnalytics_Options_Create() and passed to + * the GoogleAnalytics_Initialize() function, which would destroy them + * automatically. + * + * @param[in] options The GoogleAnalytics_Options instance to destroy. + */ +ANALYTICS_API void GoogleAnalytics_Options_Destroy( + GoogleAnalytics_Options* options); + /** * @brief Opaque type for an item. * @@ -210,6 +284,20 @@ ANALYTICS_API void GoogleAnalytics_EventParameters_InsertItemVector( ANALYTICS_API void GoogleAnalytics_EventParameters_Destroy( GoogleAnalytics_EventParameters* event_parameter_map); +/** + * @brief Initializes the Analytics SDK. Until this is called, all analytics + * functions below will be no-ops. + * + * @param[in] options The options for initializing the Analytics + * SDK. Deleted regardless of return value, if it was allocated with the + * GoogleAnalytics_Options_Create() function. + * @return true if the Analytics SDK was successfully initialized, false + * otherwise. Also returns false if the Analytics SDK has already been + * initialized. + */ +ANALYTICS_API bool GoogleAnalytics_Initialize( + const GoogleAnalytics_Options* options); + /** * @brief Logs an app event. * @@ -296,7 +384,7 @@ ANALYTICS_API void GoogleAnalytics_LogEvent( ANALYTICS_API void GoogleAnalytics_SetUserProperty(const char* name, const char* value); -/* +/** * @brief Sets the user ID property. * * This feature must be used in accordance with @@ -309,13 +397,13 @@ ANALYTICS_API void GoogleAnalytics_SetUserProperty(const char* name, */ ANALYTICS_API void GoogleAnalytics_SetUserId(const char* user_id); -/* +/** * @brief Clears all analytics data for this instance from the device and resets * the app instance ID. */ ANALYTICS_API void GoogleAnalytics_ResetAnalyticsData(); -/* +/** * @brief Sets whether analytics collection is enabled for this app on this * device. * diff --git a/analytics/windows/include/public/event_names.h b/analytics/windows/include/public/event_names.h index ef8330751f..3a3566070e 100644 --- a/analytics/windows/include/public/event_names.h +++ b/analytics/windows/include/public/event_names.h @@ -1,4 +1,5 @@ // Copyright 2025 Google LLC + #ifndef ANALYTICS_MOBILE_CONSOLE_MEASUREMENT_PUBLIC_EVENT_NAMES_H_ #define ANALYTICS_MOBILE_CONSOLE_MEASUREMENT_PUBLIC_EVENT_NAMES_H_ @@ -14,7 +15,7 @@ // alphabetic character. The "firebase_", "google_", and "ga_" prefixes are // reserved and should not be used. -namespace firebase::analytics { +namespace google::analytics { // Ad Impression event. This event signifies when a user sees an ad impression. // Note: If you supply the @c kParameterValue parameter, you must also supply @@ -421,6 +422,6 @@ inline constexpr char kEventViewPromotion[] = "view_promotion"; // inline constexpr char kEventViewSearchResults[] = "view_search_results"; -} // namespace firebase::analytics +} // namespace google::analytics #endif // ANALYTICS_MOBILE_CONSOLE_MEASUREMENT_PUBLIC_EVENT_NAMES_H_ diff --git a/analytics/windows/include/public/parameter_names.h b/analytics/windows/include/public/parameter_names.h index 7c5f45695e..c0181beea9 100644 --- a/analytics/windows/include/public/parameter_names.h +++ b/analytics/windows/include/public/parameter_names.h @@ -1,8 +1,9 @@ // Copyright 2025 Google LLC + #ifndef ANALYTICS_MOBILE_CONSOLE_MEASUREMENT_PUBLIC_PARAMETER_NAMES_H_ #define ANALYTICS_MOBILE_CONSOLE_MEASUREMENT_PUBLIC_PARAMETER_NAMES_H_ -namespace firebase::analytics { +namespace google::analytics { // Game achievement ID (string). inline constexpr char kParameterAchievementId[] = "achievement_id"; @@ -248,6 +249,6 @@ inline constexpr char kParameterValue[] = "value"; // The type of virtual currency being used (string). inline constexpr char kParameterVirtualCurrencyName[] = "virtual_currency_name"; -} // namespace firebase::analytics +} // namespace google::analytics #endif // ANALYTICS_MOBILE_CONSOLE_MEASUREMENT_PUBLIC_PARAMETER_NAMES_H_ diff --git a/analytics/windows/known_dll_hashes.txt b/analytics/windows/known_dll_hashes.txt new file mode 100644 index 0000000000..ecfa9d04ad --- /dev/null +++ b/analytics/windows/known_dll_hashes.txt @@ -0,0 +1,2 @@ +c1b9ff6e9119c30bbeb7472326dcde418f45682e6b822e25eed922fe6e3cc698 +13ae5f9349b24186f1f3667b52832076e8d14ad9656c3546b1b7fca79ac8144b