chromium/chrome/browser/nearby_sharing/nearby_notification_manager.cc

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

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

#include <string>

#include "ash/constants/notifier_catalogs.h"
#include "ash/public/cpp/holding_space/holding_space_item.h"
#include "ash/public/cpp/notification_utils.h"
#include "ash/webui/settings/public/constants/routes.mojom.h"
#include "base/files/file_util.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/notreached.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/time/time.h"
#include "build/branding_buildflags.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "chrome/app/vector_icons/vector_icons.h"
#include "chrome/browser/download/download_prefs.h"
#include "chrome/browser/image_decoder/image_decoder.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/common/nearby_share_resource_getter.h"
#include "chrome/browser/nearby_sharing/nearby_share_metrics.h"
#include "chrome/browser/nearby_sharing/nearby_sharing_service.h"
#include "chrome/browser/notifications/notification_display_service.h"
#include "chrome/browser/platform_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/ash/holding_space/holding_space_keyed_service.h"
#include "chrome/browser/ui/ash/holding_space/holding_space_keyed_service_factory.h"
#include "chrome/browser/ui/settings_window_manager_chromeos.h"
#include "chrome/grit/generated_resources.h"
#include "chromeos/ash/services/nearby/public/mojom/nearby_share_settings.mojom.h"
#include "components/cross_device/logging/logging.h"
#include "components/prefs/pref_service.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/download_manager.h"
#include "ui/base/clipboard/clipboard_buffer.h"
#include "ui/base/clipboard/scoped_clipboard_writer.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/chromeos/styles/cros_tokens_color_mappings.h"
#include "ui/message_center/public/cpp/notification.h"
#include "ui/message_center/public/cpp/notification_types.h"
#include "ui/strings/grit/ui_strings.h"

#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
#include "chrome/browser/nearby_sharing/internal/icons/vector_icons.h"
#endif  // BUILDFLAG(GOOGLE_CHROME_BRANDING)

