// 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_sharing_service_impl.h"

#include <array>
#include <utility>

#include "ash/public/cpp/new_window_delegate.h"
#include "ash/public/cpp/session/session_controller.h"
#include "base/barrier_closure.h"
#include "base/files/file.h"
#include "base/functional/bind.h"
#include "base/hash/hash.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/notimplemented.h"
#include "base/numerics/checked_math.h"
#include "base/rand_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/system/sys_info.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/thread_pool.h"
#include "base/time/time.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/download/download_prefs.h"
#include "chrome/browser/nearby_sharing/certificates/common.h"
#include "chrome/browser/nearby_sharing/certificates/nearby_share_certificate_manager_impl.h"
#include "chrome/browser/nearby_sharing/certificates/nearby_share_encrypted_metadata_key.h"
#include "chrome/browser/nearby_sharing/client/nearby_share_client_impl.h"
#include "chrome/browser/nearby_sharing/common/nearby_share_features.h"
#include "chrome/browser/nearby_sharing/common/nearby_share_prefs.h"
#include "chrome/browser/nearby_sharing/constants.h"
#include "chrome/browser/nearby_sharing/contacts/nearby_share_contact_manager_impl.h"
#include "chrome/browser/nearby_sharing/fast_initiation/fast_initiation_advertiser.h"
#include "chrome/browser/nearby_sharing/fast_initiation/fast_initiation_scanner.h"
#include "chrome/browser/nearby_sharing/local_device_data/nearby_share_local_device_data_manager_impl.h"
#include "chrome/browser/nearby_sharing/nearby_share_error.h"
#include "chrome/browser/nearby_sharing/nearby_share_feature_status.h"
#include "chrome/browser/nearby_sharing/nearby_share_metrics.h"
#include "chrome/browser/nearby_sharing/nearby_share_transfer_profiler.h"
#include "chrome/browser/nearby_sharing/paired_key_verification_runner.h"
#include "chrome/browser/nearby_sharing/share_target.h"
#include "chrome/browser/nearby_sharing/transfer_metadata.h"
#include "chrome/browser/nearby_sharing/transfer_metadata_builder.h"
#include "chrome/browser/nearby_sharing/wifi_network_configuration/wifi_network_configuration_handler.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "chrome/services/sharing/public/cpp/advertisement.h"
#include "chrome/services/sharing/public/cpp/conversions.h"
#include "chromeos/ash/components/nearby/common/connections_manager/nearby_connections_manager.h"
#include "chromeos/ash/services/nearby/public/mojom/nearby_connections_types.mojom.h"
#include "chromeos/ash/services/nearby/public/mojom/nearby_decoder.mojom.h"
#include "chromeos/ash/services/nearby/public/mojom/nearby_share_target_types.mojom.h"
#include "chromeos/constants/chromeos_features.h"
#include "components/cross_device/logging/logging.h"
#include "components/cross_device/nearby/nearby_features.h"
#include "components/prefs/pref_service.h"
#include "content/public/browser/download_manager.h"
#include "content/public/browser/storage_partition.h"
#include "crypto/random.h"
#include "device/bluetooth/bluetooth_adapter_factory.h"
#include "device/bluetooth/bluetooth_low_energy_scan_filter.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "url/gurl.h"

// static
constexpr int
    NearbySharingServiceImpl::kMaxRecentNearbyProcessUnexpectedShutdownCount;

namespace {

using NearbyProcessShutdownReason =
    ash::nearby::NearbyProcessManager::NearbyProcessShutdownReason;

constexpr base::TimeDelta kBackgroundAdvertisementRotationDelayMin =
    base::Minutes(12);
// 870 seconds represents 14:30 minutes
constexpr base::TimeDelta kBackgroundAdvertisementRotationDelayMax =
    base::Seconds(870);
constexpr base::TimeDelta kInvalidateSurfaceStateDelayAfterTransferDone =
    base::Milliseconds(3000);
constexpr base::TimeDelta kProcessShutdownPendingTimerDelay = base::Seconds(15);
constexpr base::TimeDelta kProcessNetworkChangeTimerDelay = base::Seconds(1);

// Cooldown period after a successful incoming share before we allow the "Device
// nearby is sharing" notification to appear again.
constexpr base::TimeDelta kFastInitiationScannerCooldown = base::Seconds(8);

// The maximum number of certificate downloads that can be performed during a
// discovery session.
constexpr size_t kMaxCertificateDownloadsDuringDiscovery = 3u;
// The time between certificate downloads during a discovery session. The
// download is only attempted if there are discovered, contact-based
// advertisements that cannot decrypt any currently stored public certificates.
constexpr base::TimeDelta kCertificateDownloadDuringDiscoveryPeriod =
    base::Seconds(10);

// Used to hash a token into a 4 digit string.
constexpr int kHashModulo = 9973;
constexpr int kHashBaseMultiplier = 31;

// Length of the window during which we count the amount of times the nearby
// process stops unexpectedly.
constexpr base::TimeDelta kClearNearbyProcessUnexpectedShutdownCountDelay =
    base::Minutes(1);

// The length of window during which we display visibility reminder
// notification to users. The real length set for timer should be calculated
// by (180 - kNearbySharingVisibilityReminderLastShownTimePrefName set in
// nearby_share_prefs).
constexpr base::TimeDelta kNearbyVisibilityReminderTimerDelay = base::Days(180);

// Whether or not WifiLan is supported for advertising (mDNS). Support as
// a bandwidth upgrade medium is behind a feature flag. Currently unsupported.
constexpr bool kIsWifiLanAdvertisingSupported = false;

std::string ReceiveSurfaceStateToString(
    NearbySharingService::ReceiveSurfaceState state) {
  switch (state) {
    case NearbySharingService::ReceiveSurfaceState::kForeground:
      return "FOREGROUND";
    case NearbySharingService::ReceiveSurfaceState::kBackground:
      return "BACKGROUND";
    case NearbySharingService::ReceiveSurfaceState::kUnknown:
      return "UNKNOWN";
  }
}

std::string SendSurfaceStateToString(
    NearbySharingService::SendSurfaceState state) {
  switch (state) {
    case NearbySharingService::SendSurfaceState::kForeground:
      return "FOREGROUND";
    case NearbySharingService::SendSurfaceState::kBackground:
      return "BACKGROUND";
    case NearbySharingService::SendSurfaceState::kUnknown:
      return "UNKNOWN";
  }
}

std::string PowerLevelToString(NearbyConnectionsManager::PowerLevel level) {
  switch (level) {
    case NearbyConnectionsManager::PowerLevel::kLowPower:
      return "LOW_POWER";
    case NearbyConnectionsManager::PowerLevel::kMediumPower:
      return "MEDIUM_POWER";
    case NearbyConnectionsManager::PowerLevel::kHighPower:
      return "HIGH_POWER";
    case NearbyConnectionsManager::PowerLevel::kUnknown:
      return "UNKNOWN";
  }
}

std::optional<std::vector<uint8_t>> GetBluetoothMacAddressFromCertificate(
    const NearbyShareDecryptedPublicCertificate& certificate) {
  if (!certificate.unencrypted_metadata().has_bluetooth_mac_address()) {
    RecordNearbyShareError(
        NearbyShareError::kPublicCertificateHasNoBluetoothMacAddress);
    CD_LOG(WARNING, Feature::NS)
        << __func__ << ": Public certificate "
        << base::HexEncode(certificate.id()) << " did not contain "
        << "a Bluetooth mac address.";
    return std::nullopt;
  }

  std::string mac_address =
      certificate.unencrypted_metadata().bluetooth_mac_address();
  if (mac_address.size() != 6) {
    RecordNearbyShareError(
        NearbyShareError::kPublicCertificateHasInvalidBluetoothMacAddress);
    CD_LOG(ERROR, Feature::NS)
        << __func__ << ": Invalid bluetooth mac address: '" << mac_address
        << "'";
    return std::nullopt;
  }

  return std::vector<uint8_t>(mac_address.begin(), mac_address.end());
}

std::optional<std::string> GetDeviceName(
    const sharing::mojom::AdvertisementPtr& advertisement,
    const std::optional<NearbyShareDecryptedPublicCertificate>& certificate) {
  DCHECK(advertisement);

  // Device name is always included when visible to everyone.
  if (advertisement->device_name) {
    return *(advertisement->device_name);
  }

  // For contacts only advertisements, we can't do anything without the
  // certificate.
  if (!certificate || !certificate->unencrypted_metadata().has_device_name()) {
    return std::nullopt;
  }

  return certificate->unencrypted_metadata().device_name();
}

// Return the most stable device identifier with the following priority:
//   1. Hash of Bluetooth MAC address.
//   2. Certificate ID.
//   3. Endpoint ID.
std::string GetDeviceId(
    const std::string& endpoint_id,
    const std::optional<NearbyShareDecryptedPublicCertificate>& certificate) {
  if (!certificate) {
    return endpoint_id;
  }

  std::optional<std::vector<uint8_t>> mac_address =
      GetBluetoothMacAddressFromCertificate(*certificate);
  if (mac_address) {
    return base::NumberToString(base::FastHash(base::span(*mac_address)));
  }

  if (!certificate->id().empty()) {
    return std::string(certificate->id().begin(), certificate->id().end());
  }

  return endpoint_id;
}

std::optional<std::string> ToFourDigitString(
    const std::optional<std::vector<uint8_t>>& bytes) {
  if (!bytes) {
    return std::nullopt;
  }

  int hash = 0;
  int multiplier = 1;
  for (uint8_t byte : *bytes) {
    // Java bytes are signed two's complement so cast to use the correct sign.
    hash = (hash + static_cast<int8_t>(byte) * multiplier) % kHashModulo;
    multiplier = (multiplier * kHashBaseMultiplier) % kHashModulo;
  }

  return base::StringPrintf("%04d", std::abs(hash));
}

bool IsOutOfStorage(const base::FilePath& file_path,
                    int64_t storage_required,
                    std::optional<int64_t> free_disk_space_for_testing) {
  int64_t free_space = free_disk_space_for_testing.value_or(
      base::SysInfo::AmountOfFreeDiskSpace(file_path).value_or(-1));
  return free_space < storage_required;
}

int64_t GeneratePayloadId() {
  int64_t payload_id = 0;
  crypto::RandBytes(base::byte_span_from_ref(payload_id));
  return payload_id;
}

// Wraps a call to OnTransferUpdate() to filter any updates after receiving a
// final status.
class TransferUpdateDecorator : public TransferUpdateCallback {
 public:
  using Callback = base::RepeatingCallback<void(const ShareTarget&,
                                                const TransferMetadata&)>;

  explicit TransferUpdateDecorator(Callback callback)
      : callback_(std::move(callback)) {}
  TransferUpdateDecorator(const TransferUpdateDecorator&) = delete;
  TransferUpdateDecorator& operator=(const TransferUpdateDecorator&) = delete;
  ~TransferUpdateDecorator() override = default;

  void OnTransferUpdate(const ShareTarget& share_target,
                        const TransferMetadata& transfer_metadata) override {
    if (got_final_status_) {
      // If we already got a final status, we can ignore any subsequent final
      // statuses caused by race conditions.
      CD_LOG(VERBOSE, Feature::NS)
          << __func__ << ": Transfer update decorator swallowed "
          << "status update because a final status was already received: "
          << share_target.id << ": "
          << TransferMetadata::StatusToString(transfer_metadata.status());
      return;
    }
    got_final_status_ = transfer_metadata.is_final_status();
    callback_.Run(share_target, transfer_metadata);
  }

 private:
  bool got_final_status_ = false;
  Callback callback_;
};

bool isVisibleForAdvertising(nearby_share::mojom::Visibility visibility) {
  return visibility == nearby_share::mojom::Visibility::kAllContacts ||
         visibility == nearby_share::mojom::Visibility::kSelectedContacts ||
         visibility == nearby_share::mojom::Visibility::kYourDevices;
}

NearbyShareEncryptedMetadataKey AdvertisementToKey(
    const sharing::mojom::AdvertisementPtr& advertisement) {
  return NearbyShareEncryptedMetadataKey(
      base::span<const uint8_t, kNearbyShareNumBytesMetadataEncryptionKeySalt>(
          advertisement->salt),
      base::span<const uint8_t, kNearbyShareNumBytesMetadataEncryptionKey>(
          advertisement->encrypted_metadata_key));
}

}  // namespace

NearbySharingServiceImpl::NearbySharingServiceImpl(
    user_manager::User& user,
    Profile* profile,
    NotificationDisplayService* notification_display_service,
    std::unique_ptr<NearbyConnectionsManager> nearby_connections_manager,
    ash::nearby::NearbyProcessManager* process_manager,
    std::unique_ptr<PowerClient> power_client,
    std::unique_ptr<WifiNetworkConfigurationHandler> wifi_network_handler)
    : profile_(profile),
      prefs_(profile_->GetPrefs()),
      nearby_connections_manager_(std::move(nearby_connections_manager)),
      process_manager_(process_manager),
      power_client_(std::move(power_client)),
      wifi_network_handler_(std::move(wifi_network_handler)),
      http_client_factory_(std::make_unique<NearbyShareClientFactoryImpl>(
          IdentityManagerFactory::GetForProfile(profile),
          profile->GetURLLoaderFactory(),
          &nearby_share_http_notifier_)),
      local_device_data_manager_(
          NearbyShareLocalDeviceDataManagerImpl::Factory::Create(
              user,
              http_client_factory_.get())),
      contact_manager_(NearbyShareContactManagerImpl::Factory::Create(
          profile_->GetProfileUserName(),
          prefs_,
          http_client_factory_.get(),
          local_device_data_manager_.get())),
      certificate_manager_(NearbyShareCertificateManagerImpl::Factory::Create(
          profile_->GetProfileUserName(),
          profile->GetPath(),
          prefs_,
          local_device_data_manager_.get(),
          contact_manager_.get(),
          profile->GetDefaultStoragePartition()->GetProtoDatabaseProvider(),
          http_client_factory_.get())),
      transfer_profiler_(std::make_unique<NearbyShareTransferProfiler>()),
      logger_(std::make_unique<NearbyShareLogger>()),
      settings_(prefs_, local_device_data_manager_.get()),
      feature_usage_metrics_(prefs_),
      on_network_changed_delay_timer_(
          FROM_HERE,
          kProcessNetworkChangeTimerDelay,
          base::BindRepeating(&NearbySharingServiceImpl::
                                  StopAdvertisingAndInvalidateSurfaceState,
                              base::Unretained(this))),
      visibility_reminder_timer_delay_(kNearbyVisibilityReminderTimerDelay),
      discovery_metric_logger_(
          std::make_unique<nearby::share::metrics::DiscoveryMetricLogger>()),
      throughput_metric_logger_(
          std::make_unique<nearby::share::metrics::ThroughputMetricLogger>()),
      attachment_metric_logger_(
          std::make_unique<nearby::share::metrics::AttachmentMetricLogger>()),
      neaby_share_metric_logger_(
          std::make_unique<nearby::share::metrics::NearbyShareMetricLogger>()) {
  DCHECK(profile_);
  DCHECK(nearby_connections_manager_);
  DCHECK(power_client_);

  nearby_connections_manager_->RegisterBandwidthUpgradeListener(
      weak_ptr_factory_.GetWeakPtr());

  fast_initiation_scanning_metrics_ =
      std::make_unique<FastInitiationScannerFeatureUsageMetrics>(prefs_);

  RecordNearbyShareEnabledMetric(GetNearbyShareEnabledState(prefs_));

  auto* session_controller = ash::SessionController::Get();
  if (session_controller) {
    is_screen_locked_ = session_controller->IsScreenLocked();
    session_controller->AddObserver(this);
  }

  power_client_->AddObserver(this);
  certificate_manager_->AddObserver(this);

  settings_.AddSettingsObserver(settings_receiver_.BindNewPipeAndPassRemote());

  // Register logging observers.
  AddObserver(logger_.get());
  AddObserver(discovery_metric_logger_.get());
  AddObserver(throughput_metric_logger_.get());
  AddObserver(attachment_metric_logger_.get());
  AddObserver(neaby_share_metric_logger_.get());

  GetBluetoothAdapter();

  nearby_notification_manager_ = std::make_unique<NearbyNotificationManager>(
      notification_display_service, this, prefs_, profile_);

  net::NetworkChangeNotifier::AddNetworkChangeObserver(this);

  if (settings_.GetEnabled()) {
    local_device_data_manager_->Start();
    contact_manager_->Start();
    certificate_manager_->Start();
    BindToNearbyProcess();
  }
  UpdateVisibilityReminderTimer(/*reset_timestamp=*/false);
  user_visibility_ = settings_.GetVisibility();
}

NearbySharingServiceImpl::~NearbySharingServiceImpl() {
  // Make sure the service has been shut down properly before.
  DCHECK(!nearby_notification_manager_);

  if (bluetooth_adapter_) {
    DCHECK(!bluetooth_adapter_->HasObserver(this));
  }

  // Unregister observers.
  RemoveObserver(logger_.get());
  RemoveObserver(discovery_metric_logger_.get());
  RemoveObserver(throughput_metric_logger_.get());
  RemoveObserver(attachment_metric_logger_.get());
  RemoveObserver(neaby_share_metric_logger_.get());
}

void NearbySharingServiceImpl::Shutdown() {
  // Before we clean up, lets give observers a heads up we are shutting down.
  for (auto& observer : observers_) {
    observer.OnShutdown();
  }
  observers_.Clear();

  StopAdvertising();
  StopFastInitiationScanning();
  StopFastInitiationAdvertising();
  StopScanning();
  nearby_connections_manager_->Shutdown();

  // Destroy NearbyNotificationManager as its profile has been shut down.
  nearby_notification_manager_.reset();

  // On shutdown, we want to do all the same clean up as happens when
  // the nearby process stops.
  CleanupAfterNearbyProcessStopped();

  power_client_->RemoveObserver(this);
  certificate_manager_->RemoveObserver(this);

  // TODO(crbug/1147652): The call to update the advertising interval is
  // removed to prevent a Bluez crash. We need to either reduce the global
  // advertising interval asynchronously and wait for the result or use the
  // updated API referenced in the bug which allows setting a per-advertisement
  // interval.

  if (bluetooth_adapter_) {
    bluetooth_adapter_->RemoveObserver(this);
    bluetooth_adapter_.reset();
  }

  auto* session_controller = ash::SessionController::Get();
  if (session_controller) {
    session_controller->RemoveObserver(this);
  }

  foreground_receive_callbacks_.Clear();
  background_receive_callbacks_.Clear();

  settings_receiver_.reset();

  if (settings_.GetEnabled()) {
    local_device_data_manager_->Stop();
    contact_manager_->Stop();
    certificate_manager_->Stop();
  }

  // |profile_| has now been shut down so we shouldn't use it anymore.
  profile_ = nullptr;

  net::NetworkChangeNotifier::RemoveNetworkChangeObserver(this);
  on_network_changed_delay_timer_.Stop();
  fast_initiation_scanner_cooldown_timer_.Stop();
}

void NearbySharingServiceImpl::AddObserver(
    NearbySharingService::Observer* observer) {
  observers_.AddObserver(observer);
}

void NearbySharingServiceImpl::RemoveObserver(
    NearbySharingService::Observer* observer) {
  observers_.RemoveObserver(observer);
}

bool NearbySharingServiceImpl::HasObserver(
    NearbySharingService::Observer* observer) {
  return observers_.HasObserver(observer);
}

NearbySharingService::StatusCodes NearbySharingServiceImpl::RegisterSendSurface(
    TransferUpdateCallback* transfer_callback,
    ShareTargetDiscoveredCallback* discovery_callback,
    SendSurfaceState state) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(transfer_callback);
  DCHECK(discovery_callback);
  DCHECK_NE(state, SendSurfaceState::kUnknown);

  if (foreground_send_transfer_callbacks_.HasObserver(transfer_callback) ||
      background_send_transfer_callbacks_.HasObserver(transfer_callback)) {
    RecordNearbyShareError(
        NearbyShareError::kRegisterSendSurfaceAlreadyRegistered);
    CD_LOG(VERBOSE, Feature::NS)
        << __func__
        << ": RegisterSendSurface failed. Already registered for a "
           "different state.";
    return StatusCodes::kError;
  }

  if (state == SendSurfaceState::kForeground) {
    // Only check this error case for foreground senders
    if (!HasAvailableDiscoveryMediums()) {
      RecordNearbyShareError(
          NearbyShareError::kRegisterSendSurfaceNoAvailableConnectionMedium);
      CD_LOG(VERBOSE, Feature::NS)
          << __func__ << ": No available connection medium.";
      return StatusCodes::kNoAvailableConnectionMedium;
    }

    foreground_send_transfer_callbacks_.AddObserver(transfer_callback);
    foreground_send_discovery_callbacks_.AddObserver(discovery_callback);
  } else {
    background_send_transfer_callbacks_.AddObserver(transfer_callback);
    background_send_discovery_callbacks_.AddObserver(discovery_callback);
  }

  if (is_receiving_files_) {
    UnregisterSendSurface(transfer_callback, discovery_callback);
    CD_LOG(VERBOSE, Feature::NS)
        << __func__
        << ": Ignore registering (and unregistering if registered) send "
           "surface because we're currently receiving files.";
    return StatusCodes::kTransferAlreadyInProgress;
  }

  // If the share sheet to be registered is a foreground surface, let it catch
  // up with most recent transfer metadata immediately.
  if (state == SendSurfaceState::kForeground && last_outgoing_metadata_) {
    // When a new share sheet is registered, we want to immediately show the
    // in-progress bar.
    discovery_callback->OnShareTargetDiscovered(last_outgoing_metadata_->first);
    transfer_callback->OnTransferUpdate(last_outgoing_metadata_->first,
                                        last_outgoing_metadata_->second);
  }

  // Sync down data from Nearby server when the sending flow starts, making our
  // best effort to have fresh contact and certificate data. There is no need to
  // wait for these calls to finish. The periodic server requests will typically
  // be sufficient, but we don't want the user to be blocked for hours waiting
  // for a periodic sync.
  if (state == SendSurfaceState::kForeground && !last_outgoing_metadata_) {
    CD_LOG(VERBOSE, Feature::NS)
        << __func__
        << ": Downloading local device data, contacts, and certificates from "
        << "Nearby server at start of sending flow.";
    local_device_data_manager_->DownloadDeviceData();
    contact_manager_->DownloadContacts();
    certificate_manager_->DownloadPublicCertificates();
  }

  // Let newly registered send surface catch up with discovered share targets
  // from current scanning session.
  for (const std::pair<std::string, ShareTarget>& item :
       outgoing_share_target_map_) {
    discovery_callback->OnShareTargetDiscovered(item.second);
  }

  CD_LOG(VERBOSE, Feature::NS)
      << __func__ << ": A SendSurface has been registered for state: "
      << SendSurfaceStateToString(state);
  InvalidateSendSurfaceState();
  return StatusCodes::kOk;
}

