chromium/chrome/browser/nearby_sharing/nearby_share_metrics.cc

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

#include "chrome/browser/nearby_sharing/nearby_share_metrics.h"

#include "base/files/file_path.h"
#include "base/metrics/histogram_functions.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/string_util.h"
#include "chrome/browser/ash/file_manager/path_util.h"
#include "chrome/browser/nearby_sharing/common/nearby_share_features.h"
#include "chrome/browser/nearby_sharing/nearby_share_error.h"
#include "chrome/browser/nearby_sharing/nearby_share_feature_status.h"
#include "chromeos/ash/services/nearby/public/mojom/nearby_connections_types.mojom.h"
#include "chromeos/ash/services/nearby/public/mojom/nearby_decoder_types.mojom.h"
#include "components/policy/core/common/policy_service.h"
#include "components/policy/policy_constants.h"

namespace {

const size_t kBytesPerKilobyte = 1024;
const uint64_t k5MbInBytes = 5242880;

// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused. The numbers here correspond to the
// ordering of the flow. This enum should be kept in sync with the
// NearbyShareBackgroundScanningSetupNotificationFlowEvent enum in
// //tools/metrics/histograms/metadata/nearby/enums.xml.
enum class BackgroundScanningDevicesDetectedEvent {
  kNearbyDevicesDetected = 1,
  kMaxValue = kNearbyDevicesDetected
};

// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused. If entries are added, kMaxValue should
// be updated. This enum should be kept in sync with the
// NearbyShareTransferFinalStatus enum in
// //tools/metrics/histograms/metadata/nearby/enums.xml.
enum class TransferFinalStatus {
  kComplete = 0,
  kUnknown = 1,
  kAwaitingRemoteAcceptanceFailed = 2,
  kFailed = 3,
  kRejected = 4,
  kCancelled = 5,
  kTimedOut = 6,
  kMediaUnavailable = 7,
  kNotEnoughSpace = 8,
  kUnsupportedAttachmentType = 9,
  kDecodeAdvertisementFailed = 10,
  kMissingTransferUpdateCallback = 11,
  kMissingShareTarget = 12,
  kMissingEndpointId = 13,
  kMissingPayloads = 14,
  kPairedKeyVerificationFailed = 15,
  kInvalidIntroductionFrame = 16,
  kIncompletePayloads = 17,
  kFailedToCreateShareTarget = 18,
  kFailedToInitiateOutgoingConnection = 19,
  kFailedToReadOutgoingConnectionResponse = 20,
  kUnexpectedDisconnection = 21,
  kMaxValue = kUnexpectedDisconnection
};

// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused. If entries are added, kMaxValue should
// be updated. This enum should be kept in sync with the
// NearbyShareStartAdvertisingFailureReason enum in
// //tools/metrics/histograms/metadata/nearby/enums.xml.
enum class StartAdvertisingFailureReason {
  kUnknown = 0,
  kError = 1,
  kOutOfOrderApiCall = 2,
  kAlreadyHaveActiveStrategy = 3,
  kAlreadyAdvertising = 4,
  kBluetoothError = 5,
  kBleError = 6,
  kWifiLanError = 7,
  kMaxValue = kWifiLanError
};

// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused. If entries are added, kMaxValue should
// be updated. This enum should be kept in sync with the NearbyShareFinalStatus
// enum in //tools/metrics/histograms/metadata/nearby/enums.xml.
enum class FinalStatus {
  kSuccess = 0,
  kFailure = 1,
  kCanceled = 2,
  kMaxValue = kCanceled
};

// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused. If entries are added, kMaxValue should
// be updated. This enum should be kept in sync with the
// NearbyShareAttachmentType enum in
// //tools/metrics/histograms/metadata/nearby/enums.xml.
enum class AttachmentType {
  kUnknownFileType = 0,
  kUnknownTextType = 1,
  kImage = 2,
  kVideo = 3,
  kApp = 4,
  kAudio = 5,
  kText = 6,
  kUrl = 7,
  kAddress = 8,
  kPhoneNumber = 9,
  kWifiCredentials = 10,
  kMaxValue = kWifiCredentials
};

// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused. If entries are added, kMaxValue should
// be updated. This enum should be kept in sync with the
// NearbyShareUpgradedMedium enum in
// //tools/metrics/histograms/metadata/nearby/enums.xml.
enum class UpgradedMedium {
  kUnknown = 0,
  kMdns = 1,
  kBluetooth = 2,
  kWifiHotspot = 3,
  kBle = 4,
  kWifiLan = 5,
  kWifiAware = 6,
  kNfc = 7,
  kWifiDirect = 8,
  kWebRtc = 9,
  kNoUpgrade = 10,
  kBleL2Cap = 11,
  kUsb = 12,
  kMaxValue = kUsb
};

AttachmentType FileMetadataTypeToAttachmentType(
    ::sharing::mojom::FileMetadata::Type type) {
  switch (type) {
    case ::sharing::mojom::FileMetadata::Type::kUnknown:
      return AttachmentType::kUnknownFileType;
    case ::sharing::mojom::FileMetadata::Type::kImage:
      return AttachmentType::kImage;
    case ::sharing::mojom::FileMetadata::Type::kVideo:
      return AttachmentType::kVideo;
    case ::sharing::mojom::FileMetadata::Type::kApp:
      return AttachmentType::kApp;
    case ::sharing::mojom::FileMetadata::Type::kAudio:
      return AttachmentType::kAudio;
  }
}

AttachmentType TextMetadataTypeToAttachmentType(
    ::sharing::mojom::TextMetadata::Type type) {
  switch (type) {
    case ::sharing::mojom::TextMetadata::Type::kUnknown:
      return AttachmentType::kUnknownTextType;
    case ::sharing::mojom::TextMetadata::Type::kText:
      return AttachmentType::kText;
    case ::sharing::mojom::TextMetadata::Type::kUrl:
      return AttachmentType::kUrl;
    case ::sharing::mojom::TextMetadata::Type::kAddress:
      return AttachmentType::kAddress;
    case ::sharing::mojom::TextMetadata::Type::kPhoneNumber:
      return AttachmentType::kPhoneNumber;
  }
}

TransferFinalStatus TransferMetadataStatusToTransferFinalStatus(
    TransferMetadata::Status status) {
  switch (status) {
    case TransferMetadata::Status::kComplete:
      return TransferFinalStatus::kComplete;
    case TransferMetadata::Status::kAwaitingRemoteAcceptanceFailed:
      return TransferFinalStatus::kAwaitingRemoteAcceptanceFailed;
    case TransferMetadata::Status::kFailed:
      return TransferFinalStatus::kFailed;
    case TransferMetadata::Status::kRejected:
      return TransferFinalStatus::kRejected;
    case TransferMetadata::Status::kCancelled:
      return TransferFinalStatus::kCancelled;
    case TransferMetadata::Status::kTimedOut:
      return TransferFinalStatus::kTimedOut;
    case TransferMetadata::Status::kMediaUnavailable:
      return TransferFinalStatus::kMediaUnavailable;
    case TransferMetadata::Status::kNotEnoughSpace:
      return TransferFinalStatus::kNotEnoughSpace;
    case TransferMetadata::Status::kUnsupportedAttachmentType:
      return TransferFinalStatus::kUnsupportedAttachmentType;
    case TransferMetadata::Status::kDecodeAdvertisementFailed:
      return TransferFinalStatus::kDecodeAdvertisementFailed;
    case TransferMetadata::Status::kMissingTransferUpdateCallback:
      return TransferFinalStatus::kMissingTransferUpdateCallback;
    case TransferMetadata::Status::kMissingShareTarget:
      return TransferFinalStatus::kMissingShareTarget;
    case TransferMetadata::Status::kMissingEndpointId:
      return TransferFinalStatus::kMissingEndpointId;
    case TransferMetadata::Status::kMissingPayloads:
      return TransferFinalStatus::kMissingPayloads;
    case TransferMetadata::Status::kPairedKeyVerificationFailed:
      return TransferFinalStatus::kPairedKeyVerificationFailed;
    case TransferMetadata::Status::kInvalidIntroductionFrame:
      return TransferFinalStatus::kInvalidIntroductionFrame;
    case TransferMetadata::Status::kIncompletePayloads:
      return TransferFinalStatus::kIncompletePayloads;
    case TransferMetadata::Status::kFailedToCreateShareTarget:
      return TransferFinalStatus::kFailedToCreateShareTarget;
    case TransferMetadata::Status::kFailedToInitiateOutgoingConnection:
      return TransferFinalStatus::kFailedToInitiateOutgoingConnection;
    case TransferMetadata::Status::kFailedToReadOutgoingConnectionResponse:
      return TransferFinalStatus::kFailedToReadOutgoingConnectionResponse;
    case TransferMetadata::Status::kUnexpectedDisconnection:
      return TransferFinalStatus::kUnexpectedDisconnection;
    case TransferMetadata::Status::kUnknown:
    case TransferMetadata::Status::kConnecting:
    case TransferMetadata::Status::kAwaitingLocalConfirmation:
    case TransferMetadata::Status::kAwaitingRemoteAcceptance:
    case TransferMetadata::Status::kInProgress:
    case TransferMetadata::Status::kMediaDownloading:
    case TransferMetadata::Status::kExternalProviderLaunched:
      NOTREACHED_IN_MIGRATION();
      return TransferFinalStatus::kUnknown;
  }
}

StartAdvertisingFailureReason
NearbyConnectionsStatusToStartAdvertisingFailureReason(
    nearby::connections::mojom::Status status) {
  switch (status) {
    case nearby::connections::mojom::Status::kError:
      return StartAdvertisingFailureReason::kError;
    case nearby::connections::mojom::Status::kOutOfOrderApiCall:
      return StartAdvertisingFailureReason::kOutOfOrderApiCall;
    case nearby::connections::mojom::Status::kAlreadyHaveActiveStrategy:
      return StartAdvertisingFailureReason::kAlreadyHaveActiveStrategy;
    case nearby::connections::mojom::Status::kAlreadyAdvertising:
      return StartAdvertisingFailureReason::kAlreadyAdvertising;
    case nearby::connections::mojom::Status::kBluetoothError:
      return StartAdvertisingFailureReason::kBluetoothError;
    case nearby::connections::mojom::Status::kBleError:
      return StartAdvertisingFailureReason::kBleError;
    case nearby::connections::mojom::Status::kWifiLanError:
      return StartAdvertisingFailureReason::kWifiLanError;
    case nearby::connections::mojom::Status::kSuccess:
      NOTREACHED_IN_MIGRATION();
      [[fallthrough]];
    case nearby::connections::mojom::Status::kAlreadyDiscovering:
    case nearby::connections::mojom::Status::kEndpointIOError:
    case nearby::connections::mojom::Status::kEndpointUnknown:
    case nearby::connections::mojom::Status::kConnectionRejected:
    case nearby::connections::mojom::Status::kAlreadyConnectedToEndpoint:
    case nearby::connections::mojom::Status::kNotConnectedToEndpoint:
    case nearby::connections::mojom::Status::kPayloadUnknown:
    case nearby::connections::mojom::Status::kAlreadyListening:
    case nearby::connections::mojom::Status::kReset:
    case nearby::connections::mojom::Status::kTimeout:
    case nearby::connections::mojom::Status::kUnknown:
    case nearby::connections::mojom::Status::kNextValue:
      return StartAdvertisingFailureReason::kUnknown;
  }
}

FinalStatus PayloadStatusToFinalStatus(
    nearby::connections::mojom::PayloadStatus status) {
  switch (status) {
    case nearby::connections::mojom::PayloadStatus::kSuccess:
      return FinalStatus::kSuccess;
    case nearby::connections::mojom::PayloadStatus::kFailure:
      return FinalStatus::kFailure;
    case nearby::connections::mojom::PayloadStatus::kCanceled:
      return FinalStatus::kCanceled;
    case nearby::connections::mojom::PayloadStatus::kInProgress:
      NOTREACHED_IN_MIGRATION();
      return FinalStatus::kFailure;
  }
}

std::string GetDirectionSubcategoryName(bool is_incoming) {
  return is_incoming ? ".Receive" : ".Send";
}

std::string GetShareTargetTypeSubcategoryName(
    nearby_share::mojom::ShareTargetType type) {
  switch (type) {
    case nearby_share::mojom::ShareTargetType::kUnknown:
      return ".UnknownDeviceType";
    case nearby_share::mojom::ShareTargetType::kPhone:
      return ".Phone";
    case nearby_share::mojom::ShareTargetType::kTablet:
      return ".Tablet";
    case nearby_share::mojom::ShareTargetType::kLaptop:
      return ".Laptop";
  }
}

// Note: There are many screen states besides locked and logged in. These two
// are the only states which Nearby Share is enabled for.
std::string GetScreenLockedName(bool is_screen_locked) {
  return is_screen_locked ? ".ScreenLocked" : ".LoggedIn";
}

std::string GetPayloadStatusSubcategoryName(
    nearby::connections::mojom::PayloadStatus status) {
  switch (status) {
    case nearby::connections::mojom::PayloadStatus::kSuccess:
      return ".Succeeded";
    case nearby::connections::mojom::PayloadStatus::kFailure:
      return ".Failed";
    case nearby::connections::mojom::PayloadStatus::kCanceled:
      return ".Cancelled";
    case nearby::connections::mojom::PayloadStatus::kInProgress:
      NOTREACHED_IN_MIGRATION();
      return ".Failed";
  }
}

std::string GetUpgradedMediumSubcategoryName(
    std::optional<nearby::connections::mojom::Medium> last_upgraded_medium) {
  if (!last_upgraded_medium) {
    return ".NoMediumUpgrade";
  }

  switch (*last_upgraded_medium) {
    case nearby::connections::mojom::Medium::kWebRtc:
      return ".WebRtcUpgrade";
    case nearby::connections::mojom::Medium::kWifiLan:
      return ".WifiLanUpgrade";
    case nearby::connections::mojom::Medium::kUnknown:
    case nearby::connections::mojom::Medium::kMdns:
    case nearby::connections::mojom::Medium::kBluetooth:
    case nearby::connections::mojom::Medium::kWifiHotspot:
    case nearby::connections::mojom::Medium::kBle:
    case nearby::connections::mojom::Medium::kWifiAware:
    case nearby::connections::mojom::Medium::kNfc:
    case nearby::connections::mojom::Medium::kWifiDirect:
    case nearby::connections::mojom::Medium::kBleL2Cap:
    case nearby::connections::mojom::Medium::kUsb:
      return ".UnknownMediumUpgrade";
  }
}

UpgradedMedium GetUpgradedMediumForMetrics(
    std::optional<nearby::connections::mojom::Medium> last_upgraded_medium) {
  if (!last_upgraded_medium) {
    return UpgradedMedium::kNoUpgrade;
  }

  switch (*last_upgraded_medium) {
    case nearby::connections::mojom::Medium::kUnknown:
      return UpgradedMedium::kUnknown;
    case nearby::connections::mojom::Medium::kMdns:
      return UpgradedMedium::kMdns;
    case nearby::connections::mojom::Medium::kBluetooth:
      return UpgradedMedium::kBluetooth;
    case nearby::connections::mojom::Medium::kWifiHotspot:
      return UpgradedMedium::kWifiHotspot;
    case nearby::connections::mojom::Medium::kBle:
      return UpgradedMedium::kBle;
    case nearby::connections::mojom::Medium::kWifiLan:
      return UpgradedMedium::kWifiLan;
    case nearby::connections::mojom::Medium::kWifiAware:
      return UpgradedMedium::kWifiAware;
    case nearby::connections::mojom::Medium::kNfc:
      return UpgradedMedium::kNfc;
    case nearby::connections::mojom::Medium::kWifiDirect:
      return UpgradedMedium::kWifiDirect;
    case nearby::connections::mojom::Medium::kWebRtc:
      return UpgradedMedium::kWebRtc;
    case nearby::connections::mojom::Medium::kBleL2Cap:
      return UpgradedMedium::kBleL2Cap;
    case nearby::connections::mojom::Medium::kUsb:
      return UpgradedMedium::kUsb;
  }
}

std::string GetContactStatus(bool is_contact, bool for_self_share) {
  if (for_self_share) {
    return ".SelfShare";
  }

  return is_contact ? ".Contact" : ".NonContact";
}

void RecordNearbySharePayloadAttachmentTypeMetricVariants(
    const std::string prefix,
    AttachmentType type,
    bool is_incoming,
    nearby::connections::mojom::PayloadStatus status) {
  base::UmaHistogramEnumeration(prefix, type);
  base::UmaHistogramEnumeration(
      prefix + GetDirectionSubcategoryName(is_incoming), type);
  base::UmaHistogramEnumeration(
      prefix + GetPayloadStatusSubcategoryName(status), type);
}

void RecordNearbySharePayloadAttachmentTypeMetric(
    AttachmentType type,
    bool is_incoming,
    bool is_contact,
    bool for_self_share,
    nearby::connections::mojom::PayloadStatus status) {
  RecordNearbySharePayloadAttachmentTypeMetricVariants(
      "Nearby.Share.Payload.AttachmentType", type, is_incoming, status);
  RecordNearbySharePayloadAttachmentTypeMetricVariants(
      "Nearby.Share.Payload" + GetContactStatus(is_contact, for_self_share) +
          ".AttachmentType",
      type, is_incoming, status);
}

// FuseBox (go/fuse-box) makes virtual file systems (e.g. ARC ContentProvider)
// visible on the Linux native file system through a FUSE (Filesystem in
// USErspace) abstraction layer.
bool IsFuseBoxFilePath(const base::FilePath& file_path) {
  if (file_path.empty()) {
    return false;
  }
  return base::StartsWith(file_path.value(),
                          file_manager::util::kFuseBoxMediaPath);
}

// Share Cache is used to store temporary files being shared between app
// platforms (used by ARC and WebAPK) and is owned by file manager.
bool IsShareCacheFilePath(Profile* profile, const base::FilePath& file_path) {
  if (!profile || file_path.empty()) {
    return false;
  }
  return base::StartsWith(
      file_path.value(),
      file_manager::util::GetShareCacheFilePath(profile).value());
}

// Returns true if |medium| is one that the transfer is completed
// over. This is in contrast to a discovery medium, such as BLE,
// which performs discovery but not transfer. This should reflect
// the TransferMedium variants described in
// tools/metrics/histograms/metadata/nearby/histograms.xml on as-needed basis so
// we don't create blank metrics.
bool IsTransferMedium(nearby::connections::mojom::Medium medium) {
  switch (medium) {
    case nearby::connections::mojom::Medium::kBluetooth:
    case nearby::connections::mojom::Medium::kWebRtc:
    case nearby::connections::mojom::Medium::kWifiLan:
    case nearby::connections::mojom::Medium::kWifiDirect:
      return true;
    case nearby::connections::mojom::Medium::kUnknown:
    case nearby::connections::mojom::Medium::kMdns:
    case nearby::connections::mojom::Medium::kWifiHotspot:
    case nearby::connections::mojom::Medium::kBle:
    case nearby::connections::mojom::Medium::kWifiAware:
    case nearby::connections::mojom::Medium::kNfc:
    case nearby::connections::mojom::Medium::kBleL2Cap:
    case nearby::connections::mojom::Medium::kUsb:
      return false;
  }
}

}  // namespace