namespace {

constexpr char kNearbyInProgressNotificationId[] =
    "chrome://nearby_share/in_progress";
constexpr char kNearbyTransferResultNotificationIdPrefix[] =
    "chrome://nearby_share/result/";
constexpr char kNearbyDeviceTryingToShareNotificationId[] =
    "chrome://nearby_share/nearby_device_trying_to_share";
constexpr char kNearbyVisibilityReminderNotificationId[] =
    "chrome://nearby_share/visibility_reminder";
constexpr char kNearbyNotifier[] = "nearby";

std::string CreateNotificationIdForShareTarget(
    const ShareTarget& share_target) {
    return std::string(kNearbyTransferResultNotificationIdPrefix) +
           share_target.id.ToString();
}

// Creates a default Nearby Share notification with empty content.
message_center::Notification CreateNearbyNotification(const std::string& id) {
  message_center::Notification notification(
      message_center::NOTIFICATION_TYPE_SIMPLE, id,
      /*title=*/std::u16string(),
      /*message=*/std::u16string(),
      /*icon=*/ui::ImageModel(),
      features::IsNameEnabled()
          ? NearbyShareResourceGetter::GetInstance()->GetStringWithFeatureName(
                IDS_NEARBY_NOTIFICATION_SOURCE_PH)
          : l10n_util::GetStringUTF16(IDS_NEARBY_NOTIFICATION_SOURCE),
      /*origin_url=*/GURL(),
      message_center::NotifierId(message_center::NotifierType::SYSTEM_COMPONENT,
                                 kNearbyNotifier,
                                 ash::NotificationCatalogName::kNearbyShare),
      /*optional_fields=*/{},
      /*delegate=*/nullptr);

  notification.set_accent_color_id(cros_tokens::kCrosSysPrimary);
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
  if (features::IsNameEnabled()) {
    notification.set_vector_small_image(kNearbyShareInternalIcon);
  } else {
    notification.set_vector_small_image(kNearbyShareIcon);
  }
#else   // !BUILDFLAG(GOOGLE_CHROME_BRANDING)
  notification.set_vector_small_image(kNearbyShareIcon);
#endif  // !BUILDFLAG(GOOGLE_CHROME_BRANDING)

  notification.set_settings_button_handler(
      message_center::SettingsButtonHandler::DELEGATE);

  return notification;
}

std::string GetTimestampString() {
  return base::NumberToString(
      base::Time::Now().ToDeltaSinceWindowsEpoch().InMicroseconds());
}

FileAttachment::Type GetCommonFileAttachmentType(
    const std::vector<FileAttachment>& files) {
  if (files.empty())
    return FileAttachment::Type::kUnknown;

  FileAttachment::Type type = files[0].type();
  for (size_t i = 1; i < files.size(); ++i) {
    if (files[i].type() != type)
      return FileAttachment::Type::kUnknown;
  }
  return type;
}

TextAttachment::Type GetCommonTextAttachmentType(
    const std::vector<TextAttachment>& texts) {
  if (texts.empty())
    return TextAttachment::Type::kText;

  TextAttachment::Type type = texts[0].type();
  for (size_t i = 1; i < texts.size(); ++i) {
    if (texts[i].type() != type)
      return TextAttachment::Type::kText;
  }
  return type;
}

int GetFileAttachmentsCapitalizedStringId(
    const std::vector<FileAttachment>& files) {
  switch (GetCommonFileAttachmentType(files)) {
    case FileAttachment::Type::kApp:
      return IDS_NEARBY_FILE_ATTACHMENTS_CAPITALIZED_APPS;
    case FileAttachment::Type::kImage:
      return IDS_NEARBY_FILE_ATTACHMENTS_CAPITALIZED_IMAGES;
    case FileAttachment::Type::kUnknown:
      return IDS_NEARBY_FILE_ATTACHMENTS_CAPITALIZED_UNKNOWN;
    case FileAttachment::Type::kVideo:
      return IDS_NEARBY_FILE_ATTACHMENTS_CAPITALIZED_VIDEOS;
    default:
      return IDS_NEARBY_CAPITALIZED_UNKNOWN_ATTACHMENTS;
  }
}

int GetFileAttachmentsNotCapitalizedStringId(
    const std::vector<FileAttachment>& files) {
  switch (GetCommonFileAttachmentType(files)) {
    case FileAttachment::Type::kApp:
      return IDS_NEARBY_FILE_ATTACHMENTS_NOT_CAPITALIZED_APPS;
    case FileAttachment::Type::kImage:
      return IDS_NEARBY_FILE_ATTACHMENTS_NOT_CAPITALIZED_IMAGES;
    case FileAttachment::Type::kUnknown:
      return IDS_NEARBY_FILE_ATTACHMENTS_NOT_CAPITALIZED_UNKNOWN;
    case FileAttachment::Type::kVideo:
      return IDS_NEARBY_FILE_ATTACHMENTS_NOT_CAPITALIZED_VIDEOS;
    default:
      return IDS_NEARBY_NOT_CAPITALIZED_UNKNOWN_ATTACHMENTS;
  }
}

int GetTextAttachmentsCapitalizedStringId(
    const std::vector<TextAttachment>& texts) {
  switch (GetCommonTextAttachmentType(texts)) {
    case TextAttachment::Type::kAddress:
      return IDS_NEARBY_TEXT_ATTACHMENTS_CAPITALIZED_ADDRESSES;
    case TextAttachment::Type::kPhoneNumber:
      return IDS_NEARBY_TEXT_ATTACHMENTS_CAPITALIZED_PHONE_NUMBERS;
    case TextAttachment::Type::kText:
      return IDS_NEARBY_TEXT_ATTACHMENTS_CAPITALIZED_UNKNOWN;
    case TextAttachment::Type::kUrl:
      return IDS_NEARBY_TEXT_ATTACHMENTS_CAPITALIZED_LINKS;
    default:
      return IDS_NEARBY_TEXT_ATTACHMENTS_CAPITALIZED_UNKNOWN;
  }
}

int GetTextAttachmentsNotCapitalizedStringId(
    const std::vector<TextAttachment>& texts) {
  switch (GetCommonTextAttachmentType(texts)) {
    case TextAttachment::Type::kAddress:
      return IDS_NEARBY_TEXT_ATTACHMENTS_NOT_CAPITALIZED_ADDRESSES;
    case TextAttachment::Type::kPhoneNumber:
      return IDS_NEARBY_TEXT_ATTACHMENTS_NOT_CAPITALIZED_PHONE_NUMBERS;
    case TextAttachment::Type::kText:
      return IDS_NEARBY_TEXT_ATTACHMENTS_NOT_CAPITALIZED_UNKNOWN;
    case TextAttachment::Type::kUrl:
      return IDS_NEARBY_TEXT_ATTACHMENTS_NOT_CAPITALIZED_LINKS;
    default:
      return IDS_NEARBY_TEXT_ATTACHMENTS_NOT_CAPITALIZED_UNKNOWN;
  }
}

std::u16string GetAttachmentsString(const ShareTarget& share_target,
                                    bool use_capitalized_attachments) {
  size_t file_count = share_target.file_attachments.size();
  size_t text_count = share_target.text_attachments.size();
  int resource_id = use_capitalized_attachments
                        ? IDS_NEARBY_CAPITALIZED_UNKNOWN_ATTACHMENTS
                        : IDS_NEARBY_NOT_CAPITALIZED_UNKNOWN_ATTACHMENTS;

  if (file_count > 0 && text_count == 0)
    resource_id = use_capitalized_attachments
                      ? GetFileAttachmentsCapitalizedStringId(
                            share_target.file_attachments)
                      : GetFileAttachmentsNotCapitalizedStringId(
                            share_target.file_attachments);

  if (text_count > 0 && file_count == 0)
    resource_id = use_capitalized_attachments
                      ? GetTextAttachmentsCapitalizedStringId(
                            share_target.text_attachments)
                      : GetTextAttachmentsNotCapitalizedStringId(
                            share_target.text_attachments);

  return l10n_util::GetPluralStringFUTF16(resource_id, text_count + file_count);
}

std::u16string FormatNotificationTitle(const ShareTarget& share_target,
                                       int resource_id,
                                       bool use_capitalized_attachments) {
  std::u16string attachments =
      GetAttachmentsString(share_target, use_capitalized_attachments);

  std::u16string device_name = base::UTF8ToUTF16(share_target.device_name);
  size_t attachment_count = share_target.file_attachments.size() +
                            share_target.text_attachments.size();

  if (!share_target.wifi_credentials_attachments.empty()) {
    std::u16string network_name =
        base::UTF8ToUTF16(share_target.wifi_credentials_attachments[0].ssid());
    return base::ReplaceStringPlaceholders(
        l10n_util::GetStringUTF16(resource_id), {network_name, device_name},
        /*offsets=*/nullptr);
  } else {
    return base::ReplaceStringPlaceholders(
        l10n_util::GetPluralStringFUTF16(resource_id, attachment_count),
        {attachments, device_name}, /*offsets=*/nullptr);
  }
}

std::u16string GetProgressNotificationTitle(const ShareTarget& share_target) {
  if (!share_target.wifi_credentials_attachments.empty()) {
    return FormatNotificationTitle(
        share_target,
        IDS_NEARBY_NOTIFICATION_RECEIVE_PROGRESS_TITLE_WIFI_CREDENTIALS,
        /*use_capitalized_attachments=*/false);
  } else {
    return FormatNotificationTitle(
        share_target,
        share_target.is_incoming
            ? IDS_NEARBY_NOTIFICATION_RECEIVE_PROGRESS_TITLE
            : IDS_NEARBY_NOTIFICATION_SEND_PROGRESS_TITLE,
        /*use_capitalized_attachments=*/false);
  }
}

std::u16string GetSuccessNotificationTitle(const ShareTarget& share_target) {
  if (!share_target.wifi_credentials_attachments.empty()) {
    return FormatNotificationTitle(
        share_target,
        IDS_NEARBY_NOTIFICATION_RECEIVE_SUCCESS_TITLE_WIFI_CREDENTIALS,
        /*use_capitalized_attachments=*/false);
  } else {
    return FormatNotificationTitle(
        share_target,
        share_target.is_incoming ? IDS_NEARBY_NOTIFICATION_RECEIVE_SUCCESS_TITLE
                                 : IDS_NEARBY_NOTIFICATION_SEND_SUCCESS_TITLE,
        /*use_capitalized_attachments=*/true);
  }
}

std::u16string GetFailureNotificationTitle(const ShareTarget& share_target) {
  if (!share_target.wifi_credentials_attachments.empty()) {
    return FormatNotificationTitle(
        share_target,
        IDS_NEARBY_NOTIFICATION_RECEIVE_FAILURE_TITLE_WIFI_CREDENTIALS,
        /*use_capitalized_attachments=*/false);
  } else {
    return FormatNotificationTitle(
        share_target,
        share_target.is_incoming ? IDS_NEARBY_NOTIFICATION_RECEIVE_FAILURE_TITLE
                                 : IDS_NEARBY_NOTIFICATION_SEND_FAILURE_TITLE,
        /*use_capitalized_attachments=*/false);
  }
}

std::optional<std::u16string> GetFailureNotificationMessage(
    TransferMetadata::Status status) {
  switch (status) {
    case TransferMetadata::Status::kTimedOut:
      return l10n_util::GetStringUTF16(IDS_NEARBY_ERROR_TIME_OUT);
    case TransferMetadata::Status::kNotEnoughSpace:
      return l10n_util::GetStringUTF16(IDS_NEARBY_ERROR_NOT_ENOUGH_SPACE);
    case TransferMetadata::Status::kUnsupportedAttachmentType:
      return l10n_util::GetStringUTF16(IDS_NEARBY_ERROR_UNSUPPORTED_FILE_TYPE);
    default:
      return std::nullopt;
  }
}

std::u16string GetConnectionRequestNotificationMessage(
    const ShareTarget& share_target,
    const TransferMetadata& transfer_metadata) {
  std::u16string attachments =
      GetAttachmentsString(share_target, /*use_capitalized_attachments=*/false);
  std::u16string device_name = base::UTF8ToUTF16(share_target.device_name);

  size_t attachment_count = share_target.file_attachments.size() +
                            share_target.text_attachments.size();
  std::u16string message;
  if (!share_target.wifi_credentials_attachments.empty()) {
    message = base::ReplaceStringPlaceholders(
        l10n_util::GetStringUTF16(
            IDS_NEARBY_NOTIFICATION_CONNECTION_REQUEST_MESSAGE_WIFI_CREDENTIALS),
        device_name, /*offsets=*/nullptr);
  } else {
    message = base::ReplaceStringPlaceholders(
        l10n_util::GetPluralStringFUTF16(
            IDS_NEARBY_NOTIFICATION_CONNECTION_REQUEST_MESSAGE,
            attachment_count),
        {device_name, attachments}, /*offsets=*/nullptr);
  }

  if (transfer_metadata.token()) {
    std::u16string token = l10n_util::GetStringFUTF16(
        IDS_NEARBY_SECURE_CONNECTION_ID,
        base::UTF8ToUTF16(*transfer_metadata.token()));
    message = base::StrCat({message, u"\n", token});
  }

  return message;
}

std::optional<std::u16string> GetReceivedNotificationTextMessage(
    const ShareTarget& share_target) {
  size_t text_count = share_target.text_attachments.size();
  if (text_count < 1) {
    return std::nullopt;
  }

  const TextAttachment& attachment = share_target.text_attachments[0];
  return base::UTF8ToUTF16(attachment.GetDescription());
}

ui::ImageModel GetImageFromShareTarget(const ShareTarget& share_target) {
  // TODO(crbug.com/40138752): Create or get profile picture of |share_target|.
  return ui::ImageModel();
}

NearbyNotificationManager::ReceivedContentType GetReceivedContentType(
    const ShareTarget& share_target) {
  if (!share_target.wifi_credentials_attachments.empty()) {
    return NearbyNotificationManager::ReceivedContentType::kWifiCredentials;
  }

  if (!share_target.text_attachments.empty()) {
    const TextAttachment& file = share_target.text_attachments[0];
    if (share_target.text_attachments.size() == 1 &&
        file.type() == sharing::mojom::TextMetadata::Type::kUrl) {
      return NearbyNotificationManager::ReceivedContentType::kSingleUrl;
    }

    return NearbyNotificationManager::ReceivedContentType::kText;
  }

  if (share_target.file_attachments.size() != 1)
    return NearbyNotificationManager::ReceivedContentType::kFiles;

  const FileAttachment& file = share_target.file_attachments[0];
  if (file.type() == sharing::mojom::FileMetadata::Type::kImage)
    return NearbyNotificationManager::ReceivedContentType::kSingleImage;

  return NearbyNotificationManager::ReceivedContentType::kFiles;
}

class ProgressNotificationDelegate : public NearbyNotificationDelegate {
 public:
  explicit ProgressNotificationDelegate(NearbyNotificationManager* manager,
                                        bool awaiting_remote_acceptance)
      : manager_(manager),
        awaiting_remote_acceptance_(awaiting_remote_acceptance) {}
  ~ProgressNotificationDelegate() override = default;