NearbySharingService::StatusCodes
NearbySharingServiceImpl::UnregisterSendSurface(
    TransferUpdateCallback* transfer_callback,
    ShareTargetDiscoveredCallback* discovery_callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(transfer_callback);
  DCHECK(discovery_callback);
  if (!foreground_send_transfer_callbacks_.HasObserver(transfer_callback) &&
      !background_send_transfer_callbacks_.HasObserver(transfer_callback)) {
    RecordNearbyShareError(
        NearbyShareError::kUnregisterSendSurfaceUnknownTransferUpdateCallback);
    CD_LOG(VERBOSE, Feature::NS)
        << __func__
        << ": unregisterSendSurface failed. Unknown TransferUpdateCallback";
    return StatusCodes::kError;
  }

  if (!foreground_send_transfer_callbacks_.empty() && last_outgoing_metadata_ &&
      last_outgoing_metadata_->second.is_final_status()) {
    // We already saw the final status in the foreground
    // Nullify it so the next time the user opens sharing, it starts the UI from
    // the beginning
    last_outgoing_metadata_.reset();
  }

  SendSurfaceState state = SendSurfaceState::kUnknown;
  if (foreground_send_transfer_callbacks_.HasObserver(transfer_callback)) {
    foreground_send_transfer_callbacks_.RemoveObserver(transfer_callback);
    foreground_send_discovery_callbacks_.RemoveObserver(discovery_callback);
    state = SendSurfaceState::kForeground;
  } else {
    background_send_transfer_callbacks_.RemoveObserver(transfer_callback);
    background_send_discovery_callbacks_.RemoveObserver(discovery_callback);
    state = SendSurfaceState::kBackground;
  }

  // Displays the most recent payload status processed by foreground surfaces on
  // background surfaces.
  if (foreground_send_transfer_callbacks_.empty() && last_outgoing_metadata_) {
    for (TransferUpdateCallback& background_transfer_callback :
         background_send_transfer_callbacks_) {
      background_transfer_callback.OnTransferUpdate(
          last_outgoing_metadata_->first, last_outgoing_metadata_->second);
    }
  }

  CD_LOG(VERBOSE, Feature::NS)
      << __func__ << ": A SendSurface has been unregistered: "
      << SendSurfaceStateToString(state);
  InvalidateSurfaceState();
  return StatusCodes::kOk;
}

NearbySharingService::StatusCodes
NearbySharingServiceImpl::RegisterReceiveSurface(
    TransferUpdateCallback* transfer_callback,
    ReceiveSurfaceState state) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(transfer_callback);
  DCHECK_NE(state, ReceiveSurfaceState::kUnknown);

  // Only check these errors cases for foreground receivers.
  if (state == ReceiveSurfaceState::kForeground) {
    if (is_scanning_ || is_transferring_) {
      UnregisterReceiveSurface(transfer_callback);
      CD_LOG(VERBOSE, Feature::NS)
          << __func__
          << ": Ignore registering (and unregistering if registered) receive "
             "surface, because we're currently sending or receiving files.";
      return StatusCodes::kTransferAlreadyInProgress;
    }

    if (!HasAvailableAdvertisingMediums()) {
      RecordNearbyShareError(
          NearbyShareError::kRegisterReceiveSurfaceNoAvailableConnectionMedium);
      CD_LOG(VERBOSE, Feature::NS)
          << __func__ << ": No available connection medium.";
      return StatusCodes::kNoAvailableConnectionMedium;
    }
  }

  // We specifically allow re-registring with out error so it is clear to caller
  // that the transfer_callback is currently registered.
  if (GetReceiveCallbacksFromState(state).HasObserver(transfer_callback)) {
    CD_LOG(VERBOSE, Feature::NS)
        << __func__ << ": transfer callback already registered, ignoring";
    return StatusCodes::kOk;
  } else if (foreground_receive_callbacks_.HasObserver(transfer_callback) ||
             background_receive_callbacks_.HasObserver(transfer_callback)) {
    RecordNearbyShareError(
        NearbyShareError::
            kRegisterReceiveSurfaceTransferCallbackAlreadyRegisteredDifferentState);
    CD_LOG(ERROR, Feature::NS)
        << __func__
        << ":  transfer callback already registered but for a different state.";
    return StatusCodes::kError;
  }

  // If the receive surface to be registered is a foreground surface, let it
  // catch up with most recent transfer metadata immediately.
  if (state == ReceiveSurfaceState::kForeground && last_incoming_metadata_) {
    transfer_callback->OnTransferUpdate(last_incoming_metadata_->first,
                                        last_incoming_metadata_->second);
  }

  GetReceiveCallbacksFromState(state).AddObserver(transfer_callback);

  CD_LOG(VERBOSE, Feature::NS)
      << __func__ << ": A ReceiveSurface(" << ReceiveSurfaceStateToString(state)
      << ") has been registered";

  InvalidateReceiveSurfaceState();
  return StatusCodes::kOk;
}

NearbySharingService::StatusCodes
NearbySharingServiceImpl::UnregisterReceiveSurface(
    TransferUpdateCallback* transfer_callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(transfer_callback);
  bool is_foreground =
      foreground_receive_callbacks_.HasObserver(transfer_callback);
  bool is_background =
      background_receive_callbacks_.HasObserver(transfer_callback);
  if (!is_foreground && !is_background) {
    CD_LOG(VERBOSE, Feature::NS)
        << __func__
        << ": Unknown transfer callback was un-registered, ignoring.";
    // We intentionally allow this be successful so the caller can be sure
    // they are not registered anymore.
    return StatusCodes::kOk;
  }

  if (!foreground_receive_callbacks_.empty() && last_incoming_metadata_ &&
      last_incoming_metadata_->second.is_final_status()) {
    // We already saw the final status in the foreground.
    // Nullify it so the next time the user opens sharing, it starts the UI from
    // the beginning
    last_incoming_metadata_.reset();
  }

  if (is_foreground) {
    foreground_receive_callbacks_.RemoveObserver(transfer_callback);
  } else {
    background_receive_callbacks_.RemoveObserver(transfer_callback);
  }

  // Displays the most recent payload status processed by foreground surfaces on
  // background surface.
  if (foreground_receive_callbacks_.empty() && last_incoming_metadata_) {
    for (TransferUpdateCallback& background_callback :
         background_receive_callbacks_) {
      background_callback.OnTransferUpdate(last_incoming_metadata_->first,
                                           last_incoming_metadata_->second);
    }
  }

  CD_LOG(VERBOSE, Feature::NS) << __func__ << ": A ReceiveSurface("
                               << (is_foreground ? "foreground" : "background")
                               << ") has been unregistered";
  InvalidateSurfaceState();
  return StatusCodes::kOk;
}

NearbySharingService::StatusCodes
NearbySharingServiceImpl::ClearForegroundReceiveSurfaces() {
  std::vector<TransferUpdateCallback*> fg_receivers;
  for (auto& callback : foreground_receive_callbacks_) {
    fg_receivers.push_back(&callback);
  }

  StatusCodes status = StatusCodes::kOk;
  for (TransferUpdateCallback* callback : fg_receivers) {
    if (UnregisterReceiveSurface(callback) != StatusCodes::kOk) {
      status = StatusCodes::kError;
    }
  }
  return status;
}

bool NearbySharingServiceImpl::IsInHighVisibility() const {
  if (chromeos::features::IsQuickShareV2Enabled()) {
    return prefs_->GetBoolean(prefs::kNearbySharingInHighVisibilityPrefName);
  }

  return in_high_visibility_;
}

bool NearbySharingServiceImpl::IsTransferring() const {
  return is_transferring_;
}

bool NearbySharingServiceImpl::IsReceivingFile() const {
  return is_receiving_files_;
}

bool NearbySharingServiceImpl::IsSendingFile() const {
  return is_sending_files_;
}

bool NearbySharingServiceImpl::IsScanning() const {
  return is_scanning_;
}

bool NearbySharingServiceImpl::IsConnecting() const {
  return is_connecting_;
}

NearbySharingService::StatusCodes NearbySharingServiceImpl::SendAttachments(
    const ShareTarget& share_target,
    std::vector<std::unique_ptr<Attachment>> attachments) {
  if (!is_scanning_) {
    RecordNearbyShareError(NearbyShareError::kSendAttachmentsNotScanning);
    CD_LOG(WARNING, Feature::NS)
        << __func__ << ": Failed to send attachments. Not scanning.";
    return StatusCodes::kError;
  }

  // |is_scanning_| means at least one send transfer callback.
  DCHECK(!foreground_send_transfer_callbacks_.empty() ||
         !background_send_transfer_callbacks_.empty());
  // |is_scanning_| and |is_transferring_| are mutually exclusive.
  DCHECK(!is_transferring_);

  ShareTargetInfo* info = GetShareTargetInfo(share_target);
  if (!info || !info->endpoint_id()) {
    // TODO(crbug.com/1119276): Support scanning for unknown share targets.
    RecordNearbyShareError(
        NearbyShareError::kSendAttachmentsUnknownShareTarget);
    CD_LOG(WARNING, Feature::NS)
        << __func__ << ": Failed to send attachments. Unknown ShareTarget.";
    return StatusCodes::kError;
  }

  ShareTarget share_target_copy = share_target;
  for (std::unique_ptr<Attachment>& attachment : attachments) {
    DCHECK(attachment);
    attachment->MoveToShareTarget(share_target_copy);
  }

  if (!share_target_copy.has_attachments()) {
    RecordNearbyShareError(NearbyShareError::kSendAttachmentsNoAttachments);
    CD_LOG(WARNING, Feature::NS) << __func__ << ": No attachments to send.";
    return StatusCodes::kError;
  }

  // For sending advertisement from scanner, the request advertisement should
  // always be visible to everyone.
  std::optional<std::vector<uint8_t>> endpoint_info =
      CreateEndpointInfo(local_device_data_manager_->GetDeviceName());
  if (!endpoint_info) {
    RecordNearbyShareError(
        NearbyShareError::kSendAttachmentsCouldNotCreateLocalEndpointInfo);
    CD_LOG(WARNING, Feature::NS)
        << __func__ << ": Could not create local endpoint info.";
    return StatusCodes::kError;
  }

  info->set_transfer_update_callback(std::make_unique<TransferUpdateDecorator>(
      base::BindRepeating(&NearbySharingServiceImpl::OnOutgoingTransferUpdate,
                          weak_ptr_factory_.GetWeakPtr())));
  send_attachments_timestamp_ = base::TimeTicks::Now();
  OnTransferStarted(/*is_incoming=*/false);

  CHECK(info->endpoint_id().has_value());
  transfer_profiler_->OnShareTargetSelected(info->endpoint_id().value());
  for (auto& observer : observers_) {
    observer.OnShareTargetSelected(share_target);
  }

  is_connecting_ = true;
  InvalidateSendSurfaceState();

  // Send process initialized successfully, from now on status updated will be
  // sent out via OnOutgoingTransferUpdate().
  info->transfer_update_callback()->OnTransferUpdate(
      share_target_copy, TransferMetadataBuilder()
                             .set_status(TransferMetadata::Status::kConnecting)
                             .build());

  CreatePayloads(std::move(share_target_copy),
                 base::BindOnce(&NearbySharingServiceImpl::OnCreatePayloads,
                                weak_ptr_factory_.GetWeakPtr(),
                                std::move(*endpoint_info)));

  return StatusCodes::kOk;
}

void NearbySharingServiceImpl::Accept(
    const ShareTarget& share_target,
    StatusCodesCallback status_codes_callback) {
  ShareTargetInfo* info = GetShareTargetInfo(share_target);
  if (!info || !info->connection()) {
    RecordNearbyShareError(NearbyShareError::kAcceptUnknownShareTarget);
    CD_LOG(WARNING, Feature::NS)
        << __func__ << ": Accept invoked for unknown share target";
    std::move(status_codes_callback).Run(StatusCodes::kOutOfOrderApiCall);
    return;
  }

  std::optional<std::pair<ShareTarget, TransferMetadata>> metadata =
      share_target.is_incoming ? last_incoming_metadata_
                               : last_outgoing_metadata_;
  if (!metadata || metadata->second.status() !=
                       TransferMetadata::Status::kAwaitingLocalConfirmation) {
    RecordNearbyShareError(
        NearbyShareError::kAcceptNotAwaitingLocalConfirmation);
    std::move(status_codes_callback).Run(StatusCodes::kOutOfOrderApiCall);
    return;
  }

  is_waiting_to_record_accept_to_transfer_start_metric_ =
      share_target.is_incoming;

  for (auto& observer : observers_) {
    observer.OnTransferAccepted(share_target);
  }

  // This should probably always evaluate to true, since a sender will
  // never accept a transfer.
  DCHECK(share_target.is_incoming);
  if (share_target.is_incoming) {
    incoming_share_accepted_timestamp_ = base::TimeTicks::Now();

    CHECK(info->endpoint_id().has_value());
    transfer_profiler_->OnTransferAccepted(info->endpoint_id().value());

    ReceivePayloads(share_target, std::move(status_codes_callback));
    return;
  }

  std::move(status_codes_callback).Run(SendPayloads(share_target));
}

void NearbySharingServiceImpl::Reject(
    const ShareTarget& share_target,
    StatusCodesCallback status_codes_callback) {
  ShareTargetInfo* info = GetShareTargetInfo(share_target);
  if (!info || !info->connection()) {
    RecordNearbyShareError(NearbyShareError::kRejectUnknownShareTarget);
    CD_LOG(WARNING, Feature::NS)
        << __func__ << ": Reject invoked for unknown share target";
    std::move(status_codes_callback).Run(StatusCodes::kOutOfOrderApiCall);
    return;
  }
  NearbyConnection* connection = info->connection();

  base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
      FROM_HERE,
      base::BindOnce(&NearbySharingServiceImpl::CloseConnection,
                     weak_ptr_factory_.GetWeakPtr(), share_target),
      kIncomingRejectionDelay);

  connection->SetDisconnectionListener(
      base::BindOnce(&NearbySharingServiceImpl::UnregisterShareTarget,
                     weak_ptr_factory_.GetWeakPtr(), share_target));

  WriteResponse(*connection, sharing::nearby::ConnectionResponseFrame::REJECT);
  CD_LOG(VERBOSE, Feature::NS)
      << __func__ << ": Successfully wrote a rejection response frame";

  if (info->transfer_update_callback()) {
    info->transfer_update_callback()->OnTransferUpdate(
        share_target, TransferMetadataBuilder()
                          .set_status(TransferMetadata::Status::kRejected)
                          .build());
  }

  std::move(status_codes_callback).Run(StatusCodes::kOk);
}

void NearbySharingServiceImpl::Cancel(
    const ShareTarget& share_target,
    StatusCodesCallback status_codes_callback) {
  CD_LOG(INFO, Feature::NS) << __func__ << ": User cancelled transfer";
  locally_cancelled_share_target_ids_.insert(share_target.id);
  DoCancel(share_target, std::move(status_codes_callback),
           /*is_initiator_of_cancellation=*/true);
}

void NearbySharingServiceImpl::DoCancel(
    ShareTarget share_target,
    StatusCodesCallback status_codes_callback,
    bool is_initiator_of_cancellation) {
  ShareTargetInfo* info = GetShareTargetInfo(share_target);
  if (!info || !info->endpoint_id()) {
    RecordNearbyShareError(NearbyShareError::kCancelUnknownShareTarget);
    CD_LOG(ERROR, Feature::NS)
        << __func__
        << ": Cancel invoked for unknown share target, returning "
           "kOutOfOrderApiCall";
    // Make sure to clean up files just in case.
    RemoveIncomingPayloads(share_target);
    std::move(status_codes_callback).Run(StatusCodes::kOutOfOrderApiCall);
    return;
  }

  // For metrics.
  all_cancelled_share_target_ids_.insert(share_target.id);

  // Cancel all ongoing payload transfers before invoking the transfer update
  // callback. Invoking the transfer update callback first could result in
  // payload cleanup before we have a chance to cancel the payload via Nearby
  // Connections, and the payload tracker might not receive the expected
  // cancellation signals. Also, note that there might not be any ongoing
  // payload transfer, for example, if a connection has not been established
  // yet.
  for (int64_t attachment_id : share_target.GetAttachmentIds()) {
    std::optional<int64_t> payload_id = GetAttachmentPayloadId(attachment_id);
    if (payload_id) {
      nearby_connections_manager_->Cancel(*payload_id);
    }
  }

  // Inform the user that the transfer has been cancelled before disconnecting
  // because subsequent disconnections might be interpreted as failure. The
  // TransferUpdateDecorator will ignore subsequent statuses in favor of this
  // cancelled status. Note that the transfer update callback might have already
  // been invoked as a result of the payload cancellations above, but again,
  // superfluous status updates are handled gracefully by the
  // TransferUpdateDecorator.
  if (info->transfer_update_callback()) {
    info->transfer_update_callback()->OnTransferUpdate(
        share_target, TransferMetadataBuilder()
                          .set_status(TransferMetadata::Status::kCancelled)
                          .build());
  }

  // If a connection exists, close the connection. Note: The initiator of a
  // cancellation waits for a short delay before closing the connection,
  // allowing for final processing by the other device. Otherwise, disconnect
  // from endpoint id directly. Note: A share attempt can be cancelled by the
  // user before a connection is fully established, in which case,
  // info->connection() will be null.
  if (info->connection()) {
    if (is_initiator_of_cancellation) {
      info->connection()->SetDisconnectionListener(
          base::BindOnce(&NearbySharingServiceImpl::UnregisterShareTarget,
                         weak_ptr_factory_.GetWeakPtr(), share_target));
      base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
          FROM_HERE,
          base::BindOnce(&NearbySharingServiceImpl::CloseConnection,
                         weak_ptr_factory_.GetWeakPtr(), share_target),
          kInitiatorCancelDelay);
      WriteCancel(*info->connection());
    } else {
      info->connection()->Close();
    }
  } else {
    nearby_connections_manager_->Disconnect(*info->endpoint_id());
    UnregisterShareTarget(share_target);
  }

  std::move(status_codes_callback).Run(StatusCodes::kOk);
}

bool NearbySharingServiceImpl::DidLocalUserCancelTransfer(
    const ShareTarget& share_target) {
  return locally_cancelled_share_target_ids_.contains(share_target.id);
}

void NearbySharingServiceImpl::Open(const ShareTarget& share_target,
                                    StatusCodesCallback status_codes_callback) {
  std::move(status_codes_callback).Run(StatusCodes::kOk);
}

void NearbySharingServiceImpl::OpenURL(GURL url) {
  DCHECK(profile_);
  ash::NewWindowDelegate::GetInstance()->OpenUrl(
      url, ash::NewWindowDelegate::OpenUrlFrom::kUserInteraction,
      ash::NewWindowDelegate::Disposition::kNewForegroundTab);
}

void NearbySharingServiceImpl::SetArcTransferCleanupCallback(
    base::OnceCallback<void()> callback) {
  // In the case where multiple Nearby Share sessions are started, successive
  // Nearby Share bubbles shown will prevent the user from sharing while the
  // initial bubble is still active. For the successive bubble(s), we want to
  // make sure only the original cleanup callback is valid.
  // Also in the following case:
  // 1. CrOS starts a receive transfer.
  // 2. ARC starts a send transfer and |arc_transfer_cleanup_callback_| is set
  //    erroneously if |is_transferring_| check is missing.
  // As multiple transfers cannot occur at the same time, a "Can't Share" error
  // will occur. When the transfer in [1] finishes and another ARC Nearby Share
  // session starts, the |arc_transfer_cleanup_callback_| can't be set if a
  // value is already set to ensure all clean up is performed. Hence, check if
  // not |is_transferring_| before setting |arc_transfer_cleanup_callback_|.
  if (!is_transferring_ && arc_transfer_cleanup_callback_.is_null()) {
    arc_transfer_cleanup_callback_ = std::move(callback);
  }
}

NearbyNotificationDelegate* NearbySharingServiceImpl::GetNotificationDelegate(
    const std::string& notification_id) {
  if (!nearby_notification_manager_) {
    return nullptr;
  }

  return nearby_notification_manager_->GetNotificationDelegate(notification_id);
}

void NearbySharingServiceImpl::RecordFastInitiationNotificationUsage(
    bool success) {
  fast_initiation_scanning_metrics_->RecordUsage(success);
}

NearbyShareSettings* NearbySharingServiceImpl::GetSettings() {
  return &settings_;
}

NearbyShareHttpNotifier* NearbySharingServiceImpl::GetHttpNotifier() {
  return &nearby_share_http_notifier_;
}

NearbyShareLocalDeviceDataManager*
NearbySharingServiceImpl::GetLocalDeviceDataManager() {
  return local_device_data_manager_.get();
}

NearbyShareContactManager* NearbySharingServiceImpl::GetContactManager() {
  return contact_manager_.get();
}

NearbyShareCertificateManager*
NearbySharingServiceImpl::GetCertificateManager() {
  return certificate_manager_.get();
}

NearbyNotificationManager* NearbySharingServiceImpl::GetNotificationManager() {
  return nearby_notification_manager_.get();
}

void NearbySharingServiceImpl::OnNearbyProcessStopped(
    NearbyProcessShutdownReason shutdown_reason) {
  DCHECK(process_reference_);
  CD_LOG(INFO, Feature::NS)
      << __func__ << ": Shutdown reason: " << shutdown_reason;
  CleanupAfterNearbyProcessStopped();
  ClearForegroundReceiveSurfaces();
  RestartNearbyProcessIfAppropriate(shutdown_reason);
  InvalidateSurfaceState();
  for (auto& observer : observers_) {
    observer.OnNearbyProcessStopped();
  }
}

void NearbySharingServiceImpl::CleanupAfterNearbyProcessStopped() {
  if (process_reference_) {
    process_reference_.reset();
  }

  SetInHighVisibility(false);

  endpoint_discovery_weak_ptr_factory_.InvalidateWeakPtrs();
  endpoint_discovery_events_ = base::queue<base::OnceClosure>();

  ClearOutgoingShareTargetInfoMap();
  incoming_share_target_info_map_.clear();
  discovered_advertisements_to_retry_map_.clear();

  foreground_send_transfer_callbacks_.Clear();
  background_send_transfer_callbacks_.Clear();
  foreground_send_discovery_callbacks_.Clear();
  background_send_discovery_callbacks_.Clear();

  last_incoming_metadata_.reset();
  last_outgoing_metadata_.reset();
  attachment_info_map_.clear();
  locally_cancelled_share_target_ids_.clear();

  mutual_acceptance_timeout_alarm_.Cancel();
  disconnection_timeout_alarms_.clear();

  is_scanning_ = false;
  is_transferring_ = false;
  is_receiving_files_ = false;
  is_sending_files_ = false;
  is_connecting_ = false;
  advertising_power_level_ = NearbyConnectionsManager::PowerLevel::kUnknown;

  process_shutdown_pending_timer_.Stop();
  certificate_download_during_discovery_timer_.Stop();
  rotate_background_advertisement_timer_.Stop();

  if (arc_transfer_cleanup_callback_) {
    // Cleanup send transfer resources where the user started ARC Nearby Share
    // but did not complete (i.e. cancel, abort, utility process stopped, etc.)
    // prior to shutdown.
    std::move(arc_transfer_cleanup_callback_).Run();
  }
}

void NearbySharingServiceImpl::RestartNearbyProcessIfAppropriate(
    NearbyProcessShutdownReason shutdown_reason) {
  if (!ShouldRestartNearbyProcess(shutdown_reason)) {
    return;
  }

  CD_LOG(INFO, Feature::NS)
      << __func__ << ": Attempting to restart nearby process after shutdown: "
      << shutdown_reason;

  BindToNearbyProcess();

  // Track the number of process shutdowns that occur in a fixed time window.
  recent_nearby_process_unexpected_shutdown_count_++;
  if (!clear_recent_nearby_process_shutdown_count_timer_.IsRunning()) {
    clear_recent_nearby_process_shutdown_count_timer_.Start(
        FROM_HERE, kClearNearbyProcessUnexpectedShutdownCountDelay,
        base::BindOnce(&NearbySharingServiceImpl::
                           ClearRecentNearbyProcessUnexpectedShutdownCount,
                       weak_ptr_factory_.GetWeakPtr()));
  }
}