std::string GetMediumName(nearby::connections::mojom::Medium medium) {
  switch (medium) {
    case nearby::connections::mojom::Medium::kWebRtc:
      return "WebRtc";
    case nearby::connections::mojom::Medium::kWifiLan:
      return "WifiLan";
    case nearby::connections::mojom::Medium::kUnknown:
      return "Unknown";
    case nearby::connections::mojom::Medium::kMdns:
      return "Mdns";
    case nearby::connections::mojom::Medium::kBluetooth:
      return "Bluetooth";
    case nearby::connections::mojom::Medium::kWifiHotspot:
      return "WifiHotspot";
    case nearby::connections::mojom::Medium::kBle:
      return "Ble";
    case nearby::connections::mojom::Medium::kWifiAware:
      return "WifiAware";
    case nearby::connections::mojom::Medium::kNfc:
      return "Nfc";
    case nearby::connections::mojom::Medium::kWifiDirect:
      return "WifiDirect";
    case nearby::connections::mojom::Medium::kBleL2Cap:
      return "BleL2Cap";
    case nearby::connections::mojom::Medium::kUsb:
      return "Usb";
  }
}

void RecordNearbyShareEnabledMetric(NearbyShareEnabledState state) {
  base::UmaHistogramEnumeration("Nearby.Share.Enabled", state);
}