  // NearbyNotificationDelegate:
  void OnClick(const std::string& notification_id,
               const std::optional<int>& action_index) override {
    // Clicking on the notification is a noop.
    if (!action_index)
      return;
    // Clicking on the only (cancel) button cancels the transfer.
    DCHECK_EQ(0, *action_index);

    // In the receiving case, the progress notification is showed after the
    // transfer is accepted, but before the |TransferMetadata::Status| is
    // actually |kInProgress|. In this case, it is more appropriate to reject
    // the transfer, but as far as the user is concerned, it looks like a
    // cancellation.
    if (awaiting_remote_acceptance_)
      manager_->RejectTransfer();
    else
      manager_->CancelTransfer();
  }

  void OnClose(const std::string& notification_id) override {
    if (awaiting_remote_acceptance_)
      manager_->RejectTransfer();
    else
      manager_->CancelTransfer();
  }

 private:
  raw_ptr<NearbyNotificationManager> manager_;
  bool awaiting_remote_acceptance_ = false;
};

class ConnectionRequestNotificationDelegate
    : public NearbyNotificationDelegate {
 public:
  explicit ConnectionRequestNotificationDelegate(
      NearbyNotificationManager* manager)
      : manager_(manager) {}
  ~ConnectionRequestNotificationDelegate() override = default;

  // NearbyNotificationDelegate:
  void OnClick(const std::string& notification_id,
               const std::optional<int>& action_index) override {
    // Clicking on the notification is a noop.
    if (!action_index)
      return;

    switch (*action_index) {
      case 0:
        manager_->AcceptTransfer();
        break;
      case 1:
        manager_->RejectTransfer();
        break;
      default:
        NOTREACHED_IN_MIGRATION();
        break;
    }
  }

  void OnClose(const std::string& notification_id) override {
    manager_->RejectTransfer();
  }

 private:
  raw_ptr<NearbyNotificationManager> manager_;
};

class ReceivedImageDecoder : public ImageDecoder::ImageRequest {
 public:
  using ImageCallback = base::OnceCallback<void(const SkBitmap& decoded_image)>;

  explicit ReceivedImageDecoder(ImageCallback callback)
      : callback_(std::move(callback)) {}
  ~ReceivedImageDecoder() override = default;

  void DecodeImage(const std::optional<base::FilePath>& image_path) {
    if (!image_path) {
      OnDecodeImageFailed();
      return;
    }

    auto contents = std::make_unique<std::string>();
    auto* contents_ptr = contents.get();

    base::ThreadPool::PostTaskAndReplyWithResult(
        FROM_HERE, {base::MayBlock()},
        base::BindOnce(&base::ReadFileToString, *image_path, contents_ptr),
        base::BindOnce(&ReceivedImageDecoder::OnFileRead,
                       weak_ptr_factory_.GetWeakPtr(), std::move(contents)));
  }

  // ImageDecoder::ImageRequest implementation:
  void OnImageDecoded(const SkBitmap& decoded_image) override {
    std::move(callback_).Run(decoded_image);
    delete this;
  }