bool NearbySharingServiceImpl::ShouldRestartNearbyProcess(
    NearbyProcessShutdownReason shutdown_reason) {
  // Ensure Nearby Share is still enabled.
  if (!settings_.GetEnabled()) {
    CD_LOG(INFO, Feature::NS)
        << __func__
        << ": Choosing to not restart process because Nearby Share is "
           "disabled.";
    return false;
  }

  // Check if the current shutdown reason is one which we want to restart after.
  switch (shutdown_reason) {
    case NearbyProcessShutdownReason::kNormal:
      return false;
    case NearbyProcessShutdownReason::kCrash:
    case NearbyProcessShutdownReason::kConnectionsMojoPipeDisconnection:
    case NearbyProcessShutdownReason::kPresenceMojoPipeDisconnection:
    case NearbyProcessShutdownReason::kDecoderMojoPipeDisconnection:
      break;
  }

  // Check if the process shutdown count is above the allowed threshold.
  if (recent_nearby_process_unexpected_shutdown_count_ >
      NearbySharingServiceImpl::
          kMaxRecentNearbyProcessUnexpectedShutdownCount) {
    RecordNearbyShareError(NearbyShareError::kMaxNearbyProcessRestart);
    CD_LOG(WARNING, Feature::NS)
        << __func__
        << ": Choosing to not restart process because the recent stop "
           "count has exceeded the threshold.";
    return false;
  }

  return true;
}

void NearbySharingServiceImpl::
    ClearRecentNearbyProcessUnexpectedShutdownCount() {
  recent_nearby_process_unexpected_shutdown_count_ = 0;
}

void NearbySharingServiceImpl::BindToNearbyProcess() {
  if (process_reference_ || !settings_.GetEnabled()) {
    RecordNearbyShareError(
        NearbyShareError::kBindToNearbyProcessReferenceExistsOrDisabled);
    return;
  }

  process_reference_ = process_manager_->GetNearbyProcessReference(
      base::BindOnce(&NearbySharingServiceImpl::OnNearbyProcessStopped,
                     base::Unretained(this)));

  if (!process_reference_) {
    RecordNearbyShareError(
        NearbyShareError::kBindToNearbyProcessFailedToGetReference);
    CD_LOG(WARNING, Feature::NS)
        << __func__ << ": Failed to get a reference to the nearby process.";
  }
}

sharing::mojom::NearbySharingDecoder*
NearbySharingServiceImpl::GetNearbySharingDecoder() {
  BindToNearbyProcess();

  if (!process_reference_) {
    return nullptr;
  }

  sharing::mojom::NearbySharingDecoder* decoder =
      process_reference_->GetNearbySharingDecoder().get();

  if (!decoder) {
    CD_LOG(WARNING, Feature::NS)
        << __func__ << ": Failed to get decoder from process reference.";
  }

  return decoder;
}

void NearbySharingServiceImpl::OnIncomingConnectionAccepted(
    const std::string& endpoint_id,
    const std::vector<uint8_t>& endpoint_info,
    NearbyConnection* connection) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(connection);
  DCHECK(process_reference_);

  sharing::mojom::NearbySharingDecoder* decoder = GetNearbySharingDecoder();
  if (!decoder) {
    RecordNearbyShareError(
        NearbyShareError::kOnIncomingConnectionAcceptedFailedToGetDecoder);
    return;
  }

  // Sync down data from Nearby server when the receiving flow starts, making
  // our best effort to have fresh contact and certificate data. There is no
  // need to wait for these calls to finish. The periodic server requests will
  // typically be sufficient, but we don't want the user to be blocked for hours
  // waiting for a periodic sync.
  CD_LOG(VERBOSE, Feature::NS)
      << __func__
      << ": Downloading local device data, contacts, and certificates from "
      << "Nearby server at start of receiving flow.";
  local_device_data_manager_->DownloadDeviceData();
  contact_manager_->DownloadContacts();
  certificate_manager_->DownloadPublicCertificates();

  ShareTarget placeholder_share_target;
  placeholder_share_target.is_incoming = true;
  ShareTargetInfo& share_target_info =
      GetOrCreateShareTargetInfo(placeholder_share_target, endpoint_id);
  share_target_info.set_connection(connection);

  connection->SetDisconnectionListener(
      base::BindOnce(&NearbySharingServiceImpl::RefreshUIOnDisconnection,
                     weak_ptr_factory_.GetWeakPtr(), placeholder_share_target));

  decoder->DecodeAdvertisement(
      endpoint_info,
      base::BindOnce(&NearbySharingServiceImpl::OnIncomingAdvertisementDecoded,
                     weak_ptr_factory_.GetWeakPtr(), endpoint_id,
                     std::move(placeholder_share_target)));
}

void NearbySharingServiceImpl::OnNetworkChanged(
    net::NetworkChangeNotifier::ConnectionType type) {
  CD_LOG(VERBOSE, Feature::NS) << __func__ << ": ConnectionType = " << type;
  on_network_changed_delay_timer_.Reset();
}

void NearbySharingServiceImpl::FlushMojoForTesting() {
  settings_receiver_.FlushForTesting();
}

void NearbySharingServiceImpl::OnEnabledChanged(bool enabled) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  RecordNearbyShareEnabledMetric(GetNearbyShareEnabledState(prefs_));

  if (settings_.IsOnboardingComplete()) {
    base::UmaHistogramBoolean("Nearby.Share.EnabledStateChanged", enabled);
  }

  if (enabled) {
    CD_LOG(VERBOSE, Feature::NS) << __func__ << ": Nearby sharing enabled!";
    local_device_data_manager_->Start();
    contact_manager_->Start();
    certificate_manager_->Start();
    BindToNearbyProcess();
  } else {
    CD_LOG(VERBOSE, Feature::NS) << __func__ << ": Nearby sharing disabled!";
    StopAdvertising();
    StopScanning();
    nearby_connections_manager_->Shutdown();
    local_device_data_manager_->Stop();
    contact_manager_->Stop();
    certificate_manager_->Stop();
    process_reference_.reset();
  }

  UpdateVisibilityReminderTimer(/*reset_timestamp=*/false);
  InvalidateSurfaceState();
}

void NearbySharingServiceImpl::OnFastInitiationNotificationStateChanged(
    nearby_share::mojom::FastInitiationNotificationState state) {
  CD_LOG(VERBOSE, Feature::NS)
      << __func__ << ": Fast Initiation Notification state: " << state;
  // Runs through a series of checks to determine if background scanning should
  // be started or stopped.
  InvalidateReceiveSurfaceState();
}

void NearbySharingServiceImpl::OnDeviceNameChanged(
    const std::string& device_name) {
  CD_LOG(INFO, Feature::NS)
      << __func__ << ": Nearby sharing device name changed";
  // TODO(vecore): handle device name change
}

void NearbySharingServiceImpl::OnDataUsageChanged(
    nearby_share::mojom::DataUsage data_usage) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  CD_LOG(INFO, Feature::NS)
      << __func__ << ": Nearby sharing data usage changed to " << data_usage;
  StopAdvertisingAndInvalidateSurfaceState();
}

void NearbySharingServiceImpl::OnVisibilityChanged(
    nearby_share::mojom::Visibility new_visibility) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  CD_LOG(INFO, Feature::NS)
      << __func__ << ": Nearby sharing visibility changed to "
      << new_visibility;

  UpdateVisibilityReminderTimer(/*reset_timestamp=*/true);

  StopAdvertisingAndInvalidateSurfaceState();
}

void NearbySharingServiceImpl::OnAllowedContactsChanged(
    const std::vector<std::string>& allowed_contacts) {
  CD_LOG(INFO, Feature::NS)
      << __func__ << ": Nearby sharing visible contacts changed";
  // TODO(vecore): handle visible contacts change
}

void NearbySharingServiceImpl::OnPublicCertificatesDownloaded() {
  if (!is_scanning_ || discovered_advertisements_to_retry_map_.empty()) {
    return;
  }

  CD_LOG(INFO, Feature::NS)
      << __func__ << ": Public certificates downloaded while scanning. "
      << "Retrying decryption with "
      << discovered_advertisements_to_retry_map_.size()
      << " previously discovered advertisements.";
  const auto map_copy = discovered_advertisements_to_retry_map_;
  discovered_advertisements_to_retry_map_.clear();
  for (const auto& id_info_pair : map_copy) {
    OnEndpointDiscovered(id_info_pair.first, id_info_pair.second);
  }
}

void NearbySharingServiceImpl::OnPrivateCertificatesChanged() {
  // If we are currently advertising, restart advertising using the updated
  // private certificates.
  if (rotate_background_advertisement_timer_.IsRunning()) {
    CD_LOG(VERBOSE, Feature::NS)
        << __func__
        << ": Private certificates changed; rotating background advertisement.";
    rotate_background_advertisement_timer_.FireNow();
  }
}

void NearbySharingServiceImpl::OnEndpointDiscovered(
    const std::string& endpoint_id,
    const std::vector<uint8_t>& endpoint_info) {
  AddEndpointDiscoveryEvent(
      base::BindOnce(&NearbySharingServiceImpl::HandleEndpointDiscovered,
                     base::Unretained(this), endpoint_id, endpoint_info));
}

void NearbySharingServiceImpl::OnEndpointLost(const std::string& endpoint_id) {
  AddEndpointDiscoveryEvent(
      base::BindOnce(&NearbySharingServiceImpl::HandleEndpointLost,
                     base::Unretained(this), endpoint_id));
}

void NearbySharingServiceImpl::OnInitialMedium(const std::string& endpoint_id,
                                               const Medium medium) {
  // Our |share_target_map_| is populated in CreateShareTarget. This
  // is deterministically called *before* this method when sending,
  // and *after* this method when receiving. In other words, we can
  // expect to *not* record the initial medium when receiving.
  // We determined this acceptable as the initial medium when receiving
  // will always be Bluetooth, until other mediums are supported (Wifi LAN
  // can be an initial medium when sending due to mDNS discovery.)
  if (!share_target_map_.contains(endpoint_id)) {
    return;
  }
  RecordNearbyShareInitialConnectionMedium(medium);
  auto share_target = share_target_map_[endpoint_id];
  for (auto& observer : observers_) {
    observer.OnInitialMedium(share_target, medium);
  }
}

void NearbySharingServiceImpl::OnBandwidthUpgrade(
    const std::string& endpoint_id,
    const Medium medium) {
  transfer_profiler_->OnBandwidthUpgrade(endpoint_id, medium);

  // This gets triggered when connecting as a receiver.
  CHECK(share_target_map_.contains(endpoint_id));
  auto share_target = share_target_map_[endpoint_id];
  for (auto& observer : observers_) {
    observer.OnBandwidthUpgrade(share_target, medium);
  }
}

void NearbySharingServiceImpl::OnBandwidthUpgradeV3(
    nearby::presence::PresenceDevice remote_device,
    const Medium medium) {
  // Because `NearbySharingServiceImpl` is currently only consuming V1 APIs from
  // `NearbyConnections`, this function is to be left as `NOTIMPLEMENTED()` as
  // only Nearby Presence is using V3 APIs.
  NOTIMPLEMENTED();
}

void NearbySharingServiceImpl::OnLockStateChanged(bool locked) {
  CD_LOG(VERBOSE, Feature::NS)
      << __func__ << ": Screen lock state changed. (" << locked << ")";
  is_screen_locked_ = locked;

  // Set visibility to 'Your Devices' if the screen is locked and visibility is
  // not Hidden.
  nearby_share::mojom::Visibility current_visibility =
      settings_.GetVisibility();
  if (current_visibility != nearby_share::mojom::Visibility::kNoOne) {
    if (locked) {
      // Store old visibility setting.
      user_visibility_ = current_visibility;
      user_allowed_contacts_ = contact_manager_->GetAllowedContacts();

      // Set visibility to Your Devices.
      settings_.SetVisibility(nearby_share::mojom::Visibility::kYourDevices);
      contact_manager_->SetAllowedContacts(std::set<std::string>());
    } else {
      settings_.SetVisibility(user_visibility_);
      contact_manager_->SetAllowedContacts(user_allowed_contacts_);
    }
  }

  InvalidateSurfaceState();
}

void NearbySharingServiceImpl::AdapterPresentChanged(
    device::BluetoothAdapter* adapter,
    bool present) {
  CD_LOG(VERBOSE, Feature::NS)
      << __func__ << ": Bluetooth present changed: " << present;
  InvalidateSurfaceState();
}

void NearbySharingServiceImpl::AdapterPoweredChanged(
    device::BluetoothAdapter* adapter,
    bool powered) {
  CD_LOG(VERBOSE, Feature::NS)
      << __func__ << ": Bluetooth powered changed: " << powered;
  InvalidateSurfaceState();
}

void NearbySharingServiceImpl::
    LowEnergyScanSessionHardwareOffloadingStatusChanged(
        device::BluetoothAdapter::LowEnergyScanSessionHardwareOffloadingStatus
            status) {
  CD_LOG(VERBOSE, Feature::NS)
      << __func__
      << ": Bluetooth low energy scan session hardware offloading status : "
      << (status == device::BluetoothAdapter::
                        LowEnergyScanSessionHardwareOffloadingStatus::kSupported
              ? "enabled"
              : "disabled");
  InvalidateSurfaceState();
}

void NearbySharingServiceImpl::SuspendImminent() {
  CD_LOG(VERBOSE, Feature::NS) << __func__ << ": Suspend imminent.";
  InvalidateSurfaceState();
}

void NearbySharingServiceImpl::SuspendDone() {
  CD_LOG(VERBOSE, Feature::NS) << __func__ << ": Suspend done.";
  InvalidateSurfaceState();
}

base::ObserverList<TransferUpdateCallback>&
NearbySharingServiceImpl::GetReceiveCallbacksFromState(
    ReceiveSurfaceState state) {
  switch (state) {
    case ReceiveSurfaceState::kForeground:
      return foreground_receive_callbacks_;
    case ReceiveSurfaceState::kBackground:
      return background_receive_callbacks_;
    case ReceiveSurfaceState::kUnknown:
      NOTREACHED();
  }
}

bool NearbySharingServiceImpl::IsVisibleInBackground(
    nearby_share::mojom::Visibility visibility) {
  return isVisibleForAdvertising(visibility);
}

const std::optional<std::vector<uint8_t>>
NearbySharingServiceImpl::CreateEndpointInfo(
    const std::optional<std::string>& device_name) {
  std::array<uint8_t, sharing::Advertisement::kSaltSize> salt;
  salt = GenerateRandomBytes<sharing::Advertisement::kSaltSize>();
  std::array<uint8_t,
             sharing::Advertisement::kMetadataEncryptionKeyHashByteSize>
      encrypted_key;
  encrypted_key = GenerateRandomBytes<
      sharing::Advertisement::kMetadataEncryptionKeyHashByteSize>();

  nearby_share::mojom::Visibility visibility = settings_.GetVisibility();
  if (isVisibleForAdvertising(visibility)) {
    std::optional<NearbyShareEncryptedMetadataKey> encrypted_metadata_key =
        certificate_manager_->EncryptPrivateCertificateMetadataKey(visibility);
    if (encrypted_metadata_key) {
      base::span(salt).copy_from(encrypted_metadata_key->salt());
      base::span(encrypted_key)
          .copy_from(encrypted_metadata_key->encrypted_key());
    } else {
      CD_LOG(WARNING, Feature::NS)
          << __func__ << ": Failed to encrypt private certificate metadata key "
          << "for advertisement.";
    }
  }

  nearby_share::mojom::ShareTargetType device_type =
      nearby_share::mojom::ShareTargetType::kLaptop;

  std::unique_ptr<sharing::Advertisement> advertisement =
      sharing::Advertisement::NewInstance(salt, encrypted_key, device_type,
                                          device_name);
  return advertisement ? std::make_optional(advertisement->ToEndpointInfo())
                       : std::nullopt;
}

void NearbySharingServiceImpl::GetBluetoothAdapter() {
  auto* adapter_factory = device::BluetoothAdapterFactory::Get();
  if (!adapter_factory->IsBluetoothSupported()) {
    RecordNearbyShareError(NearbyShareError::kGetBluetoothAdapterUnsupported);
    return;
  }

  // Because this will be called from the constructor, GetAdapter() may call
  // OnGetBluetoothAdapter() immediately which can cause problems during tests
  // since the class is not fully constructed yet.
  base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE,
      base::BindOnce(
          &device::BluetoothAdapterFactory::GetAdapter,
          base::Unretained(adapter_factory),
          base::BindOnce(&NearbySharingServiceImpl::OnGetBluetoothAdapter,
                         weak_ptr_factory_.GetWeakPtr())));
}

void NearbySharingServiceImpl::OnGetBluetoothAdapter(
    scoped_refptr<device::BluetoothAdapter> adapter) {
  bluetooth_adapter_ = adapter;
  bluetooth_adapter_->AddObserver(this);
  fast_initiation_scanning_metrics_->SetBluetoothAdapter(adapter);

  // TODO(crbug/1147652): The call to update the advertising interval is
  // removed to prevent a Bluez crash. We need to either reduce the global
  // advertising interval asynchronously and wait for the result or use the
  // updated API referenced in the bug which allows setting a per-advertisement
  // interval.

  // TODO(crbug.com/1132469): This was added to fix an issue where advertising
  // was not starting on sign-in. Add a unit test to cover this case.
  InvalidateSurfaceState();
}

void NearbySharingServiceImpl::StartFastInitiationAdvertising() {
  CD_LOG(VERBOSE, Feature::NS)
      << __func__ << ": Starting fast initiation advertising.";

  fast_initiation_advertiser_ =
      FastInitiationAdvertiser::Factory::Create(bluetooth_adapter_);

  // TODO(crbug/1147652): The call to update the advertising interval is
  // removed to prevent a Bluez crash. We need to either reduce the global
  // advertising interval asynchronously and wait for the result or use the
  // updated API referenced in the bug which allows setting a per-advertisement
  // interval.

  // TODO(crbug.com/1100686): Determine whether to call StartAdvertising() with
  // kNotify or kSilent.
  fast_initiation_advertiser_->StartAdvertising(
      FastInitiationAdvertiser::FastInitType::kNotify,
      base::BindOnce(
          &NearbySharingServiceImpl::OnStartFastInitiationAdvertising,
          weak_ptr_factory_.GetWeakPtr()),
      base::BindOnce(
          &NearbySharingServiceImpl::OnStartFastInitiationAdvertisingError,
          weak_ptr_factory_.GetWeakPtr()));
}

void NearbySharingServiceImpl::OnStartFastInitiationAdvertising() {
  // TODO(hansenmichael): Do not invoke
  // |register_send_surface_callback_| until Nearby Connections
  // scanning is kicked off.
  CD_LOG(VERBOSE, Feature::NS)
      << __func__ << ": Started advertising FastInitiation.";
}

void NearbySharingServiceImpl::OnStartFastInitiationAdvertisingError() {
  fast_initiation_advertiser_.reset();
  RecordNearbyShareError(
      NearbyShareError::kStartFastInitiationAdvertisingFailed);
  CD_LOG(ERROR, Feature::NS)
      << __func__ << ": Failed to start FastInitiation advertising.";
}

void NearbySharingServiceImpl::StopFastInitiationAdvertising() {
  if (!fast_initiation_advertiser_) {
    CD_LOG(VERBOSE, Feature::NS)
        << __func__ << ": Not advertising FastInitiation, ignoring.";
    return;
  }

  fast_initiation_advertiser_->StopAdvertising(
      base::BindOnce(&NearbySharingServiceImpl::OnStopFastInitiationAdvertising,
                     weak_ptr_factory_.GetWeakPtr()));
}

void NearbySharingServiceImpl::OnStopFastInitiationAdvertising() {
  fast_initiation_advertiser_.reset();
  CD_LOG(VERBOSE, Feature::NS)
      << __func__ << ": Stopped advertising FastInitiation";

  // TODO(crbug/1147652): The call to update the advertising interval is
  // removed to prevent a Bluez crash. We need to either reduce the global
  // advertising interval asynchronously and wait for the result or use the
  // updated API referenced in the bug which allows setting a per-advertisement
  // interval.
}

void NearbySharingServiceImpl::AddEndpointDiscoveryEvent(
    base::OnceClosure event) {
  endpoint_discovery_events_.push(std::move(event));
  if (endpoint_discovery_events_.size() == 1u) {
    std::move(endpoint_discovery_events_.front()).Run();
  }
}

void NearbySharingServiceImpl::HandleEndpointDiscovered(
    const std::string& endpoint_id,
    const std::vector<uint8_t>& endpoint_info) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  CD_LOG(VERBOSE, Feature::NS)
      << __func__ << ": endpoint_id=" << endpoint_id
      << ", endpoint_info=" << base::HexEncode(endpoint_info);
  transfer_profiler_->OnEndpointDiscovered(endpoint_id);

  if (!is_scanning_) {
    CD_LOG(VERBOSE, Feature::NS)
        << __func__
        << ": Ignoring discovered endpoint because we're no longer scanning";
    FinishEndpointDiscoveryEvent();
    return;
  }

  sharing::mojom::NearbySharingDecoder* decoder = GetNearbySharingDecoder();
  if (!decoder) {
    RecordNearbyShareError(
        NearbyShareError::HandleEndpointDiscoveredFailedToGetDecoder);
    FinishEndpointDiscoveryEvent();
    return;
  }

  decoder->DecodeAdvertisement(
      endpoint_info,
      base::BindOnce(&NearbySharingServiceImpl::OnOutgoingAdvertisementDecoded,
                     endpoint_discovery_weak_ptr_factory_.GetWeakPtr(),
                     endpoint_id, endpoint_info));
}

void NearbySharingServiceImpl::HandleEndpointLost(
    const std::string& endpoint_id) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  CD_LOG(VERBOSE, Feature::NS) << __func__ << ": endpoint_id=" << endpoint_id;
  transfer_profiler_->OnEndpointLost(endpoint_id);

  if (!is_scanning_) {
    CD_LOG(VERBOSE, Feature::NS)
        << __func__
        << ": Ignoring lost endpoint because we're no longer scanning";
    FinishEndpointDiscoveryEvent();
    return;
  }

  discovered_advertisements_to_retry_map_.erase(endpoint_id);
  RemoveOutgoingShareTargetWithEndpointId(endpoint_id);
  FinishEndpointDiscoveryEvent();
}

void NearbySharingServiceImpl::FinishEndpointDiscoveryEvent() {
  DCHECK(!endpoint_discovery_events_.empty());
  DCHECK(endpoint_discovery_events_.front().is_null());
  endpoint_discovery_events_.pop();

  // Handle the next queued up endpoint discovered/lost event.
  if (!endpoint_discovery_events_.empty()) {
    DCHECK(!endpoint_discovery_events_.front().is_null());
    std::move(endpoint_discovery_events_.front()).Run();
  }
}

void NearbySharingServiceImpl::OnOutgoingAdvertisementDecoded(
    const std::string& endpoint_id,
    const std::vector<uint8_t>& endpoint_info,
    sharing::mojom::AdvertisementPtr advertisement) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (!advertisement) {
    RecordNearbyShareError(
        NearbyShareError::kOutgoingAdvertisementDecodedFailedToParse);
    CD_LOG(WARNING, Feature::NS)
        << __func__ << ": Failed to parse discovered advertisement.";
    FinishEndpointDiscoveryEvent();
    return;
  }

  transfer_profiler_->OnOutgoingEndpointDecoded(endpoint_id);

  // Now we will report endpoints met before in NearbyConnectionsManager.
  // Check outgoingShareTargetInfoMap first and pass the same shareTarget if we
  // found one.

  // Looking for the ShareTarget based on endpoint id.
  if (outgoing_share_target_map_.find(endpoint_id) !=
      outgoing_share_target_map_.end()) {
    FinishEndpointDiscoveryEvent();
    return;
  }

  // Once we get the advertisement, the first thing to do is decrypt the
  // certificate.
  NearbyShareEncryptedMetadataKey encrypted_metadata_key =
      AdvertisementToKey(advertisement);
  GetCertificateManager()->GetDecryptedPublicCertificate(
      std::move(encrypted_metadata_key),
      base::BindOnce(&NearbySharingServiceImpl::OnOutgoingDecryptedCertificate,
                     endpoint_discovery_weak_ptr_factory_.GetWeakPtr(),
                     endpoint_id, endpoint_info, std::move(advertisement)));
}

