// 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.
#include "chromeos/ash/components/network/metrics/cellular_network_metrics_logger.h"
#include "ash/constants/ash_features.h"
#include "base/metrics/histogram_functions.h"
#include "chromeos/ash/components/dbus/hermes/constants.h"
#include "chromeos/ash/components/network/metrics/connection_info_metrics_logger.h"
#include "chromeos/ash/components/network/metrics/connection_results.h"
#include "chromeos/ash/components/network/network_metadata_store.h"
#include "chromeos/ash/components/network/network_state_handler.h"
#include "chromeos/ash/components/network/text_message_suppression_state.h"
#include "components/device_event_log/device_event_log.h"
#include "components/onc/onc_constants.h"
namespace ash {
namespace {
using ApnType = chromeos::network_config::mojom::ApnType;
const base::TimeDelta kSmdsScanDurationMinimum = base::Milliseconds(1);
const base::TimeDelta kSmdsScanDurationMaximum = base::Milliseconds(
::ash::hermes_constants::kHermesNetworkOperationTimeoutMs);
const size_t kSmdsScanDurationBuckets = 50;
std::optional<CellularNetworkMetricsLogger::ApnTypes> GetApnTypes(
std::vector<ApnType> apn_types) {
if (apn_types.empty())
return std::nullopt;
bool is_default = false;
bool is_attach = false;
for (const auto& apn_type : apn_types) {
if (apn_type == ApnType::kDefault)
is_default = true;
if (apn_type == ApnType::kAttach)
is_attach = true;
}
if (is_default && is_attach)
return CellularNetworkMetricsLogger::ApnTypes::kDefaultAndAttach;
if (is_attach)
return CellularNetworkMetricsLogger::ApnTypes::kAttach;
return CellularNetworkMetricsLogger::ApnTypes::kDefault;
}
const char* GetESimUserInstallationResultHistogram(
CellularNetworkMetricsLogger::ESimUserInstallMethod method,
bool user_errors_included) {
using ESimUserInstallMethod =
CellularNetworkMetricsLogger::ESimUserInstallMethod;
switch (method) {
case ESimUserInstallMethod::kViaSmds:
return user_errors_included
? CellularNetworkMetricsLogger::
kESimUserInstallUserErrorsIncludedViaSmds
: CellularNetworkMetricsLogger::
kESimUserInstallUserErrorsFilteredViaSmds;
case ESimUserInstallMethod::kViaActivationCodeAfterSmds:
return user_errors_included
? CellularNetworkMetricsLogger::
kESimUserInstallUserErrorsIncludedViaActivationCodeAfterSmds
: CellularNetworkMetricsLogger::
kESimUserInstallUserErrorsFilteredViaActivationCodeAfterSmds;
case ESimUserInstallMethod::kViaActivationCodeSkippedSmds:
return user_errors_included
? CellularNetworkMetricsLogger::
kESimUserInstallUserErrorsIncludedViaActivationCodeSkippedSmds
: CellularNetworkMetricsLogger::
kESimUserInstallUserErrorsFilteredViaActivationCodeSkippedSmds;
case ESimUserInstallMethod::kViaQrCodeAfterSmds:
return user_errors_included
? CellularNetworkMetricsLogger::
kESimUserInstallUserErrorsIncludedViaQrCodeAfterSmds
: CellularNetworkMetricsLogger::
kESimUserInstallUserErrorsFilteredViaQrCodeAfterSmds;
case ESimUserInstallMethod::kViaQrCodeSkippedSmds:
return user_errors_included
? CellularNetworkMetricsLogger::
kESimUserInstallUserErrorsIncludedViaQrCodeSkippedSmds
: CellularNetworkMetricsLogger::
kESimUserInstallUserErrorsFilteredViaQrCodeSkippedSmds;
}
}
const char* GetESimPolicyInstallationResultHistogram(
CellularNetworkMetricsLogger::ESimPolicyInstallMethod method,
bool is_initial,
bool user_errors_included) {
using ESimPolicyInstallMethod =
CellularNetworkMetricsLogger::ESimPolicyInstallMethod;
switch (method) {
case ESimPolicyInstallMethod::kViaSmdp:
if (is_initial) {
return user_errors_included
? CellularNetworkMetricsLogger::
kESimPolicyInstallUserErrorsIncludedViaSmdpInitial
: CellularNetworkMetricsLogger::
kESimPolicyInstallUserErrorsFilteredViaSmdpInitial;
}
return user_errors_included
? CellularNetworkMetricsLogger::
kESimPolicyInstallUserErrorsIncludedViaSmdpRetry
: CellularNetworkMetricsLogger::
kESimPolicyInstallUserErrorsFilteredViaSmdpRetry;
case ESimPolicyInstallMethod::kViaSmds:
if (is_initial) {
return user_errors_included
? CellularNetworkMetricsLogger::
kESimPolicyInstallUserErrorsIncludedViaSmdsInitial
: CellularNetworkMetricsLogger::
kESimPolicyInstallUserErrorsFilteredViaSmdsInitial;
}
return user_errors_included
? CellularNetworkMetricsLogger::
kESimPolicyInstallUserErrorsIncludedViaSmdsRetry
: CellularNetworkMetricsLogger::
kESimPolicyInstallUserErrorsFilteredViaSmdsRetry;
}
}
bool IsAndroidActivationCode(const std::string& smds_activation_code) {
return smds_activation_code == cellular_utils::kSmdsAndroidProduction ||
smds_activation_code == cellular_utils::kSmdsAndroidStaging;
}
bool IsGmsaActivationCode(const std::string& smds_activation_code) {
return smds_activation_code == cellular_utils::kSmdsGsma;
}
const char* GetSmdsScanResultHistogram(const std::string& smds_activation_code,
bool user_errors_included) {
if (IsAndroidActivationCode(smds_activation_code)) {
return user_errors_included ? CellularNetworkMetricsLogger::
kESimSmdsScanAndroidUserErrorsIncluded
: CellularNetworkMetricsLogger::
kESimSmdsScanAndroidUserErrorsFiltered;
}
if (smds_activation_code == cellular_utils::kSmdsGsma) {
return user_errors_included ? CellularNetworkMetricsLogger::
kESimSmdsScanGsmaUserErrorsIncluded
: CellularNetworkMetricsLogger::
kESimSmdsScanGsmaUserErrorsFiltered;
}
return user_errors_included ? CellularNetworkMetricsLogger::
kESimSmdsScanOtherUserErrorsIncluded
: CellularNetworkMetricsLogger::
kESimSmdsScanOtherUserErrorsFiltered;
}
} // namespace
CellularNetworkMetricsLogger::CellularNetworkMetricsLogger(
NetworkStateHandler* network_state_handler,
NetworkMetadataStore* network_metadata_store,
ConnectionInfoMetricsLogger* connection_info_metrics_logger)
: network_state_handler_(network_state_handler),
network_metadata_store_(network_metadata_store) {
if (connection_info_metrics_logger) {
connection_info_metrics_logger_observation_.Observe(
connection_info_metrics_logger);
}
}
CellularNetworkMetricsLogger::~CellularNetworkMetricsLogger() = default;
// static
void CellularNetworkMetricsLogger::LogCreateCustomApnResult(
bool success,
chromeos::network_config::mojom::ApnPropertiesPtr apn) {
base::UmaHistogramBoolean(kCreateCustomApnResultHistogram, success);
// Only emit APN property metrics if the APN was successfully added.
if (!success)
return;
base::UmaHistogramEnumeration(kCreateCustomApnAuthenticationTypeHistogram,
apn->authentication);
base::UmaHistogramEnumeration(kCreateCustomApnIpTypeHistogram, apn->ip_type);
std::optional<CellularNetworkMetricsLogger::ApnTypes> apn_types =
GetApnTypes(apn->apn_types);
if (!apn_types.has_value()) {
NET_LOG(DEBUG) << "CreateCustomApn.ApnTypes not logged for APN because it "
<< "doesn't have any APN types.";
return;
}
base::UmaHistogramEnumeration(kCreateCustomApnApnTypesHistogram,
apn_types.value());
}
// static
void CellularNetworkMetricsLogger::LogCreateExclusivelyEnabledCustomApnResult(
bool success,
chromeos::network_config::mojom::ApnPropertiesPtr apn) {
base::UmaHistogramBoolean(kCreateExclusivelyEnabledCustomApnResultHistogram,
success);
// Only emit APN property metrics if the APN was successfully added.
if (!success) {
return;
}
base::UmaHistogramEnumeration(
kCreateExclusivelyEnabledCustomApnAuthenticationTypeHistogram,
apn->authentication);
base::UmaHistogramEnumeration(
kCreateExclusivelyEnabledCustomApnIpTypeHistogram, apn->ip_type);
std::optional<CellularNetworkMetricsLogger::ApnTypes> apn_types =
GetApnTypes(apn->apn_types);
if (!apn_types.has_value()) {
NET_LOG(DEBUG) << "CreateExclusivelyEnabledCustomApn.ApnTypes not logged "
<< "for APN because it doesn't have any APN types.";
return;
}
base::UmaHistogramEnumeration(
kCreateExclusivelyEnabledCustomApnApnTypesHistogram, apn_types.value());
}
// static
void CellularNetworkMetricsLogger::LogRemoveCustomApnResult(
bool success,
std::vector<chromeos::network_config::mojom::ApnType> apn_types) {
base::UmaHistogramBoolean(kRemoveCustomApnResultHistogram, success);
// Only emit APN property metrics if the APN was successfully removed.
if (!success)
return;
std::optional<CellularNetworkMetricsLogger::ApnTypes> apn_types_enum =
GetApnTypes(apn_types);
if (!apn_types_enum.has_value()) {
NET_LOG(DEBUG) << "RemoveCustomApn.ApnTypes not logged for APN because it "
<< "doesn't have any APN types.";
return;
}
base::UmaHistogramEnumeration(kRemoveCustomApnApnTypesHistogram,
apn_types_enum.value());
}
// static
void CellularNetworkMetricsLogger::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) {
using ApnState = chromeos::network_config::mojom::ApnState;
base::UmaHistogramBoolean(kModifyCustomApnResultHistogram, success);
bool has_apn_state = old_apn_state.has_value() && apn_state.has_value();
bool was_apn_disabled = has_apn_state &&
old_apn_state == ApnState::kEnabled &&
apn_state == ApnState::kDisabled;
bool was_apn_enabled = has_apn_state &&
old_apn_state == ApnState::kDisabled &&
apn_state == ApnState::kEnabled;
if (was_apn_enabled) {
base::UmaHistogramBoolean(kEnableCustomApnResultHistogram, success);
} else if (was_apn_disabled) {
base::UmaHistogramBoolean(kDisableCustomApnResultHistogram, success);
}
// Only emit APN property metrics if the APN was successfully modified.
if (!success) {
return;
}
std::optional<CellularNetworkMetricsLogger::ApnTypes> apn_types_enum =
GetApnTypes(old_apn_types);
if (!apn_types_enum.has_value()) {
NET_LOG(DEBUG) << "ApnTypes not logged for APN because it "
<< "doesn't have any APN types.";
return;
}
base::UmaHistogramEnumeration(kModifyCustomApnApnTypesHistogram,
apn_types_enum.value());
if (was_apn_enabled) {
base::UmaHistogramEnumeration(kEnableCustomApnApnTypesHistogram,
apn_types_enum.value());
} else if (was_apn_disabled) {
base::UmaHistogramEnumeration(kDisableCustomApnApnTypesHistogram,
apn_types_enum.value());
}
}
// static
void CellularNetworkMetricsLogger::LogUnmanagedCustomApnMigrationType(
UnmanagedApnMigrationType type) {
base::UmaHistogramEnumeration(kCustomApnsUnmanagedMigrationTypeHistogram,
type);
}
// static
void CellularNetworkMetricsLogger::LogManagedCustomApnMigrationType(
ManagedApnMigrationType type) {
base::UmaHistogramEnumeration(kCustomApnsManagedMigrationTypeHistogram, type);
}
// static
void CellularNetworkMetricsLogger::LogSmdsScanProfileCount(
size_t count,
SmdsScanMethod method) {
switch (method) {
case SmdsScanMethod::kViaPolicy:
base::UmaHistogramCounts100(kSmdsScanViaPolicyProfileCount, count);
break;
case SmdsScanMethod::kViaUser:
base::UmaHistogramCounts100(kSmdsScanViaUserProfileCount, count);
break;
}
}
// static
void CellularNetworkMetricsLogger::LogSmdsScanDuration(
const base::TimeDelta& duration,
bool success,
const std::string& smds_activation_code) {
std::string histogram;
if (IsAndroidActivationCode(smds_activation_code)) {
histogram = success ? kSmdsScanAndroidDurationSuccess
: kSmdsScanAndroidDurationFailure;
} else if (IsGmsaActivationCode(smds_activation_code)) {
histogram =
success ? kSmdsScanGsmaDurationSuccess : kSmdsScanGsmaDurationFailure;
} else {
histogram =
success ? kSmdsScanOtherDurationSuccess : kSmdsScanOtherDurationFailure;
}
base::UmaHistogramCustomTimes(histogram, duration, kSmdsScanDurationMinimum,
kSmdsScanDurationMaximum,
kSmdsScanDurationBuckets);
}
// static
void CellularNetworkMetricsLogger::LogESimUserInstallMethod(
ESimUserInstallMethod method) {
base::UmaHistogramEnumeration(kESimUserInstallMethod, method);
}
// static
void CellularNetworkMetricsLogger::LogESimPolicyInstallMethod(
ESimPolicyInstallMethod method) {
base::UmaHistogramEnumeration(kESimPolicyInstallMethod, method);
}
// static
void CellularNetworkMetricsLogger::LogESimPolicyInstallNoAvailableProfiles(
ESimPolicyInstallMethod method) {
base::UmaHistogramEnumeration(kESimPolicyInstallNoAvailableProfiles, method);
}
// static
void CellularNetworkMetricsLogger::LogESimUserInstallResult(
ESimUserInstallMethod method,
ESimOperationResult result,
bool is_user_error) {
if (!is_user_error) {
base::UmaHistogramEnumeration(kESimUserInstallUserErrorsFilteredAll,
result);
base::UmaHistogramEnumeration(GetESimUserInstallationResultHistogram(
method, /*user_errors_included=*/false),
result);
}
base::UmaHistogramEnumeration(kESimUserInstallUserErrorsIncludedAll, result);
base::UmaHistogramEnumeration(GetESimUserInstallationResultHistogram(
method, /*user_errors_included=*/true),
result);
}
// static
void CellularNetworkMetricsLogger::LogESimPolicyInstallResult(
ESimPolicyInstallMethod method,
ESimOperationResult result,
bool is_initial,
bool is_user_error) {
if (!is_user_error) {
base::UmaHistogramEnumeration(kESimPolicyInstallUserErrorsFilteredAll,
result);
base::UmaHistogramEnumeration(GetESimPolicyInstallationResultHistogram(
method, /*is_initial=*/is_initial,
/*user_errors_included=*/false),
result);
}
base::UmaHistogramEnumeration(kESimPolicyInstallUserErrorsIncludedAll,
result);
base::UmaHistogramEnumeration(
GetESimPolicyInstallationResultHistogram(
method, /*is_initial=*/is_initial, /*user_errors_included=*/true),
result);
}
// static
void CellularNetworkMetricsLogger::LogSmdsScanResult(
const std::string& smds_activation_code,
std::optional<HermesResponseStatus> status) {
const bool is_user_error =
status.has_value() &&
CellularNetworkMetricsLogger::HermesResponseStatusIsUserError(*status);
const ESimOperationResult result =
CellularNetworkMetricsLogger::ComputeESimOperationResult(status);
if (!is_user_error) {
base::UmaHistogramEnumeration(
GetSmdsScanResultHistogram(smds_activation_code,
/*user_errors_included=*/false),
result);
}
base::UmaHistogramEnumeration(
GetSmdsScanResultHistogram(smds_activation_code,
/*user_errors_included=*/true),
result);
}
// static
CellularNetworkMetricsLogger::ESimOperationResult
CellularNetworkMetricsLogger::ComputeESimOperationResult(
std::optional<HermesResponseStatus> status) {
if (status.has_value()) {
return *status == HermesResponseStatus::kSuccess
? ESimOperationResult::kSuccess
: ESimOperationResult::kHermesFailed;
}
return ESimOperationResult::kInhibitFailed;
}
// static
bool CellularNetworkMetricsLogger::HermesResponseStatusIsUserError(
HermesResponseStatus status) {
switch (status) {
case HermesResponseStatus::kSuccess:
[[fallthrough]];
case HermesResponseStatus::kErrorUnknown:
[[fallthrough]];
case HermesResponseStatus::kErrorInternalLpaFailure:
[[fallthrough]];
case HermesResponseStatus::kErrorSendNotificationFailure:
[[fallthrough]];
case HermesResponseStatus::kErrorTestProfileInProd:
[[fallthrough]];
case HermesResponseStatus::kErrorUnsupported:
[[fallthrough]];
case HermesResponseStatus::kErrorWrongState:
[[fallthrough]];
case HermesResponseStatus::kErrorBadRequest:
[[fallthrough]];
case HermesResponseStatus::kErrorBadNotification:
[[fallthrough]];
case HermesResponseStatus::kErrorPendingProfile:
[[fallthrough]];
case HermesResponseStatus::kErrorSendApduFailure:
[[fallthrough]];
case HermesResponseStatus::kErrorUnexpectedModemManagerState:
[[fallthrough]];
case HermesResponseStatus::kErrorModemMessageProcessing:
[[fallthrough]];
case HermesResponseStatus::kErrorNoResponse:
[[fallthrough]];
case HermesResponseStatus::kErrorUnknownResponse:
return false;
case HermesResponseStatus::kErrorAlreadyDisabled:
[[fallthrough]];
case HermesResponseStatus::kErrorAlreadyEnabled:
[[fallthrough]];
case HermesResponseStatus::kErrorInvalidActivationCode:
[[fallthrough]];
case HermesResponseStatus::kErrorInvalidIccid:
[[fallthrough]];
case HermesResponseStatus::kErrorInvalidParameter:
[[fallthrough]];
case HermesResponseStatus::kErrorNeedConfirmationCode:
[[fallthrough]];
case HermesResponseStatus::kErrorInvalidResponse:
[[fallthrough]];
case HermesResponseStatus::kErrorMalformedResponse:
[[fallthrough]];
case HermesResponseStatus::kErrorSendHttpsFailure:
[[fallthrough]];
case HermesResponseStatus::kErrorEmptyResponse:
return true;
}
// Do not provide a default return here; all cases should be handled inside
// the switch statement above.
}
// static
void CellularNetworkMetricsLogger::LogUserTextMessageSuppressionState(
ash::UserTextMessageSuppressionState state) {
UserTextMessageSuppressionState histogram_type;
switch (state) {
case ash::UserTextMessageSuppressionState::kAllow:
histogram_type = UserTextMessageSuppressionState::kTextMessagesAllow;
break;
case ash::UserTextMessageSuppressionState::kSuppress:
histogram_type = UserTextMessageSuppressionState::kTextMessagesSuppress;
break;
}
base::UmaHistogramEnumeration(kUserAllowTextMessagesSuppressionStateHistogram,
histogram_type);
}
// static
void CellularNetworkMetricsLogger::LogPolicyTextMessageSuppressionState(
ash::PolicyTextMessageSuppressionState state) {
PolicyTextMessageSuppressionState histogram_type;
switch (state) {
case ash::PolicyTextMessageSuppressionState::kAllow:
histogram_type = PolicyTextMessageSuppressionState::kTextMessagesAllow;
break;
case ash::PolicyTextMessageSuppressionState::kSuppress:
histogram_type = PolicyTextMessageSuppressionState::kTextMessagesSuppress;
break;
case ash::PolicyTextMessageSuppressionState::kUnset:
histogram_type = PolicyTextMessageSuppressionState::kUnset;
break;
}
base::UmaHistogramEnumeration(
kPolicyAllowTextMessagesSuppressionStateHistogram, histogram_type);
}
// static
void CellularNetworkMetricsLogger::CellularNetworkMetricsLogger::
LogTextMessageNotificationSuppressionState(
NotificationSuppressionState state) {
base::UmaHistogramEnumeration(kAllowTextMessagesNotificationSuppressionState,
state);
}
void CellularNetworkMetricsLogger::OnConnectionResult(
const std::string& guid,
const std::optional<std::string>& shill_error) {
DCHECK(network_metadata_store_)
<< "OnConnectionResult() called with no NetworkMetadataStore.";
const NetworkState* network_state =
network_state_handler_->GetNetworkStateFromGuid(guid);
if (!network_state) {
NET_LOG(ERROR)
<< "OnConnectionResult() call but no network found for guid: " << guid;
return;
}
// Ignore any non-cellular networks.
if (network_state->GetNetworkTechnologyType() !=
NetworkState::NetworkTechnologyType::kCellular) {
return;
}
ShillConnectResult connect_result =
shill_error ? ShillErrorToConnectResult(*shill_error)
: ShillConnectResult::kSuccess;
size_t custom_apns_count = 0u;
size_t enabled_custom_apns_count = 0u;
const base::Value::List* custom_apn_list =
network_metadata_store_->GetCustomApnList(network_state->guid());
if (custom_apn_list) {
custom_apns_count = custom_apn_list->size();
if (ash::features::IsApnRevampEnabled()) {
enabled_custom_apns_count = std::count_if(
custom_apn_list->begin(), custom_apn_list->end(),
[](const base::Value& apn) -> bool {
const std::string* apn_type =
apn.GetDict().FindString(::onc::cellular_apn::kState);
return *apn_type == ::onc::cellular_apn::kStateEnabled;
});
}
}
// If the connection was successful, log the number of custom APNs the network
// has saved for it.
if (!shill_error) {
base::UmaHistogramCounts100(kCustomApnsCountHistogram, custom_apns_count);
if (ash::features::IsApnRevampEnabled() && custom_apns_count > 0) {
base::UmaHistogramCounts100(kCustomApnsEnabledCountHistogram,
enabled_custom_apns_count);
base::UmaHistogramCounts100(
kCustomApnsDisabledCountHistogram,
custom_apns_count - enabled_custom_apns_count);
}
}
// For pre-revamp cases, we consider all custom APNs to be enabled.
const bool has_enabled_custom_apns = ash::features::IsApnRevampEnabled()
? (enabled_custom_apns_count > 0)
: (custom_apns_count > 0);
base::UmaHistogramEnumeration(
has_enabled_custom_apns ? kConnectResultHasEnabledCustomApnsAllHistogram
: kConnectResultNoEnabledCustomApnsAllHistogram,
connect_result);
}
} // namespace ash