  void OnDecodeImageFailed() override {
    std::move(callback_).Run(SkBitmap());
    delete this;
  }

 private:
  void OnFileRead(std::unique_ptr<std::string> contents,
                  bool is_contents_read) {
    if (!is_contents_read || !contents || contents->empty()) {
      CD_LOG(VERBOSE, Feature::NS) << __func__ << ": Image contents not found.";
      OnDecodeImageFailed();
      return;
    }

    ImageDecoder::Start(this, std::move(*contents));
  }

  ImageCallback callback_;
  base::WeakPtrFactory<ReceivedImageDecoder> weak_ptr_factory_{this};
};

class SuccessNotificationDelegate : public NearbyNotificationDelegate {
 public:
  SuccessNotificationDelegate(
      NearbyNotificationManager* manager,
      Profile* profile,
      ShareTarget share_target,
      NearbyNotificationManager::ReceivedContentType type,
      const SkBitmap& image,
      base::OnceCallback<
          void(NearbyNotificationManager::SuccessNotificationAction)>
          testing_callback)
      : manager_(manager),
        profile_(profile),
        share_target_(std::move(share_target)),
        type_(type),
        image_(image),
        testing_callback_(std::move(testing_callback)) {}
  ~SuccessNotificationDelegate() override = default;

  // NearbyNotificationDelegate:
  void OnClick(const std::string& notification_id,
               const std::optional<int>& action_index) override {
    switch (type_) {
      case NearbyNotificationManager::ReceivedContentType::kText:
        if (action_index.has_value() && action_index.value() == 0) {
          // Don't overwrite clipboard if user clicks notification body
          CopyTextToClipboard();
        }
        break;
      case NearbyNotificationManager::ReceivedContentType::kSingleUrl:
        if (!action_index.has_value()) {
          OpenTextLink();
          break;
        }
        switch (*action_index) {
          case 0:
            OpenTextLink();
            break;
          case 1:
            CopyTextToClipboard();
            break;
          default:
            NOTREACHED_IN_MIGRATION();
            break;
        }
        break;
      case NearbyNotificationManager::ReceivedContentType::kSingleImage:
        if (!action_index.has_value()) {
          OpenDownloadsFolder();
          break;
        }
        switch (*action_index) {
          case 0:
            OpenDownloadsFolder();
            break;
          case 1:
            CopyImageToClipboard();
            break;
          default:
            NOTREACHED_IN_MIGRATION();
            break;
        }
        break;
      case NearbyNotificationManager::ReceivedContentType::kFiles:
        OpenDownloadsFolder();
        break;
      case NearbyNotificationManager::ReceivedContentType::kWifiCredentials:
        OpenWifiNetworksList();
        break;
    }

    manager_->CloseSuccessNotification(notification_id);
  }

  void OnClose(const std::string& notification_id) override {
    manager_->CloseSuccessNotification(notification_id);
  }

 private:
  void OpenDownloadsFolder() {
    platform_util::OpenItem(
        profile_,
        DownloadPrefs::FromDownloadManager(profile_->GetDownloadManager())
            ->DownloadPath(),
        platform_util::OPEN_FOLDER, platform_util::OpenOperationCallback());

    if (testing_callback_) {
      std::move(testing_callback_)
          .Run(NearbyNotificationManager::SuccessNotificationAction::
                   kOpenDownloads);
    }
  }

  void OpenTextLink() {
    const std::string& url = share_target_.text_attachments[0].text_body();
    manager_->OpenURL(GURL(url));

    if (testing_callback_) {
      std::move(testing_callback_)
          .Run(NearbyNotificationManager::SuccessNotificationAction::kOpenUrl);
    }
  }

  void CopyTextToClipboard() {
    DCHECK_GT(share_target_.text_attachments.size(), 0u);
    const std::string& text = share_target_.text_attachments[0].text_body();
    ui::ScopedClipboardWriter(ui::ClipboardBuffer::kCopyPaste)
        .WriteText(base::UTF8ToUTF16(text));

    if (testing_callback_) {
      std::move(testing_callback_)
          .Run(NearbyNotificationManager::SuccessNotificationAction::kCopyText);
    }
  }

  void CopyImageToClipboard() {
    DCHECK(!image_.isNull());
    ui::ScopedClipboardWriter(ui::ClipboardBuffer::kCopyPaste)
        .WriteImage(image_);

    if (testing_callback_) {
      std::move(testing_callback_)
          .Run(
              NearbyNotificationManager::SuccessNotificationAction::kCopyImage);
    }
  }

  void OpenWifiNetworksList() {
    manager_->OpenWifiNetworksList();

    if (testing_callback_) {
      std::move(testing_callback_)
          .Run(NearbyNotificationManager::SuccessNotificationAction::
                   kOpenWifiNetworksList);
    }
  }