void NearbySharingServiceImpl::OnOutgoingDecryptedCertificate(
    const std::string& endpoint_id,
    const std::vector<uint8_t>& endpoint_info,
    sharing::mojom::AdvertisementPtr advertisement,
    std::optional<NearbyShareDecryptedPublicCertificate> certificate) {
  // Check again for this endpoint id, to avoid race conditions.
  if (outgoing_share_target_map_.find(endpoint_id) !=
      outgoing_share_target_map_.end()) {
    FinishEndpointDiscoveryEvent();
    return;
  }

  // The certificate provides the device name, in order to create a ShareTarget
  // to represent this remote device.
  std::optional<ShareTarget> share_target = CreateShareTarget(
      endpoint_id, std::move(advertisement), std::move(certificate),
      /*is_incoming=*/false);
  if (!share_target) {
    RecordNearbyShareError(
        NearbyShareError::
            kOutgoingDecryptedCertificateFailedToCreateShareTarget);
    CD_LOG(INFO, Feature::NS)
        << __func__ << ": Failed to convert discovered advertisement to share "
        << "target. Ignoring endpoint until next certificate download.";
    discovered_advertisements_to_retry_map_[endpoint_id] = endpoint_info;
    FinishEndpointDiscoveryEvent();
    return;
  }

  // Update the endpoint id for the share target.
  CD_LOG(INFO, Feature::NS)
      << __func__
      << ": An endpoint has been discovered, with an advertisement "
         "containing a valid share target.";

  // Notifies the user that we discovered a device.
  for (ShareTargetDiscoveredCallback& discovery_callback :
       foreground_send_discovery_callbacks_) {
    discovery_callback.OnShareTargetDiscovered(*share_target);
  }
  for (ShareTargetDiscoveredCallback& discovery_callback :
       background_send_discovery_callbacks_) {
    discovery_callback.OnShareTargetDiscovered(*share_target);
  }

  CD_LOG(VERBOSE, Feature::NS)
      << __func__ << ": Reported OnShareTargetDiscovered "
      << (base::Time::Now() - scanning_start_timestamp_);

  // TODO(crbug/1108348) CachingManager should cache known and non-external
  // share targets.

  FinishEndpointDiscoveryEvent();
}

void NearbySharingServiceImpl::ScheduleCertificateDownloadDuringDiscovery(
    size_t download_count) {
  if (download_count >= kMaxCertificateDownloadsDuringDiscovery) {
    return;
  }

  certificate_download_during_discovery_timer_.Start(
      FROM_HERE, kCertificateDownloadDuringDiscoveryPeriod,
      base::BindOnce(&NearbySharingServiceImpl::
                         OnCertificateDownloadDuringDiscoveryTimerFired,
                     weak_ptr_factory_.GetWeakPtr(), download_count));
}

void NearbySharingServiceImpl::OnCertificateDownloadDuringDiscoveryTimerFired(
    size_t download_count) {
  if (!is_scanning_) {
    return;
  }

  if (!discovered_advertisements_to_retry_map_.empty()) {
    CD_LOG(VERBOSE, Feature::NS)
        << __func__ << ": Detected "
        << discovered_advertisements_to_retry_map_.size()
        << " discovered advertisements that could not decrypt any "
        << "public certificates. Re-downloading certificates.";
    certificate_manager_->DownloadPublicCertificates();
    ++download_count;
  }
  ScheduleCertificateDownloadDuringDiscovery(download_count);
}

bool NearbySharingServiceImpl::IsBluetoothPresent() const {
  return bluetooth_adapter_.get() && bluetooth_adapter_->IsPresent();
}

bool NearbySharingServiceImpl::IsBluetoothPowered() const {
  return IsBluetoothPresent() && bluetooth_adapter_->IsPowered();
}

bool NearbySharingServiceImpl::HasAvailableAdvertisingMediums() {
  // Advertising is currently unsupported unless bluetooth is known to be
  // enabled. When Wifi LAN advertising (mDNS) is supported, we also need
  // to check network conditions.
  net::NetworkChangeNotifier::ConnectionType connection_type =
      net::NetworkChangeNotifier::GetConnectionType();
  bool hasNetworkConnection =
      connection_type ==
          net::NetworkChangeNotifier::ConnectionType::CONNECTION_WIFI ||
      connection_type ==
          net::NetworkChangeNotifier::ConnectionType::CONNECTION_ETHERNET;
  return IsBluetoothPowered() ||
         (hasNetworkConnection && kIsWifiLanAdvertisingSupported);
}

bool NearbySharingServiceImpl::HasAvailableDiscoveryMediums() {
  // Discovery is supported over both Bluetooth and Wifi LAN (mDNS),
  // so either of those mediums must be enabled. mDNS discovery
  // additionally needs a network connection.
  net::NetworkChangeNotifier::ConnectionType connection_type =
      net::NetworkChangeNotifier::GetConnectionType();
  bool hasNetworkConnection =
      connection_type ==
          net::NetworkChangeNotifier::ConnectionType::CONNECTION_WIFI ||
      connection_type ==
          net::NetworkChangeNotifier::ConnectionType::CONNECTION_ETHERNET;
  return IsBluetoothPowered() || hasNetworkConnection;
}

void NearbySharingServiceImpl::InvalidateSurfaceState() {
  InvalidateSendSurfaceState();
  InvalidateReceiveSurfaceState();
  if (process_reference_ && ShouldStopNearbyProcess()) {
    // We need to debounce the call to shut down the process in case this state
    // is temporary (we don't want to the thrash the process). Any
    // advertisement, scanning or transferring will stop this timer from
    // triggering.
    if (!process_shutdown_pending_timer_.IsRunning()) {
      CD_LOG(INFO, Feature::NS)
          << __func__
          << ": Scheduling process shutdown if not needed in 15 seconds";
      // NOTE: Using base::Unretained is safe because if shutdown_pending_timer_
      // goes out of scope the timer will be cancelled.
      process_shutdown_pending_timer_.Start(
          FROM_HERE, kProcessShutdownPendingTimerDelay,
          base::BindOnce(&NearbySharingServiceImpl::OnProcessShutdownTimerFired,
                         base::Unretained(this)));
    }
  } else {
    process_shutdown_pending_timer_.Stop();
  }
}

bool NearbySharingServiceImpl::ShouldStopNearbyProcess() {
  // Nothing to do if we're shutting down the profile.
  if (!profile_) {
    return false;
  }

  // We're currently advertising.
  if (advertising_power_level_ !=
      NearbyConnectionsManager::PowerLevel::kUnknown) {
    return false;
  }

  // We're currently discovering.
  if (is_scanning_) {
    return false;
  }

  // We're currently attempting to connect to a remote device.
  if (is_connecting_) {
    return false;
  }

  // We're currently sending or receiving a file.
  if (is_transferring_) {
    return false;
  }

  // We're not using NearbyConnections, should stop the process.
  return true;
}

void NearbySharingServiceImpl::OnProcessShutdownTimerFired() {
  if (ShouldStopNearbyProcess() && process_reference_) {
    CD_LOG(INFO, Feature::NS)
        << __func__
        << ": Shutdown Process timer fired, releasing process reference";

    // Manually firing this callback will handle destroying
    // |process_reference_|.
    //
    // The NearbyProcessManager would ordinarily be responsible for firing this
    // callback, but it assumes that is unnecessary if the owner destroys the
    // process reference, so we're responsible for calling it to ensure that
    // downstream listeners are notified.
    OnNearbyProcessStopped(NearbyProcessShutdownReason::kNormal);
  }
}

void NearbySharingServiceImpl::InvalidateSendSurfaceState() {
  InvalidateScanningState();
  InvalidateFastInitiationAdvertising();
}

void NearbySharingServiceImpl::InvalidateScanningState() {
  // Nothing to do if we're shutting down the profile.
  if (!profile_) {
    return;
  }

  if (power_client_->IsSuspended()) {
    StopScanning();
    CD_LOG(VERBOSE, Feature::NS)
        << __func__ << ": Stopping discovery because the system is suspended.";
    return;
  }

  // Screen is off. Do no work.
  if (is_screen_locked_) {
    StopScanning();
    CD_LOG(VERBOSE, Feature::NS)
        << __func__ << ": Stopping discovery because the screen is locked.";
    return;
  }

  if (!HasAvailableDiscoveryMediums()) {
    StopScanning();
    CD_LOG(VERBOSE, Feature::NS)
        << __func__
        << ": Stopping scanning because both bluetooth and wifi LAN are "
           "disabled.";
    return;
  }

  // Nearby Sharing is disabled. Don't advertise.
  if (!settings_.GetEnabled()) {
    StopScanning();
    CD_LOG(VERBOSE, Feature::NS)
        << __func__
        << ": Stopping discovery because Nearby Sharing is disabled.";
    return;
  }

  if (is_transferring_ || is_connecting_) {
    StopScanning();
    CD_LOG(VERBOSE, Feature::NS)
        << __func__
        << ": Stopping discovery because we're currently in the midst of a "
           "transfer.";
    return;
  }

  if (foreground_send_transfer_callbacks_.empty()) {
    StopScanning();
    CD_LOG(VERBOSE, Feature::NS)
        << __func__
        << ": Stopping discovery because no scanning surface has been "
           "registered.";
    return;
  }

  process_shutdown_pending_timer_.Stop();
  // Screen is on, Bluetooth is enabled, and Nearby Sharing is enabled! Start
  // discovery.
  StartScanning();
}

void NearbySharingServiceImpl::InvalidateFastInitiationAdvertising() {
  // Nothing to do if we're shutting down the profile.
  if (!profile_) {
    return;
  }

  if (power_client_->IsSuspended()) {
    StopFastInitiationAdvertising();
    CD_LOG(VERBOSE, Feature::NS)
        << __func__
        << ": Stopping fast init advertising because the system is suspended.";
    return;
  }

  // Screen is off. Do no work.
  if (is_screen_locked_) {
    StopFastInitiationAdvertising();
    CD_LOG(VERBOSE, Feature::NS)
        << __func__
        << ": Stopping fast init advertising because the screen is locked.";
    return;
  }

  if (!IsBluetoothPowered()) {
    StopFastInitiationAdvertising();
    CD_LOG(VERBOSE, Feature::NS)
        << __func__
        << ": Stopping fast init advertising because both "
           "bluetooth is disabled.";
    return;
  }

  // Nearby Sharing is disabled. Don't fast init advertise.
  if (!settings_.GetEnabled()) {
    StopFastInitiationAdvertising();
    CD_LOG(VERBOSE, Feature::NS)
        << __func__
        << ": Stopping fast init advertising because Nearby "
           "Sharing is disabled.";
    return;
  }

  if (foreground_send_transfer_callbacks_.empty()) {
    StopFastInitiationAdvertising();
    CD_LOG(VERBOSE, Feature::NS)
        << __func__
        << ": Stopping fast init advertising because no send "
           "surface is registered.";
    return;
  }

  if (fast_initiation_advertiser_) {
    CD_LOG(VERBOSE, Feature::NS)
        << __func__ << ": Already advertising fast init, ignoring.";
    return;
  }

  process_shutdown_pending_timer_.Stop();

  StartFastInitiationAdvertising();
}

void NearbySharingServiceImpl::InvalidateReceiveSurfaceState() {
  InvalidateAdvertisingState();
  InvalidateFastInitiationScanning();
}

void NearbySharingServiceImpl::InvalidateAdvertisingState() {
  // Nothing to do if we're shutting down the profile.
  if (!profile_) {
    return;
  }

  if (power_client_->IsSuspended()) {
    StopAdvertising();
    CD_LOG(VERBOSE, Feature::NS)
        << __func__
        << ": Stopping advertising because the system is suspended.";
    return;
  }

  if (!HasAvailableAdvertisingMediums()) {
    StopAdvertising();
    CD_LOG(VERBOSE, Feature::NS)
        << __func__
        << ": Stopping advertising because both bluetooth and wifi LAN are "
           "disabled.";
    return;
  }

  // Nearby Sharing is disabled. Don't advertise.
  if (!settings_.GetEnabled()) {
    StopAdvertising();
    CD_LOG(VERBOSE, Feature::NS)
        << __func__
        << ": Stopping advertising because Nearby Sharing is disabled.";
    return;
  }

  // We're scanning for other nearby devices. Don't advertise.
  if (is_scanning_) {
    StopAdvertising();
    CD_LOG(VERBOSE, Feature::NS)
        << __func__
        << ": Stopping advertising because we're scanning for other devices.";
    return;
  }

  if (is_transferring_) {
    StopAdvertising();
    CD_LOG(VERBOSE, Feature::NS)
        << __func__
        << ": Stopping advertising because we're currently in the midst of "
           "a transfer.";
    return;
  }

  if (foreground_receive_callbacks_.empty() &&
      background_receive_callbacks_.empty()) {
    StopAdvertising();
    CD_LOG(VERBOSE, Feature::NS)
        << __func__
        << ": Stopping advertising because no receive surface is registered.";
    return;
  }

  if (!IsVisibleInBackground(settings_.GetVisibility()) &&
      foreground_receive_callbacks_.empty()) {
    StopAdvertising();
    CD_LOG(VERBOSE, Feature::NS)
        << __func__
        << ": Stopping advertising because no high power receive surface "
           "is registered and device is visible to NO_ONE.";
    return;
  }

  process_shutdown_pending_timer_.Stop();

  NearbyConnectionsManager::PowerLevel power_level;
  if (!foreground_receive_callbacks_.empty()) {
    power_level = NearbyConnectionsManager::PowerLevel::kHighPower;
    // TODO(crbug/1100367) handle fast init
    // } else if (isFastInitDeviceNearby) {
    //   power_level = NearbyConnectionsManager::PowerLevel::kMediumPower;
  } else {
    power_level = NearbyConnectionsManager::PowerLevel::kLowPower;
  }

  nearby_share::mojom::DataUsage data_usage = settings_.GetDataUsage();
  if (advertising_power_level_ !=
      NearbyConnectionsManager::PowerLevel::kUnknown) {
    if (power_level == advertising_power_level_) {
      CD_LOG(VERBOSE, Feature::NS)
          << __func__ << ": Ignoring, already advertising with power level "
          << PowerLevelToString(advertising_power_level_)
          << " and data usage preference " << data_usage;
      return;
    }

    StopAdvertising();
    CD_LOG(VERBOSE, Feature::NS)
        << __func__ << ": Restart advertising with power level "
        << PowerLevelToString(power_level) << " and data usage preference "
        << data_usage;
  }

  std::optional<std::string> device_name;
  if (!foreground_receive_callbacks_.empty()) {
    device_name = local_device_data_manager_->GetDeviceName();
  }

  // Starts advertising through Nearby Connections. Caller is expected to ensure
  // |listener| remains valid until StopAdvertising is called.
  std::optional<std::vector<uint8_t>> endpoint_info =
      CreateEndpointInfo(device_name);
  if (!endpoint_info) {
    CD_LOG(VERBOSE, Feature::NS)
        << __func__
        << ": Unable to advertise since could not parse the "
           "endpoint info from the advertisement.";
    return;
  }

  // TODO(crbug/1147652): The call to update the advertising interval is
  // removed to prevent a Bluez crash. We need to either reduce the global
  // advertising interval asynchronously and wait for the result or use the
  // updated API referenced in the bug which allows setting a per-advertisement
  // interval.

  // TODO(crbug/1155669): This will suppress the system notification that
  // alerts the user that their device is discoverable, but it exposes Nearby
  // Share logic to external components. We should clean this up with a better
  // abstraction.
  bool used_device_name = device_name.has_value();
  if (used_device_name) {
    for (auto& observer : observers_) {
      observer.OnHighVisibilityChangeRequested();
    }
  }

  nearby_connections_manager_->StartAdvertising(
      *endpoint_info,
      /*listener=*/this, power_level, data_usage,
      base::BindOnce(&NearbySharingServiceImpl::OnStartAdvertisingResult,
                     weak_ptr_factory_.GetWeakPtr(), used_device_name));

  advertising_power_level_ = power_level;
  CD_LOG(VERBOSE, Feature::NS)
      << __func__ << ": StartAdvertising requested over Nearby Connections: "
      << " power level: " << PowerLevelToString(power_level)
      << " visibility: " << settings_.GetVisibility()
      << " data usage: " << data_usage << " advertise device name?: "
      << (device_name.has_value() ? "yes" : "no");

  ScheduleRotateBackgroundAdvertisementTimer();
}

void NearbySharingServiceImpl::StopAdvertising() {
  if (advertising_power_level_ ==
      NearbyConnectionsManager::PowerLevel::kUnknown) {
    CD_LOG(VERBOSE, Feature::NS)
        << __func__ << ": Not currently advertising, ignoring.";
    return;
  }

  nearby_connections_manager_->StopAdvertising(
      base::BindOnce(&NearbySharingServiceImpl::OnStopAdvertisingResult,
                     weak_ptr_factory_.GetWeakPtr()));

  // TODO(crbug/1147652): The call to update the advertising interval is
  // removed to prevent a Bluez crash. We need to either reduce the global
  // advertising interval asynchronously and wait for the result or use the
  // updated API referenced in the bug which allows setting a per-advertisement
  // interval.
  CD_LOG(VERBOSE, Feature::NS) << __func__ << ": Stop advertising requested";

  // Set power level to unknown immediately instead of waiting for the callback.
  // In the case of restarting advertising (e.g. turning off high visibility
  // with contact-based enabled), StartAdvertising will be called
  // immediately after StopAdvertising and will fail if the power level
  // indicates already advertising.
  advertising_power_level_ = NearbyConnectionsManager::PowerLevel::kUnknown;
}

void NearbySharingServiceImpl::StartScanning() {
  DCHECK(profile_);
  DCHECK(!power_client_->IsSuspended());
  DCHECK(settings_.GetEnabled());
  DCHECK(!is_screen_locked_);
  DCHECK(HasAvailableDiscoveryMediums());
  DCHECK(!foreground_send_transfer_callbacks_.empty());

  if (is_scanning_) {
    CD_LOG(VERBOSE, Feature::NS)
        << __func__ << ": We're currently scanning, ignoring.";
    return;
  }

  scanning_start_timestamp_ = base::Time::Now();
  is_scanning_ = true;
  InvalidateReceiveSurfaceState();

  ClearOutgoingShareTargetInfoMap();
  discovered_advertisements_to_retry_map_.clear();

  nearby_connections_manager_->StartDiscovery(
      /*listener=*/this, settings_.GetDataUsage(),
      base::BindOnce(&NearbySharingServiceImpl::OnStartDiscoveryResult,
                     weak_ptr_factory_.GetWeakPtr()));

  InvalidateSendSurfaceState();
  CD_LOG(VERBOSE, Feature::NS) << __func__ << ": Scanning has started";
}

NearbySharingService::StatusCodes NearbySharingServiceImpl::StopScanning() {
  if (!is_scanning_) {
    CD_LOG(VERBOSE, Feature::NS)
        << __func__ << ": Not currently scanning, ignoring.";
    return StatusCodes::kStatusAlreadyStopped;
  }

  nearby_connections_manager_->StopDiscovery();
  is_scanning_ = false;

  // TODO(b/313950374): This should really happen when NC actually stops
  // discovery, which could happen outside of this function.
  for (auto& observer : observers_) {
    observer.OnShareTargetDiscoveryStopped();
  }

  certificate_download_during_discovery_timer_.Stop();
  discovered_advertisements_to_retry_map_.clear();

  // Note: We don't know if we stopped scanning in preparation to send a file,
  // or we stopped because the user left the page. We'll invalidate after a
  // short delay.
  base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
      FROM_HERE,
      base::BindOnce(&NearbySharingServiceImpl::InvalidateSurfaceState,
                     weak_ptr_factory_.GetWeakPtr()),
      kInvalidateDelay);

  CD_LOG(VERBOSE, Feature::NS) << __func__ << ": Scanning has stopped.";
  return StatusCodes::kOk;
}

void NearbySharingServiceImpl::StopAdvertisingAndInvalidateSurfaceState() {
  if (advertising_power_level_ !=
      NearbyConnectionsManager::PowerLevel::kUnknown) {
    StopAdvertising();
  }

  InvalidateSurfaceState();
}

void NearbySharingServiceImpl::InvalidateFastInitiationScanning() {
  bool is_hardware_offloading_supported =
      IsBluetoothPresent() &&
      FastInitiationScanner::Factory::IsHardwareSupportAvailable(
          bluetooth_adapter_.get());

  // Hardware offloading support is computed when the bluetooth adapter becomes
  // available. We set the hardware supported state on |settings_| to notify the
  // UI of state changes. InvalidateFastInitiationScanning gets triggered on
  // adapter change events.
  settings_.SetIsFastInitiationHardwareSupported(
      is_hardware_offloading_supported);

  // Nothing to do if we're shutting down the profile.
  if (!profile_) {
    return;
  }

  if (fast_initiation_scanner_cooldown_timer_.IsRunning()) {
    CD_LOG(VERBOSE, Feature::NS)
        << __func__
        << ": Stopping background scanning due to post-transfer "
           "cooldown period";
    StopFastInitiationScanning();
    return;
  }

  if (settings_.GetFastInitiationNotificationState() !=
      nearby_share::mojom::FastInitiationNotificationState::kEnabled) {
    CD_LOG(VERBOSE, Feature::NS)
        << __func__
        << ": Stopping background scanning; fast initiation "
           "notification is disabled";
    StopFastInitiationScanning();
    return;
  }

  if (GetNearbyShareEnabledState(prefs_) ==
      NearbyShareEnabledState::kDisallowedByPolicy) {
    CD_LOG(VERBOSE, Feature::NS)
        << __func__
        << ": Stopping background scanning because Nearby Sharing "
           "is disallowed by policy ";
    StopFastInitiationScanning();
    return;
  }

  if (power_client_->IsSuspended()) {
    CD_LOG(VERBOSE, Feature::NS)
        << __func__
        << ": Stopping background scanning because the system is suspended.";
    StopFastInitiationScanning();
    return;
  }

  // Screen is off. Do no work.
  if (is_screen_locked_) {
    CD_LOG(VERBOSE, Feature::NS)
        << __func__
        << ": Stopping background scanning because the screen is locked.";
    StopFastInitiationScanning();
    return;
  }

  if (!IsBluetoothPowered()) {
    CD_LOG(VERBOSE, Feature::NS)
        << __func__
        << ": Stopping background scanning because bluetooth is powered down.";
    StopFastInitiationScanning();
    return;
  }

  // We're scanning for other nearby devices. Don't background scan.
  if (is_scanning_) {
    CD_LOG(VERBOSE, Feature::NS)
        << __func__
        << ": Stopping background scanning because we're scanning "
           "for other devices.";
    StopFastInitiationScanning();
    return;
  }

  if (is_transferring_) {
    CD_LOG(VERBOSE, Feature::NS)
        << __func__
        << ": Stopping background scanning because we're currently "
           "in the midst of "
           "a transfer.";
    StopFastInitiationScanning();
    return;
  }

  if (advertising_power_level_ ==
      NearbyConnectionsManager::PowerLevel::kHighPower) {
    CD_LOG(VERBOSE, Feature::NS)
        << __func__
        << ": Stopping background scanning because we're already "
           "in high visibility mode.";
    StopFastInitiationScanning();
    return;
  }

  if (!is_hardware_offloading_supported) {
    CD_LOG(VERBOSE, Feature::NS)
        << __func__
        << ": Stopping background scanning because hardware "
           "support is not available or not ready.";
    StopFastInitiationScanning();
    return;
  }

  process_shutdown_pending_timer_.Stop();

  if (fast_initiation_scanner_) {
    CD_LOG(VERBOSE, Feature::NS)
        << __func__ << ": Ignoring, already background scanning.";
    return;
  }

  StartFastInitiationScanning();
}