void RecordNearbyShareEstablishConnectionMetrics(
    bool success,
    bool cancelled,
    base::TimeDelta time_to_connect) {
  FinalStatus status;
  if (success) {
    status = FinalStatus::kSuccess;
    base::UmaHistogramTimes(
        "Nearby.Share.Connection.TimeToEstablishOutgoingConnection",
        time_to_connect);
  } else {
    status = cancelled ? FinalStatus::kCanceled : FinalStatus::kFailure;
  }
  base::UmaHistogramEnumeration(
      "Nearby.Share.Connection.EstablishOutgoingConnectionStatus", status);

  // Log a high-level success/failure metric, ignoring cancellations.
  if (!cancelled) {
    base::UmaHistogramBoolean(
        "Nearby.Share.Connection.EstablishOutgoingConnection.Success", success);
  }
}

void RecordNearbyShareInitialConnectionMedium(
    nearby::connections::mojom::Medium medium) {
  CHECK(IsTransferMedium(medium));
  base::UmaHistogramEnumeration("Nearby.Share.Connection.InitialMedium",
                                GetUpgradedMediumForMetrics(medium));
}

void RecordNearbyShareTimeFromInitiateSendToRemoteDeviceNotificationMetric(
    base::TimeDelta time) {
  base::UmaHistogramTimes(
      "Nearby.Share.TimeFromInitiateSendToRemoteDeviceNotification", time);
}