  raw_ptr<NearbyNotificationManager> manager_;
  raw_ptr<Profile> profile_;
  ShareTarget share_target_;
  NearbyNotificationManager::ReceivedContentType type_;
  SkBitmap image_;
  base::OnceCallback<void(NearbyNotificationManager::SuccessNotificationAction)>
      testing_callback_;
};

class NearbyDeviceTryingToShareNotificationDelegate
    : public NearbyNotificationDelegate {
 public:
  explicit NearbyDeviceTryingToShareNotificationDelegate(
      NearbyNotificationManager* manager)
      : manager_(manager) {}
  ~NearbyDeviceTryingToShareNotificationDelegate() override = default;

  // NearbyNotificationDelegate:
  void OnClick(const std::string& notification_id,
               const std::optional<int>& action_index) override {
    if (!action_index) {
      return;
    }

    switch (*action_index) {
      case 0:
        manager_->OnNearbyDeviceTryingToShareClicked();
        break;
      case 1:
        manager_->OnNearbyDeviceTryingToShareDismissed(
            /*did_click_dismiss=*/true);
        break;
      default:
        NOTREACHED_IN_MIGRATION();
        break;
    }
  }

  void OnClose(const std::string& notification_id) override {
    manager_->OnNearbyDeviceTryingToShareDismissed(/*did_click_dismiss=*/false);
  }

 private:
  raw_ptr<NearbyNotificationManager> manager_;
};

class NearbyVisibilityReminderNotificationDelegate
    : public NearbyNotificationDelegate {
 public:
  explicit NearbyVisibilityReminderNotificationDelegate(
      NearbyNotificationManager* manager)
      : manager_(manager) {}

  ~NearbyVisibilityReminderNotificationDelegate() override = default;

  void OnClick(const std::string& notification_id,
               const std::optional<int>& action_index) override {
    if (!action_index) {
      // Open settings when user click the notification.
      manager_->OnNearbyVisibilityReminderClicked();
      return;
    }

    switch (*action_index) {
      case 0:
        manager_->OnNearbyVisibilityReminderClicked();
        break;
      case 1:
        manager_->OnNearbyVisibilityReminderDismissed();
        break;
      default:
        NOTREACHED_IN_MIGRATION();
        break;
    }
  }

  void OnClose(const std::string& notification_id) override {
    manager_->OnNearbyVisibilityReminderDismissed();
  }

 private:
  raw_ptr<NearbyNotificationManager> manager_;
};

bool ShouldShowNearbyDeviceTryingToShareNotification(
    PrefService* pref_service) {
  base::Time last_dismissed = pref_service->GetTime(
      prefs::kNearbySharingNearbyDeviceTryingToShareDismissedTimePrefName);
  if (last_dismissed.is_null())
    return true;

  base::TimeDelta last_dismissed_delta = base::Time::Now() - last_dismissed;
  if (last_dismissed_delta <
      NearbyNotificationManager::kNearbyDeviceTryingToShareDismissedTimeout) {
    CD_LOG(VERBOSE, Feature::NS)
        << "Not showing onboarding notification: the user recently "
           "dismissed the notification.";
    return false;
  }

  return true;
}

bool ShouldShowNearbyVisibilityReminderNotification(PrefService* pref_service) {
  nearby_share::mojom::Visibility visibility =
      static_cast<nearby_share::mojom::Visibility>(pref_service->GetInteger(
          prefs::kNearbySharingBackgroundVisibilityName));

  return visibility == nearby_share::mojom::Visibility::kAllContacts ||
         visibility == nearby_share::mojom::Visibility::kSelectedContacts;
}

void UpdateNearbyDeviceTryingToShareDismissedTime(PrefService* pref_service) {
  pref_service->SetTime(
      prefs::kNearbySharingNearbyDeviceTryingToShareDismissedTimePrefName,
      base::Time::Now());
}

bool ShouldClearNotification(
    std::optional<TransferMetadata::Status> last_status,
    TransferMetadata::Status new_status) {
  if (!last_status)
    return true;

  // While receiving and waiting for the sender to accept, we are showing a
  // progress notification with 0% progress. We need not close the
  // progress notification when we move to showing determinate progress.
  if (*last_status == TransferMetadata::Status::kAwaitingRemoteAcceptance &&
      new_status == TransferMetadata::Status::kInProgress)
    return false;

  // In all other cases, if the status has changed, the previous notification
  // should be cleared.
  return *last_status != new_status;
}

}  // namespace

// static
constexpr base::TimeDelta
    NearbyNotificationManager::kNearbyDeviceTryingToShareDismissedTimeout;

void NearbyNotificationManager::SettingsOpener::ShowSettingsPage(
    Profile* profile,
    const std::string& sub_page) {
  chrome::SettingsWindowManager::GetInstance()->ShowOSSettings(profile,
                                                               sub_page);
}

NearbyNotificationManager::NearbyNotificationManager(
    NotificationDisplayService* notification_display_service,
    NearbySharingService* nearby_service,
    PrefService* pref_service,
    Profile* profile)
    : notification_display_service_(notification_display_service),
      nearby_service_(nearby_service),
      pref_service_(pref_service),
      profile_(profile),
      settings_opener_(std::make_unique<SettingsOpener>()) {
  DCHECK(notification_display_service_);
  DCHECK(nearby_service_);
  DCHECK(pref_service_);
  nearby_service_->AddObserver(this);
  nearby_service_->RegisterReceiveSurface(
      this, NearbySharingService::ReceiveSurfaceState::kBackground);
  nearby_service_->RegisterSendSurface(
      this, this, NearbySharingService::SendSurfaceState::kBackground);
}

NearbyNotificationManager::~NearbyNotificationManager() {
  nearby_service_->RemoveObserver(this);
  nearby_service_->UnregisterReceiveSurface(this);
  nearby_service_->UnregisterSendSurface(this, this);
}

void NearbyNotificationManager::OnTransferUpdate(
    const ShareTarget& share_target,
    const TransferMetadata& transfer_metadata) {
  if (!share_target_)
    share_target_ = share_target;
  DCHECK_EQ(share_target_->id, share_target.id);

  if (ShouldClearNotification(last_transfer_status_,
                              transfer_metadata.status())) {
    // Close any previous notifications, to allow subsequent high-priority
    // notifications to pop up.
    CloseTransfer();
  }
  last_transfer_status_ = transfer_metadata.status();

  switch (transfer_metadata.status()) {
    case TransferMetadata::Status::kInProgress:
      ShowProgress(share_target, transfer_metadata);
      break;
    case TransferMetadata::Status::kCancelled:
      // Only show the notification if the remote cancelled.
      if (!nearby_service_->DidLocalUserCancelTransfer(share_target))
        ShowCancelled(share_target);
      break;
    case TransferMetadata::Status::kAwaitingRemoteAcceptanceFailed:
    case TransferMetadata::Status::kExternalProviderLaunched:
      // Any previous notifications have been closed with the status change
      // check above that called CloseTransfer(). No notification is currently
      // shown for these statuses, so break.
      break;
    case TransferMetadata::Status::kAwaitingRemoteAcceptance:
      // Only incoming transfers are handled via notifications.
      if (share_target.is_incoming)
        // Show a progress notification with 0% progress while
        // waiting for the sender to accept.
        ShowProgress(share_target, transfer_metadata);
      break;
    case TransferMetadata::Status::kAwaitingLocalConfirmation:
      // Only incoming transfers are handled via notifications.
      // Don't show notification for auto-accept self shares.
      if (share_target.is_incoming && !share_target.CanAutoAccept()) {
        ShowConnectionRequest(share_target, transfer_metadata);
      }
      break;
    case TransferMetadata::Status::kComplete:
      ShowSuccess(share_target);
      break;
    case TransferMetadata::Status::kRejected:
    case TransferMetadata::Status::kTimedOut:
    case TransferMetadata::Status::kFailed:
    case TransferMetadata::Status::kNotEnoughSpace:
    case TransferMetadata::Status::kUnsupportedAttachmentType:
    case TransferMetadata::Status::kDecodeAdvertisementFailed:
    case TransferMetadata::Status::kMissingTransferUpdateCallback:
    case TransferMetadata::Status::kMissingShareTarget:
    case TransferMetadata::Status::kMissingEndpointId:
    case TransferMetadata::Status::kMissingPayloads:
    case TransferMetadata::Status::kPairedKeyVerificationFailed:
    case TransferMetadata::Status::kInvalidIntroductionFrame:
    case TransferMetadata::Status::kIncompletePayloads:
    case TransferMetadata::Status::kFailedToCreateShareTarget:
    case TransferMetadata::Status::kFailedToInitiateOutgoingConnection:
    case TransferMetadata::Status::kFailedToReadOutgoingConnectionResponse:
    case TransferMetadata::Status::kUnexpectedDisconnection:
      ShowFailure(share_target, transfer_metadata);
      break;
    case TransferMetadata::Status::kUnknown:
    case TransferMetadata::Status::kConnecting:
    case TransferMetadata::Status::kMediaUnavailable:
    case TransferMetadata::Status::kMediaDownloading:
      // Ignore
      break;
  }

  if (transfer_metadata.is_final_status()) {
    share_target_.reset();
    last_transfer_status_.reset();
  }
}