void NearbySharingServiceImpl::StartFastInitiationScanning() {
  DCHECK(!fast_initiation_scanner_);
  CD_LOG(VERBOSE, Feature::NS) << __func__ << ": Starting background scanning.";
  fast_initiation_scanner_ =
      FastInitiationScanner::Factory::Create(bluetooth_adapter_);
  fast_initiation_scanner_->StartScanning(
      base::BindRepeating(
          &NearbySharingServiceImpl::OnFastInitiationDevicesDetected,
          weak_ptr_factory_.GetWeakPtr()),
      base::BindRepeating(
          &NearbySharingServiceImpl::OnFastInitiationDevicesNotDetected,
          weak_ptr_factory_.GetWeakPtr()),
      base::BindOnce(&NearbySharingServiceImpl::StopFastInitiationScanning,
                     weak_ptr_factory_.GetWeakPtr()));
}

void NearbySharingServiceImpl::OnFastInitiationDevicesDetected() {
  CD_LOG(VERBOSE, Feature::NS) << __func__;
  for (auto& observer : observers_) {
    observer.OnFastInitiationDevicesDetected();
  }
}

void NearbySharingServiceImpl::OnFastInitiationDevicesNotDetected() {
  CD_LOG(VERBOSE, Feature::NS) << __func__;
  for (auto& observer : observers_) {
    observer.OnFastInitiationDevicesNotDetected();
  }
}

void NearbySharingServiceImpl::StopFastInitiationScanning() {
  if (!fast_initiation_scanner_) {
    CD_LOG(VERBOSE, Feature::NS)
        << __func__ << ": Ignoring, not background scanning.";
    return;
  }

  fast_initiation_scanner_.reset();
  for (auto& observer : observers_) {
    observer.OnFastInitiationScanningStopped();
  }
  CD_LOG(VERBOSE, Feature::NS) << __func__ << ": Stopped background scanning.";
}

void NearbySharingServiceImpl::ScheduleRotateBackgroundAdvertisementTimer() {
  uint64_t delayRangeMilliseconds = base::checked_cast<uint64_t>(
      kBackgroundAdvertisementRotationDelayMax.InMilliseconds() -
      kBackgroundAdvertisementRotationDelayMin.InMilliseconds());
  uint64_t delayMilliseconds =
      base::RandGenerator(delayRangeMilliseconds) +
      base::checked_cast<uint64_t>(
          kBackgroundAdvertisementRotationDelayMin.InMilliseconds());
  rotate_background_advertisement_timer_.Start(
      FROM_HERE,
      base::Milliseconds(base::checked_cast<uint64_t>(delayMilliseconds)),
      base::BindOnce(
          &NearbySharingServiceImpl::OnRotateBackgroundAdvertisementTimerFired,
          weak_ptr_factory_.GetWeakPtr()));
}

void NearbySharingServiceImpl::OnRotateBackgroundAdvertisementTimerFired() {
  if (!foreground_receive_callbacks_.empty()) {
    ScheduleRotateBackgroundAdvertisementTimer();
  } else {
    StopAdvertising();
    InvalidateSurfaceState();
  }
}

void NearbySharingServiceImpl::RemoveOutgoingShareTargetWithEndpointId(
    const std::string& endpoint_id) {
  auto it = outgoing_share_target_map_.find(endpoint_id);
  if (it == outgoing_share_target_map_.end()) {
    return;
  }

  // Share target state needs to be cleared before the move below.
  share_target_map_.erase(endpoint_id);
  transfer_size_map_.erase(endpoint_id);

  CD_LOG(VERBOSE, Feature::NS)
      << __func__ << ": Removing (endpoint_id=" << it->first
      << ", share_target.id=" << it->second.id
      << ") from outgoing share target map";
  ShareTarget share_target = std::move(it->second);
  outgoing_share_target_map_.erase(it);

  auto info_it = outgoing_share_target_info_map_.find(share_target.id);
  if (info_it != outgoing_share_target_info_map_.end()) {
    file_handler_.ReleaseFilePayloads(info_it->second.ExtractFilePayloads());
    outgoing_share_target_info_map_.erase(info_it);
  }

  for (ShareTargetDiscoveredCallback& discovery_callback :
       foreground_send_discovery_callbacks_) {
    discovery_callback.OnShareTargetLost(share_target);
  }
  for (ShareTargetDiscoveredCallback& discovery_callback :
       background_send_discovery_callbacks_) {
    discovery_callback.OnShareTargetLost(share_target);
  }

  for (auto& observer : observers_) {
    observer.OnShareTargetRemoved(share_target);
  }

  CD_LOG(VERBOSE, Feature::NS) << __func__ << ": Reported OnShareTargetLost";
}

void NearbySharingServiceImpl::OnTransferComplete() {
  bool was_sending_files = is_sending_files_;
  is_receiving_files_ = false;
  is_transferring_ = false;
  is_sending_files_ = false;

  // Cleanup ARC after send transfer completes since reading from file
  // descriptor(s) are done at this point even though there could be Nearby
  // Connection frames cached that are not yet sent to the remote device.
  if (was_sending_files && arc_transfer_cleanup_callback_) {
    std::move(arc_transfer_cleanup_callback_).Run();
  }

  CD_LOG(VERBOSE, Feature::NS)
      << __func__ << ": NearbySharing state change transfer finished";
  // Files transfer is done! Receivers can immediately cancel, but senders
  // should add a short delay to ensure the final in-flight packet(s) make
  // it to the remote device.
  base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
      FROM_HERE,
      base::BindOnce(&NearbySharingServiceImpl::InvalidateSurfaceState,
                     weak_ptr_factory_.GetWeakPtr()),
      was_sending_files ? kInvalidateSurfaceStateDelayAfterTransferDone
                        : base::TimeDelta());
}

void NearbySharingServiceImpl::OnTransferStarted(bool is_incoming) {
  is_transferring_ = true;
  if (is_incoming) {
    is_receiving_files_ = true;
  } else {
    is_sending_files_ = true;
  }
  InvalidateSurfaceState();
}

void NearbySharingServiceImpl::ReceivePayloads(
    ShareTarget share_target,
    StatusCodesCallback status_codes_callback) {
  DCHECK(profile_);
  mutual_acceptance_timeout_alarm_.Cancel();

  base::FilePath download_path =
      DownloadPrefs::FromDownloadManager(profile_->GetDownloadManager())
          ->DownloadPath();

  // Register payload path for all valid file payloads.
  base::flat_map<int64_t, base::FilePath> valid_file_payloads;
  for (auto& file : share_target.file_attachments) {
    std::optional<int64_t> payload_id = GetAttachmentPayloadId(file.id());
    if (!payload_id) {
      CD_LOG(WARNING, Feature::NS)
          << __func__
          << ": Failed to register payload path for attachment id - "
          << file.id();
      continue;
    }

    base::FilePath file_path = download_path.Append(file.file_name());
    valid_file_payloads.emplace(file.id(), std::move(file_path));
  }

  auto aggregated_success = std::make_unique<bool>(true);
  bool* aggregated_success_ptr = aggregated_success.get();

  if (valid_file_payloads.empty()) {
    OnPayloadPathsRegistered(share_target, std::move(aggregated_success),
                             std::move(status_codes_callback));
    return;
  }

  auto all_paths_registered_callback = base::BarrierClosure(
      valid_file_payloads.size(),
      base::BindOnce(&NearbySharingServiceImpl::OnPayloadPathsRegistered,
                     weak_ptr_factory_.GetWeakPtr(), share_target,
                     std::move(aggregated_success),
                     std::move(status_codes_callback)));

  for (const auto& payload : valid_file_payloads) {
    std::optional<int64_t> payload_id = GetAttachmentPayloadId(payload.first);
    DCHECK(payload_id);

    file_handler_.GetUniquePath(
        payload.second,
        base::BindOnce(
            &NearbySharingServiceImpl::OnUniquePathFetched,
            weak_ptr_factory_.GetWeakPtr(), payload.first, *payload_id,
            base::BindOnce(
                &NearbySharingServiceImpl::OnPayloadPathRegistered,
                weak_ptr_factory_.GetWeakPtr(),
                base::ScopedClosureRunner(all_paths_registered_callback),
                aggregated_success_ptr)));
  }
}

NearbySharingService::StatusCodes NearbySharingServiceImpl::SendPayloads(
    const ShareTarget& share_target) {
  CD_LOG(VERBOSE, Feature::NS)
      << __func__ << ": Preparing to send payloads to " << share_target.id;
  ShareTargetInfo* info = GetShareTargetInfo(share_target);
  if (!info || !info->connection()) {
    RecordNearbyShareError(NearbyShareError::kSendPayloadsMissingConnection);
    CD_LOG(WARNING, Feature::NS)
        << __func__ << ": Failed to send payload due to missing connection.";
    return StatusCodes::kOutOfOrderApiCall;
  }
  if (!info->transfer_update_callback()) {
    RecordNearbyShareError(
        NearbyShareError::kSendPayloadsMissingTransferUpdateCallback);
    CD_LOG(WARNING, Feature::NS)
        << __func__
        << ": Failed to send payload due to missing transfer update "
           "callback. Disconnecting.";
    AbortAndCloseConnectionIfNecessary(
        TransferMetadata::Status::kMissingTransferUpdateCallback, share_target);
    return StatusCodes::kOutOfOrderApiCall;
  }

  info->transfer_update_callback()->OnTransferUpdate(
      share_target,
      TransferMetadataBuilder()
          .set_token(info->token())
          .set_status(TransferMetadata::Status::kAwaitingRemoteAcceptance)
          .build());

  if (!info->endpoint_id()) {
    RecordNearbyShareError(NearbyShareError::kSendPayloadsMissingEndpointId);
    CD_LOG(WARNING, Feature::NS)
        << __func__ << ": Failed to send payload due to missing endpoint id.";
    AbortAndCloseConnectionIfNecessary(
        TransferMetadata::Status::kMissingEndpointId, share_target);
    return StatusCodes::kOutOfOrderApiCall;
  }

  ReceiveConnectionResponse(share_target);
  return StatusCodes::kOk;
}

void NearbySharingServiceImpl::OnUniquePathFetched(
    int64_t attachment_id,
    int64_t payload_id,
    base::OnceCallback<void(nearby::connections::mojom::Status)> callback,
    base::FilePath path) {
  attachment_info_map_[attachment_id].file_path = path;
  nearby_connections_manager_->RegisterPayloadPath(payload_id, path,
                                                   std::move(callback));
}

void NearbySharingServiceImpl::OnPayloadPathRegistered(
    base::ScopedClosureRunner closure_runner,
    bool* aggregated_success,
    ::nearby::connections::mojom::Status status) {
  if (status != ::nearby::connections::mojom::Status::kSuccess) {
    *aggregated_success = false;
  }
}

void NearbySharingServiceImpl::OnPayloadPathsRegistered(
    const ShareTarget& share_target,
    std::unique_ptr<bool> aggregated_success,
    StatusCodesCallback status_codes_callback) {
  DCHECK(aggregated_success);
  if (!*aggregated_success) {
    RecordNearbyShareError(NearbyShareError::kPayloadPathsRegisteredFailed);
    CD_LOG(WARNING, Feature::NS)
        << __func__
        << ": Not all payload paths could be registered successfully.";
    std::move(status_codes_callback).Run(StatusCodes::kError);
    return;
  }

  ShareTargetInfo* info = GetShareTargetInfo(share_target);
  if (!info || !info->connection()) {
    RecordNearbyShareError(
        NearbyShareError::kPayloadPathsRegisteredUnknownShareTarget);
    CD_LOG(WARNING, Feature::NS)
        << __func__ << ": Accept invoked for unknown share target";
    std::move(status_codes_callback).Run(StatusCodes::kOutOfOrderApiCall);
    return;
  }
  NearbyConnection* connection = info->connection();

  if (!info->transfer_update_callback()) {
    RecordNearbyShareError(
        NearbyShareError::kPayloadPathsRegisteredMissingTransferUpdateCallback);
    CD_LOG(WARNING, Feature::NS)
        << __func__
        << ": Accept invoked for share target without transfer "
           "update callback. Disconnecting.";
    AbortAndCloseConnectionIfNecessary(
        TransferMetadata::Status::kMissingTransferUpdateCallback, share_target);
    std::move(status_codes_callback).Run(StatusCodes::kOutOfOrderApiCall);
    return;
  }

  info->set_payload_tracker(std::make_unique<PayloadTracker>(
      share_target, attachment_info_map_,
      base::BindRepeating(&NearbySharingServiceImpl::OnPayloadTransferUpdate,
                          weak_ptr_factory_.GetWeakPtr())));

  // Register status listener for all payloads.
  for (int64_t attachment_id : share_target.GetAttachmentIds()) {
    std::optional<int64_t> payload_id = GetAttachmentPayloadId(attachment_id);
    if (!payload_id) {
      CD_LOG(WARNING, Feature::NS)
          << __func__ << ": Failed to retrieve payload for attachment id - "
          << attachment_id;
      continue;
    }

    CD_LOG(VERBOSE, Feature::NS)
        << __func__ << ": Started listening for progress on payload - "
        << *payload_id;

    nearby_connections_manager_->RegisterPayloadStatusListener(
        *payload_id, info->payload_tracker());

    CD_LOG(VERBOSE, Feature::NS)
        << __func__ << ": Accepted incoming files from share target - "
        << share_target.id;
  }

  WriteResponse(*connection, sharing::nearby::ConnectionResponseFrame::ACCEPT);
  CD_LOG(VERBOSE, Feature::NS)
      << __func__ << ": Successfully wrote response frame";

  // Receiver event
  for (auto& observer : observers_) {
    observer.OnTransferStarted(share_target,
                               transfer_size_map_[info->endpoint_id().value()]);
  }

  info->transfer_update_callback()->OnTransferUpdate(
      share_target,
      TransferMetadataBuilder()
          .set_status(TransferMetadata::Status::kAwaitingRemoteAcceptance)
          .set_token(info->token())
          .build());

  std::optional<std::string> endpoint_id = info->endpoint_id();
  if (endpoint_id) {
    // Upgrade bandwidth regardless of advertising visibility because either
    // the system or the user has verified the sender's identity; the
    // stable identifiers potentially exposed by performing a bandwidth
    // upgrade are no longer a concern.
    nearby_connections_manager_->UpgradeBandwidth(*endpoint_id);
  } else {
    RecordNearbyShareError(
        NearbyShareError::kPayloadPathsRegisteredMissingEndpointId);
    CD_LOG(WARNING, Feature::NS)
        << __func__
        << ": Failed to initiate bandwidth upgrade. No endpoint_id "
           "found for target - "
        << share_target.id;
    std::move(status_codes_callback).Run(StatusCodes::kOutOfOrderApiCall);
    return;
  }

  std::move(status_codes_callback).Run(StatusCodes::kOk);
}

void NearbySharingServiceImpl::OnOutgoingConnection(
    const ShareTarget& share_target,
    base::TimeTicks connect_start_time,
    NearbyConnection* connection) {
  OutgoingShareTargetInfo* info = GetOutgoingShareTargetInfo(share_target);
  bool success = info && info->endpoint_id() && connection;
  RecordNearbyShareEstablishConnectionMetrics(
      success, /*cancelled=*/
      all_cancelled_share_target_ids_.contains(share_target.id),
      base::TimeTicks::Now() - connect_start_time);

  if (!success) {
    RecordNearbyShareError(
        NearbyShareError::kOutgoingConnectionFailedtoInitiateConnection);
    CD_LOG(WARNING, Feature::NS)
        << __func__ << ": Failed to initate connection to share target "
        << share_target.id;
    AbortAndCloseConnectionIfNecessary(
        TransferMetadata::Status::kFailedToInitiateOutgoingConnection,
        share_target);
    return;
  }

  info->set_connection(connection);

  CHECK(info->endpoint_id().has_value());
  transfer_profiler_->OnConnectionEstablished(info->endpoint_id().value());
  for (auto& observer : observers_) {
    observer.OnShareTargetConnected(share_target);
  }

  connection->SetDisconnectionListener(base::BindOnce(
      &NearbySharingServiceImpl::OnOutgoingConnectionDisconnected,
      weak_ptr_factory_.GetWeakPtr(), share_target));

  std::optional<std::string> four_digit_token =
      ToFourDigitString(nearby_connections_manager_->GetRawAuthenticationToken(
          *info->endpoint_id()));

  RunPairedKeyVerification(
      share_target, *info->endpoint_id(),
      base::BindOnce(
          &NearbySharingServiceImpl::OnOutgoingConnectionKeyVerificationDone,
          weak_ptr_factory_.GetWeakPtr(), share_target,
          std::move(four_digit_token)));
}

void NearbySharingServiceImpl::SendIntroduction(
    const ShareTarget& share_target,
    std::optional<std::string> four_digit_token) {
  // We successfully connected! Now lets build up Payloads for all the files we
  // want to send them. We won't send any just yet, but we'll send the Payload
  // IDs in our our introduction frame so that they know what to expect if they
  // accept.
  CD_LOG(VERBOSE, Feature::NS)
      << __func__ << ": Preparing to send introduction to " << share_target.id;

  ShareTargetInfo* info = GetShareTargetInfo(share_target);
  if (!info || !info->connection()) {
    RecordNearbyShareError(
        NearbyShareError::kSendIntroductionFailedToGetShareTarget);
    CD_LOG(WARNING, Feature::NS)
        << __func__ << ": No NearbyConnection tied to " << share_target.id;
    return;
  }
  NearbyConnection* connection = info->connection();

  if (!info->transfer_update_callback()) {
    RecordNearbyShareError(
        NearbyShareError::kSendIntroductionMissingTransferUpdateCallback);
    CD_LOG(WARNING, Feature::NS)
        << __func__ << ": No transfer update callback, disconnecting.";
    AbortAndCloseConnectionIfNecessary(
        TransferMetadata::Status::kMissingTransferUpdateCallback, share_target);
    return;
  }

  if (foreground_send_transfer_callbacks_.empty() &&
      background_send_transfer_callbacks_.empty()) {
    RecordNearbyShareError(
        NearbyShareError::kSendIntroductionNoSendTransferCallbacks);
    CD_LOG(WARNING, Feature::NS)
        << __func__ << ": No transfer callbacks, disconnecting.";
    connection->Close();
    return;
  }

  // Build the introduction.
  auto introduction = std::make_unique<sharing::nearby::IntroductionFrame>();
  CD_LOG(VERBOSE, Feature::NS)
      << __func__ << ": Sending attachments to " << share_target.id;

  // Write introduction of file payloads.
  int64_t transfer_size = 0;
  for (const auto& file : share_target.file_attachments) {
    std::optional<int64_t> payload_id = GetAttachmentPayloadId(file.id());
    if (!payload_id) {
      CD_LOG(VERBOSE, Feature::NS)
          << __func__ << ": Skipping unknown file attachment";
      continue;
    }
    auto* file_metadata = introduction->add_file_metadata();
    file_metadata->set_id(file.id());
    file_metadata->set_name(file.file_name());
    file_metadata->set_payload_id(*payload_id);
    file_metadata->set_type(sharing::ConvertFileMetadataType(file.type()));
    file_metadata->set_mime_type(file.mime_type());
    file_metadata->set_size(file.size());
    transfer_size += file.size();
  }

  // Write introduction of text payloads.
  for (const auto& text : share_target.text_attachments) {
    std::optional<int64_t> payload_id = GetAttachmentPayloadId(text.id());
    if (!payload_id) {
      CD_LOG(VERBOSE, Feature::NS)
          << __func__ << ": Skipping unknown text attachment";
      continue;
    }
    auto* text_metadata = introduction->add_text_metadata();
    text_metadata->set_id(text.id());
    text_metadata->set_text_title(text.text_title());
    text_metadata->set_type(sharing::ConvertTextMetadataType(text.type()));
    text_metadata->set_size(text.size());
    text_metadata->set_payload_id(*payload_id);
    transfer_size += text.size();
  }

  if (introduction->file_metadata_size() == 0 &&
      introduction->text_metadata_size() == 0) {
    RecordNearbyShareError(NearbyShareError::kSendIntroductionNoPayloads);
    CD_LOG(WARNING, Feature::NS)
        << __func__ << ": No payloads tied to transfer, disconnecting.";
    AbortAndCloseConnectionIfNecessary(
        TransferMetadata::Status::kMissingPayloads, share_target);
    return;
  }

  // Write the introduction to the remote device.
  sharing::nearby::Frame frame;
  frame.set_version(sharing::nearby::Frame::V1);
  sharing::nearby::V1Frame* v1_frame = frame.mutable_v1();
  v1_frame->set_type(sharing::nearby::V1Frame::INTRODUCTION);
  v1_frame->set_allocated_introduction(introduction.release());

  std::vector<uint8_t> data(frame.ByteSizeLong());
  frame.SerializeToArray(data.data(), frame.ByteSizeLong());
  connection->Write(std::move(data));

  // We've successfully written the introduction, so we now have to wait for the
  // remote side to accept.
  RecordNearbyShareTimeFromInitiateSendToRemoteDeviceNotificationMetric(
      base::TimeTicks::Now() - send_attachments_timestamp_);
  CD_LOG(VERBOSE, Feature::NS)
      << __func__ << ": Successfully wrote the introduction frame";

  CHECK(info->endpoint_id().has_value());
  transfer_profiler_->OnIntroductionFrameSent(info->endpoint_id().value());

  // Store the file size for use when the transfer actually begins.
  transfer_size_map_[info->endpoint_id().value()] = transfer_size;

  mutual_acceptance_timeout_alarm_.Reset(base::BindOnce(
      &NearbySharingServiceImpl::OnOutgoingMutualAcceptanceTimeout,
      weak_ptr_factory_.GetWeakPtr(), share_target));

  base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
      FROM_HERE, base::BindOnce(mutual_acceptance_timeout_alarm_.callback()),
      kReadResponseFrameTimeout);

  info->transfer_update_callback()->OnTransferUpdate(
      share_target,
      TransferMetadataBuilder()
          .set_status(TransferMetadata::Status::kAwaitingLocalConfirmation)
          .set_token(four_digit_token)
          .build());
}

void NearbySharingServiceImpl::CreatePayloads(
    ShareTarget share_target,
    base::OnceCallback<void(ShareTarget, bool)> callback) {
  OutgoingShareTargetInfo* info = GetOutgoingShareTargetInfo(share_target);
  if (!info || !share_target.has_attachments()) {
    RecordNearbyShareError(NearbyShareError::kCreatePayloadsNoAttachments);
    std::move(callback).Run(std::move(share_target), /*success=*/false);
    return;
  }

  if (!info->file_payloads().empty() || !info->text_payloads().empty()) {
    // We may have already created the payloads in the case of retry, so we can
    // skip this step.
    RecordNearbyShareError(
        NearbyShareError::kCreatePayloadsNoFileOrTextPayloads);
    std::move(callback).Run(std::move(share_target), /*success=*/false);
    return;
  }

  info->set_text_payloads(CreateTextPayloads(share_target.text_attachments));
  if (share_target.file_attachments.empty()) {
    std::move(callback).Run(std::move(share_target), /*success=*/true);
    return;
  }

  std::vector<base::FilePath> file_paths;
  for (const FileAttachment& attachment : share_target.file_attachments) {
    if (!attachment.file_path()) {
      RecordNearbyShareError(
          NearbyShareError::kCreatePayloadsFilePayloadWithoutPath);
      CD_LOG(WARNING, Feature::NS)
          << __func__ << ": Got file attachment without path";
      std::move(callback).Run(std::move(share_target), /*success=*/false);
      return;
    }
    file_paths.push_back(*attachment.file_path());
  }

  file_handler_.OpenFiles(
      std::move(file_paths),
      base::BindOnce(&NearbySharingServiceImpl::OnOpenFiles,
                     weak_ptr_factory_.GetWeakPtr(), std::move(share_target),
                     std::move(callback)));
}