void RecordNearbyShareTimeFromLocalAcceptToTransferStartMetric(
    base::TimeDelta time) {
  base::UmaHistogramTimes("Nearby.Share.TimeFromLocalAcceptToTransferStart",
                          time);
}

void RecordNearbySharePayloadFileAttachmentTypeMetric(
    ::sharing::mojom::FileMetadata::Type type,
    bool is_incoming,
    bool is_contact,
    bool for_self_share,
    nearby::connections::mojom::PayloadStatus status) {
  RecordNearbySharePayloadAttachmentTypeMetric(
      FileMetadataTypeToAttachmentType(type), is_incoming, is_contact,
      for_self_share, status);
}

void RecordNearbySharePayloadTextAttachmentTypeMetric(
    ::sharing::mojom::TextMetadata::Type type,
    bool is_incoming,
    bool is_contact,
    bool for_self_share,
    nearby::connections::mojom::PayloadStatus status) {
  RecordNearbySharePayloadAttachmentTypeMetric(
      TextMetadataTypeToAttachmentType(type), is_incoming, is_contact,
      for_self_share, status);
}

void RecordNearbySharePayloadWifiCredentialsAttachmentTypeMetric(
    bool is_incoming,
    bool is_contact,
    bool for_self_share,
    nearby::connections::mojom::PayloadStatus status) {
  RecordNearbySharePayloadAttachmentTypeMetric(AttachmentType::kWifiCredentials,
                                               is_incoming, is_contact,
                                               for_self_share, status);
}

