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

// Copyright 2023 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_HOTSPOT_METRICS_HELPER_H_
#define CHROMEOS_ASH_COMPONENTS_NETWORK_METRICS_HOTSPOT_METRICS_HELPER_H_

#include <optional>

#include "base/component_export.h"
#include "base/gtest_prod_util.h"
#include "base/memory/raw_ptr.h"
#include "base/time/time.h"
#include "base/timer/elapsed_timer.h"
#include "base/timer/timer.h"
#include "chromeos/ash/components/login/login_state/login_state.h"
#include "chromeos/ash/components/network/hotspot_capabilities_provider.h"
#include "chromeos/ash/components/network/hotspot_enabled_state_notifier.h"
#include "chromeos/ash/components/network/hotspot_state_handler.h"
#include "chromeos/ash/services/hotspot_config/public/mojom/cros_hotspot_config.mojom.h"
#include "mojo/public/cpp/bindings/receiver.h"

namespace ash {

class EnterpriseManagedMetadataStore;
class HotspotConfigurationHandler;
class HotspotController;

// This class is used to track the hotspot capabilities and status update and
// emits UMA metrics to the related histogram.
class COMPONENT_EXPORT(CHROMEOS_NETWORK) HotspotMetricsHelper
    : public LoginState::Observer,
      public HotspotCapabilitiesProvider::Observer,
      public HotspotStateHandler::Observer,
      public hotspot_config::mojom::HotspotEnabledStateObserver {
 public:
  // Emits enable/disable hotspot operation result to related UMA histogram.
  static void RecordSetTetheringEnabledResult(
      bool enabled,
      hotspot_config::mojom::HotspotControlResult result);

  // Emits check tethering readiness operation result to related UMA histogram.
  static void RecordCheckTetheringReadinessResult(
      HotspotCapabilitiesProvider::CheckTetheringReadinessResult result);

  // Emits set hotspot configuration operation result to related UMA histogram.
  static void RecordSetHotspotConfigResult(
      hotspot_config::mojom::SetHotspotConfigResult result,
      const std::string& shill_error = "");

  // Emits hotspot enable operation latency to related UMA histogram.
  static void RecordEnableHotspotLatency(const base::TimeDelta& latency);

  HotspotMetricsHelper();
  HotspotMetricsHelper(const HotspotMetricsHelper&) = delete;
  HotspotMetricsHelper& operator=(const HotspotMetricsHelper&) = delete;
  ~HotspotMetricsHelper() override;

  void Init(EnterpriseManagedMetadataStore* enterprise_managed_metadata_store,
            HotspotCapabilitiesProvider* hotspot_capabilities_provider,
            HotspotStateHandler* hotspot_state_handler,
            HotspotController* hotspot_controller,
            HotspotConfigurationHandler* hotspot_configuration_handler,
            HotspotEnabledStateNotifier* hotspot_enabled_state_notifier,
            NetworkStateHandler* network_state_handler);

 private:
  friend class HotspotMetricsHelperTest;
  friend class HotspotControllerTest;
  friend class HotspotControllerConcurrencyApiTest;

  FRIEND_TEST_ALL_PREFIXES(HotspotMetricsHelperTest,
                           HotspotAllowStatusHistogram);
  FRIEND_TEST_ALL_PREFIXES(HotspotMetricsHelperTest,
                           HotspotUsageConfigHistogram);
  FRIEND_TEST_ALL_PREFIXES(HotspotMetricsHelperTest,
                           HotspotUsageDurationHistogram);
  FRIEND_TEST_ALL_PREFIXES(HotspotMetricsHelperTest,
                           HotspotMaxClientCountHistogram);
  FRIEND_TEST_ALL_PREFIXES(HotspotMetricsHelperTest,
                           HotspotIsDeviceManagedHistogram);
  FRIEND_TEST_ALL_PREFIXES(HotspotMetricsHelperTest,
                           HotspotEnabledUpstreamStatusHistogram);
  FRIEND_TEST_ALL_PREFIXES(HotspotMetricsHelperTest,
                           HotspotDisableReasonHistogram);
  FRIEND_TEST_ALL_PREFIXES(HotspotMetricsHelperTest, HotspotSetConfigHistogram);
  FRIEND_TEST_ALL_PREFIXES(HotspotControllerTest, EnableTetheringSuccess);
  FRIEND_TEST_ALL_PREFIXES(HotspotControllerTest, AbortEnableTethering);
  FRIEND_TEST_ALL_PREFIXES(HotspotControllerTest,
                           ShillOperationFailureWhileAborting);
  FRIEND_TEST_ALL_PREFIXES(HotspotControllerTest,
                           EnableTetheringReadinessCheckFailure);
  FRIEND_TEST_ALL_PREFIXES(HotspotControllerTest,
                           EnableTetheringNetworkSetupFailure);
  FRIEND_TEST_ALL_PREFIXES(HotspotControllerTest, DisableTetheringSuccess);
  FRIEND_TEST_ALL_PREFIXES(HotspotControllerConcurrencyApiTest,
                           EnableTetheringSuccess);
  FRIEND_TEST_ALL_PREFIXES(HotspotControllerConcurrencyApiTest,
                           AbortEnableTethering);
  FRIEND_TEST_ALL_PREFIXES(HotspotControllerConcurrencyApiTest,
                           ShillOperationFailureWhileAborting);
  FRIEND_TEST_ALL_PREFIXES(HotspotControllerConcurrencyApiTest,
                           EnableTetheringReadinessCheckFailure);
  FRIEND_TEST_ALL_PREFIXES(HotspotControllerConcurrencyApiTest,
                           EnableTetheringNetworkSetupFailure);
  FRIEND_TEST_ALL_PREFIXES(HotspotControllerConcurrencyApiTest,
                           DisableTetheringSuccess);
  FRIEND_TEST_ALL_PREFIXES(HotspotConfigurationHandlerTest,
                           SetAndGetHotspotConfig);
  FRIEND_TEST_ALL_PREFIXES(HotspotCapabilitiesProviderTest,
                           CheckTetheringReadiness_Ready);
  FRIEND_TEST_ALL_PREFIXES(HotspotCapabilitiesProviderTest,
                           CheckTetheringReadiness_NotAllowed);
  FRIEND_TEST_ALL_PREFIXES(HotspotCapabilitiesProviderTest,
                           CheckTetheringReadiness_NotAllowedByCarrier);
  FRIEND_TEST_ALL_PREFIXES(HotspotCapabilitiesProviderTest,
                           CheckTetheringReadiness_UpstreamNotAvailable);
  FRIEND_TEST_ALL_PREFIXES(HotspotCapabilitiesProviderTest,
                           CheckTetheringReadiness_EmptyResult);
  FRIEND_TEST_ALL_PREFIXES(HotspotCapabilitiesProviderTest,
                           CheckTetheringReadiness_Failure);

  enum class HotspotMetricsSetEnabledResult;
  enum class HotspotMetricsSetConfigResult;
  enum class HotspotMetricsCheckReadinessResult;
  enum class HotspotMetricsDisableReason;

  static const char kHotspotAllowStatusHistogram[];
  static const char kHotspotAllowStatusAtLoginHistogram[];
  static const char kHotspotEnableResultHistogram[];
  static const char kHotspotDisableResultHistogram[];
  static const char kHotspotSetConfigResultHistogram[];
  static const char kHotspotCheckReadinessResultHistogram[];
  static const char kHotspotUsageConfigAutoDisable[];
  static const char kHotspotUsageConfigMAR[];
  static const char kHotspotUsageConfigCompatibilityMode[];
  static const char kHotspotUsageDuration[];
  static const char kHotspotMaxClientCount[];
  static const char kHotspotIsDeviceManaged[];
  static const char kHotspotEnableLatency[];
  static const char kHotspotUpstreamStatusWhenEnabled[];
  static const char kHotspotDisableReasonHistogram[];
  static const base::TimeDelta kLogAllowStatusAtLoginTimeout;

  static HotspotMetricsCheckReadinessResult GetCheckReadinessMetricsResult(
      const HotspotCapabilitiesProvider::CheckTetheringReadinessResult& result);
  static HotspotMetricsSetEnabledResult GetSetEnabledMetricsResult(
      const hotspot_config::mojom::HotspotControlResult& result);
  static HotspotMetricsSetConfigResult GetSetConfigMetricsResult(
      const hotspot_config::mojom::SetHotspotConfigResult& result,
      const std::string& shill_error);
  static HotspotMetricsDisableReason GetMetricsDisableReason(
      const hotspot_config::mojom::DisableReason& reason);

  // Represents the hotspot allow status on device. Note:
  // kDisallowNoCellularUpstream is not logged in the metric because it means
  // the device is not cellular capable, and it would drown out the metric by
  // adding the bucket. These values are persisted to logs. Entries should not
  // be renumbered and numeric values should never be reused.
  enum class HotspotMetricsAllowStatus {
    kAllowed = 0,
    kDisallowedWiFiDownstreamNotSupported = 1,
    kDisallowedNoWiFiSecurityModes = 2,
    kDisallowedNoMobileData = 3,
    kDisallowedReadinessCheckFail = 4,
    kDisallowedByPolicy = 5,
    kMaxValue = kDisallowedByPolicy,
  };

  // Represents the operation result of set hotspot configuration used for
  // related UMA histogram. These values are persisted to logs. Entries should
  // not be renumbered and numeric values should never be reused.
  enum class HotspotMetricsSetConfigResult {
    kSuccess = 0,
    kFailedNotLogin = 1,
    kFailedInvalidConfiguration = 2,
    kFailedIllegalOperation = 3,
    kFailedPermissionDenied = 4,
    kFailedInvalidArgument = 5,
    kFailedShillOperation = 6,
    kFailedUnknownShillError = 7,
    kMaxValue = kFailedUnknownShillError,
  };

  // Represents the operation result of check tethering readiness used for
  // related UMA histogram. These values are persisted to logs. Entries should
  // not be renumbered and numeric values should never be reused.
  enum class HotspotMetricsCheckReadinessResult {
    kReady = 0,
    kNotAllowed = 1,
    kUpstreamNetworkNotAvailable = 2,
    kShillOperationFailed = 3,
    kUnknownResult = 4,
    kNotAllowedByCarrier = 5,
    kNotAllowedOnFW = 6,
    kNotAllowedOnVariant = 7,
    kNotAllowedUserNotEntitled = 8,
    kMaxValue = kNotAllowedUserNotEntitled,
  };

  // Represents the operation result of enable/disable hotspot used for related
  // UMA histograms. These values are persisted to logs. Entries should not be
  // renumbered and numeric values should never be reused.
  enum class HotspotMetricsSetEnabledResult {
    kSuccess = 0,
    kNotAllowed = 1,
    kReadinessCheckFailure = 2,
    kDisableWifiFailure = 3,
    kInvalidConfiguration = 4,
    kUpstreamNotAvailable = 5,
    kNetworkSetupFailure = 6,
    kDownstreamWifiFailure = 7,
    kUpstreamFailure = 8,
    kShillOperationFailure = 9,
    kUnknownFailure = 10,
    kAlreadyFulfilled = 11,
    kAborted = 12,
    kInvalid = 13,
    kMaxValue = kInvalid,
  };

  // Represents the upstream status when hotspot is enabled. These values are
  // persisted to logs. Entries should not be renumbered and numeric values
  // should never be reused.
  enum class HotspotMetricsUpstreamStatus {
    kWifiWithCellularConnected = 0,
    kWifiWithCellularNotConnected = 1,
    kMaxValue = kWifiWithCellularNotConnected,
  };

  // Represents the hotspot disable reason. These values are persisted to logs.
  // Entries should not be renumbered and numeric values should never be used.
  enum class HotspotMetricsDisableReason {
    kAutoDisabled = 0,
    kInternalError = 1,
    kUserInitiated = 2,
    kWifiEnabled = 3,
    kProhibitedByPolicy = 4,
    kUpstreamNetworkNotAvailable = 5,
    kSuspended = 6,
    kRestart = 7,
    kUpstreamNoInternet = 8,
    kDownstreamLinkDisconnect = 9,
    kDownstreamNetworkDisconnect = 10,
    kStartTimeout = 11,
    kUpstreamNotAvailable = 12,
    kUnknownError = 13,
    kMaxValue = kUnknownError,
  };

  // HotspotCapabilitiesProvider::Observer:
  void OnHotspotCapabilitiesChanged() override;

  // HotspotStateHandler::Observer:
  void OnHotspotStatusChanged() override;

  // LoginState::Observer:
  void LoggedInStateChanged() override;

  // hotspot_config::mojom::HotspotEnabledStateObserver:
  void OnHotspotTurnedOn() override;
  void OnHotspotTurnedOff(hotspot_config::mojom::DisableReason reason) override;

  void LogAllowStatus();
  void LogAllowStatusAtLogin();
  void LogUsageConfig();
  void LogUsageDuration();
  void LogMaxClientCount();
  void LogIsDeviceManaged();
  void LogUpstreamStatus();
  void LogDisableReason(const hotspot_config::mojom::DisableReason& reason);

  // Retrieves the latest hotspot allow status and converts to
  // HotspotMetricsAllowStatus enum. Return std::nullopt if it is disallowed
  // due to device is not cellular capable.
  std::optional<HotspotMetricsAllowStatus> GetMetricsAllowStatus();

  raw_ptr<EnterpriseManagedMetadataStore> enterprise_managed_metadata_store_ =
      nullptr;
  raw_ptr<HotspotCapabilitiesProvider> hotspot_capabilities_provider_ = nullptr;
  raw_ptr<HotspotStateHandler> hotspot_state_handler_ = nullptr;
  raw_ptr<HotspotConfigurationHandler> hotspot_configuration_handler_ = nullptr;
  raw_ptr<HotspotEnabledStateNotifier, DanglingUntriaged>
      hotspot_enabled_state_notifier_ = nullptr;
  raw_ptr<NetworkStateHandler> network_state_handler_ = nullptr;

  // A timer to wait for user connecting to their upstream cellular network
  // after login.
  base::OneShotTimer timer_;

  // Tracks the maximum connected client count per hotspot session.
  size_t max_client_count_ = 0;

  // Tracks whether the metrics are already logged for this session.
  bool is_metrics_logged_ = false;

  // Tracks whether the hotspot is active.
  bool is_hotspot_active_ = false;

  // Tracks the usage time for each hotspot session.
  std::optional<base::ElapsedTimer> usage_timer_;

  // Tracks if the device is enterprise managed or not.
  bool is_enterprise_managed_ = false;

  mojo::Receiver<hotspot_config::mojom::HotspotEnabledStateObserver>
      hotspot_enabled_state_notifier_receiver_{this};
};

}  // namespace ash

#endif  // CHROMEOS_ASH_COMPONENTS_NETWORK_METRICS_HOTSPOT_METRICS_HELPER_H_