void NearbySharingServiceImpl::OnCreatePayloads(
    std::vector<uint8_t> endpoint_info,
    ShareTarget share_target,
    bool success) {
  OutgoingShareTargetInfo* info = GetOutgoingShareTargetInfo(share_target);
  bool has_payloads = info && (!info->text_payloads().empty() ||
                               !info->file_payloads().empty());
  if (!success || !has_payloads || !info->endpoint_id()) {
    RecordNearbyShareError(NearbyShareError::kOnCreatePayloadsFailed);
    CD_LOG(WARNING, Feature::NS)
        << __func__
        << ": Failed to send file to remote ShareTarget. Failed to "
           "create payloads.";
    if (info && info->transfer_update_callback()) {
      info->transfer_update_callback()->OnTransferUpdate(
          share_target,
          TransferMetadataBuilder()
              .set_status(TransferMetadata::Status::kMediaUnavailable)
              .build());
    }
    return;
  }

  std::optional<std::vector<uint8_t>> bluetooth_mac_address =
      GetBluetoothMacAddressForShareTarget(share_target);

  // For metrics.
  all_cancelled_share_target_ids_.clear();

  nearby_connections_manager_->Connect(
      std::move(endpoint_info), *info->endpoint_id(),
      std::move(bluetooth_mac_address), settings_.GetDataUsage(),
      base::BindOnce(&NearbySharingServiceImpl::OnOutgoingConnection,
                     weak_ptr_factory_.GetWeakPtr(), share_target,
                     base::TimeTicks::Now()));
}

void NearbySharingServiceImpl::OnOpenFiles(
    ShareTarget share_target,
    base::OnceCallback<void(ShareTarget, bool)> callback,
    std::vector<NearbyFileHandler::FileInfo> files) {
  OutgoingShareTargetInfo* info = GetOutgoingShareTargetInfo(share_target);
  const bool files_open_success =
      (files.size() == share_target.file_attachments.size());
  RecordNearbySharePayloadFileOperationMetrics(
      profile_, share_target, PayloadFileOperation::kOpen, files_open_success);
  if (!info || !files_open_success) {
    RecordNearbyShareError(NearbyShareError::kOnOpenFilesFailed);
    std::move(callback).Run(std::move(share_target), /*success=*/false);
    return;
  }

  std::vector<nearby::connections::mojom::PayloadPtr> payloads;
  payloads.reserve(files.size());

  for (size_t i = 0; i < files.size(); ++i) {
    FileAttachment& attachment = share_target.file_attachments[i];
    attachment.set_size(files[i].size);
    base::File& file = files[i].file;
    int64_t payload_id = GeneratePayloadId();
    SetAttachmentPayloadId(attachment, payload_id);
    payloads.push_back(nearby::connections::mojom::Payload::New(
        payload_id,
        nearby::connections::mojom::PayloadContent::NewFile(
            nearby::connections::mojom::FilePayload::New(std::move(file)))));
  }

  info->set_file_payloads(std::move(payloads));
  std::move(callback).Run(std::move(share_target), /*success=*/true);
}

std::vector<nearby::connections::mojom::PayloadPtr>
NearbySharingServiceImpl::CreateTextPayloads(
    const std::vector<TextAttachment>& attachments) {
  std::vector<nearby::connections::mojom::PayloadPtr> payloads;
  payloads.reserve(attachments.size());
  for (const TextAttachment& attachment : attachments) {
    const std::string& body = attachment.text_body();
    std::vector<uint8_t> bytes(body.begin(), body.end());

    int64_t payload_id = GeneratePayloadId();
    SetAttachmentPayloadId(attachment, payload_id);
    payloads.push_back(nearby::connections::mojom::Payload::New(
        payload_id,
        nearby::connections::mojom::PayloadContent::NewBytes(
            nearby::connections::mojom::BytesPayload::New(std::move(bytes)))));
  }
  return payloads;
}

void NearbySharingServiceImpl::WriteResponse(
    NearbyConnection& connection,
    sharing::nearby::ConnectionResponseFrame::Status status) {
  sharing::nearby::Frame frame;
  frame.set_version(sharing::nearby::Frame::V1);
  sharing::nearby::V1Frame* v1_frame = frame.mutable_v1();
  v1_frame->set_type(sharing::nearby::V1Frame::RESPONSE);
  v1_frame->mutable_connection_response()->set_status(status);

  std::vector<uint8_t> data(frame.ByteSizeLong());
  frame.SerializeToArray(data.data(), frame.ByteSizeLong());

  connection.Write(std::move(data));
}

void NearbySharingServiceImpl::WriteCancel(NearbyConnection& connection) {
  CD_LOG(INFO, Feature::NS) << __func__ << ": Writing cancel frame.";

  sharing::nearby::Frame frame;
  frame.set_version(sharing::nearby::Frame::V1);
  sharing::nearby::V1Frame* v1_frame = frame.mutable_v1();
  v1_frame->set_type(sharing::nearby::V1Frame::CANCEL);

  std::vector<uint8_t> data(frame.ByteSizeLong());
  frame.SerializeToArray(data.data(), frame.ByteSizeLong());

  connection.Write(std::move(data));
}

void NearbySharingServiceImpl::Fail(const ShareTarget& share_target,
                                    TransferMetadata::Status status) {
  ShareTargetInfo* info = GetShareTargetInfo(share_target);
  if (!info || !info->connection()) {
    RecordNearbyShareError(NearbyShareError::kFailUnknownShareTarget);
    CD_LOG(WARNING, Feature::NS)
        << __func__ << ": Fail invoked for unknown share target.";
    return;
  }
  NearbyConnection* connection = info->connection();

  base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
      FROM_HERE,
      base::BindOnce(&NearbySharingServiceImpl::CloseConnection,
                     weak_ptr_factory_.GetWeakPtr(), share_target),
      kIncomingRejectionDelay);

  connection->SetDisconnectionListener(
      base::BindOnce(&NearbySharingServiceImpl::UnregisterShareTarget,
                     weak_ptr_factory_.GetWeakPtr(), share_target));

  // Send response to remote device.
  sharing::nearby::ConnectionResponseFrame::Status response_status;
  switch (status) {
    case TransferMetadata::Status::kNotEnoughSpace:
      response_status =
          sharing::nearby::ConnectionResponseFrame::NOT_ENOUGH_SPACE;
      break;

    case TransferMetadata::Status::kUnsupportedAttachmentType:
      response_status =
          sharing::nearby::ConnectionResponseFrame::UNSUPPORTED_ATTACHMENT_TYPE;
      break;

    case TransferMetadata::Status::kTimedOut:
      response_status = sharing::nearby::ConnectionResponseFrame::TIMED_OUT;
      break;

    default:
      response_status = sharing::nearby::ConnectionResponseFrame::UNKNOWN;
      break;
  }

  WriteResponse(*connection, response_status);

  if (info->transfer_update_callback()) {
    info->transfer_update_callback()->OnTransferUpdate(
        share_target, TransferMetadataBuilder().set_status(status).build());
  }
}

void NearbySharingServiceImpl::OnIncomingAdvertisementDecoded(
    const std::string& endpoint_id,
    ShareTarget placeholder_share_target,
    sharing::mojom::AdvertisementPtr advertisement) {
  NearbyConnection* connection = GetConnection(placeholder_share_target);
  if (!connection) {
    RecordNearbyShareError(
        NearbyShareError::kIncomingAdvertisementDecodedInvalidConnection);
    CD_LOG(WARNING, Feature::NS)
        << __func__ << ": Invalid connection for endoint id - " << endpoint_id;
    return;
  }

  if (!advertisement) {
    RecordNearbyShareError(
        NearbyShareError::kIncomingAdvertisementDecodedFailedToParse);
    CD_LOG(WARNING, Feature::NS)
        << __func__ << ": Failed to parse incoming connection from endpoint - "
        << endpoint_id << ", disconnecting.";
    AbortAndCloseConnectionIfNecessary(
        TransferMetadata::Status::kDecodeAdvertisementFailed,
        placeholder_share_target);
    return;
  }

  transfer_profiler_->OnIncomingEndpointDecoded(endpoint_id,
                                                IsInHighVisibility());

  NearbyShareEncryptedMetadataKey encrypted_metadata_key =
      AdvertisementToKey(advertisement);
  GetCertificateManager()->GetDecryptedPublicCertificate(
      std::move(encrypted_metadata_key),
      base::BindOnce(&NearbySharingServiceImpl::OnIncomingDecryptedCertificate,
                     weak_ptr_factory_.GetWeakPtr(), endpoint_id,
                     std::move(advertisement),
                     std::move(placeholder_share_target)));
}

void NearbySharingServiceImpl::OnIncomingTransferUpdate(
    const ShareTarget& share_target,
    const TransferMetadata& metadata) {
  // kInProgress status is logged extensively elsewhere so avoid the spam.
  if (metadata.status() != TransferMetadata::Status::kInProgress) {
    CD_LOG(VERBOSE, Feature::NS)
        << __func__ << ": Nearby Share service: "
        << "Incoming transfer update for share target with ID "
        << share_target.id << ": "
        << TransferMetadata::StatusToString(metadata.status());
  }
  if (metadata.status() != TransferMetadata::Status::kCancelled &&
      metadata.status() != TransferMetadata::Status::kRejected) {
    last_incoming_metadata_ =
        std::make_pair(share_target, TransferMetadataBuilder::Clone(metadata)
                                         .set_is_original(false)
                                         .build());
  } else {
    last_incoming_metadata_ = std::nullopt;
  }

  // Failed or cancelled transfers result in the progress being set to 0.
  if (!metadata.is_final_status()) {
    for (auto& observer : observers_) {
      observer.OnTransferUpdated(share_target, metadata.progress());
    }
  }

  if (metadata.is_final_status()) {
    RecordNearbyShareTransferFinalStatusMetric(
        &feature_usage_metrics_,
        /*is_incoming=*/true, share_target.type, metadata.status(),
        share_target.is_known, share_target.for_self_share, is_screen_locked_);

    ShareTargetInfo* info = GetShareTargetInfo(share_target);
    CHECK(info->endpoint_id().has_value());
    transfer_profiler_->OnReceiveComplete(info->endpoint_id().value(),
                                          metadata.status());

    for (auto& observer : observers_) {
      observer.OnTransferCompleted(share_target, metadata.status());
    }

    OnTransferComplete();
    if (metadata.status() != TransferMetadata::Status::kComplete) {
      // For any type of failure, lets make sure any pending files get cleaned
      // up.
      RemoveIncomingPayloads(share_target);
    }
  } else if (metadata.status() ==
             TransferMetadata::Status::kAwaitingLocalConfirmation) {
    OnTransferStarted(/*is_incoming=*/true);
  }

  base::ObserverList<TransferUpdateCallback>& transfer_callbacks =
      foreground_receive_callbacks_.empty() ? background_receive_callbacks_
                                            : foreground_receive_callbacks_;

  for (TransferUpdateCallback& callback : transfer_callbacks) {
    callback.OnTransferUpdate(share_target, metadata);
  }
}

void NearbySharingServiceImpl::OnOutgoingTransferUpdate(
    const ShareTarget& share_target,
    const TransferMetadata& metadata) {
  // kInProgress status is logged extensively elsewhere so avoid the spam.
  if (metadata.status() != TransferMetadata::Status::kInProgress) {
    CD_LOG(VERBOSE, Feature::NS)
        << __func__ << ": Nearby Share service: "
        << "Outgoing transfer update for share target with ID "
        << share_target.id << ": "
        << TransferMetadata::StatusToString(metadata.status());
  }

  // Failed or cancelled transfers result in the progress being set to 0.
  if (!metadata.is_final_status()) {
    for (auto& observer : observers_) {
      observer.OnTransferUpdated(share_target, metadata.progress());
    }
  }

  if (metadata.is_final_status()) {
    is_connecting_ = false;
    RecordNearbyShareTransferFinalStatusMetric(
        &feature_usage_metrics_,
        /*is_incoming=*/false, share_target.type, metadata.status(),
        share_target.is_known, share_target.for_self_share, is_screen_locked_);

    ShareTargetInfo* info = GetShareTargetInfo(share_target);
    CHECK(info->endpoint_id().has_value());
    transfer_profiler_->OnSendComplete(info->endpoint_id().value(),
                                       metadata.status());

    for (auto& observer : observers_) {
      observer.OnTransferCompleted(share_target, metadata.status());
    }

    OnTransferComplete();
  } else if (metadata.status() == TransferMetadata::Status::kMediaDownloading ||
             metadata.status() ==
                 TransferMetadata::Status::kAwaitingLocalConfirmation) {
    is_connecting_ = false;
    OnTransferStarted(/*is_incoming=*/false);
  }

  bool has_foreground_send_surface =
      !foreground_send_transfer_callbacks_.empty();
  base::ObserverList<TransferUpdateCallback>& transfer_callbacks =
      has_foreground_send_surface ? foreground_send_transfer_callbacks_
                                  : background_send_transfer_callbacks_;

  for (TransferUpdateCallback& callback : transfer_callbacks) {
    callback.OnTransferUpdate(share_target, metadata);
  }

  if (has_foreground_send_surface && metadata.is_final_status()) {
    last_outgoing_metadata_ = std::nullopt;
  } else {
    last_outgoing_metadata_ =
        std::make_pair(share_target, TransferMetadataBuilder::Clone(metadata)
                                         .set_is_original(false)
                                         .build());
  }
}

void NearbySharingServiceImpl::CloseConnection(
    const ShareTarget& share_target) {
  NearbyConnection* connection = GetConnection(share_target);
  if (!connection) {
    RecordNearbyShareError(NearbyShareError::kCloseConnectionInvalidConnection);
    CD_LOG(WARNING, Feature::NS)
        << __func__ << ": Invalid connection for target - " << share_target.id;
    return;
  }
  connection->Close();
}

void NearbySharingServiceImpl::OnIncomingDecryptedCertificate(
    const std::string& endpoint_id,
    sharing::mojom::AdvertisementPtr advertisement,
    ShareTarget placeholder_share_target,
    std::optional<NearbyShareDecryptedPublicCertificate> certificate) {
  NearbyConnection* connection = GetConnection(placeholder_share_target);
  if (!connection) {
    RecordNearbyShareError(
        NearbyShareError::kIncomingDecryptedCertificateInvalidConnection);
    CD_LOG(VERBOSE, Feature::NS)
        << __func__ << ": Invalid connection for endpoint id - " << endpoint_id;
    return;
  }

  // Remove placeholder share target since we are creating the actual share
  // target below.
  incoming_share_target_info_map_.erase(placeholder_share_target.id);

  std::optional<ShareTarget> share_target = CreateShareTarget(
      endpoint_id, advertisement, std::move(certificate), /*is_incoming=*/true);

  if (!share_target) {
    RecordNearbyShareError(
        NearbyShareError::
            kIncomingDecryptedCertificateFailedToCreateShareTarget);
    CD_LOG(WARNING, Feature::NS)
        << __func__
        << ": Failed to convert advertisement to share target for "
           "incoming connection, disconnecting";
    AbortAndCloseConnectionIfNecessary(
        TransferMetadata::Status::kMissingShareTarget,
        placeholder_share_target);
    return;
  }

  CD_LOG(VERBOSE, Feature::NS)
      << __func__ << ": Received incoming connection from " << share_target->id;

  for (auto& observer : observers_) {
    observer.OnShareTargetConnected(share_target.value());
  }

  ShareTargetInfo* share_target_info = GetShareTargetInfo(*share_target);
  DCHECK(share_target_info);
  share_target_info->set_connection(connection);

  share_target_info->set_transfer_update_callback(
      std::make_unique<TransferUpdateDecorator>(base::BindRepeating(
          &NearbySharingServiceImpl::OnIncomingTransferUpdate,
          weak_ptr_factory_.GetWeakPtr())));

  connection->SetDisconnectionListener(
      base::BindOnce(&NearbySharingServiceImpl::UnregisterShareTarget,
                     weak_ptr_factory_.GetWeakPtr(), *share_target));

  std::optional<std::string> four_digit_token = ToFourDigitString(
      nearby_connections_manager_->GetRawAuthenticationToken(endpoint_id));

  RunPairedKeyVerification(
      *share_target, endpoint_id,
      base::BindOnce(
          &NearbySharingServiceImpl::OnIncomingConnectionKeyVerificationDone,
          weak_ptr_factory_.GetWeakPtr(), *share_target,
          std::move(four_digit_token)));
}

void NearbySharingServiceImpl::RunPairedKeyVerification(
    const ShareTarget& share_target,
    const std::string& endpoint_id,
    base::OnceCallback<void(
        PairedKeyVerificationRunner::PairedKeyVerificationResult)> callback) {
  DCHECK(profile_);
  std::optional<std::vector<uint8_t>> token =
      nearby_connections_manager_->GetRawAuthenticationToken(endpoint_id);
  if (!token) {
    RecordNearbyShareError(
        NearbyShareError::
            kRunPairedKeyVerificationFailedToReadAuthenticationToken);
    CD_LOG(VERBOSE, Feature::NS)
        << __func__ << ": Failed to read authentication token from endpoint - "
        << endpoint_id;
    std::move(callback).Run(
        PairedKeyVerificationRunner::PairedKeyVerificationResult::kFail);
    return;
  }

  ShareTargetInfo* share_target_info = GetShareTargetInfo(share_target);
  DCHECK(share_target_info);

  share_target_info->set_frames_reader(std::make_unique<IncomingFramesReader>(
      process_manager_, share_target_info->connection()));

  bool restrict_to_contacts =
      features::IsRestrictToContactsEnabled() && share_target.is_incoming &&
      advertising_power_level_ !=
          NearbyConnectionsManager::PowerLevel::kHighPower;
  share_target_info->set_key_verification_runner(
      std::make_unique<PairedKeyVerificationRunner>(
          share_target, endpoint_id, *token, share_target_info->connection(),
          share_target_info->certificate(), GetCertificateManager(),
          settings_.GetVisibility(), restrict_to_contacts,
          share_target_info->frames_reader(), kReadFramesTimeout));
  share_target_info->key_verification_runner()->Run(std::move(callback));
}

void NearbySharingServiceImpl::OnIncomingConnectionKeyVerificationDone(
    ShareTarget share_target,
    std::optional<std::string> four_digit_token,
    PairedKeyVerificationRunner::PairedKeyVerificationResult result) {
  ShareTargetInfo* info = GetShareTargetInfo(share_target);
  if (!info || !info->connection() || !info->endpoint_id()) {
    RecordNearbyShareError(
        NearbyShareError::
            kIncomingConnectionKeyVerificationInvalidConnectionOrEndpointId);
    CD_LOG(VERBOSE, Feature::NS)
        << __func__ << ": Invalid connection or endpoint id";
    return;
  }

  switch (result) {
    case PairedKeyVerificationRunner::PairedKeyVerificationResult::kFail:
      RecordNearbyShareError(
          NearbyShareError::kIncomingConnectionKeyVerificationFailed);
      CD_LOG(VERBOSE, Feature::NS)
          << __func__ << ": Paired key handshake failed for target "
          << share_target.id << ". Disconnecting.";
      AbortAndCloseConnectionIfNecessary(
          TransferMetadata::Status::kPairedKeyVerificationFailed, share_target);
      return;

    case PairedKeyVerificationRunner::PairedKeyVerificationResult::kSuccess:
      CD_LOG(VERBOSE, Feature::NS)
          << __func__ << ": Paired key handshake succeeded for target - "
          << share_target.id;

      CHECK(info->endpoint_id().has_value());
      transfer_profiler_->OnPairedKeyHandshakeComplete(
          info->endpoint_id().value());

      // Upgrade bandwidth regardless of advertising visibility because the
      // sender's identity has been confirmed; the stable identifiers
      // potentially exposed by performing a bandwidth upgrade are no longer a
      // concern.
      nearby_connections_manager_->UpgradeBandwidth(*info->endpoint_id());
      ReceiveIntroduction(share_target, /*four_digit_token=*/std::nullopt);
      break;

    case PairedKeyVerificationRunner::PairedKeyVerificationResult::kUnable:
      CD_LOG(VERBOSE, Feature::NS)
          << __func__
          << ": Unable to verify paired key encryption when "
             "receiving connection from target - "
          << share_target.id;

      CHECK(info->endpoint_id().has_value());
      transfer_profiler_->OnPairedKeyHandshakeComplete(
          info->endpoint_id().value());

      if (advertising_power_level_ ==
          NearbyConnectionsManager::PowerLevel::kHighPower) {
        // Upgrade bandwidth if advertising at high-visibility. Bandwidth
        // upgrades may expose stable identifiers, but this isn't a concern
        // here because high-visibility already leaks the device name.
        nearby_connections_manager_->UpgradeBandwidth(*info->endpoint_id());
      }

      if (four_digit_token) {
        info->set_token(*four_digit_token);
      }

      ReceiveIntroduction(share_target, std::move(four_digit_token));
      break;

    case PairedKeyVerificationRunner::PairedKeyVerificationResult::kUnknown:
      RecordNearbyShareError(
          NearbyShareError::kIncomingConnectionKeyVerificationUnknownResult);
      CD_LOG(VERBOSE, Feature::NS)
          << __func__ << ": Unknown PairedKeyVerificationResult for target "
          << share_target.id << ". Disconnecting.";
      AbortAndCloseConnectionIfNecessary(
          TransferMetadata::Status::kPairedKeyVerificationFailed, share_target);
      break;
  }
}

void NearbySharingServiceImpl::OnOutgoingConnectionKeyVerificationDone(
    const ShareTarget& share_target,
    std::optional<std::string> four_digit_token,
    PairedKeyVerificationRunner::PairedKeyVerificationResult result) {
  ShareTargetInfo* info = GetShareTargetInfo(share_target);
  if (!info || !info->connection()) {
    RecordNearbyShareError(
        NearbyShareError::kOutgoingConnectionKeyVerificationMissingConnection);
    return;
  }

  if (!info->transfer_update_callback()) {
    RecordNearbyShareError(
        NearbyShareError::
            kOutgoingConnectionKeyVerificationMissingTransferUpdateCallback);
    CD_LOG(VERBOSE, Feature::NS)
        << __func__ << ": No transfer update callback. Disconnecting.";
    AbortAndCloseConnectionIfNecessary(
        TransferMetadata::Status::kMissingTransferUpdateCallback, share_target);
    return;
  }

  // TODO(crbug.com/1119279): Check if we need to set this to false for
  // Advanced Protection users.
  bool sender_skips_confirmation = true;

  switch (result) {
    case PairedKeyVerificationRunner::PairedKeyVerificationResult::kFail:
      RecordNearbyShareError(
          NearbyShareError::kOutgoingConnectionKeyVerificationFailed);
      CD_LOG(VERBOSE, Feature::NS)
          << __func__ << ": Paired key handshake failed for target "
          << share_target.id << ". Disconnecting.";
      AbortAndCloseConnectionIfNecessary(
          TransferMetadata::Status::kPairedKeyVerificationFailed, share_target);
      return;

    case PairedKeyVerificationRunner::PairedKeyVerificationResult::kSuccess:
      CD_LOG(VERBOSE, Feature::NS)
          << __func__ << ": Paired key handshake succeeded for target - "
          << share_target.id;
      SendIntroduction(share_target, /*four_digit_token=*/std::nullopt);
      SendPayloads(share_target);
      return;

    case PairedKeyVerificationRunner::PairedKeyVerificationResult::kUnable:
      CD_LOG(VERBOSE, Feature::NS)
          << __func__
          << ": Unable to verify paired key encryption when "
             "initating connection to target - "
          << share_target.id;

      if (four_digit_token) {
        info->set_token(*four_digit_token);
      }

      if (sender_skips_confirmation) {
        CD_LOG(VERBOSE, Feature::NS)
            << __func__
            << ": Sender-side verification is disabled. Skipping "
               "token comparison with "
            << share_target.id;
        SendIntroduction(share_target, /*four_digit_token=*/std::nullopt);
        SendPayloads(share_target);
      } else {
        SendIntroduction(share_target, std::move(four_digit_token));
      }
      return;

    case PairedKeyVerificationRunner::PairedKeyVerificationResult::kUnknown:
      RecordNearbyShareError(
          NearbyShareError::kOutgoingConnectionKeyVerificationUnknownResult);
      CD_LOG(VERBOSE, Feature::NS)
          << __func__ << ": Unknown PairedKeyVerificationResult for target "
          << share_target.id << ". Disconnecting.";
      AbortAndCloseConnectionIfNecessary(
          TransferMetadata::Status::kPairedKeyVerificationFailed, share_target);
      break;
  }
}