void RecordNearbySharePayloadFileOperationMetrics(
    Profile* profile,
    const ShareTarget& share_target,
    PayloadFileOperation operation,
    const bool success) {
  DCHECK(profile);

  if (!share_target.has_attachments()) {
    return;
  }

  const std::optional<base::FilePath>& path =
      share_target.file_attachments[0].file_path();
  if (path) {
    // To determine the file path type, only first attachment file is checked as
    // it is expected that all file attachments from the volume location will be
    // using the same path (e.g. MTP, Downloads, Fusebox, ShareCache, etc.).
    // Only FuseBox and ShareCache are highlighted here as they are paths for
    // virtual file systems, but other specific path metrics can be added.
    const std::string metric_str = (operation == PayloadFileOperation::kOpen)
                                       ? "Nearby.Share.Payload.Open.Success"
                                       : "Nearby.Share.Payload.Read.Success";
    if (IsFuseBoxFilePath(*path)) {
      base::UmaHistogramBoolean(metric_str + ".FuseBox", success);
    } else if (IsShareCacheFilePath(profile, *path)) {
      base::UmaHistogramBoolean(metric_str + ".ShareCache", success);
    } else {
      base::UmaHistogramBoolean(metric_str + ".UnknownPath", success);
    }
    // Overall success/failure independent of the file path.
    base::UmaHistogramBoolean(metric_str, success);
  }
}

void RecordNearbySharePayloadFinalStatusMetric(
    nearby::connections::mojom::PayloadStatus status,
    std::optional<nearby::connections::mojom::Medium> medium) {
  DCHECK_NE(status, nearby::connections::mojom::PayloadStatus::kInProgress);
  base::UmaHistogramEnumeration("Nearby.Share.Payload.FinalStatus",
                                PayloadStatusToFinalStatus(status));
  base::UmaHistogramEnumeration("Nearby.Share.Payload.FinalStatus" +
                                    GetUpgradedMediumSubcategoryName(medium),
                                PayloadStatusToFinalStatus(status));
}