void NearbyNotificationManager::OnShareTargetDiscovered(
    ShareTarget share_target) {
  // Nothing to do here.
}

void NearbyNotificationManager::OnShareTargetLost(ShareTarget share_target) {
  // Nothing to do here.
}

void NearbyNotificationManager::OnNearbyProcessStopped() {
  if (share_target_ && last_transfer_status_) {
    CloseTransfer();
    ShowFailure(
        *share_target_,
        TransferMetadataBuilder().set_status(*last_transfer_status_).build());
  }
  share_target_ = std::nullopt;
  last_transfer_status_ = std::nullopt;
}

void NearbyNotificationManager::OnFastInitiationDevicesDetected() {
  ShowNearbyDeviceTryingToShare();
}

void NearbyNotificationManager::OnFastInitiationDevicesNotDetected() {
  CloseNearbyDeviceTryingToShare();
}

void NearbyNotificationManager::OnFastInitiationScanningStopped() {
  CloseNearbyDeviceTryingToShare();
}

void NearbyNotificationManager::ShowProgress(
    const ShareTarget& share_target,
    const TransferMetadata& transfer_metadata) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  message_center::Notification notification =
      CreateNearbyNotification(kNearbyInProgressNotificationId);
  notification.set_type(message_center::NOTIFICATION_TYPE_PROGRESS);
  notification.set_title(GetProgressNotificationTitle(share_target));
  notification.set_never_timeout(true);
  notification.set_pinned(true);
  notification.set_priority(message_center::NotificationPriority::MAX_PRIORITY);

  // Show 0% progress while waiting for remote device to accept.
  if (transfer_metadata.status() == TransferMetadata::Status::kInProgress)
    notification.set_progress(transfer_metadata.progress());
  else
    notification.set_progress(0);

  std::vector<message_center::ButtonInfo> notification_actions;
  notification_actions.emplace_back(l10n_util::GetStringUTF16(IDS_APP_CANCEL));
  notification.set_buttons(notification_actions);

  delegate_map_[notification.id()] =
      std::make_unique<ProgressNotificationDelegate>(
          this, /*awaiting_remote_acceptance=*/transfer_metadata.status() ==
                    TransferMetadata::Status::kAwaitingRemoteAcceptance);

  notification_display_service_->Display(
      NotificationHandler::Type::NEARBY_SHARE, notification,
      /*metadata=*/nullptr);
}

void NearbyNotificationManager::ShowConnectionRequest(
    const ShareTarget& share_target,
    const TransferMetadata& transfer_metadata) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  message_center::Notification notification =
      CreateNearbyNotification(kNearbyInProgressNotificationId);
  notification.set_title(
      features::IsNameEnabled()
          ? NearbyShareResourceGetter::GetInstance()->GetStringWithFeatureName(
                IDS_NEARBY_NOTIFICATION_CONNECTION_REQUEST_TITLE_PH)
          : l10n_util::GetStringUTF16(
                IDS_NEARBY_NOTIFICATION_CONNECTION_REQUEST_TITLE));
  notification.set_message(
      GetConnectionRequestNotificationMessage(share_target, transfer_metadata));
  notification.set_icon(GetImageFromShareTarget(share_target));
  notification.set_never_timeout(true);
  notification.set_priority(message_center::NotificationPriority::MAX_PRIORITY);

  std::vector<message_center::ButtonInfo> notification_actions;
  notification_actions.emplace_back(
      l10n_util::GetStringUTF16(IDS_NEARBY_NOTIFICATION_ACCEPT_ACTION));
  notification_actions.emplace_back(
      l10n_util::GetStringUTF16(IDS_NEARBY_NOTIFICATION_DECLINE_ACTION));
  notification.set_buttons(notification_actions);

  delegate_map_[notification.id()] =
      std::make_unique<ConnectionRequestNotificationDelegate>(this);

  notification_display_service_->Display(
      NotificationHandler::Type::NEARBY_SHARE, notification,
      /*metadata=*/nullptr);
}

void NearbyNotificationManager::ShowNearbyDeviceTryingToShare() {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  if (!ShouldShowNearbyDeviceTryingToShareNotification(pref_service_))
    return;

  CD_LOG(INFO, Feature::NS) << "Showing fast initiation notification.";

  message_center::Notification notification =
      CreateNearbyNotification(kNearbyDeviceTryingToShareNotificationId);

  bool is_onboarding_complete = pref_service_->GetBoolean(
      prefs::kNearbySharingOnboardingCompletePrefName);

  notification.set_title(
      l10n_util::GetStringUTF16(IDS_NEARBY_NOTIFICATION_ONBOARDING_TITLE));

  const std::u16string onboarding_message =
      features::IsNameEnabled()
          ? NearbyShareResourceGetter::GetInstance()->GetStringWithFeatureName(
                IDS_NEARBY_NOTIFICATION_ONBOARDING_MESSAGE_PH)
          : l10n_util::GetStringUTF16(
                IDS_NEARBY_NOTIFICATION_ONBOARDING_MESSAGE);

  notification.set_message(is_onboarding_complete
                               ? l10n_util::GetStringUTF16(
                                     IDS_NEARBY_NOTIFICATION_GO_VISIBLE_MESSAGE)
                               : onboarding_message);

  std::vector<message_center::ButtonInfo> notification_actions;
  notification_actions.emplace_back(l10n_util::GetStringUTF16(
      is_onboarding_complete ? IDS_NEARBY_NOTIFICATION_GO_VISIBLE_ACTION
                             : IDS_NEARBY_NOTIFICATION_SET_UP_ACTION));
  notification_actions.emplace_back(
      l10n_util::GetStringUTF16(IDS_NEARBY_NOTIFICATION_DISMISS_ACTION));
  notification.set_buttons(notification_actions);

  delegate_map_[kNearbyDeviceTryingToShareNotificationId] =
      std::make_unique<NearbyDeviceTryingToShareNotificationDelegate>(this);

  notification_display_service_->Display(
      NotificationHandler::Type::NEARBY_SHARE, notification,
      /*metadata=*/nullptr);

  last_device_nearby_sharing_notification_shown_timestamp_ =
      base::TimeTicks::Now();

  if (is_onboarding_complete) {
    RecordNearbyShareDeviceNearbySharingNotificationFlowEvent(
        NearbyShareBackgroundScanningDeviceNearbySharingNotificationFlowEvent::
            kNotificationShown);
  } else {
    RecordNearbyShareSetupNotificationFlowEvent(
        NearbyShareBackgroundScanningSetupNotificationFlowEvent::
            kNotificationShown);
  }
}