void NearbySharingServiceImpl::RefreshUIOnDisconnection(
    ShareTarget share_target) {
  ShareTargetInfo* info = GetShareTargetInfo(share_target);
  if (info && info->transfer_update_callback()) {
    info->transfer_update_callback()->OnTransferUpdate(
        share_target,
        TransferMetadataBuilder()
            .set_status(
                TransferMetadata::Status::kAwaitingRemoteAcceptanceFailed)
            .build());
  }

  UnregisterShareTarget(share_target);
}

void NearbySharingServiceImpl::ReceiveIntroduction(
    ShareTarget share_target,
    std::optional<std::string> four_digit_token) {
  CD_LOG(INFO, Feature::NS)
      << __func__ << ": Receiving introduction from " << share_target.id;

  ShareTargetInfo* info = GetShareTargetInfo(share_target);
  DCHECK(info && info->connection());

  CHECK(info->endpoint_id().has_value());
  transfer_profiler_->OnIntroductionFrameReceived(info->endpoint_id().value());

  info->frames_reader()->ReadFrame(
      sharing::mojom::V1Frame::Tag::kIntroduction,
      base::BindOnce(&NearbySharingServiceImpl::OnReceivedIntroduction,
                     weak_ptr_factory_.GetWeakPtr(), std::move(share_target),
                     std::move(four_digit_token)),
      kReadFramesTimeout);
}

void NearbySharingServiceImpl::OnReceivedIntroduction(
    ShareTarget share_target,
    std::optional<std::string> four_digit_token,
    std::optional<sharing::mojom::V1FramePtr> frame) {
  ShareTargetInfo* info = GetShareTargetInfo(share_target);
  if (!info || !info->connection()) {
    RecordNearbyShareError(
        NearbyShareError::kReceivedIntroductionMissingConnection);
    CD_LOG(WARNING, Feature::NS)
        << __func__
        << ": Ignore received introduction, due to no connection established.";
    return;
  }

  DCHECK(profile_);

  if (!frame) {
    RecordNearbyShareError(NearbyShareError::kReceivedIntroductionInvalidFrame);
    AbortAndCloseConnectionIfNecessary(
        TransferMetadata::Status::kInvalidIntroductionFrame, share_target);
    CD_LOG(WARNING, Feature::NS) << __func__ << ": Invalid introduction frame";
    return;
  }

  CD_LOG(INFO, Feature::NS)
      << __func__ << ": Successfully read the introduction frame.";

  base::CheckedNumeric<int64_t> file_size_sum(0);
  int64_t transfer_size = 0;

  sharing::mojom::IntroductionFramePtr introduction_frame =
      std::move((*frame)->get_introduction());
  for (const auto& file : introduction_frame->file_metadata) {
    if (file->size <= 0) {
      RecordNearbyShareError(
          NearbyShareError::kReceivedIntroductionInvalidAttachmentSize);
      Fail(share_target, TransferMetadata::Status::kUnsupportedAttachmentType);
      CD_LOG(WARNING, Feature::NS)
          << __func__
          << ": Ignore introduction, due to invalid attachment size";
      return;
    }

    CD_LOG(VERBOSE, Feature::NS)
        << __func__ << ": Found file attachment: id=" << file->id
        << ", type= " << file->type << ", size=" << file->size
        << ", payload_id=" << file->payload_id
        << ", mime_type=" << file->mime_type;
    FileAttachment attachment(file->id, file->size, file->name, file->mime_type,
                              file->type);
    SetAttachmentPayloadId(attachment, file->payload_id);
    share_target.file_attachments.push_back(std::move(attachment));

    file_size_sum += file->size;
    transfer_size += file->size;
    if (!file_size_sum.IsValid()) {
      RecordNearbyShareError(
          NearbyShareError::kReceivedIntroductionTotalFileSizeOverflow);
      Fail(share_target, TransferMetadata::Status::kNotEnoughSpace);
      CD_LOG(WARNING, Feature::NS)
          << __func__
          << ": Ignoring introduction, total file size overflowed "
             "64 bit integer.";
      return;
    }
  }

  for (const auto& text : introduction_frame->text_metadata) {
    transfer_size += text->size;
    if (text->size <= 0) {
      RecordNearbyShareError(
          NearbyShareError::kReceivedIntroductionInvalidTextAttachmentSize);
      Fail(share_target, TransferMetadata::Status::kUnsupportedAttachmentType);
      CD_LOG(WARNING, Feature::NS)
          << __func__
          << ": Ignore introduction, due to invalid attachment size";
      return;
    }

    CD_LOG(VERBOSE, Feature::NS)
        << __func__ << ": Found text attachment: id=" << text->id
        << ", type= " << text->type << ", size=" << text->size
        << ", payload_id=" << text->payload_id;
    TextAttachment attachment(text->id, text->type, text->text_title,
                              text->size);
    SetAttachmentPayloadId(attachment, text->payload_id);
    share_target.text_attachments.push_back(std::move(attachment));
  }

  for (const auto& wifi_credentials :
       introduction_frame->wifi_credentials_metadata) {
    if (wifi_credentials->ssid.empty()) {
      RecordNearbyShareError(
          NearbyShareError::kReceivedIntroductionInvalidWifiSSID);
      Fail(share_target, TransferMetadata::Status::kUnsupportedAttachmentType);
      CD_LOG(WARNING, Feature::NS)
          << __func__ << ": Ignore introduction, due to invalid Wi-Fi SSID";
      return;
    }

    CD_LOG(VERBOSE, Feature::NS)
        << __func__ << ": Found Wi-Fi Credentials: id=" << wifi_credentials->id
        << ", payload_id=" << wifi_credentials->payload_id
        << ", security_type=" << wifi_credentials->security_type;

    WifiCredentialsAttachment attachment(wifi_credentials->id,
                                         wifi_credentials->security_type,
                                         wifi_credentials->ssid);
    SetAttachmentPayloadId(attachment, wifi_credentials->payload_id);
    share_target.wifi_credentials_attachments.push_back(std::move(attachment));
  }

  if (!share_target.has_attachments()) {
    RecordNearbyShareError(
        NearbyShareError::kReceivedIntroductionShareTargetNoAttachment);
    CD_LOG(WARNING, Feature::NS)
        << __func__
        << ": No attachment is found for this share target. It can "
           "be result of unrecognizable attachment type";
    Fail(share_target, TransferMetadata::Status::kUnsupportedAttachmentType);

    CD_LOG(VERBOSE, Feature::NS)
        << __func__
        << ": We don't support the attachments sent by the sender. "
           "We have informed "
        << share_target.id;
    return;
  }

  if (file_size_sum.ValueOrDie() == 0) {
    OnStorageCheckCompleted(std::move(share_target),
                            std::move(four_digit_token),
                            /*is_out_of_storage=*/false);
    return;
  }

  CHECK(info->endpoint_id().has_value());
  transfer_size_map_[info->endpoint_id().value()] = transfer_size;

  base::FilePath download_path =
      DownloadPrefs::FromDownloadManager(profile_->GetDownloadManager())
          ->DownloadPath();

  base::ThreadPool::PostTaskAndReplyWithResult(
      FROM_HERE, {base::MayBlock()},
      base::BindOnce(&IsOutOfStorage, std::move(download_path),
                     file_size_sum.ValueOrDie(), free_disk_space_for_testing_),
      base::BindOnce(&NearbySharingServiceImpl::OnStorageCheckCompleted,
                     weak_ptr_factory_.GetWeakPtr(), std::move(share_target),
                     std::move(four_digit_token)));
}

void NearbySharingServiceImpl::ReceiveConnectionResponse(
    ShareTarget share_target) {
  CD_LOG(VERBOSE, Feature::NS)
      << __func__ << ": Receiving response frame from " << share_target.id;
  ShareTargetInfo* info = GetShareTargetInfo(share_target);
  DCHECK(info && info->connection());

  info->frames_reader()->ReadFrame(
      sharing::mojom::V1Frame::Tag::kConnectionResponse,
      base::BindOnce(&NearbySharingServiceImpl::OnReceiveConnectionResponse,
                     weak_ptr_factory_.GetWeakPtr(), std::move(share_target)),
      kReadResponseFrameTimeout);
}

void NearbySharingServiceImpl::OnReceiveConnectionResponse(
    ShareTarget share_target,
    std::optional<sharing::mojom::V1FramePtr> frame) {
  OutgoingShareTargetInfo* info = GetOutgoingShareTargetInfo(share_target);
  if (!info || !info->connection()) {
    RecordNearbyShareError(
        NearbyShareError::kReceiveConnectionResponseMissingConnection);
    CD_LOG(WARNING, Feature::NS)
        << __func__
        << ": Ignore received connection response, due to no "
           "connection established.";
    return;
  }

  if (!info->transfer_update_callback()) {
    RecordNearbyShareError(
        NearbyShareError::
            kReceiveConnectionResponseMissingTransferUpdateCallback);
    CD_LOG(WARNING, Feature::NS)
        << __func__ << ": No transfer update callback. Disconnecting.";
    AbortAndCloseConnectionIfNecessary(
        TransferMetadata::Status::kMissingTransferUpdateCallback, share_target);
    return;
  }

  if (!frame) {
    RecordNearbyShareError(
        NearbyShareError::kReceiveConnectionResponseInvalidFrame);
    CD_LOG(WARNING, Feature::NS)
        << __func__
        << ": Failed to read a response from the remote device. Disconnecting.";
    AbortAndCloseConnectionIfNecessary(
        TransferMetadata::Status::kFailedToReadOutgoingConnectionResponse,
        share_target);
    return;
  }

  mutual_acceptance_timeout_alarm_.Cancel();

  CD_LOG(VERBOSE, Feature::NS)
      << __func__ << ": Successfully read the connection response frame.";

  sharing::mojom::ConnectionResponseFramePtr response =
      std::move((*frame)->get_connection_response());
  switch (response->status) {
    case sharing::mojom::ConnectionResponseFrame::Status::kAccept: {
      info->frames_reader()->ReadFrame(
          base::BindOnce(&NearbySharingServiceImpl::OnFrameRead,
                         weak_ptr_factory_.GetWeakPtr(), share_target));

      info->transfer_update_callback()->OnTransferUpdate(
          share_target, TransferMetadataBuilder()
                            .set_status(TransferMetadata::Status::kInProgress)
                            .build());

      info->set_payload_tracker(std::make_unique<PayloadTracker>(
          share_target, attachment_info_map_,
          base::BindRepeating(
              &NearbySharingServiceImpl::OnPayloadTransferUpdate,
              weak_ptr_factory_.GetWeakPtr())));

      for (auto& payload : info->ExtractTextPayloads()) {
        nearby_connections_manager_->Send(
            *info->endpoint_id(), std::move(payload), info->payload_tracker());
      }
      for (auto& payload : info->ExtractFilePayloads()) {
        nearby_connections_manager_->Send(
            *info->endpoint_id(), std::move(payload), info->payload_tracker());
      }
      CD_LOG(VERBOSE, Feature::NS)
          << __func__
          << ": The connection was accepted. Payloads are now being sent.";

      CHECK(info->endpoint_id().has_value());
      transfer_profiler_->OnSendStart(info->endpoint_id().value());

      // Sender events
      for (auto& observer : observers_) {
        observer.OnTransferAccepted(share_target);
        observer.OnTransferStarted(
            share_target, transfer_size_map_[info->endpoint_id().value()]);
      }
      break;
    }
    case sharing::mojom::ConnectionResponseFrame::Status::kReject:
      AbortAndCloseConnectionIfNecessary(TransferMetadata::Status::kRejected,
                                         share_target);
      CD_LOG(VERBOSE, Feature::NS)
          << __func__
          << ": The connection was rejected. The connection has been closed.";
      break;
    case sharing::mojom::ConnectionResponseFrame::Status::kNotEnoughSpace:
      RecordNearbyShareError(
          NearbyShareError::kReceiveConnectionResponseNotEnoughSpace);
      AbortAndCloseConnectionIfNecessary(
          TransferMetadata::Status::kNotEnoughSpace, share_target);
      CD_LOG(VERBOSE, Feature::NS)
          << __func__
          << ": The connection was rejected because the remote device "
             "does not have enough space for our attachments. The "
             "connection has been closed.";
      break;
    case sharing::mojom::ConnectionResponseFrame::Status::
        kUnsupportedAttachmentType:
      RecordNearbyShareError(
          NearbyShareError::
              kReceiveConnectionResponseUnsupportedAttachmentType);
      AbortAndCloseConnectionIfNecessary(
          TransferMetadata::Status::kUnsupportedAttachmentType, share_target);
      CD_LOG(VERBOSE, Feature::NS)
          << __func__
          << ": The connection was rejected because the remote device "
             "does not support the attachments we were sending. The "
             "connection has been closed.";
      break;
    case sharing::mojom::ConnectionResponseFrame::Status::kTimedOut:
      RecordNearbyShareError(
          NearbyShareError::kReceiveConnectionResponseTimedOut);
      AbortAndCloseConnectionIfNecessary(TransferMetadata::Status::kTimedOut,
                                         share_target);
      CD_LOG(VERBOSE, Feature::NS)
          << __func__
          << ": The connection was rejected because the remote device "
             "timed out. The connection has been closed.";
      break;
    default:
      RecordNearbyShareError(
          NearbyShareError::kReceiveConnectionResponseConnectionFailed);
      AbortAndCloseConnectionIfNecessary(TransferMetadata::Status::kFailed,
                                         share_target);
      CD_LOG(VERBOSE, Feature::NS)
          << __func__
          << ": The connection failed. The connection has been closed.";
      break;
  }
}

void NearbySharingServiceImpl::OnStorageCheckCompleted(
    ShareTarget share_target,
    std::optional<std::string> four_digit_token,
    bool is_out_of_storage) {
  if (is_out_of_storage) {
    RecordNearbyShareError(
        NearbyShareError::kStorageCheckCompletedNotEnoughSpace);
    Fail(share_target, TransferMetadata::Status::kNotEnoughSpace);
    CD_LOG(WARNING, Feature::NS)
        << __func__ << ": Not enough space on the receiver. We have informed "
        << share_target.id;
    return;
  }

  ShareTargetInfo* info = GetShareTargetInfo(share_target);
  if (!info || !info->connection()) {
    RecordNearbyShareError(
        NearbyShareError::kStorageCheckCompletedMissingConnection);
    CD_LOG(WARNING, Feature::NS)
        << __func__ << ": Invalid connection for share target - "
        << share_target.id;
    return;
  }
  NearbyConnection* connection = info->connection();

  if (!info->transfer_update_callback()) {
    RecordNearbyShareError(
        NearbyShareError::kStorageCheckCompletedMissingTransferUpdateCallback);
    CD_LOG(VERBOSE, Feature::NS)
        << __func__ << ": No transfer update callback. Disconnecting.";
    AbortAndCloseConnectionIfNecessary(
        TransferMetadata::Status::kMissingTransferUpdateCallback, share_target);
    return;
  }

  mutual_acceptance_timeout_alarm_.Reset(base::BindOnce(
      &NearbySharingServiceImpl::OnIncomingMutualAcceptanceTimeout,
      weak_ptr_factory_.GetWeakPtr(), share_target));

  base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
      FROM_HERE, base::BindOnce(mutual_acceptance_timeout_alarm_.callback()),
      kReadResponseFrameTimeout);

  info->transfer_update_callback()->OnTransferUpdate(
      share_target,
      TransferMetadataBuilder()
          .set_status(TransferMetadata::Status::kAwaitingLocalConfirmation)
          .set_token(std::move(four_digit_token))
          .build());

  if (!incoming_share_target_info_map_.count(share_target.id)) {
    RecordNearbyShareError(
        NearbyShareError::kStorageCheckCompletedNoIncomingShareTarget);
    CD_LOG(VERBOSE, Feature::NS)
        << __func__ << ": IncomingShareTarget not found, disconnecting "
        << share_target.id;
    AbortAndCloseConnectionIfNecessary(
        TransferMetadata::Status::kMissingShareTarget, share_target);
    return;
  }

  connection->SetDisconnectionListener(base::BindOnce(
      &NearbySharingServiceImpl::OnIncomingConnectionDisconnected,
      weak_ptr_factory_.GetWeakPtr(), share_target));

  auto* frames_reader = info->frames_reader();
  if (!frames_reader) {
    RecordNearbyShareError(
        NearbyShareError::kStorageCheckCompletedNoFramesReader);
    CD_LOG(WARNING, Feature::NS)
        << __func__
        << ": Stopped reading further frames, due to no connection "
           "established.";
    return;
  }

  // Auto-accept self shares when not in high-visibility mode, unless the
  // filetype includes WiFi credentials.
  if (share_target.CanAutoAccept() && !IsInHighVisibility()) {
    CD_LOG(INFO, Feature::NS) << __func__ << ": Auto-accepting self share.";
    Accept(share_target, base::DoNothing());
  } else {
    CD_LOG(INFO, Feature::NS) << __func__ << ": Can't auto-accept transfer.";
  }

  frames_reader->ReadFrame(
      base::BindOnce(&NearbySharingServiceImpl::OnFrameRead,
                     weak_ptr_factory_.GetWeakPtr(), std::move(share_target)));
}

void NearbySharingServiceImpl::OnFrameRead(
    ShareTarget share_target,
    std::optional<sharing::mojom::V1FramePtr> frame) {
  if (!frame) {
    // This is the case when the connection has been closed since we wait
    // indefinitely for incoming frames.
    return;
  }

  sharing::mojom::V1FramePtr v1_frame = std::move(*frame);
  switch (v1_frame->which()) {
    case sharing::mojom::V1Frame::Tag::kCancelFrame:
      CD_LOG(INFO, Feature::NS)
          << __func__ << ": Read the cancel frame, closing connection";
      DoCancel(share_target, base::DoNothing(),
               /*is_initiator_of_cancellation=*/false);
      break;

    case sharing::mojom::V1Frame::Tag::kCertificateInfo:
      HandleCertificateInfoFrame(v1_frame->get_certificate_info());
      break;

    default:
      CD_LOG(VERBOSE, Feature::NS)
          << __func__ << ": Discarding unknown frame of type";
      break;
  }

  ShareTargetInfo* info = GetShareTargetInfo(share_target);
  if (!info || !info->frames_reader()) {
    RecordNearbyShareError(NearbyShareError::kFrameReadNoFrameReader);
    CD_LOG(WARNING, Feature::NS)
        << __func__
        << ": Stopped reading further frames, due to no connection "
           "established.";
    return;
  }

  info->frames_reader()->ReadFrame(
      base::BindOnce(&NearbySharingServiceImpl::OnFrameRead,
                     weak_ptr_factory_.GetWeakPtr(), std::move(share_target)));
}

void NearbySharingServiceImpl::HandleCertificateInfoFrame(
    const sharing::mojom::CertificateInfoFramePtr& certificate_frame) {
  DCHECK(certificate_frame);
}

void NearbySharingServiceImpl::OnIncomingConnectionDisconnected(
    const ShareTarget& share_target) {
  ShareTargetInfo* info = GetShareTargetInfo(share_target);
  if (info && info->transfer_update_callback()) {
    info->transfer_update_callback()->OnTransferUpdate(
        share_target,
        TransferMetadataBuilder()
            .set_status(TransferMetadata::Status::kUnexpectedDisconnection)
            .build());
  }
  UnregisterShareTarget(share_target);
}

void NearbySharingServiceImpl::OnOutgoingConnectionDisconnected(
    const ShareTarget& share_target) {
  ShareTargetInfo* info = GetShareTargetInfo(share_target);
  if (info && info->transfer_update_callback()) {
    info->transfer_update_callback()->OnTransferUpdate(
        share_target,
        TransferMetadataBuilder()
            .set_status(TransferMetadata::Status::kUnexpectedDisconnection)
            .build());
  }
  UnregisterShareTarget(share_target);
}

void NearbySharingServiceImpl::OnIncomingMutualAcceptanceTimeout(
    const ShareTarget& share_target) {
  DCHECK(share_target.is_incoming);

  RecordNearbyShareError(NearbyShareError::kIncomingMutualAcceptanceTimeout);
  CD_LOG(VERBOSE, Feature::NS)
      << __func__
      << ": Incoming mutual acceptance timed out, closing connection for "
      << share_target.id;

  Fail(share_target, TransferMetadata::Status::kTimedOut);
}

void NearbySharingServiceImpl::OnOutgoingMutualAcceptanceTimeout(
    const ShareTarget& share_target) {
  DCHECK(!share_target.is_incoming);

  RecordNearbyShareError(NearbyShareError::kOutgoingMutualAcceptanceTimeout);
  CD_LOG(VERBOSE, Feature::NS)
      << __func__
      << ": Outgoing mutual acceptance timed out, closing connection for "
      << share_target.id;

  AbortAndCloseConnectionIfNecessary(TransferMetadata::Status::kTimedOut,
                                     share_target);
}

std::optional<ShareTarget> NearbySharingServiceImpl::CreateShareTarget(
    const std::string& endpoint_id,
    const sharing::mojom::AdvertisementPtr& advertisement,
    std::optional<NearbyShareDecryptedPublicCertificate> certificate,
    bool is_incoming) {
  DCHECK(advertisement);

  if (!advertisement->device_name && !certificate) {
    RecordNearbyShareError(
        NearbyShareError::kCreateShareTargetFailedToRetreivePublicCertificate);
    CD_LOG(VERBOSE, Feature::NS)
        << __func__
        << ": Failed to retrieve public certificate for contact "
           "only advertisement.";
    return std::nullopt;
  }

  std::optional<std::string> device_name =
      GetDeviceName(advertisement, certificate);
  if (!device_name) {
    RecordNearbyShareError(
        NearbyShareError::kCreateShareTargetFailedToRetreiveDeviceName);
    CD_LOG(VERBOSE, Feature::NS)
        << __func__ << ": Failed to retrieve device name for advertisement.";
    return std::nullopt;
  }

  ShareTarget target;
  target.type = advertisement->device_type;
  target.device_name = std::move(*device_name);
  target.is_incoming = is_incoming;
  target.device_id = GetDeviceId(endpoint_id, certificate);
  target.for_self_share = certificate && certificate->for_self_share();

  ShareTargetInfo& info = GetOrCreateShareTargetInfo(target, endpoint_id);

  if (certificate) {
    if (certificate->unencrypted_metadata().has_full_name()) {
      target.full_name = certificate->unencrypted_metadata().full_name();
    }

    if (certificate->unencrypted_metadata().has_icon_url()) {
      target.image_url = GURL(certificate->unencrypted_metadata().icon_url());
    }

    target.is_known = true;
    info.set_certificate(std::move(*certificate));
  }

  share_target_map_[endpoint_id] = target;
  for (auto& observer : observers_) {
    observer.OnShareTargetAdded(target);
  }

  return target;
}