void RecordNearbySharePayloadMediumMetric(
    std::optional<nearby::connections::mojom::Medium> medium,
    nearby_share::mojom::ShareTargetType type,
    uint64_t num_bytes_transferred) {
  base::UmaHistogramEnumeration("Nearby.Share.Payload.Medium",
                                GetUpgradedMediumForMetrics(medium));
  if (num_bytes_transferred >= k5MbInBytes) {
    base::UmaHistogramEnumeration(
        "Nearby.Share.Payload.Medium.Over5MbTransferred",
        GetUpgradedMediumForMetrics(medium));
    base::UmaHistogramEnumeration(
        "Nearby.Share.Payload.Medium.Over5MbTransferred" +
            GetShareTargetTypeSubcategoryName(type),
        GetUpgradedMediumForMetrics(medium));
  }
}

void RecordNearbySharePayloadNumAttachmentsMetric(
    size_t num_text_attachments,
    size_t num_file_attachments,
    size_t num_wifi_credentials_attachments) {
  base::UmaHistogramCounts100("Nearby.Share.Payload.NumAttachments",
                              num_text_attachments + num_file_attachments +
                                  num_wifi_credentials_attachments);
  base::UmaHistogramCounts100("Nearby.Share.Payload.NumAttachments.Text",
                              num_text_attachments);
  base::UmaHistogramCounts100("Nearby.Share.Payload.NumAttachments.File",
                              num_file_attachments);
  base::UmaHistogramCounts100(
      "Nearby.Share.Payload.NumAttachments.WiFiCredentials",
      num_wifi_credentials_attachments);
}

void RecordNearbySharePayloadSizeMetric(
    bool is_incoming,
    nearby_share::mojom::ShareTargetType type,
    std::optional<nearby::connections::mojom::Medium> last_upgraded_medium,
    nearby::connections::mojom::PayloadStatus status,
    uint64_t payload_size_bytes) {
  DCHECK_NE(status, nearby::connections::mojom::PayloadStatus::kInProgress);

  int kilobytes =
      base::saturated_cast<int>(payload_size_bytes / kBytesPerKilobyte);

  const std::string prefix = "Nearby.Share.Payload.TotalSize";
  base::UmaHistogramCounts1M(prefix, kilobytes);
  base::UmaHistogramCounts1M(prefix + GetDirectionSubcategoryName(is_incoming),
                             kilobytes);
  base::UmaHistogramCounts1M(prefix + GetShareTargetTypeSubcategoryName(type),
                             kilobytes);
  base::UmaHistogramCounts1M(
      prefix + GetUpgradedMediumSubcategoryName(last_upgraded_medium),
      kilobytes);
  base::UmaHistogramCounts1M(prefix + GetPayloadStatusSubcategoryName(status),
                             kilobytes);
}

void RecordNearbySharePayloadTransferRateMetric(
    bool is_incoming,
    nearby_share::mojom::ShareTargetType type,
    std::optional<nearby::connections::mojom::Medium> last_upgraded_medium,
    nearby::connections::mojom::PayloadStatus status,
    uint64_t transferred_payload_bytes,
    base::TimeDelta time_elapsed) {
  DCHECK_NE(status, nearby::connections::mojom::PayloadStatus::kInProgress);

  int kilobytes_per_second = base::saturated_cast<int>(base::ClampDiv(
      base::ClampDiv(transferred_payload_bytes, time_elapsed.InSecondsF()),
      kBytesPerKilobyte));

  const std::string prefix = "Nearby.Share.Payload.TransferRate";
  base::UmaHistogramCounts100000(prefix, kilobytes_per_second);
  base::UmaHistogramCounts100000(
      prefix + GetDirectionSubcategoryName(is_incoming), kilobytes_per_second);
  base::UmaHistogramCounts100000(
      prefix + GetShareTargetTypeSubcategoryName(type), kilobytes_per_second);
  base::UmaHistogramCounts100000(
      prefix + GetUpgradedMediumSubcategoryName(last_upgraded_medium),
      kilobytes_per_second);
  base::UmaHistogramCounts100000(
      prefix + GetPayloadStatusSubcategoryName(status), kilobytes_per_second);
}

void RecordNearbyShareStartAdvertisingResultMetric(
    bool is_high_visibility,
    nearby::connections::mojom::Status status) {
  const std::string mode_suffix =
      is_high_visibility ? ".HighVisibility" : ".ContactsVisibility";
  const bool success = status == nearby::connections::mojom::Status::kSuccess;

  const std::string result_prefix = "Nearby.Share.StartAdvertising.Result";
  base::UmaHistogramBoolean(result_prefix, success);
  base::UmaHistogramBoolean(result_prefix + mode_suffix, success);

  if (!success) {
    const std::string failure_prefix =
        "Nearby.Share.StartAdvertising.Result.FailureReason";
    StartAdvertisingFailureReason reason =
        NearbyConnectionsStatusToStartAdvertisingFailureReason(status);
    base::UmaHistogramEnumeration(failure_prefix, reason);
    base::UmaHistogramEnumeration(failure_prefix + mode_suffix, reason);
  }
}

