chromium/chromeos/ash/components/network/metrics/cellular_network_metrics_logger.h

// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CHROMEOS_ASH_COMPONENTS_NETWORK_METRICS_CELLULAR_NETWORK_METRICS_LOGGER_H_
#define CHROMEOS_ASH_COMPONENTS_NETWORK_METRICS_CELLULAR_NETWORK_METRICS_LOGGER_H_

#include "base/component_export.h"
#include "base/memory/raw_ptr.h"

#include "base/scoped_observation.h"
#include "chromeos/ash/components/dbus/hermes/hermes_response_status.h"
#include "chromeos/ash/components/network/metrics/connection_info_metrics_logger.h"
#include "chromeos/ash/components/network/text_message_suppression_state.h"
#include "chromeos/services/network_config/public/mojom/cros_network_config.mojom.h"

namespace ash {

class NetworkMetadataStore;

// Provides APIs for logging metrics related to cellular networks.
class COMPONENT_EXPORT(CHROMEOS_NETWORK) CellularNetworkMetricsLogger
    : public ConnectionInfoMetricsLogger::Observer {
 public:
  // These values are persisted to logs. Entries should not be renumbered and
  // numeric values should never be reused.
  enum class ApnTypes {
    kDefault = 0,
    kAttach = 1,
    kDefaultAndAttach = 2,
    kMaxValue = kDefaultAndAttach
  };

  // These values are persisted to logs. Entries should not be renumbered and
  // numeric values should never be reused.
  enum class UnmanagedApnMigrationType {
    kMatchesLastGoodApn = 0,
    kDoesNotMatchLastGoodApn = 1,
    kMatchesLastConnectedAttachAndDefault = 2,
    kMatchesLastConnectedAttachHasMatchingDatabaseApn = 3,    // deprecated
    kMatchesLastConnectedAttachHasNoMatchingDatabaseApn = 4,  // deprecated
    kMatchesLastConnectedDefaultNoLastConnectedAttach = 5,
    kNoMatchingConnectedApn = 6,
    kMatchesLastConnectedAttachOnlyAndDefaultExists = 7,
    kMatchesLastConnectedDefaultOnlyAndAttachExists = 8,
    kMaxValue = kMatchesLastConnectedDefaultOnlyAndAttachExists
  };

  // These values are persisted to logs. Entries should not be renumbered and
  // numeric values should never be reused.
  enum class ManagedApnMigrationType {
    kMatchesSelectedApn = 0,
    kDoesNotMatchSelectedApn = 1,
    kMaxValue = kDoesNotMatchSelectedApn
  };

  // These values are persisted to logs. Entries should not be renumbered and
  // numeric values should never be reused.
  enum class ESimOperationResult {
    kSuccess = 0,
    kInhibitFailed = 1,
    kHermesFailed = 2,
    kMaxValue = kHermesFailed,
  };

  // These values are not persisted to logs and are used as helper values for
  // identifying the trigger of an SM-DS scan.
  enum class SmdsScanMethod {
    kViaPolicy = 0,
    kViaUser = 1,
    kMaxValue = kViaUser,
  };

  // These values are persisted to logs. Entries should not be renumbered and
  // numeric values should never be reused.
  enum class ESimUserInstallMethod {
    kViaSmds = 0,
    kViaQrCodeAfterSmds = 1,
    kViaQrCodeSkippedSmds = 2,
    kViaActivationCodeAfterSmds = 3,
    kViaActivationCodeSkippedSmds = 4,
    kMaxValue = kViaActivationCodeSkippedSmds,
  };

  // These values are persisted to logs. Entries should not be renumbered and
  // numeric values should never be reused.
  enum class ESimPolicyInstallMethod {
    kViaSmdp = 0,
    kViaSmds = 1,
    kMaxValue = kViaSmds,
  };

  // These values are persisted to logs. Entries should not be renumbered and
  // numeric values should never be reused.
  enum class UserTextMessageSuppressionState {
    kTextMessagesAllow = 0,
    kTextMessagesSuppress = 1,
    kMaxValue = kTextMessagesSuppress,
  };

  // These values are persisted to logs. Entries should not be renumbered and
  // numeric values should never be reused.
  enum class PolicyTextMessageSuppressionState {
    kUnset = 0,
    kTextMessagesAllow = 1,
    kTextMessagesSuppress = 2,
    kMaxValue = kTextMessagesSuppress,
  };

  // These values are persisted to logs. Entries should not be renumbered and
  // numeric values should never be reused.
  enum class NotificationSuppressionState {
    kNotSuppressed = 0,
    kUserSuppressed = 1,
    kPolicySuppressed = 2,
    kMaxValue = kPolicySuppressed,
  };

  static constexpr char kCreateCustomApnResultHistogram[] =
      "Network.Ash.Cellular.Apn.CreateCustomApn.Result";
  static constexpr char kCreateCustomApnAuthenticationTypeHistogram[] =
      "Network.Ash.Cellular.Apn.CreateCustomApn.AuthenticationType";
  static constexpr char kCreateCustomApnIpTypeHistogram[] =
      "Network.Ash.Cellular.Apn.CreateCustomApn.IpType";
  static constexpr char kCreateCustomApnApnTypesHistogram[] =
      "Network.Ash.Cellular.Apn.CreateCustomApn.ApnTypes";
  static constexpr char kCreateExclusivelyEnabledCustomApnResultHistogram[] =
      "Network.Ash.Cellular.Apn.CreateExclusivelyEnabledCustomApn.Result";
  static constexpr char
      kCreateExclusivelyEnabledCustomApnAuthenticationTypeHistogram[] =
          "Network.Ash.Cellular.Apn.CreateExclusivelyEnabledCustomApn."
          "AuthenticationType";
  static constexpr char kCreateExclusivelyEnabledCustomApnIpTypeHistogram[] =
      "Network.Ash.Cellular.Apn.CreateExclusivelyEnabledCustomApn.IpType";
  static constexpr char kCreateExclusivelyEnabledCustomApnApnTypesHistogram[] =
      "Network.Ash.Cellular.Apn.CreateExclusivelyEnabledCustomApn.ApnTypes";
  static constexpr char kRemoveCustomApnResultHistogram[] =
      "Network.Ash.Cellular.Apn.RemoveCustomApn.Result";
  static constexpr char kRemoveCustomApnApnTypesHistogram[] =
      "Network.Ash.Cellular.Apn.RemoveCustomApn.ApnTypes";
  static constexpr char kModifyCustomApnResultHistogram[] =
      "Network.Ash.Cellular.Apn.ModifyCustomApn.Result";
  static constexpr char kModifyCustomApnApnTypesHistogram[] =
      "Network.Ash.Cellular.Apn.ModifyCustomApn.ApnTypes";
  static constexpr char kEnableCustomApnResultHistogram[] =
      "Network.Ash.Cellular.Apn.EnableCustomApn.Result";
  static constexpr char kEnableCustomApnApnTypesHistogram[] =
      "Network.Ash.Cellular.Apn.EnableCustomApn.ApnTypes";
  static constexpr char kDisableCustomApnResultHistogram[] =
      "Network.Ash.Cellular.Apn.DisableCustomApn.Result";
  static constexpr char kDisableCustomApnApnTypesHistogram[] =
      "Network.Ash.Cellular.Apn.DisableCustomApn.ApnTypes";
  static constexpr char kConnectResultHasEnabledCustomApnsAllHistogram[] =
      "Network.Ash.Cellular.ConnectionResult.HasEnabledCustomApns.All";
  static constexpr char kConnectResultNoEnabledCustomApnsAllHistogram[] =
      "Network.Ash.Cellular.ConnectionResult.NoEnabledCustomApns.All";
  static constexpr char kCustomApnsCountHistogram[] =
      "Network.Ash.Cellular.Apn.CustomApns.Count";
  static constexpr char kCustomApnsEnabledCountHistogram[] =
      "Network.Ash.Cellular.Apn.CustomApns.Enabled.Count";
  static constexpr char kCustomApnsDisabledCountHistogram[] =
      "Network.Ash.Cellular.Apn.CustomApns.Disabled.Count";
  static constexpr char kCustomApnsUnmanagedMigrationTypeHistogram[] =
      "Network.Ash.Cellular.Apn.Unmanaged.MigrationType";
  static constexpr char kCustomApnsManagedMigrationTypeHistogram[] =
      "Network.Ash.Cellular.Apn.Managed.MigrationType";

  static constexpr char kSmdsScanViaPolicyProfileCount[] =
      "Network.Ash.Cellular.ESim.SmdsScan.ViaPolicy.ProfileCount";
  static constexpr char kSmdsScanViaUserProfileCount[] =
      "Network.Ash.Cellular.ESim.SmdsScan.ViaUser.ProfileCount";
  static constexpr char kSmdsScanOtherDurationSuccess[] =
      "Network.Ash.Cellular.ESim.SmdsScanDuration2.Other.OnSuccess";
  static constexpr char kSmdsScanOtherDurationFailure[] =
      "Network.Ash.Cellular.ESim.SmdsScanDuration2.Other.OnFailure";
  static constexpr char kSmdsScanAndroidDurationSuccess[] =
      "Network.Ash.Cellular.ESim.SmdsScanDuration2.Android.OnSuccess";
  static constexpr char kSmdsScanAndroidDurationFailure[] =
      "Network.Ash.Cellular.ESim.SmdsScanDuration2.Android.OnFailure";
  static constexpr char kSmdsScanGsmaDurationSuccess[] =
      "Network.Ash.Cellular.ESim.SmdsScanDuration2.Gsma.OnSuccess";
  static constexpr char kSmdsScanGsmaDurationFailure[] =
      "Network.Ash.Cellular.ESim.SmdsScanDuration2.Gsma.OnFailure";
  static constexpr char kESimUserInstallMethod[] =
      "Network.Ash.Cellular.ESim.UserInstall.Method";
  static constexpr char kESimPolicyInstallMethod[] =
      "Network.Ash.Cellular.ESim.PolicyInstall.Method";
  static constexpr char kESimPolicyInstallNoAvailableProfiles[] =
      "Network.Ash.Cellular.ESim.PolicyInstall.NoAvailableProfiles";
  static constexpr char kESimPolicyInstallUserErrorsFilteredAll[] =
      "Network.Ash.Cellular.ESim.PolicyInstall.UserErrorsFiltered.All";
  static constexpr char kESimPolicyInstallUserErrorsFilteredViaSmdpInitial[] =
      "Network.Ash.Cellular.ESim.PolicyInstall.UserErrorsFiltered."
      "ViaSmdpInitial";
  static constexpr char kESimPolicyInstallUserErrorsFilteredViaSmdpRetry[] =
      "Network.Ash.Cellular.ESim.PolicyInstall.UserErrorsFiltered."
      "ViaSmdpRetry";
  static constexpr char kESimPolicyInstallUserErrorsFilteredViaSmdsInitial[] =
      "Network.Ash.Cellular.ESim.PolicyInstall.UserErrorsFiltered."
      "ViaSmdsInitial";
  static constexpr char kESimPolicyInstallUserErrorsFilteredViaSmdsRetry[] =
      "Network.Ash.Cellular.ESim.PolicyInstall.UserErrorsFiltered."
      "ViaSmdsRetry";
  static constexpr char kESimPolicyInstallUserErrorsIncludedAll[] =
      "Network.Ash.Cellular.ESim.PolicyInstall.UserErrorsIncluded.All";
  static constexpr char kESimPolicyInstallUserErrorsIncludedViaSmdpInitial[] =
      "Network.Ash.Cellular.ESim.PolicyInstall.UserErrorsIncluded."
      "ViaSmdpInitial";
  static constexpr char kESimPolicyInstallUserErrorsIncludedViaSmdpRetry[] =
      "Network.Ash.Cellular.ESim.PolicyInstall.UserErrorsIncluded."
      "ViaSmdpRetry";
  static constexpr char kESimPolicyInstallUserErrorsIncludedViaSmdsInitial[] =
      "Network.Ash.Cellular.ESim.PolicyInstall.UserErrorsIncluded."
      "ViaSmdsInitial";
  static constexpr char kESimPolicyInstallUserErrorsIncludedViaSmdsRetry[] =
      "Network.Ash.Cellular.ESim.PolicyInstall.UserErrorsIncluded."
      "ViaSmdsRetry";
  static constexpr char kESimUserInstallUserErrorsFilteredAll[] =
      "Network.Ash.Cellular.ESim.UserInstall.UserErrorsFiltered.All";
  static constexpr char
      kESimUserInstallUserErrorsFilteredViaActivationCodeAfterSmds[] =
          "Network.Ash.Cellular.ESim.UserInstall.UserErrorsFiltered."
          "ViaActivationCodeAfterSmds";
  static constexpr char
      kESimUserInstallUserErrorsFilteredViaActivationCodeSkippedSmds[] =
          "Network.Ash.Cellular.ESim.UserInstall.UserErrorsFiltered."
          "ViaActivationCodeSkippedSmds";
  static constexpr char kESimUserInstallUserErrorsFilteredViaQrCodeAfterSmds[] =
      "Network.Ash.Cellular.ESim.UserInstall.UserErrorsFiltered."
      "ViaQrCodeAfterSmds";
  static constexpr char
      kESimUserInstallUserErrorsFilteredViaQrCodeSkippedSmds[] =
          "Network.Ash.Cellular.ESim.UserInstall.UserErrorsFiltered."
          "ViaQrCodeSkippedSmds";
  static constexpr char kESimUserInstallUserErrorsFilteredViaSmds[] =
      "Network.Ash.Cellular.ESim.UserInstall.UserErrorsFiltered.ViaSmds";
  static constexpr char kESimUserInstallUserErrorsIncludedAll[] =
      "Network.Ash.Cellular.ESim.UserInstall.UserErrorsIncluded.All";
  static constexpr char
      kESimUserInstallUserErrorsIncludedViaActivationCodeAfterSmds[] =
          "Network.Ash.Cellular.ESim.UserInstall.UserErrorsIncluded."
          "ViaActivationCodeAfterSmds";
  static constexpr char
      kESimUserInstallUserErrorsIncludedViaActivationCodeSkippedSmds[] =
          "Network.Ash.Cellular.ESim.UserInstall.UserErrorsIncluded."
          "ViaActivationCodeSkippedSmds";
  static constexpr char kESimUserInstallUserErrorsIncludedViaQrCodeAfterSmds[] =
      "Network.Ash.Cellular.ESim.UserInstall.UserErrorsIncluded."
      "ViaQrCodeAfterSmds";
  static constexpr char
      kESimUserInstallUserErrorsIncludedViaQrCodeSkippedSmds[] =
          "Network.Ash.Cellular.ESim.UserInstall.UserErrorsIncluded."
          "ViaQrCodeSkippedSmds";
  static constexpr char kESimUserInstallUserErrorsIncludedViaSmds[] =
      "Network.Ash.Cellular.ESim.UserInstall.UserErrorsIncluded.ViaSmds";
  static constexpr char kESimSmdsScanOtherUserErrorsFiltered[] =
      "Network.Ash.Cellular.ESim.SmdsScan.Other.UserErrorsFiltered";
  static constexpr char kESimSmdsScanOtherUserErrorsIncluded[] =
      "Network.Ash.Cellular.ESim.SmdsScan.Other.UserErrorsIncluded";
  static constexpr char kESimSmdsScanAndroidUserErrorsFiltered[] =
      "Network.Ash.Cellular.ESim.SmdsScan.Android.UserErrorsFiltered";
  static constexpr char kESimSmdsScanAndroidUserErrorsIncluded[] =
      "Network.Ash.Cellular.ESim.SmdsScan.Android.UserErrorsIncluded";
  static constexpr char kESimSmdsScanGsmaUserErrorsFiltered[] =
      "Network.Ash.Cellular.ESim.SmdsScan.Gsma.UserErrorsFiltered";
  static constexpr char kESimSmdsScanGsmaUserErrorsIncluded[] =
      "Network.Ash.Cellular.ESim.SmdsScan.Gsma.UserErrorsIncluded";

  static constexpr char kUserAllowTextMessagesSuppressionStateHistogram[] =
      "Network.Ash.Cellular.AllowTextMessages.User.SuppressionState";
  static constexpr char kPolicyAllowTextMessagesSuppressionStateHistogram[] =
      "Network.Ash.Cellular.AllowTextMessages.Policy.SuppressionState";
  static constexpr char kAllowTextMessagesNotificationSuppressionState[] =
      "Network.Ash.Cellular.AllowTextMessages."
      "TextMessageNotificationSuppressionState";

  CellularNetworkMetricsLogger(
      NetworkStateHandler* network_state_handler,
      NetworkMetadataStore* network_metadata_store,
      ConnectionInfoMetricsLogger* connection_info_metrics_logger);
  CellularNetworkMetricsLogger(const CellularNetworkMetricsLogger&) = delete;
  CellularNetworkMetricsLogger& operator=(const CellularNetworkMetricsLogger&) =
      delete;
  ~CellularNetworkMetricsLogger() override;

  // Logs results from attempting operations related to custom APNs.
  static void LogCreateCustomApnResult(
      bool success,
      chromeos::network_config::mojom::ApnPropertiesPtr apn);
  static void LogCreateExclusivelyEnabledCustomApnResult(
      bool success,
      chromeos::network_config::mojom::ApnPropertiesPtr apn);
  static void LogRemoveCustomApnResult(
      bool success,
      std::vector<chromeos::network_config::mojom::ApnType> apn_types);
  static void LogModifyCustomApnResult(
      bool success,
      std::vector<chromeos::network_config::mojom::ApnType> old_apn_types,
      std::optional<chromeos::network_config::mojom::ApnState> apn_state,
      std::optional<chromeos::network_config::mojom::ApnState> old_apn_state);
  static void LogUnmanagedCustomApnMigrationType(
      UnmanagedApnMigrationType type);
  static void LogManagedCustomApnMigrationType(ManagedApnMigrationType type);

  // Logs results from attempting operations related to eSIM.
  static void LogSmdsScanProfileCount(size_t count, SmdsScanMethod method);
  static void LogSmdsScanDuration(const base::TimeDelta& duration,
                                  bool success,
                                  const std::string& smds_activation_code);

  static void LogESimUserInstallMethod(ESimUserInstallMethod method);
  static void LogESimPolicyInstallMethod(ESimPolicyInstallMethod method);
  static void LogESimPolicyInstallNoAvailableProfiles(
      ESimPolicyInstallMethod method);
  // The |is_user_error| parameter is used to indicate that |result| was caused
  // by something outside the control of ChromeOS, e.g. an invalid activation
  // code, and is not actionable. We do not include this category of errors in
  // the "filtered" version of our histograms so that we may have a better
  // understanding of eSIM installations on ChromeOS with minimal noise.
  static void LogESimUserInstallResult(ESimUserInstallMethod method,
                                       ESimOperationResult result,
                                       bool is_user_error);
  static void LogESimPolicyInstallResult(ESimPolicyInstallMethod method,
                                         ESimOperationResult result,
                                         bool is_initial,
                                         bool is_user_error);
  // Record the result of a single SM-DS scan of a single SM-DS server. When
  // |status| is not provided this function assumes that we failed to inhibit
  // the cellular device.
  static void LogSmdsScanResult(const std::string& smds_activation_code,
                                std::optional<HermesResponseStatus> status);

  // Returns the eSIM installation result for the provided Hermes response
  // status. When the status is unavailable, assume that we failed to inhibit
  // the cellular device.
  static ESimOperationResult ComputeESimOperationResult(
      std::optional<HermesResponseStatus> status);

  // Returns whether |status| is considered a "user error" and should be
  // filtered when emitting to eSIM installation result histograms. An Hermes
  // response status is considered a "user error" when we believe the result is
  // due to an error or situation outside the control of ChromeOS, e.g. an
  // invalid activation code.
  static bool HermesResponseStatusIsUserError(HermesResponseStatus status);

  static void LogUserTextMessageSuppressionState(
      ash::UserTextMessageSuppressionState state);
  static void LogPolicyTextMessageSuppressionState(
      ash::PolicyTextMessageSuppressionState state);

  static void LogTextMessageNotificationSuppressionState(
      NotificationSuppressionState state);

 private:
  // ConnectionInfoMetricsLogger::Observer:
  void OnConnectionResult(
      const std::string& guid,
      const std::optional<std::string>& shill_error) override;

  raw_ptr<NetworkStateHandler> network_state_handler_ = nullptr;
  raw_ptr<NetworkMetadataStore, DanglingUntriaged> network_metadata_store_ =
      nullptr;

  base::ScopedObservation<ConnectionInfoMetricsLogger,
                          ConnectionInfoMetricsLogger::Observer>
      connection_info_metrics_logger_observation_{this};
};

}  // namespace ash

#endif  // CHROMEOS_ASH_COMPONENTS_NETWORK_METRICS_CELLULAR_NETWORK_METRICS_LOGGER_H_