void NearbyNotificationManager::ShowSuccess(const ShareTarget& share_target) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  if (!share_target.is_incoming) {
    std::string notification_id =
        CreateNotificationIdForShareTarget(share_target);
    message_center::Notification notification =
        CreateNearbyNotification(notification_id);
    notification.set_title(GetSuccessNotificationTitle(share_target));

    delegate_map_.erase(notification_id);

    notification_display_service_->Display(
        NotificationHandler::Type::NEARBY_SHARE, notification,
        /*metadata=*/nullptr);
    return;
  }

  ReceivedContentType type = GetReceivedContentType(share_target);

  if (type != ReceivedContentType::kSingleImage) {
    ShowIncomingSuccess(share_target, type, /*image=*/SkBitmap());
    return;
  }

  // ReceivedContentType::kSingleImage means exactly one image file.
  DCHECK_EQ(1u, share_target.file_attachments.size());

  // ReceivedImageDecoder will delete itself.
  auto* image_decoder = new ReceivedImageDecoder(
      base::BindOnce(&NearbyNotificationManager::ShowIncomingSuccess,
                     weak_ptr_factory_.GetWeakPtr(), share_target, type));
  image_decoder->DecodeImage(share_target.file_attachments[0].file_path());
}

void NearbyNotificationManager::ShowIncomingSuccess(
    const ShareTarget& share_target,
    ReceivedContentType type,
    const SkBitmap& image) {
  std::string notification_id =
      CreateNotificationIdForShareTarget(share_target);
  message_center::Notification notification =
      CreateNearbyNotification(notification_id);
  notification.set_title(GetSuccessNotificationTitle(share_target));

  // Revert to generic file handling if image decoding failed.
  if (type == ReceivedContentType::kSingleImage && image.isNull())
    type = ReceivedContentType::kFiles;

  if (!image.isNull()) {
    notification.set_type(message_center::NOTIFICATION_TYPE_IMAGE);
    notification.SetImage(gfx::Image::CreateFrom1xBitmap(image));
  }

  std::vector<message_center::ButtonInfo> notification_actions;
  switch (type) {
    case ReceivedContentType::kText: {
      notification_actions.emplace_back(l10n_util::GetStringUTF16(
          IDS_NEARBY_NOTIFICATION_ACTION_COPY_TO_CLIPBOARD));
      std::optional<std::u16string> message =
          GetReceivedNotificationTextMessage(share_target);
      if (message) {
        notification.set_message(message.value());
      }
      break;
    }
    case ReceivedContentType::kSingleUrl:
      notification_actions.emplace_back(
          l10n_util::GetStringUTF16(IDS_NEARBY_NOTIFICATION_ACTION_OPEN_URL));
      notification_actions.emplace_back(l10n_util::GetStringUTF16(
          IDS_NEARBY_NOTIFICATION_ACTION_COPY_TO_CLIPBOARD));
      break;
    case ReceivedContentType::kSingleImage:
      notification_actions.emplace_back(l10n_util::GetStringUTF16(
          IDS_NEARBY_NOTIFICATION_ACTION_OPEN_FOLDER));
      notification_actions.emplace_back(l10n_util::GetStringUTF16(
          IDS_NEARBY_NOTIFICATION_ACTION_COPY_TO_CLIPBOARD));
      break;
    case ReceivedContentType::kFiles:
      notification_actions.emplace_back(l10n_util::GetStringUTF16(
          IDS_NEARBY_NOTIFICATION_ACTION_OPEN_FOLDER));
      break;
    case ReceivedContentType::kWifiCredentials:
      notification_actions.emplace_back(l10n_util::GetStringUTF16(
          IDS_NEARBY_NOTIFICATION_ACTION_OPEN_NETWORK_LIST));
      break;
  }
  notification.set_buttons(notification_actions);

  delegate_map_[notification_id] =
      std::make_unique<SuccessNotificationDelegate>(
          this, profile_, share_target, type, image,
          std::move(success_action_test_callback_));

  notification_display_service_->Display(
      NotificationHandler::Type::NEARBY_SHARE, notification,
      /*metadata=*/nullptr);

  ash::HoldingSpaceKeyedService* holding_space_keyed_service =
      ash::HoldingSpaceKeyedServiceFactory::GetInstance()->GetService(profile_);
  if (holding_space_keyed_service) {
    for (const auto& file : share_target.file_attachments) {
      if (file.file_path().has_value())
        holding_space_keyed_service->AddItemOfType(
            ash::HoldingSpaceItem::Type::kNearbyShare,
            file.file_path().value());
    }
  }
}

void NearbyNotificationManager::ShowFailure(
    const ShareTarget& share_target,
    const TransferMetadata& transfer_metadata) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  std::string notification_id =
      CreateNotificationIdForShareTarget(share_target);
  message_center::Notification notification =
      CreateNearbyNotification(notification_id);
  notification.set_title(GetFailureNotificationTitle(share_target));

  std::optional<std::u16string> message =
      GetFailureNotificationMessage(transfer_metadata.status());
  if (message) {
    notification.set_message(*message);
  }

  delegate_map_.erase(notification_id);

  notification_display_service_->Display(
      NotificationHandler::Type::NEARBY_SHARE, notification,
      /*metadata=*/nullptr);
}

void NearbyNotificationManager::ShowCancelled(const ShareTarget& share_target) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  std::string notification_id =
      CreateNotificationIdForShareTarget(share_target);
  message_center::Notification notification =
      CreateNearbyNotification(notification_id);

  notification.set_title(base::ReplaceStringPlaceholders(
      l10n_util::GetStringUTF16(IDS_NEARBY_NOTIFICATION_SENDER_CANCELLED),
      {base::UTF8ToUTF16(share_target.device_name)}, /*offsets=*/nullptr));

  delegate_map_.erase(notification_id);

  notification_display_service_->Display(
      NotificationHandler::Type::NEARBY_SHARE, notification,
      /*metadata=*/nullptr);
}