void RecordNearbyShareTransferFinalStatusMetric(
    NearbyShareFeatureUsageMetrics* feature_usage_metrics,
    bool is_incoming,
    nearby_share::mojom::ShareTargetType type,
    TransferMetadata::Status status,
    bool is_known,
    bool for_self_share,
    bool is_screen_locked) {
  DCHECK(TransferMetadata::IsFinalStatus(status));

  // Emit success/failure to Standard Feature Usage Logging if there was a
  // definitive result.
  switch (TransferMetadata::ToResult(status)) {
    case TransferMetadata::Result::kSuccess:
      feature_usage_metrics->RecordUsage(/*success=*/true);
      break;
    case TransferMetadata::Result::kFailure:
      feature_usage_metrics->RecordUsage(/*success=*/false);
      break;
    case TransferMetadata::Result::kIndeterminate:
      break;
  }

  base::UmaHistogramBoolean("Nearby.Share.IsKnownContact", is_known);

  base::UmaHistogramBoolean("Nearby.Share.IsSelfShare", for_self_share);

  std::string send_or_receive = GetDirectionSubcategoryName(is_incoming);
  std::string share_target_type = GetShareTargetTypeSubcategoryName(type);

  base::UmaHistogramEnumeration("Nearby.Share.DeviceType", type);
  base::UmaHistogramEnumeration("Nearby.Share.DeviceType" + send_or_receive,
                                type);

  // Log the detailed transfer final status enum.
  {
    TransferFinalStatus final_status =
        TransferMetadataStatusToTransferFinalStatus(status);
    const std::string prefix = "Nearby.Share.Transfer.FinalStatus";
    base::UmaHistogramEnumeration(prefix, final_status);
    base::UmaHistogramEnumeration(prefix + send_or_receive, final_status);
    base::UmaHistogramEnumeration(prefix + share_target_type, final_status);
    base::UmaHistogramEnumeration(
        prefix + GetContactStatus(is_known, for_self_share), final_status);
  }

  // Log the transfer success/failure for high-level success and Critical User
  // Journey (CUJ) metrics.
  {
    std::optional<bool> success;
    switch (TransferMetadata::ToResult(status)) {
      case TransferMetadata::Result::kSuccess:
        success = true;
        break;
      case TransferMetadata::Result::kFailure:
        success = false;
        break;
      case TransferMetadata::Result::kIndeterminate:
        success.reset();
        break;
    }
    if (success.has_value()) {
      const std::string prefix = "Nearby.Share.Transfer.Success";
      const std::string contact_status =
          GetContactStatus(is_known, for_self_share);
      base::UmaHistogramBoolean(prefix, *success);
      base::UmaHistogramBoolean(
          prefix + send_or_receive + share_target_type + contact_status,
          *success);
      if (for_self_share && is_incoming) {
        base::UmaHistogramBoolean(prefix + ".Receive" + share_target_type +
                                      ".SelfShare" +
                                      GetScreenLockedName(is_screen_locked),
                                  success.value());
      }
    }
  }
}

void RecordNearbyShareDeviceNearbySharingNotificationFlowEvent(
    NearbyShareBackgroundScanningDeviceNearbySharingNotificationFlowEvent
        event) {
  base::UmaHistogramSparse(
      "Nearby.Share.BackgroundScanning.DeviceNearbySharing.Notification.Flow",
      static_cast<int>(event));
}

void RecordNearbyShareDeviceNearbySharingNotificationTimeToAction(
    base::TimeDelta time) {
  base::UmaHistogramMediumTimes(
      "Nearby.Share.BackgroundScanning.DeviceNearbySharing.Notification."
      "TimeToAction",
      time);
}

void RecordNearbyShareBackgroundScanningDevicesDetected() {
  base::UmaHistogramEnumeration(
      "Nearby.Share.BackgroundScanning.DevicesDetected",
      BackgroundScanningDevicesDetectedEvent::kNearbyDevicesDetected);
}

void RecordNearbyShareBackgroundScanningDevicesDetectedDuration(
    base::TimeDelta duration) {
  base::UmaHistogramLongTimes(
      "Nearby.Share.BackgroundScanning.DevicesDetected.Duration", duration);
}

void RecordNearbyShareBackgroundScanningSessionStarted(bool success) {
  base::UmaHistogramBoolean("Nearby.Share.BackgroundScanning.SessionStarted",
                            success);
}

void RecordNearbyShareSetupNotificationFlowEvent(
    NearbyShareBackgroundScanningSetupNotificationFlowEvent event) {
  base::UmaHistogramSparse(
      "Nearby.Share.BackgroundScanning.Setup.Notification.Flow",
      static_cast<int>(event));
}

void RecordNearbyShareSetupNotificationTimeToAction(base::TimeDelta time) {
  base::UmaHistogramMediumTimes(
      "Nearby.Share.BackgroundScanning.Setup.Notification.TimeToAction", time);
}

void RecordNearbyShareWifiConfigurationResultMetric(bool success) {
  base::UmaHistogramBoolean("Nearby.Share.WifiNetworkConfiguration.Result",
                            success);
}

void RecordNearbyShareDiscoveredToConnectionEstablishedDuration(
    base::TimeDelta delta) {
  base::UmaHistogramMediumTimes(
      "Nearby.Share.TransferDuration.Sender.DiscoveredToConnectionEstablished",
      delta);
}