void NearbySharingServiceImpl::OnPayloadTransferUpdate(
    ShareTarget share_target,
    TransferMetadata metadata) {
  bool is_in_progress =
      metadata.status() == TransferMetadata::Status::kInProgress;

  if (is_in_progress && share_target.is_incoming &&
      is_waiting_to_record_accept_to_transfer_start_metric_) {
    RecordNearbyShareTimeFromLocalAcceptToTransferStartMetric(
        base::TimeTicks::Now() - incoming_share_accepted_timestamp_);
    is_waiting_to_record_accept_to_transfer_start_metric_ = false;
  }

  // kInProgress status is logged extensively elsewhere so avoid the spam.
  if (!is_in_progress) {
    CD_LOG(VERBOSE, Feature::NS)
        << __func__ << ": Nearby Share service: "
        << "Payload transfer update for share target with ID "
        << share_target.id << ": "
        << TransferMetadata::StatusToString(metadata.status());
  }

  if (metadata.status() == TransferMetadata::Status::kComplete &&
      share_target.is_incoming) {
    if (!OnIncomingPayloadsComplete(share_target)) {
      metadata = TransferMetadataBuilder()
                     .set_status(TransferMetadata::Status::kIncompletePayloads)
                     .build();

      // Reset file paths for file attachments.
      for (auto& file : share_target.file_attachments) {
        file.set_file_path(std::nullopt);
      }

      // Reset body of text attachments.
      for (auto& text : share_target.text_attachments) {
        text.set_text_body(std::string());
      }
    }

    fast_initiation_scanner_cooldown_timer_.Start(
        FROM_HERE, kFastInitiationScannerCooldown,
        base::BindRepeating(
            &NearbySharingServiceImpl::InvalidateFastInitiationScanning,
            base::Unretained(this)));
  }

  // Make sure to call this before calling Disconnect or we risk losing some
  // transfer updates in the receive case due to the Disconnect call cleaning up
  // share targets.
  ShareTargetInfo* info = GetShareTargetInfo(share_target);
  if (info && info->transfer_update_callback()) {
    info->transfer_update_callback()->OnTransferUpdate(share_target, metadata);
  }

  // Cancellation has its own disconnection strategy, possibly adding a delay
  // before disconnection to provide the other party time to process the
  // cancellation.
  if (TransferMetadata::IsFinalStatus(metadata.status()) &&
      metadata.status() != TransferMetadata::Status::kCancelled) {
    if (share_target.has_attachments() &&
        share_target.file_attachments.size()) {
      // For file payloads, the |PayloadTracker| callback for updates is
      // |OnTransferUpdate| which will set status |kComplete| if payload reading
      // is successful.
      const bool files_read_success =
          (metadata.status() == TransferMetadata::Status::kComplete);
      RecordNearbySharePayloadFileOperationMetrics(profile_, share_target,
                                                   PayloadFileOperation::kRead,
                                                   files_read_success);
    }
    Disconnect(share_target, metadata);
  }
}

bool NearbySharingServiceImpl::OnIncomingPayloadsComplete(
    ShareTarget& share_target) {
  DCHECK(share_target.is_incoming);

  ShareTargetInfo* info = GetShareTargetInfo(share_target);
  if (!info || !info->connection()) {
    RecordNearbyShareError(
        NearbyShareError::kIncomingPayloadsCompleteMissingConnection);
    CD_LOG(VERBOSE, Feature::NS)
        << __func__ << ": Connection not found for target - "
        << share_target.id;

    return false;
  }
  NearbyConnection* connection = info->connection();

  connection->SetDisconnectionListener(
      base::BindOnce(&NearbySharingServiceImpl::UnregisterShareTarget,
                     weak_ptr_factory_.GetWeakPtr(), share_target));

  for (auto& file : share_target.file_attachments) {
    AttachmentInfo& attachment_info = attachment_info_map_[file.id()];
    std::optional<int64_t> payload_id = attachment_info.payload_id;
    if (!payload_id) {
      RecordNearbyShareError(
          NearbyShareError::kIncomingPayloadsCompleteMissingPayloadId);
      CD_LOG(WARNING, Feature::NS)
          << __func__ << ": No payload id found for file - " << file.id();
      return false;
    }

    nearby::connections::mojom::Payload* incoming_payload =
        nearby_connections_manager_->GetIncomingPayload(*payload_id);
    if (!incoming_payload || !incoming_payload->content ||
        !incoming_payload->content->is_file()) {
      RecordNearbyShareError(
          NearbyShareError::kIncomingPayloadsCompleteMissingPayload);
      CD_LOG(WARNING, Feature::NS)
          << __func__ << ": No payload found for file - " << file.id();
      return false;
    }

    file.set_file_path(attachment_info.file_path);
  }

  for (auto& text : share_target.text_attachments) {
    AttachmentInfo& attachment_info = attachment_info_map_[text.id()];
    std::optional<int64_t> payload_id = attachment_info.payload_id;
    if (!payload_id) {
      RecordNearbyShareError(
          NearbyShareError::kIncomingPayloadsCompleteMissingTextPayloadId);
      CD_LOG(WARNING, Feature::NS)
          << __func__ << ": No payload id found for text - " << text.id();
      return false;
    }

    nearby::connections::mojom::Payload* incoming_payload =
        nearby_connections_manager_->GetIncomingPayload(*payload_id);
    if (!incoming_payload || !incoming_payload->content ||
        !incoming_payload->content->is_bytes()) {
      RecordNearbyShareError(
          NearbyShareError::kIncomingPayloadsCompleteMissingTextPayload);
      CD_LOG(WARNING, Feature::NS)
          << __func__ << ": No payload found for text - " << text.id();
      return false;
    }

    std::vector<uint8_t>& bytes = incoming_payload->content->get_bytes()->bytes;
    if (bytes.empty()) {
      RecordNearbyShareError(
          NearbyShareError::kIncomingPayloadsCompleteTextPayloadEmptyBytes);
      CD_LOG(WARNING, Feature::NS)
          << __func__
          << ": Incoming bytes is empty for text payload with payload_id - "
          << *payload_id;
      return false;
    }

    std::string text_body(bytes.begin(), bytes.end());
    text.set_text_body(text_body);

    attachment_info.text_body = std::move(text_body);
  }

  for (auto& wifi_credentials : share_target.wifi_credentials_attachments) {
    AttachmentInfo& attachment_info =
        attachment_info_map_[wifi_credentials.id()];
    std::optional<int64_t> payload_id = attachment_info.payload_id;
    if (!payload_id) {
      RecordNearbyShareError(
          NearbyShareError::kIncomingPayloadsCompleteMissingWifiPayloadId);
      CD_LOG(WARNING, Feature::NS)
          << __func__ << ": No payload id found for wifi credentials - "
          << wifi_credentials.id();
      return false;
    }

    nearby::connections::mojom::Payload* incoming_payload =
        nearby_connections_manager_->GetIncomingPayload(*payload_id);
    if (!incoming_payload || !incoming_payload->content ||
        !incoming_payload->content->is_bytes()) {
      RecordNearbyShareError(
          NearbyShareError::kIncomingPayloadsCompleteMissingWifiPayload);
      CD_LOG(WARNING, Feature::NS)
          << __func__ << ": No payload found for Wi-Fi credentials - "
          << wifi_credentials.id();
      return false;
    }

    const std::vector<uint8_t>& bytes =
        incoming_payload->content->get_bytes()->bytes;
    if (bytes.empty()) {
      RecordNearbyShareError(
          NearbyShareError::kIncomingPayloadsCompleteWifiPayloadEmptyBytes);
      CD_LOG(WARNING, Feature::NS)
          << __func__
          << ": Incoming bytes is empty for Wi-Fi password with payload_id - "
          << *payload_id;
      return false;
    }

    sharing::nearby::WifiCredentials credentials_proto;
    if (!credentials_proto.ParseFromArray(bytes.data(), bytes.size())) {
      RecordNearbyShareError(
          NearbyShareError::kIncomingPayloadsCompleteWifiFailedToParse);
      CD_LOG(WARNING, Feature::NS)
          << __func__ << ": Failed to parse Wi-Fi credentials proto.";
      return false;
    }

    if (credentials_proto.password().empty()) {
      RecordNearbyShareError(
          NearbyShareError::kIncomingPayloadsCompleteWifiNoPassword);
      CD_LOG(WARNING, Feature::NS) << __func__ << ": No Wi-Fi password found.";
      return false;
    }

    if (credentials_proto.has_hidden_ssid() &&
        credentials_proto.hidden_ssid()) {
      RecordNearbyShareError(
          NearbyShareError::kIncomingPayloadsCompleteWifiHiddenNetwork);
      CD_LOG(WARNING, Feature::NS) << __func__ << ": Network is hidden.";
      return false;
    }

    std::string wifi_password(credentials_proto.password());
    wifi_credentials.set_wifi_password(wifi_password);

    // Automatically set up the Wi-Fi network for the user.
    wifi_network_handler_->ConfigureWifiNetwork(wifi_credentials,
                                                base::DoNothing());
  }
  return true;
}

void NearbySharingServiceImpl::RemoveIncomingPayloads(
    ShareTarget share_target) {
  if (!share_target.is_incoming) {
    return;
  }

  CD_LOG(INFO, Feature::NS)
      << __func__
      << ": Cleaning up payloads due to transfer cancelled or failure.";

  nearby_connections_manager_->ClearIncomingPayloads();
  std::vector<base::FilePath> files_for_deletion;
  for (const auto& file : share_target.file_attachments) {
    auto it = attachment_info_map_.find(file.id());
    if (it == attachment_info_map_.end()) {
      continue;
    }

    files_for_deletion.push_back(it->second.file_path);
  }

  file_handler_.DeleteFilesFromDisk(std::move(files_for_deletion));
}

void NearbySharingServiceImpl::Disconnect(const ShareTarget& share_target,
                                          TransferMetadata metadata) {
  ShareTargetInfo* share_target_info = GetShareTargetInfo(share_target);
  if (!share_target_info) {
    RecordNearbyShareError(
        NearbyShareError::kDisconnectFailedToGetShareTargetInfo);
    CD_LOG(WARNING, Feature::NS)
        << __func__
        << ": Failed to disconnect. No share target info found for target - "
        << share_target.id;
    return;
  }

  std::optional<std::string> endpoint_id = share_target_info->endpoint_id();
  if (!endpoint_id) {
    RecordNearbyShareError(NearbyShareError::kDisconnectMissingEndpointId);
    CD_LOG(WARNING, Feature::NS)
        << __func__
        << ": Failed to disconnect. No endpoint id found for share target - "
        << share_target.id;
    return;
  }

  // Failed to send or receive. No point in continuing, so disconnect
  // immediately.
  if (metadata.status() != TransferMetadata::Status::kComplete) {
    if (share_target_info->connection()) {
      share_target_info->connection()->Close();
    } else {
      nearby_connections_manager_->Disconnect(*endpoint_id);
    }
    return;
  }

  // Files received successfully. Receivers can immediately cancel.
  if (share_target.is_incoming) {
    if (share_target_info->connection()) {
      share_target_info->connection()->Close();
    } else {
      nearby_connections_manager_->Disconnect(*endpoint_id);
    }
    return;
  }

  // Disconnect after a timeout to make sure any pending payloads are sent.
  auto timer = std::make_unique<base::CancelableOnceClosure>(base::BindOnce(
      &NearbySharingServiceImpl::OnDisconnectingConnectionTimeout,
      weak_ptr_factory_.GetWeakPtr(), *endpoint_id));
  base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
      FROM_HERE, timer->callback(), kOutgoingDisconnectionDelay);
  disconnection_timeout_alarms_[*endpoint_id] = std::move(timer);

  // Stop the disconnection timeout if the connection has been closed already.
  if (share_target_info->connection()) {
    share_target_info->connection()->SetDisconnectionListener(base::BindOnce(
        &NearbySharingServiceImpl::OnDisconnectingConnectionDisconnected,
        weak_ptr_factory_.GetWeakPtr(), share_target, *endpoint_id));
  }
}

void NearbySharingServiceImpl::OnDisconnectingConnectionTimeout(
    const std::string& endpoint_id) {
  disconnection_timeout_alarms_.erase(endpoint_id);
  nearby_connections_manager_->Disconnect(endpoint_id);
}

void NearbySharingServiceImpl::OnDisconnectingConnectionDisconnected(
    const ShareTarget& share_target,
    const std::string& endpoint_id) {
  disconnection_timeout_alarms_.erase(endpoint_id);
  UnregisterShareTarget(share_target);
}

ShareTargetInfo& NearbySharingServiceImpl::GetOrCreateShareTargetInfo(
    const ShareTarget& share_target,
    const std::string& endpoint_id) {
  if (share_target.is_incoming) {
    auto& info = incoming_share_target_info_map_[share_target.id];
    info.set_endpoint_id(endpoint_id);
    return info;
  } else {
    // We need to explicitly remove any previous share target for
    // |endpoint_id| if one exists, notifying observers that a share target is
    // lost.
    const auto it = outgoing_share_target_map_.find(endpoint_id);
    if (it != outgoing_share_target_map_.end() &&
        it->second.id != share_target.id) {
      RemoveOutgoingShareTargetWithEndpointId(endpoint_id);
    }

    CD_LOG(VERBOSE, Feature::NS)
        << __func__ << ": Adding (endpoint_id=" << endpoint_id
        << ", share_target_id=" << share_target.id
        << ") to outgoing share target map";
    outgoing_share_target_map_.insert_or_assign(endpoint_id, share_target);
    auto& info = outgoing_share_target_info_map_[share_target.id];
    info.set_endpoint_id(endpoint_id);
    return info;
  }
}

ShareTargetInfo* NearbySharingServiceImpl::GetShareTargetInfo(
    const ShareTarget& share_target) {
  if (share_target.is_incoming) {
    return GetIncomingShareTargetInfo(share_target);
  } else {
    return GetOutgoingShareTargetInfo(share_target);
  }
}

IncomingShareTargetInfo* NearbySharingServiceImpl::GetIncomingShareTargetInfo(
    const ShareTarget& share_target) {
  auto it = incoming_share_target_info_map_.find(share_target.id);
  if (it == incoming_share_target_info_map_.end()) {
    return nullptr;
  }

  return &it->second;
}

OutgoingShareTargetInfo* NearbySharingServiceImpl::GetOutgoingShareTargetInfo(
    const ShareTarget& share_target) {
  auto it = outgoing_share_target_info_map_.find(share_target.id);
  if (it == outgoing_share_target_info_map_.end()) {
    return nullptr;
  }

  return &it->second;
}

NearbyConnection* NearbySharingServiceImpl::GetConnection(
    const ShareTarget& share_target) {
  ShareTargetInfo* share_target_info = GetShareTargetInfo(share_target);
  return share_target_info ? share_target_info->connection() : nullptr;
}

std::optional<std::vector<uint8_t>>
NearbySharingServiceImpl::GetBluetoothMacAddressForShareTarget(
    const ShareTarget& share_target) {
  ShareTargetInfo* info = GetShareTargetInfo(share_target);
  if (!info) {
    RecordNearbyShareError(
        NearbyShareError::
            kGetBluetoothMacAddressForShareTargetNoShareTargetInfo);
    CD_LOG(ERROR, Feature::NS) << __func__ << ": No ShareTargetInfo found for "
                               << "share target id: " << share_target.id;
    return std::nullopt;
  }

  const std::optional<NearbyShareDecryptedPublicCertificate>& certificate =
      info->certificate();
  if (!certificate) {
    RecordNearbyShareError(
        NearbyShareError::
            kGetBluetoothMacAddressForShareTargetNoDecryptedPublicCertificate);
    CD_LOG(ERROR, Feature::NS)
        << __func__ << ": No decrypted public certificate found for "
        << "share target id: " << share_target.id;
    return std::nullopt;
  }

  return GetBluetoothMacAddressFromCertificate(*certificate);
}

void NearbySharingServiceImpl::ClearOutgoingShareTargetInfoMap() {
  CD_LOG(VERBOSE, Feature::NS)
      << __func__ << ": Clearing outgoing share target map.";
  while (!outgoing_share_target_map_.empty()) {
    RemoveOutgoingShareTargetWithEndpointId(
        /*endpoint_id=*/outgoing_share_target_map_.begin()->first);
  }
  DCHECK(outgoing_share_target_map_.empty());
  DCHECK(outgoing_share_target_info_map_.empty());
}

void NearbySharingServiceImpl::SetAttachmentPayloadId(
    const Attachment& attachment,
    int64_t payload_id) {
  attachment_info_map_[attachment.id()].payload_id = payload_id;
}

std::optional<int64_t> NearbySharingServiceImpl::GetAttachmentPayloadId(
    int64_t attachment_id) {
  auto it = attachment_info_map_.find(attachment_id);
  if (it == attachment_info_map_.end()) {
    return std::nullopt;
  }

  return it->second.payload_id;
}

void NearbySharingServiceImpl::UnregisterShareTarget(
    const ShareTarget& share_target) {
  CD_LOG(VERBOSE, Feature::NS)
      << __func__ << ": Unregistering share target - " << share_target.id;

  // For metrics.
  all_cancelled_share_target_ids_.erase(share_target.id);

  if (share_target.is_incoming) {
    if (last_incoming_metadata_ &&
        last_incoming_metadata_->first.id == share_target.id) {
      last_incoming_metadata_.reset();
    }

    // Clear legacy incoming payloads to release resource.
    nearby_connections_manager_->ClearIncomingPayloads();
    incoming_share_target_info_map_.erase(share_target.id);
  } else {
    if (last_outgoing_metadata_ &&
        last_outgoing_metadata_->first.id == share_target.id) {
      last_outgoing_metadata_.reset();
    }
    // Find the endpoint id that matches the given share target.
    std::optional<std::string> endpoint_id;
    auto it = outgoing_share_target_info_map_.find(share_target.id);
    if (it != outgoing_share_target_info_map_.end()) {
      endpoint_id = it->second.endpoint_id();
    }

    // Be careful not to clear out the share target info map if a new session
    // was started during the cancelation delay.
    if (!is_scanning_ && !is_transferring_) {
      // TODO(crbug/1108348): Support caching manager by keeping track of the
      // share_target/endpoint_id for next time.
      ClearOutgoingShareTargetInfoMap();
    }

    CD_LOG(VERBOSE, Feature::NS)
        << __func__ << ": Unregister share target: " << share_target.id;
  }
  mutual_acceptance_timeout_alarm_.Cancel();
}

void NearbySharingServiceImpl::OnStartAdvertisingResult(
    bool used_device_name,
    NearbyConnectionsManager::ConnectionsStatus status) {
  RecordNearbyShareStartAdvertisingResultMetric(
      /*is_high_visibility=*/used_device_name, status);

  if (status == NearbyConnectionsManager::ConnectionsStatus::kSuccess) {
    CD_LOG(VERBOSE, Feature::NS)
        << __func__
        << ": StartAdvertising over Nearby Connections was successful.";
    SetInHighVisibility(used_device_name);
  } else {
    RecordNearbyShareError(NearbyShareError::kStartAdvertisingFailed);
    CD_LOG(ERROR, Feature::NS)
        << __func__ << ": StartAdvertising over Nearby Connections failed: "
        << NearbyConnectionsManager::ConnectionsStatusToString(status);
    SetInHighVisibility(false);
    for (auto& observer : observers_) {
      observer.OnStartAdvertisingFailure();
    }
  }
}

void NearbySharingServiceImpl::OnStopAdvertisingResult(
    NearbyConnectionsManager::ConnectionsStatus status) {
  if (status == NearbyConnectionsManager::ConnectionsStatus::kSuccess) {
    CD_LOG(VERBOSE, Feature::NS)
        << __func__
        << ": StopAdvertising over Nearby Connections was successful.";
  } else {
    RecordNearbyShareError(NearbyShareError::kStopAdvertisingFailed);
    CD_LOG(ERROR, Feature::NS)
        << __func__ << ": StopAdvertising over Nearby Connections failed: "
        << NearbyConnectionsManager::ConnectionsStatusToString(status);
  }

  // The |advertising_power_level_| is set in |StopAdvertising| instead of here
  // at the callback because when restarting advertising, |StartAdvertising| is
  // called immediately after |StopAdvertising| without waiting for the
  // callback. Nearby Connections queues the requests and completes them in
  // order, so waiting for Stop to complete is unnecessary, but Start will fail
  // if the |advertising_power_level_| indicates we are already advertising.
  SetInHighVisibility(false);
}

void NearbySharingServiceImpl::OnStartDiscoveryResult(
    NearbyConnectionsManager::ConnectionsStatus status) {
  bool success =
      status == NearbyConnectionsManager::ConnectionsStatus::kSuccess;
  if (success) {
    CD_LOG(VERBOSE, Feature::NS)
        << __func__
        << ": StartDiscovery over Nearby Connections was successful.";

    // Periodically download certificates if there are discovered, contact-based
    // advertisements that cannot decrypt any currently stored certificates.
    ScheduleCertificateDownloadDuringDiscovery(/*attempt_count=*/0);
  } else {
    RecordNearbyShareError(NearbyShareError::kStartDiscoveryFailed);
    CD_LOG(ERROR, Feature::NS)
        << __func__ << ": StartDiscovery over Nearby Connections failed: "
        << NearbyConnectionsManager::ConnectionsStatusToString(status);
  }
  for (auto& observer : observers_) {
    observer.OnStartDiscoveryResult(success);
    if (success) {
      observer.OnShareTargetDiscoveryStarted();
    }
  }
}

void NearbySharingServiceImpl::SetInHighVisibility(
    bool new_in_high_visibility) {
  if (IsInHighVisibility() == new_in_high_visibility) {
    return;
  }

  if (chromeos::features::IsQuickShareV2Enabled()) {
    prefs_->SetBoolean(prefs::kNearbySharingInHighVisibilityPrefName,
                       /*value=*/new_in_high_visibility);
  } else {
    in_high_visibility_ = new_in_high_visibility;
  }

  for (auto& observer : observers_) {
    observer.OnHighVisibilityChanged(new_in_high_visibility);
  }
}

void NearbySharingServiceImpl::AbortAndCloseConnectionIfNecessary(
    const TransferMetadata::Status status,
    const ShareTarget& share_target) {
  TransferMetadata metadata =
      TransferMetadataBuilder().set_status(status).build();
  ShareTargetInfo* info = GetShareTargetInfo(share_target);

  // First invoke the appropriate transfer callback with the final |status|.
  if (info && info->transfer_update_callback()) {
    info->transfer_update_callback()->OnTransferUpdate(share_target, metadata);
  } else if (share_target.is_incoming) {
    OnIncomingTransferUpdate(share_target, metadata);
  } else {
    OnOutgoingTransferUpdate(share_target, metadata);
  }

  // Close connection if necessary.
  if (info && info->connection()) {
    // Ensure that the disconnect listener is set to UnregisterShareTarget
    // because the other listenrs also try to record a final status metric.
    info->connection()->SetDisconnectionListener(
        base::BindOnce(&NearbySharingServiceImpl::UnregisterShareTarget,
                       weak_ptr_factory_.GetWeakPtr(), share_target));

    info->connection()->Close();
  }
}

void NearbySharingServiceImpl::UpdateVisibilityReminderTimer(
    bool reset_timestamp) {
  if (!settings_.GetEnabled() ||
      !IsVisibleInBackground(settings_.GetVisibility())) {
    visibility_reminder_timer_.Stop();
    return;
  }

  if (reset_timestamp ||
      prefs_->GetTime(prefs::kNearbySharingNextVisibilityReminderTimePrefName)
          .is_null()) {
    prefs_->SetTime(prefs::kNearbySharingNextVisibilityReminderTimePrefName,
                    base::Time::Now() + visibility_reminder_timer_delay_);
  }

  visibility_reminder_timer_.Start(
      FROM_HERE, GetTimeUntilNextVisibilityReminder(),
      base::BindOnce(&NearbySharingServiceImpl::OnVisibilityReminderTimerFired,
                     weak_ptr_factory_.GetWeakPtr()));
}

void NearbySharingServiceImpl::OnVisibilityReminderTimerFired() {
  nearby_notification_manager_->ShowVisibilityReminder();
  UpdateVisibilityReminderTimer(/*reset_timestamp=*/true);
}

// Calculate the actual time when next visibility reminder will be shown.
base::TimeDelta NearbySharingServiceImpl::GetTimeUntilNextVisibilityReminder() {
  base::Time next_visibility_reminder_time =
      prefs_->GetTime(prefs::kNearbySharingNextVisibilityReminderTimePrefName);
  base::TimeDelta time_until_next_reminder =
      next_visibility_reminder_time - base::Time::Now();

  // Immediately show visibility reminder if it's already passed 180 days since
  // last time user saw the reminder.
  return std::max(base::Seconds(0), time_until_next_reminder);
}