void NearbyNotificationManager::ShowVisibilityReminder() {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  if (!ShouldShowNearbyVisibilityReminderNotification(pref_service_)) {
    return;
  }

  message_center::Notification notification =
      CreateNearbyNotification(kNearbyVisibilityReminderNotificationId);
  notification.set_title(l10n_util::GetStringUTF16(
      IDS_NEARBY_NOTIFICATION_VISIBILITY_REMINDER_TITLE));
  notification.set_message(l10n_util::GetStringUTF16(
      IDS_NEARBY_NOTIFICATION_VISIBILITY_REMINDER_MESSAGE));

  std::vector<message_center::ButtonInfo> notification_actions;
  notification_actions.emplace_back(
      l10n_util::GetStringUTF16(IDS_NEARBY_NOTIFICATION_GO_TO_SETTINGS_ACTION));
  notification_actions.emplace_back(
      l10n_util::GetStringUTF16(IDS_NEARBY_NOTIFICATION_DISMISS_ACTION));

  notification.set_buttons(notification_actions);

  delegate_map_[kNearbyVisibilityReminderNotificationId] =
      std::make_unique<NearbyVisibilityReminderNotificationDelegate>(this);

  notification_display_service_->Display(
      NotificationHandler::Type::NEARBY_SHARE, notification,
      /*metadata=*/nullptr);
}

void NearbyNotificationManager::CloseTransfer() {
  delegate_map_.erase(kNearbyInProgressNotificationId);
  notification_display_service_->Close(NotificationHandler::Type::NEARBY_SHARE,
                                       kNearbyInProgressNotificationId);
}

void NearbyNotificationManager::CloseNearbyDeviceTryingToShare() {
  delegate_map_.erase(kNearbyDeviceTryingToShareNotificationId);
  notification_display_service_->Close(
      NotificationHandler::Type::NEARBY_SHARE,
      kNearbyDeviceTryingToShareNotificationId);
}

void NearbyNotificationManager::CloseVisibilityReminder() {
  delegate_map_.erase(kNearbyVisibilityReminderNotificationId);
  notification_display_service_->Close(NotificationHandler::Type::NEARBY_SHARE,
                                       kNearbyVisibilityReminderNotificationId);
}

NearbyNotificationDelegate* NearbyNotificationManager::GetNotificationDelegate(
    const std::string& notification_id) {
  auto iter = delegate_map_.find(notification_id);
  if (iter == delegate_map_.end())
    return nullptr;

  return iter->second.get();
}

void NearbyNotificationManager::OpenURL(GURL url) {
  nearby_service_->OpenURL(url);
}

void NearbyNotificationManager::OpenWifiNetworksList() {
  settings_opener_->ShowSettingsPage(
      profile_, chromeos::settings::mojom::kKnownNetworksSubpagePath);
}

void NearbyNotificationManager::CancelTransfer() {
  CloseTransfer();
  nearby_service_->Cancel(*share_target_, base::DoNothing());
}

void NearbyNotificationManager::RejectTransfer() {
  CloseTransfer();
  nearby_service_->Reject(*share_target_, base::DoNothing());
}

void NearbyNotificationManager::AcceptTransfer() {
  // Do not close the notification as it will be replaced soon.
  nearby_service_->Accept(*share_target_, base::DoNothing());
}

void NearbyNotificationManager::OnNearbyDeviceTryingToShareClicked() {
  CloseNearbyDeviceTryingToShare();

  std::string path =
      std::string(chromeos::settings::mojom::kNearbyShareSubpagePath) +
      "?receive&entrypoint=notification&";

  // Append a timestamp to ensure that the URL that we use to open the receive
  // dialog on the Nearby Share subpage is unique each time the user clicks on
  // an instance of this notification. This ensures that the page will reload
  // and we will restart high visibility mode. Since we don't actually read this
  // timestamp, it will not cause a problem if the user manually reloads the
  // page with a stale timestamp.
  path += "&time=" + GetTimestampString();
  settings_opener_->ShowSettingsPage(profile_, path);

  bool is_onboarding_complete = pref_service_->GetBoolean(
      prefs::kNearbySharingOnboardingCompletePrefName);
  if (is_onboarding_complete) {
    RecordNearbyShareDeviceNearbySharingNotificationFlowEvent(
        NearbyShareBackgroundScanningDeviceNearbySharingNotificationFlowEvent::
            kEnable);
    RecordNearbyShareDeviceNearbySharingNotificationTimeToAction(
        base::TimeTicks::Now() -
        last_device_nearby_sharing_notification_shown_timestamp_);
  } else {
    RecordNearbyShareSetupNotificationFlowEvent(
        NearbyShareBackgroundScanningSetupNotificationFlowEvent::kSetup);
    RecordNearbyShareSetupNotificationTimeToAction(
        base::TimeTicks::Now() -
        last_device_nearby_sharing_notification_shown_timestamp_);
  }
}

void NearbyNotificationManager::OnNearbyDeviceTryingToShareDismissed(
    bool did_click_dismiss) {
  CloseNearbyDeviceTryingToShare();
  UpdateNearbyDeviceTryingToShareDismissedTime(pref_service_);

  bool is_onboarding_complete = pref_service_->GetBoolean(
      prefs::kNearbySharingOnboardingCompletePrefName);

  if (is_onboarding_complete) {
    RecordNearbyShareDeviceNearbySharingNotificationFlowEvent(
        did_click_dismiss
            ? NearbyShareBackgroundScanningDeviceNearbySharingNotificationFlowEvent::
                  kDismiss
            : NearbyShareBackgroundScanningDeviceNearbySharingNotificationFlowEvent::
                  kExit);
  } else {
    RecordNearbyShareSetupNotificationFlowEvent(
        did_click_dismiss
            ? NearbyShareBackgroundScanningSetupNotificationFlowEvent::kDismiss
            : NearbyShareBackgroundScanningSetupNotificationFlowEvent::kExit);
    RecordNearbyShareSetupNotificationTimeToAction(
        base::TimeTicks::Now() -
        last_device_nearby_sharing_notification_shown_timestamp_);
  }
}

void NearbyNotificationManager::CloseSuccessNotification(
    const std::string& notification_id) {
  delegate_map_.erase(notification_id);
  notification_display_service_->Close(NotificationHandler::Type::NEARBY_SHARE,
                                       notification_id);
}

void NearbyNotificationManager::OnNearbyVisibilityReminderClicked() {
  CloseVisibilityReminder();

  std::string path =
      std::string(chromeos::settings::mojom::kNearbyShareSubpagePath) +
      "?visibility";

  settings_opener_->ShowSettingsPage(profile_, path);
}

void NearbyNotificationManager::OnNearbyVisibilityReminderDismissed() {
  CloseVisibilityReminder();
}

void NearbyNotificationManager::SetOnSuccessClickedForTesting(
    base::OnceCallback<void(SuccessNotificationAction)> callback) {
  success_action_test_callback_ = std::move(callback);
}

void NearbyNotificationManager::SetSettingsOpenerForTesting(
    std::unique_ptr<SettingsOpener> settings_opener) {
  settings_opener_ = std::move(settings_opener);
}