void RecordNearbyShareInitiatedToSentIntroductionFrameDuration(
    base::TimeDelta delta) {
  base::UmaHistogramMediumTimes(
      "Nearby.Share.TransferDuration.Sender.InitiatedToSentIntroductionFrame",
      delta);
}

void RecordNearbyShareEndpointDecodedToReceivedIntroductionFrameDuration(
    base::TimeDelta delta) {
  base::UmaHistogramMediumTimes(
      "Nearby.Share.TransferDuration.Receiver."
      "EndpointDecodedToReceivedIntroductionFrame",
      delta);
}

void RecordNearbyShareConnectionEstablishedToBandwidthUpgradeDuration(
    nearby::connections::mojom::Medium medium,
    base::TimeDelta delta) {
  CHECK(IsTransferMedium(medium));
  base::UmaHistogramMediumTimes(
      "Nearby.Share.TransferDuration.Sender."
      "ConnectionEstablishedToBandwidthUpgrade2",
      delta);
  base::UmaHistogramMediumTimes(
      "Nearby.Share.TransferDuration.Sender."
      "ConnectionEstablishedToBandwidthUpgrade2." +
          GetMediumName(medium),
      delta);
}

void RecordNearbyShareHighVisibilityEndpointDecodedToBandwidthUpgradeDuration(
    nearby::connections::mojom::Medium medium,
    base::TimeDelta delta) {
  CHECK(IsTransferMedium(medium));
  base::UmaHistogramMediumTimes(
      "Nearby.Share.TransferDuration.Receiver."
      "HighVisibilityEndpointDecodedToBandwidthUpgrade2",
      delta);
  base::UmaHistogramMediumTimes(
      "Nearby.Share.TransferDuration.Receiver."
      "HighVisibilityEndpointDecodedToBandwidthUpgrade2." +
          GetMediumName(medium),
      delta);
}

void RecordNearbyShareNonHighVisibilityPairedKeyCompleteToBandwidthUpgradeDuration(
    nearby::connections::mojom::Medium medium,
    base::TimeDelta delta) {
  CHECK(IsTransferMedium(medium));
  base::UmaHistogramMediumTimes(
      "Nearby.Share.TransferDuration.Receiver."
      "NonHighVisibilityPairedKeyCompleteToBandwidthUpgrade2",
      delta);
  base::UmaHistogramMediumTimes(
      "Nearby.Share.TransferDuration.Receiver."
      "NonHighVisibilityPairedKeyCompleteToBandwidthUpgrade2." +
          GetMediumName(medium),
      delta);
}

void RecordNearbyShareBandwidthUpgradeToAllFilesReceivedDuration(
    nearby::connections::mojom::Medium medium,
    base::TimeDelta delta) {
  CHECK(IsTransferMedium(medium));
  base::UmaHistogramLongTimes(
      "Nearby.Share.TransferDuration.Receiver."
      "BandwidthUpgradeToAllFilesReceived2",
      delta);
  base::UmaHistogramLongTimes(
      "Nearby.Share.TransferDuration.Receiver."
      "BandwidthUpgradeToAllFilesReceived2." +
          GetMediumName(medium),
      delta);
}

void RecordNearbyShareAcceptedTransferToAllFilesReceivedDuration(
    base::TimeDelta delta) {
  base::UmaHistogramLongTimes(
      "Nearby.Share.TransferDuration.Receiver."
      "AcceptedTransferToAllFilesReceived",
      delta);
}

void RecordNearbyShareReceivedIntroductionFrameToAllFilesReceivedDuration(
    base::TimeDelta delta) {
  base::UmaHistogramLongTimes(
      "Nearby.Share.TransferDuration.Receiver."
      "ReceivedIntroductionFrameToAllFilesReceived",
      delta);
}

void RecordNearbyShareBandwidthUpgradeToAllFilesSentDuration(
    nearby::connections::mojom::Medium medium,
    base::TimeDelta delta) {
  CHECK(IsTransferMedium(medium));
  base::UmaHistogramLongTimes(
      "Nearby.Share.TransferDuration.Sender."
      "BandwidthUpgradeToAllFilesSent2",
      delta);
  base::UmaHistogramLongTimes(
      "Nearby.Share.TransferDuration.Sender.BandwidthUpgradeToAllFilesSent2." +
          GetMediumName(medium),
      delta);
}

void RecordNearbyShareStartSendFilesToAllFilesSentDuration(
    base::TimeDelta delta) {
  base::UmaHistogramLongTimes(
      "Nearby.Share.TransferDuration.Sender.StartSendFilesToAllFilesSent",
      delta);
}

void RecordNearbyShareInitiatedToAllFilesSentDuration(base::TimeDelta delta) {
  base::UmaHistogramLongTimes(
      "Nearby.Share.TransferDuration.Sender.InitiatedToAllFilesSent", delta);
}

void RecordNearbyShareError(NearbyShareError error_code) {
  base::UmaHistogramEnumeration("Nearby.Share.Error", error_code);
}

void RecordNearbySharePairedKeyVerificationError(
    NearbySharePairedKeyVerificationError error) {
  base::UmaHistogramEnumeration("Nearby.Share.PairedKeyVerificationError",
                                error);
}