chromium/chrome/browser/ash/policy/dlp/files_policy_notification_manager.cc

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

#include "chrome/browser/ash/policy/dlp/files_policy_notification_manager.h"

#include <cstddef>
#include <memory>
#include <optional>
#include <string>

#include "base/check.h"
#include "base/check_is_test.h"
#include "base/containers/contains.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/functional/callback_forward.h"
#include "base/memory/scoped_refptr.h"
#include "base/notreached.h"
#include "base/strings/string_number_conversions.h"
#include "base/types/optional_util.h"
#include "chrome/browser/ash/extensions/file_manager/system_notification_manager.h"
#include "chrome/browser/ash/file_manager/io_task.h"
#include "chrome/browser/ash/file_manager/io_task_controller.h"
#include "chrome/browser/ash/file_manager/url_util.h"
#include "chrome/browser/ash/file_manager/volume_manager.h"
#include "chrome/browser/ash/policy/dlp/dialogs/files_policy_dialog.h"
#include "chrome/browser/ash/policy/dlp/files_policy_string_util.h"
#include "chrome/browser/chromeos/policy/dlp/dialogs/policy_dialog_base.h"
#include "chrome/browser/chromeos/policy/dlp/dlp_confidential_file.h"
#include "chrome/browser/chromeos/policy/dlp/dlp_file_destination.h"
#include "chrome/browser/chromeos/policy/dlp/dlp_files_utils.h"
#include "chrome/browser/notifications/notification_display_service.h"
#include "chrome/browser/notifications/notification_display_service_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/ash/system_web_apps/system_web_app_ui_utils.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/common/chrome_features.h"
#include "components/enterprise/data_controls/core/browser/dlp_histogram_helper.h"
#include "components/strings/grit/components_strings.h"
#include "content/public/browser/browser_context.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/message_center/public/cpp/notification.h"
#include "ui/message_center/public/cpp/notification_delegate.h"

namespace policy {

namespace {
// How long to wait for a Files App to open before falling back to a system
// modal.
const base::TimeDelta kOpenFilesAppTimeout = base::Milliseconds(3000);

// Warning time out is 5 mins.
const base::TimeDelta kWarningTimeout = base::Minutes(5);

constexpr char kDlpFilesNotificationId[] = "dlp_files";

std::u16string GetNotificationTitle(NotificationType type,
                                    dlp::FileAction action,
                                    std::optional<size_t> file_count) {
  switch (type) {
    case NotificationType::kError:
      CHECK(file_count.has_value());
      return policy::files_string_util::GetBlockTitle(action,
                                                      file_count.value());
    case NotificationType::kWarning:
      return policy::files_string_util::GetWarnTitle(action);
  }
}

// Returns the message for notification of `type` and with `file_count`
// blocked/warned files. `first_file` is the name of the first restricted file
// and is only used for single file notifications. `reason` is the block reason
// of the first restricted file and is only used for single file block
// notifications.
std::u16string GetNotificationMessage(
    NotificationType type,
    size_t file_count,
    const std::u16string& first_file,
    std::optional<FilesPolicyDialog::BlockReason> reason) {
  switch (type) {
    case NotificationType::kError:
      CHECK(reason.has_value());
      return file_count > 1
                 ? l10n_util::GetStringUTF16(IDS_POLICY_DLP_FILES_BLOCK_MESSAGE)
                 : policy::files_string_util::GetBlockReasonMessage(
                       reason.value(), first_file);
    case NotificationType::kWarning:
      const std::u16string placeholder_value =
          file_count == 1 ? first_file : base::NumberToString16(file_count);
      return base::ReplaceStringPlaceholders(
          l10n_util::GetPluralStringFUTF16(IDS_POLICY_DLP_FILES_WARN_MESSAGE,
                                           file_count),
          placeholder_value,
          /*offset=*/nullptr);
  }
}

std::u16string GetOkButton(NotificationType type,
                           dlp::FileAction action,
                           size_t file_count,
                           bool always_show_review) {
  // Multiple files or custom dialog settings - both warnings and errors have a
  // Review button.
  if (file_count > 1 || always_show_review) {
    return l10n_util::GetStringUTF16(IDS_POLICY_DLP_FILES_REVIEW_BUTTON);
  }
  // Single file and no admin defined custom dialog settings - button text
  // depends on the type.
  switch (type) {
    case NotificationType::kError:
      return l10n_util::GetStringUTF16(IDS_LEARN_MORE);
    case NotificationType::kWarning:
      return policy::files_string_util::GetContinueAnywayButton(action);
  }
}

std::u16string GetCancelButton(NotificationType type) {
  switch (type) {
    case NotificationType::kError:
      return l10n_util::GetStringUTF16(IDS_POLICY_DLP_FILES_DISMISS_BUTTON);
    case NotificationType::kWarning:
      return l10n_util::GetStringUTF16(IDS_POLICY_DLP_WARN_CANCEL_BUTTON);
  }
}

std::u16string GetTimeoutNotificationTitle(dlp::FileAction action) {
  switch (action) {
    case dlp::FileAction::kDownload:
      return l10n_util::GetStringUTF16(
          IDS_POLICY_DLP_FILES_DOWNLOAD_TIMEOUT_TITLE);
    case dlp::FileAction::kTransfer:
    case dlp::FileAction::kUnknown:
      // kUnknown is used for internal checks - treat as kTransfer.
      return l10n_util::GetStringUTF16(
          IDS_POLICY_DLP_FILES_TRANSFER_TIMEOUT_TITLE);
    case dlp::FileAction::kUpload:
      return l10n_util::GetStringUTF16(
          IDS_POLICY_DLP_FILES_UPLOAD_TIMEOUT_TITLE);
    case dlp::FileAction::kCopy:
      return l10n_util::GetStringUTF16(IDS_POLICY_DLP_FILES_COPY_TIMEOUT_TITLE);
    case dlp::FileAction::kMove:
      return l10n_util::GetStringUTF16(IDS_POLICY_DLP_FILES_MOVE_TIMEOUT_TITLE);
    case dlp::FileAction::kOpen:
    case dlp::FileAction::kShare:
      return l10n_util::GetStringUTF16(IDS_POLICY_DLP_FILES_OPEN_TIMEOUT_TITLE);
  }
}

std::u16string GetTimeoutNotificationMessage(dlp::FileAction action) {
  switch (action) {
    case dlp::FileAction::kDownload:
      return l10n_util::GetStringUTF16(
          IDS_POLICY_DLP_FILES_DOWNLOAD_TIMEOUT_MESSAGE);
    case dlp::FileAction::kTransfer:
    case dlp::FileAction::kUnknown:
      // kUnknown is used for internal checks - treat as kTransfer.
      return l10n_util::GetStringUTF16(
          IDS_POLICY_DLP_FILES_TRANSFER_TIMEOUT_MESSAGE);
    case dlp::FileAction::kUpload:
      return l10n_util::GetStringUTF16(
          IDS_POLICY_DLP_FILES_UPLOAD_TIMEOUT_MESSAGE);
    case dlp::FileAction::kCopy:
      return l10n_util::GetStringUTF16(
          IDS_POLICY_DLP_FILES_COPY_TIMEOUT_MESSAGE);
    case dlp::FileAction::kMove:
      return l10n_util::GetStringUTF16(
          IDS_POLICY_DLP_FILES_MOVE_TIMEOUT_MESSAGE);
    case dlp::FileAction::kOpen:
    case dlp::FileAction::kShare:
      return l10n_util::GetStringUTF16(
          IDS_POLICY_DLP_FILES_OPEN_TIMEOUT_MESSAGE);
  }
}

// Dismisses the notification with `notification_id`.
void Dismiss(content::BrowserContext* context,
             const std::string& notification_id) {
  auto* profile = Profile::FromBrowserContext(context);
  DCHECK(profile);
  NotificationDisplayServiceFactory::GetForProfile(profile)->Close(
      NotificationHandler::Type::TRANSIENT, notification_id);
}

file_manager::io_task::IOTaskController* GetIOTaskController(
    content::BrowserContext* context) {
  DCHECK(context);
  file_manager::VolumeManager* const volume_manager =
      file_manager::VolumeManager::Get(Profile::FromBrowserContext(context));
  if (!volume_manager) {
    LOG(ERROR) << "FilesPolicyNotificationManager failed to find "
                  "file_manager::VolumeManager";
    return nullptr;
  }
  return volume_manager->io_task_controller();
}

// Computes and returns a new notification ID by appending `count` to the
// prefix.
std::string GetNotificationId(size_t count) {
  return kDlpFilesNotificationId + std::string("_") +
         base::NumberToString(count);
}

// Returns whether custom dialog settings have been defined for at least one
// block reason.
bool HasCustomDialogSettings(
    const std::map<FilesPolicyDialog::BlockReason, FilesPolicyDialog::Info>&
        dialog_info_map) {
  for (const auto& [_, dialog_info] : dialog_info_map) {
    if (dialog_info.HasCustomDetails()) {
      return true;
    }
  }
  return false;
}

// Notification click handler implementation for files policy notifications.
// The handler ensures that we only handle the button click once. This is
// required because some of the parameters are move-only types and wouldn't be
// valid on the second invocation.
class PolicyNotificationClickHandler
    : public message_center::NotificationDelegate {
 public:
  explicit PolicyNotificationClickHandler(
      base::OnceCallback<void(std::optional<int>)> callback)
      : callback_(std::move(callback)) {}

  void Close(bool by_user) override {
    // Treat any close reason as the user clicking the Cancel button.
    Click(NotificationButton::CANCEL, /*reply=*/std::nullopt);
  }

  // message_center::NotificationDelegate overrides:
  void Click(const std::optional<int>& button_index,
             const std::optional<std::u16string>& reply) override {
    if (!button_index.has_value()) {
      // Ignore clicks on the notification, but not on the buttons.
      return;
    }

    // The callback might have already been invoked earlier, so check first.
    if (callback_) {
      std::move(callback_).Run(button_index);
    }
  }

 private:
  ~PolicyNotificationClickHandler() override = default;

  base::OnceCallback<void(std::optional<int>)> callback_;
};

}  // namespace

FilesPolicyNotificationManager::FilesPolicyNotificationManager(
    content::BrowserContext* context)
    : context_(context),
      task_runner_(base::SequencedTaskRunner::GetCurrentDefault()) {
  DCHECK(context);

  auto* io_task_controller = GetIOTaskController(context_);
  if (!io_task_controller) {
    LOG(ERROR) << "FilesPolicyNotificationManager failed to find "
                  "file_manager::io_task::IOTaskController";
    return;
  }
  io_task_controller->AddObserver(this);
}

FilesPolicyNotificationManager::~FilesPolicyNotificationManager() = default;

void FilesPolicyNotificationManager::Shutdown() {
  file_manager::VolumeManager* const volume_manager =
      file_manager::VolumeManager::Get(Profile::FromBrowserContext(context_));
  if (volume_manager) {
    auto* io_task_controller = volume_manager->io_task_controller();
    if (io_task_controller) {
      io_task_controller->RemoveObserver(this);
    }
  }
}

void FilesPolicyNotificationManager::ShowDlpBlockedFiles(
    std::optional<file_manager::io_task::IOTaskId> task_id,
    std::vector<base::FilePath> blocked_files,
    dlp::FileAction action) {
  data_controls::DlpHistogramEnumeration(
      data_controls::dlp::kFileActionBlockedUMA, action);
  data_controls::DlpCountHistogram10000(
      data_controls::dlp::kFilesBlockedCountUMA, blocked_files.size());

  // If `task_id` has value, the corresponding IOTask should be updated
  // accordingly.
  if (task_id.has_value()) {
    // Sometimes DLP checks are done before FilesPolicyNotificationManager is
    // lazily created, so the task is not tracked and the blocked files won't
    // be added. On the other hand, the IO task may be aborted/canceled
    // already so the info saved may be not needed anymore.
    if (!HasIOTask(task_id.value())) {
      AddIOTask(task_id.value(), action);
    }
    io_tasks_.at(task_id.value())
        .SetBlockedFiles(
            FilesPolicyDialog::BlockReason::kDlp,
            FilesPolicyDialog::Info::Error(FilesPolicyDialog::BlockReason::kDlp,
                                           blocked_files));
  } else {
    ShowDlpBlockNotification(std::move(blocked_files), action);
  }
}

void FilesPolicyNotificationManager::SetConnectorsBlockedFiles(
    file_manager::io_task::IOTaskId task_id,
    dlp::FileAction action,
    FilesPolicyDialog::BlockReason reason,
    FilesPolicyDialog::Info dialog_info) {
  // Sometimes EC checks are done before FilesPolicyNotificationManager is
  // lazily created, so the task is not tracked and the blocked files won't
  // be added. On the other hand, the IOTask may be aborted/canceled already so
  // the info saved may be not needed anymore.
  if (!HasIOTask(task_id)) {
    AddIOTask(task_id, action);
  }
  io_tasks_.at(task_id).SetBlockedFiles(reason, std::move(dialog_info));
}

void FilesPolicyNotificationManager::ShowDlpWarning(
    WarningWithJustificationCallback callback,
    std::optional<file_manager::io_task::IOTaskId> task_id,
    std::vector<base::FilePath> warning_files,
    const DlpFileDestination& destination,
    dlp::FileAction action) {
  data_controls::DlpHistogramEnumeration(
      data_controls::dlp::kFileActionWarnedUMA, action);
  data_controls::DlpCountHistogram10000(
      data_controls::dlp::kFilesWarnedCountUMA, warning_files.size());

  // If `task_id` has value, the corresponding IOTask should be paused.
  if (task_id.has_value()) {
    PauseIOTask(task_id.value(), std::move(callback), action, Policy::kDlp,
                FilesPolicyDialog::Info::Warn(
                    FilesPolicyDialog::BlockReason::kDlp, warning_files));
  } else {
    ShowDlpWarningNotification(std::move(callback), std::move(warning_files),
                               destination, action);
  }
}

void FilesPolicyNotificationManager::ShowConnectorsWarning(
    WarningWithJustificationCallback callback,
    file_manager::io_task::IOTaskId task_id,
    dlp::FileAction action,
    FilesPolicyDialog::Info dialog_info) {
  PauseIOTask(task_id, std::move(callback), action,
              Policy::kEnterpriseConnectors, std::move(dialog_info));
}

void FilesPolicyNotificationManager::ShowFilesPolicyNotification(
    const std::string& notification_id,
    const file_manager::io_task::ProgressStatus& status) {
  const file_manager::io_task::IOTaskId id(status.task_id);
  const dlp::FileAction action =
      status.type == file_manager::io_task::OperationType::kCopy
          ? dlp::FileAction::kCopy
          : dlp::FileAction::kMove;
  if (status.HasPolicyError() &&
      status.policy_error->type ==
          file_manager::io_task::PolicyErrorType::kDlpWarningTimeout) {
    ShowDlpWarningTimeoutNotification(action, notification_id);
    return;
  }
  // Only show the notification if we have either warning or blocked files.
  if (HasWarning(id) || HasBlockedFiles(id)) {
    ShowFilesPolicyNotification(notification_id, status.task_id);
  }
}

void FilesPolicyNotificationManager::ShowDialog(
    file_manager::io_task::IOTaskId task_id,
    FilesDialogType type) {
  auto* profile = Profile::FromBrowserContext(context_);
  DCHECK(profile);

  // Get the last active Files app window.
  Browser* browser =
      FindSystemWebAppBrowser(profile, ash::SystemWebAppType::FILE_MANAGER);
  gfx::NativeWindow modal_parent =
      browser ? browser->window()->GetNativeWindow() : nullptr;
  if (modal_parent) {
    ShowDialogForIOTask(task_id, type, modal_parent);
    return;
  }

  // No window found, so open a new one. This should notify us through
  // OnBrowserSetLastActive() to show the dialog.
  LaunchFilesApp(std::make_unique<DialogInfo>(
      base::BindOnce(&FilesPolicyNotificationManager::ShowDialogForIOTask,
                     weak_factory_.GetWeakPtr(), task_id, type),
      task_id,
      base::BindOnce(&FilesPolicyNotificationManager::OnIOTaskAppLaunchTimedOut,
                     weak_factory_.GetWeakPtr(), task_id)));
}

void FilesPolicyNotificationManager::ShowDlpWarningTimeoutNotification(
    dlp::FileAction action,
    std::optional<std::string> notification_id) {
  if (!notification_id.has_value()) {
    notification_id = GetNotificationId(notification_count_++);
  }
  // The notification should stay visible until dismissed.
  message_center::RichNotificationData optional_fields;
  optional_fields.never_timeout = true;
  auto notification = file_manager::CreateSystemNotification(
      notification_id.value(), GetTimeoutNotificationTitle(action),
      GetTimeoutNotificationMessage(action),
      base::MakeRefCounted<message_center::HandleNotificationClickDelegate>(
          base::BindRepeating(&Dismiss, context_, notification_id.value())),
      optional_fields);
  notification->set_buttons(
      {message_center::ButtonInfo(GetCancelButton(NotificationType::kError))});
  auto* profile = Profile::FromBrowserContext(context_);
  DCHECK(profile);
  NotificationDisplayServiceFactory::GetForProfile(profile)->Display(
      NotificationHandler::Type::TRANSIENT, *notification,
      /*metadata=*/nullptr);
}

bool FilesPolicyNotificationManager::HasIOTask(
    file_manager::io_task::IOTaskId task_id) const {
  return base::Contains(io_tasks_, task_id);
}

void FilesPolicyNotificationManager::OnIOTaskResumed(
    file_manager::io_task::IOTaskId task_id) {
  if (base::Contains(io_tasks_warning_timers_, task_id)) {
    io_tasks_warning_timers_.erase(task_id);
  }

  if (!HasIOTask(task_id)) {
    // Task is already completed or timed out.
    return;
  }

  if (!HasWarning(task_id)) {
    // Warning callback is already run.
    return;
  }

  auto* warning_info = io_tasks_.at(task_id).GetWarningInfo();
  std::move(warning_info->warning_callback)
      .Run(/*user_justification=*/warning_info->user_justification,
           /*should_proceed=*/true);
  io_tasks_.at(task_id).ResetWarningInfo();
}

void FilesPolicyNotificationManager::ShowBlockedNotifications() {
  for (const auto& task : io_tasks_) {
    if (HasBlockedFiles(task.first)) {
      ShowFilesPolicyNotification(file_manager::GetNotificationId(task.first),
                                  task.first);
    }
  }
}

void FilesPolicyNotificationManager::OnErrorItemDismissed(
    file_manager::io_task::IOTaskId task_id) {
  io_tasks_.erase(task_id);
}

std::map<FilesPolicyDialog::BlockReason, FilesPolicyDialog::Info>
FilesPolicyNotificationManager::GetIOTaskDialogInfoMapForTesting(
    file_manager::io_task::IOTaskId task_id) const {
  if (!HasIOTask(task_id)) {
    return {};
  }
  return io_tasks_.at(task_id).block_info_map();
}

bool FilesPolicyNotificationManager::HasWarningTimerForTesting(
    file_manager::io_task::IOTaskId task_id) const {
  return base::Contains(io_tasks_warning_timers_, task_id);
}

void FilesPolicyNotificationManager::HandleDlpWarningNotificationClick(
    std::string notification_id,
    std::optional<int> button_index) {
  if (!button_index.has_value()) {
    return;
  }

  Dismiss(context_, notification_id);

  CHECK(HasWarning(notification_id));
  auto* warning_info = non_io_tasks_.at(notification_id).GetWarningInfo();
  CHECK(!warning_info->warning_callback.is_null());

  switch (button_index.value()) {
    case NotificationButton::CANCEL:
      std::move(warning_info->warning_callback)
          .Run(/*user_justification=*/std::nullopt, /*should_proceed=*/false);
      non_io_tasks_.erase(notification_id);
      non_io_tasks_warning_timers_.erase(notification_id);
      break;

    case NotificationButton::OK:
      CHECK(warning_info->dialog_info.GetFiles().size() >= 1);

      if (warning_info->dialog_info.GetFiles().size() == 1 &&
          !warning_info->dialog_info.HasCustomDetails()) {
        // Action anyway.
        std::move(warning_info->warning_callback)
            .Run(/*user_justification=*/std::nullopt, /*should_proceed=*/true);
        non_io_tasks_.erase(notification_id);
        non_io_tasks_warning_timers_.erase(notification_id);
      } else {
        // Review
        // Always open the Files app. This should notify us through
        // OnBrowserSetLastActive() to show the dialog.
        LaunchFilesApp(std::make_unique<DialogInfo>(
            base::BindOnce(
                &FilesPolicyNotificationManager::ShowDialogForNonIOTask,
                weak_factory_.GetWeakPtr(), notification_id,
                FilesDialogType::kWarning),
            notification_id,
            base::BindOnce(
                &FilesPolicyNotificationManager::OnNonIOTaskAppLaunchTimedOut,
                weak_factory_.GetWeakPtr(), notification_id)));
      }
      break;
    default:
      NOTREACHED_IN_MIGRATION();
  }
}

void FilesPolicyNotificationManager::HandleDlpErrorNotificationClick(
    std::string notification_id,
    std::vector<base::FilePath> files,
    dlp::FileAction action,
    std::optional<int> button_index) {
  if (!button_index.has_value()) {
    return;
  }

  Dismiss(context_, notification_id);

  auto dialog_info = FilesPolicyDialog::Info::Error(
      FilesPolicyDialog::BlockReason::kDlp, files);
  bool always_show_review = dialog_info.HasCustomDetails();

  switch (button_index.value()) {
    case NotificationButton::CANCEL:
      // Nothing more to do.
      break;
    case NotificationButton::OK:
      DCHECK(files.size() >= 1);

      if (files.size() == 1 && !always_show_review) {
        // Learn more.
        dlp::OpenLearnMore();
      } else {
        // Review.
        FileTaskInfo info(action);
        info.SetBlockedFiles(FilesPolicyDialog::BlockReason::kDlp,
                             std::move(dialog_info));
        non_io_tasks_.emplace(notification_id, std::move(info));
        // Always open the Files app. This should notify us through
        // OnBrowserSetLastActive() to show the dialog.
        LaunchFilesApp(std::make_unique<DialogInfo>(
            base::BindOnce(
                &FilesPolicyNotificationManager::ShowDialogForNonIOTask,
                weak_factory_.GetWeakPtr(), notification_id,
                FilesDialogType::kError),
            notification_id,
            base::BindOnce(
                &FilesPolicyNotificationManager::OnNonIOTaskAppLaunchTimedOut,
                weak_factory_.GetWeakPtr(), notification_id)));
      }
      break;
    default:
      NOTREACHED_IN_MIGRATION();
  }
}

FilesPolicyNotificationManager::WarningInfo::WarningInfo(
    Policy warning_reason,
    WarningWithJustificationCallback warning_callback,
    WarningWithJustificationCallback dialog_callback,
    FilesPolicyDialog::Info dialog_info)
    : warning_reason(warning_reason),
      warning_callback(std::move(warning_callback)),
      dialog_callback(std::move(dialog_callback)),
      dialog_info(std::move(dialog_info)) {}

FilesPolicyNotificationManager::WarningInfo::WarningInfo(WarningInfo&& other)
    : warning_reason(other.warning_reason),
      warning_callback(std::move(other.warning_callback)),
      dialog_callback(std::move(other.dialog_callback)),
      dialog_info(std::move(other.dialog_info)) {}

FilesPolicyNotificationManager::WarningInfo::~WarningInfo() = default;

FilesPolicyNotificationManager::FileTaskInfo::FileTaskInfo(
    dlp::FileAction action)
    : action_(action) {}

FilesPolicyNotificationManager::FileTaskInfo::FileTaskInfo(
    FileTaskInfo&& other) {
  if (other.warning_info_.has_value()) {
    warning_info_.emplace(std::move(other.warning_info_.value()));
  }
  block_info_map_ = std::move(other.block_info_map_);
  action_ = other.action_;
  widget_ = other.widget_;
  if (widget_) {
    widget_observation_.Observe(widget_);
  }
}

FilesPolicyNotificationManager::FileTaskInfo::~FileTaskInfo() = default;

void FilesPolicyNotificationManager::FileTaskInfo::AddWidget(
    views::Widget* widget) {
  if (!widget) {
    CHECK_IS_TEST();
    return;
  }
  widget_ = widget;
  widget_observation_.Observe(widget);
}

void FilesPolicyNotificationManager::FileTaskInfo::CloseWidget() {
  if (!widget_) {
    return;
  }
  widget_observation_.Reset();
  widget_->Close();
  widget_ = nullptr;
}

void FilesPolicyNotificationManager::FileTaskInfo::SetWarningInfo(
    WarningInfo warning_info) {
  warning_info_.emplace(std::move(warning_info));
}

void FilesPolicyNotificationManager::FileTaskInfo::ResetWarningInfo() {
  warning_info_.reset();
}

FilesPolicyNotificationManager::WarningInfo*
FilesPolicyNotificationManager::FileTaskInfo::GetWarningInfo() {
  return base::OptionalToPtr(warning_info_);
}

bool FilesPolicyNotificationManager::FileTaskInfo::HasWarningInfo() const {
  return warning_info_.has_value();
}

void FilesPolicyNotificationManager::FileTaskInfo::SetBlockedFiles(
    FilesPolicyDialog::BlockReason reason,
    FilesPolicyDialog::Info dialog_info) {
  if (dialog_info.GetFiles().empty()) {
    return;
  }
  auto it = block_info_map_.find(reason);
  if (it == block_info_map_.end()) {
    block_info_map_.insert({reason, std::move(dialog_info)});
  } else {
    it->second = std::move(dialog_info);
  }
}

size_t FilesPolicyNotificationManager::FileTaskInfo::GetBlockedFilesSize()
    const {
  size_t size = 0;
  for (const auto& [_, dialog_info] : block_info_map_) {
    size += dialog_info.GetFiles().size();
  }
  return size;
}

void FilesPolicyNotificationManager::FileTaskInfo::OnWidgetDestroying(
    views::Widget* widget) {
  widget_ = nullptr;
  widget_observation_.Reset();
}

FilesPolicyNotificationManager::DialogInfo::DialogInfo(
    ShowDialogCallback dialog_callback,
    file_manager::io_task::IOTaskId task_id,
    base::OnceClosure timeout_callback)
    : task_id(task_id),
      notification_id(std::nullopt),
      dialog_callback(std::move(dialog_callback)),
      timeout_callback(std::move(timeout_callback)) {}

FilesPolicyNotificationManager::DialogInfo::DialogInfo(
    ShowDialogCallback dialog_callback,
    std::string notification_id,
    base::OnceClosure timeout_callback)
    : task_id(std::nullopt),
      notification_id(notification_id),
      dialog_callback(std::move(dialog_callback)),
      timeout_callback(std::move(timeout_callback)) {}

FilesPolicyNotificationManager::DialogInfo::~DialogInfo() = default;

void FilesPolicyNotificationManager::ShowFilesPolicyNotification(
    const std::string& notification_id,
    file_manager::io_task::IOTaskId task_id) {
  if (!HasWarning(task_id) && !HasBlockedFiles(task_id)) {
    return;
  }

  const dlp::FileAction action = io_tasks_.at(task_id).action();
  auto callback =
      HasWarning(task_id)
          ? base::BindRepeating(&FilesPolicyNotificationManager::
                                    HandleFilesPolicyWarningNotificationClick,
                                weak_factory_.GetWeakPtr(), task_id,
                                notification_id)
          : base::BindRepeating(&FilesPolicyNotificationManager::
                                    HandleFilesPolicyErrorNotificationClick,
                                weak_factory_.GetWeakPtr(), task_id,
                                notification_id);
  // The notification should stay visible until dismissed.
  message_center::RichNotificationData optional_fields;
  optional_fields.never_timeout = true;
  const NotificationType type = HasWarning(task_id) ? NotificationType::kWarning
                                                    : NotificationType::kError;
  size_t file_count;
  std::u16string file_name;
  std::optional<FilesPolicyDialog::BlockReason> reason;
  FileTaskInfo& task_info = io_tasks_.at(task_id);
  bool always_show_review;
  if (HasWarning(task_id)) {
    CHECK(!task_info.GetWarningInfo()->dialog_info.GetFiles().empty());
    file_count = task_info.GetWarningInfo()->dialog_info.GetFiles().size();
    file_name =
        task_info.GetWarningInfo()->dialog_info.GetFiles().begin()->title;
    always_show_review =
        task_info.GetWarningInfo()->dialog_info.HasCustomDetails();
  } else {
    CHECK(HasBlockedFiles(task_id));
    file_count = task_info.GetBlockedFilesSize();
    file_name =
        task_info.block_info_map().begin()->second.GetFiles().front().title;
    reason = task_info.block_info_map().begin()->first;
    always_show_review = HasCustomDialogSettings(task_info.block_info_map());
  }
  auto notification = file_manager::CreateSystemNotification(
      notification_id, GetNotificationTitle(type, action, file_count),
      GetNotificationMessage(type, file_count, file_name, reason),
      base::MakeRefCounted<message_center::HandleNotificationClickDelegate>(
          std::move(callback)),
      optional_fields);
  notification->set_buttons(
      {message_center::ButtonInfo(GetCancelButton(type)),
       message_center::ButtonInfo(
           GetOkButton(type, action, file_count, always_show_review))});
  auto* profile = Profile::FromBrowserContext(context_);
  DCHECK(profile);
  NotificationDisplayServiceFactory::GetForProfile(profile)->Display(
      NotificationHandler::Type::TRANSIENT, *notification,
      /*metadata=*/nullptr);
}

void FilesPolicyNotificationManager::HandleFilesPolicyWarningNotificationClick(
    file_manager::io_task::IOTaskId task_id,
    std::string notification_id,
    std::optional<int> button_index) {
  if (!button_index.has_value()) {
    return;
  }
  if (!HasIOTask(task_id)) {
    // Task already completed.
    return;
  }
  if (!HasWarning(task_id)) {
    LOG(WARNING) << "Warning notification clicked but no warning info found";
    return;
  }
  Dismiss(context_, notification_id);

  switch (button_index.value()) {
    case NotificationButton::CANCEL:
      Cancel(task_id);
      break;
    case NotificationButton::OK:
      if (io_tasks_.at(task_id)
                  .GetWarningInfo()
                  ->dialog_info.GetFiles()
                  .size() == 1 &&
          !io_tasks_.at(task_id)
               .GetWarningInfo()
               ->dialog_info.HasCustomDetails()) {
        // Single file, no custom dialog settings - proceed.
        Resume(task_id);
      } else {
        // Multiple files or custom dialog settings - review.
        ShowDialog(task_id, FilesDialogType::kWarning);
      }
      break;
  }
}

void FilesPolicyNotificationManager::HandleFilesPolicyErrorNotificationClick(
    file_manager::io_task::IOTaskId task_id,
    std::string notification_id,
    std::optional<int> button_index) {
  if (!button_index.has_value()) {
    return;
  }
  if (!HasIOTask(task_id)) {
    // Task already completed.
    return;
  }
  if (!HasBlockedFiles(task_id)) {
    LOG(WARNING) << "Error notification clicked but no blocked files found";
    return;
  }

  Dismiss(context_, notification_id);

  switch (button_index.value()) {
    case NotificationButton::CANCEL:
      OnErrorItemDismissed(task_id);
      return;
    case NotificationButton::OK:
      if (io_tasks_.at(task_id).GetBlockedFilesSize() == 1 &&
          !io_tasks_.at(task_id)
               .block_info_map()
               .begin()
               ->second.HasCustomDetails()) {
        // Single file, no custom dialog settings - open help page.
        // Note that this can only be DLP, since when custom learn more URL is
        // available, currently only for EC, we show the review button.
        dlp::OpenLearnMore(GURL(dlp::kDlpLearnMoreUrl));
        // Only delete if we don't need to show the dialog.
        OnErrorItemDismissed(task_id);
      } else {
        // Multiple files or custom dialog settings - review.
        ShowDialog(task_id, FilesDialogType::kError);
      }
      return;
    default:
      NOTREACHED_IN_MIGRATION();
  }
}

void FilesPolicyNotificationManager::ShowDialogForIOTask(
    file_manager::io_task::IOTaskId task_id,
    FilesDialogType type,
    gfx::NativeWindow modal_parent) {
  if (!HasIOTask(task_id)) {
    // Task already completed or timed out.
    return;
  }

  ShowFilesPolicyDialog(std::ref(io_tasks_.at(task_id)), type, modal_parent);
  if (type == FilesDialogType::kError) {
    io_tasks_.erase(task_id);
  }
}

void FilesPolicyNotificationManager::ShowDialogForNonIOTask(
    std::string notification_id,
    FilesDialogType type,
    gfx::NativeWindow modal_parent) {
  if (!HasNonIOTask(notification_id)) {
    // Task already completed or timed out.
    return;
  }
  ShowFilesPolicyDialog(std::ref(non_io_tasks_.at(notification_id)), type,
                        modal_parent);

  if (type == FilesDialogType::kError) {
    non_io_tasks_.erase(notification_id);
  }
}

void FilesPolicyNotificationManager::ShowFilesPolicyDialog(
    FileTaskInfo& info,
    FilesDialogType type,
    gfx::NativeWindow modal_parent) {
  switch (type) {
    case FilesDialogType::kUnknown:
      LOG(WARNING) << "Unknown FilesDialogType passed";
      return;

    case FilesDialogType::kError:
      if (info.block_info_map().empty() || info.widget()) {
        return;
      }
      info.AddWidget(FilesPolicyDialog::CreateErrorDialog(
          info.block_info_map(), info.action(), modal_parent));
      return;

    case FilesDialogType::kWarning:
      if (!info.GetWarningInfo() || info.widget()) {
        return;
      }
      WarningInfo* warning_info = info.GetWarningInfo();
      CHECK(!warning_info->warning_callback.is_null());
      info.AddWidget(FilesPolicyDialog::CreateWarnDialog(
          std::move(warning_info->dialog_callback), info.action(), modal_parent,
          std::move(warning_info->dialog_info),
          /*destination=*/std::nullopt));
      return;
  }
}

void FilesPolicyNotificationManager::AddIOTask(
    file_manager::io_task::IOTaskId task_id,
    dlp::FileAction action) {
  io_tasks_.emplace(std::move(task_id), FileTaskInfo(action));
}

void FilesPolicyNotificationManager::LaunchFilesApp(
    std::unique_ptr<DialogInfo> info) {
  // Start observing the browser list only if the queue is empty.
  if (pending_dialogs_.empty()) {
    BrowserList::AddObserver(this);
  }
  // Start timer.
  info->timeout_timer.SetTaskRunner(task_runner_);
  info->timeout_timer.Start(FROM_HERE, kOpenFilesAppTimeout,
                            std::move(info->timeout_callback));

  pending_dialogs_.emplace(std::move(info));

  ui::SelectFileDialog::FileTypeInfo file_type_info;
  file_type_info.allowed_paths =
      ui::SelectFileDialog::FileTypeInfo::ANY_PATH_OR_URL;
  GURL files_swa_url = file_manager::util::GetFileManagerMainPageUrlWithParams(
      ui::SelectFileDialog::SELECT_NONE,
      /*title=*/{},
      /*current_directory_url=*/{},
      /*selection_url=*/{},
      /*target_name=*/{}, &file_type_info,
      /*file_type_index=*/0,
      /*search_query=*/{},
      /*show_android_picker_apps=*/false,
      /*volume_filter=*/{});
  ash::SystemAppLaunchParams params;
  params.url = files_swa_url;
  ash::LaunchSystemWebAppAsync(Profile::FromBrowserContext(context_),
                               ash::SystemWebAppType::FILE_MANAGER, params);
}

void FilesPolicyNotificationManager::OnBrowserAdded(Browser* browser) {
  if (!ash::IsBrowserForSystemWebApp(browser,
                                     ash::SystemWebAppType::FILE_MANAGER)) {
    LOG(WARNING) << "Browser did not match Files app";
    return;
  }

  // Files app successfully opened.
  data_controls::DlpBooleanHistogram(
      data_controls::dlp::kFilesAppOpenTimedOutUMA, /*value=*/false);
  ShowPendingDialog(browser->window()->GetNativeWindow());
}

void FilesPolicyNotificationManager::SetTaskRunnerForTesting(
    scoped_refptr<base::SequencedTaskRunner> task_runner) {
  task_runner_ = task_runner;
}

void FilesPolicyNotificationManager::OnIOTaskStatus(
    const file_manager::io_task::ProgressStatus& status) {
  // Observe only Copy, Move, and RestoreToDestination tasks.
  if (status.type != file_manager::io_task::OperationType::kCopy &&
      status.type != file_manager::io_task::OperationType::kMove &&
      status.type !=
          file_manager::io_task::OperationType::kRestoreToDestination) {
    return;
  }
  // RestoreToDestination have an underlying Move task, so we show the same UI
  // as for Move.
  dlp::FileAction action =
      status.type == file_manager::io_task::OperationType::kCopy
          ? dlp::FileAction::kCopy
          : dlp::FileAction::kMove;

  if (!HasIOTask(status.task_id) &&
      status.state == file_manager::io_task::State::kQueued) {
    AddIOTask(status.task_id, action);
    return;
  }

  if (HasIOTask(status.task_id) && status.IsCompleted()) {
    io_tasks_warning_timers_.erase(status.task_id);
    // If task is cancelled or completed with error and has a warning, run the
    // warning callback to cancel the warning.
    if ((status.state == file_manager::io_task::State::kCancelled ||
         status.state == file_manager::io_task::State::kError) &&
        HasWarning(status.task_id)) {
      CHECK(!io_tasks_.at(status.task_id)
                 .GetWarningInfo()
                 ->warning_callback.is_null());
      std::move(io_tasks_.at(status.task_id).GetWarningInfo()->warning_callback)
          .Run(/*user_justification=*/std::nullopt, /*should_proceed=*/false);
      io_tasks_.at(status.task_id).ResetWarningInfo();
    }
    // Remove only if the IOTask doesn't have any blocked file.
    if (!HasBlockedFiles(status.task_id)) {
      io_tasks_.erase(status.task_id);
    }
  }
}

bool FilesPolicyNotificationManager::HasBlockedFiles(
    file_manager::io_task::IOTaskId task_id) const {
  return HasIOTask(task_id) && !io_tasks_.at(task_id).block_info_map().empty();
}

bool FilesPolicyNotificationManager::HasWarning(
    file_manager::io_task::IOTaskId task_id) const {
  return HasIOTask(task_id) && io_tasks_.at(task_id).HasWarningInfo();
}

bool FilesPolicyNotificationManager::HasNonIOTask(
    const std::string notification_id) const {
  return base::Contains(non_io_tasks_, notification_id);
}

bool FilesPolicyNotificationManager::HasBlockedFiles(
    const std::string notification_id) const {
  return HasNonIOTask(notification_id) &&
         !non_io_tasks_.at(notification_id).block_info_map().empty();
}

bool FilesPolicyNotificationManager::HasWarning(
    const std::string notification_id) const {
  return HasNonIOTask(notification_id) &&
         non_io_tasks_.at(notification_id).HasWarningInfo();
}

void FilesPolicyNotificationManager::OnIOTaskWarningDialogClicked(
    file_manager::io_task::IOTaskId task_id,
    Policy warning_reason,
    std::optional<std::u16string> user_justification,
    bool should_proceed) {
  if (!HasIOTask(task_id) || !HasWarning(task_id)) {
    // Task probably timed out.
    return;
  }
  if (should_proceed) {
    io_tasks_.at(task_id).GetWarningInfo()->user_justification =
        user_justification;
    Resume(task_id);
  } else {
    Cancel(task_id);
  }
}

void FilesPolicyNotificationManager::OnNonIOTaskWarningDialogClicked(
    const std::string& notification_id,
    std::optional<std::u16string> user_justification,
    bool should_proceed) {
  if (!HasWarning(notification_id)) {
    // Task probably timed out.
    return;
  }
  std::move(
      non_io_tasks_.at(notification_id).GetWarningInfo()->warning_callback)
      .Run(user_justification, should_proceed);
  non_io_tasks_.erase(notification_id);
}

void FilesPolicyNotificationManager::OnDlpLearnMoreButtonClicked(
    const std::string& notification_id,
    std::optional<int> button_index) {
  if (!button_index || button_index.value() != 0) {
    return;
  }

  dlp::OpenLearnMore();

  Dismiss(context_, notification_id);
}

void FilesPolicyNotificationManager::Resume(
    file_manager::io_task::IOTaskId task_id) {
  io_tasks_warning_timers_.erase(task_id);
  if (!HasIOTask(task_id) || !HasWarning(task_id)) {
    return;
  }
  auto* io_task_controller = GetIOTaskController(context_);
  if (!io_task_controller) {
    LOG(ERROR) << "FilesPolicyNotificationManager failed to find "
                  "file_manager::io_task::IOTaskController";
    return;
  }
  file_manager::io_task::ResumeParams params;
  params.policy_params = file_manager::io_task::PolicyResumeParams(
      io_tasks_.at(task_id).GetWarningInfo()->warning_reason);
  io_task_controller->Resume(task_id, std::move(params));
}

void FilesPolicyNotificationManager::Cancel(
    file_manager::io_task::IOTaskId task_id) {
  io_tasks_warning_timers_.erase(task_id);
  if (!HasIOTask(task_id) || !HasWarning(task_id)) {
    return;
  }
  auto* io_task_controller = GetIOTaskController(context_);
  if (!io_task_controller) {
    LOG(ERROR) << "FilesPolicyNotificationManager failed to find "
                  "file_manager::io_task::IOTaskController";
    return;
  }
  io_task_controller->Cancel(task_id);
}

void FilesPolicyNotificationManager::ShowDlpBlockNotification(
    std::vector<base::FilePath> blocked_files,
    dlp::FileAction action) {
  const std::string notification_id = GetNotificationId(notification_count_++);
  std::unique_ptr<message_center::Notification> notification;

  if (base::FeatureList::IsEnabled(features::kNewFilesPolicyUX)) {
    // The notification should stay visible until actioned upon.
    message_center::RichNotificationData optional_fields;
    optional_fields.never_timeout = true;
    notification = file_manager::CreateSystemNotification(
        notification_id,
        GetNotificationTitle(NotificationType::kError, action,
                             blocked_files.size()),
        GetNotificationMessage(
            NotificationType::kError, blocked_files.size(),
            blocked_files.begin()->BaseName().LossyDisplayName(),
            FilesPolicyDialog::BlockReason::kDlp),
        base::MakeRefCounted<PolicyNotificationClickHandler>(base::BindOnce(
            &FilesPolicyNotificationManager::HandleDlpErrorNotificationClick,
            weak_factory_.GetWeakPtr(), notification_id, blocked_files,
            action)),
        optional_fields);
    notification->set_buttons(
        {message_center::ButtonInfo(GetCancelButton(NotificationType::kError)),
         message_center::ButtonInfo(
             GetOkButton(NotificationType::kError, action, blocked_files.size(),
                         /*always_show_review=*/false))});
  } else {
    std::u16string title;
    std::u16string message;
    switch (action) {
      case dlp::FileAction::kDownload:
        title = l10n_util::GetStringUTF16(
            IDS_POLICY_DLP_FILES_DOWNLOAD_BLOCK_TITLE);
        // ignore `blocked_files.size()` for downloads.
        message = l10n_util::GetStringUTF16(
            IDS_POLICY_DLP_FILES_DOWNLOAD_BLOCK_MESSAGE);
        break;
      case dlp::FileAction::kUpload:
        title =
            l10n_util::GetStringUTF16(IDS_POLICY_DLP_FILES_UPLOAD_BLOCK_TITLE);
        message = l10n_util::GetPluralStringFUTF16(
            IDS_POLICY_DLP_FILES_UPLOAD_BLOCK_MESSAGE, blocked_files.size());
        break;
      case dlp::FileAction::kOpen:
      case dlp::FileAction::kShare:
        title =
            l10n_util::GetStringUTF16(IDS_POLICY_DLP_FILES_OPEN_BLOCK_TITLE);
        message = l10n_util::GetPluralStringFUTF16(
            IDS_POLICY_DLP_FILES_OPEN_BLOCK_MESSAGE, blocked_files.size());
        break;
      case dlp::FileAction::kCopy:
      case dlp::FileAction::kMove:
      case dlp::FileAction::kTransfer:
      case dlp::FileAction::kUnknown:
        // TODO(b/269609831): Show correct notification here.
        return;
    }
    notification = file_manager::CreateSystemNotification(
        notification_id, title, message,
        base::MakeRefCounted<message_center::HandleNotificationClickDelegate>(
            base::BindRepeating(
                &FilesPolicyNotificationManager::OnDlpLearnMoreButtonClicked,
                weak_factory_.GetWeakPtr(), notification_id)));
    notification->set_buttons({message_center::ButtonInfo(
        l10n_util::GetStringUTF16(IDS_LEARN_MORE))});
  }

  auto* profile = Profile::FromBrowserContext(context_);
  DCHECK(profile);
  NotificationDisplayServiceFactory::GetForProfile(profile)->Display(
      NotificationHandler::Type::TRANSIENT, *notification,
      /*metadata=*/nullptr);
}

void FilesPolicyNotificationManager::ShowDlpWarningNotification(
    WarningWithJustificationCallback callback,
    std::vector<base::FilePath> warning_files,
    const DlpFileDestination& destination,
    dlp::FileAction action) {
  if (base::FeatureList::IsEnabled(features::kNewFilesPolicyUX)) {
    const std::string& notification_id =
        GetNotificationId(notification_count_++);
    // Store the task info.
    FileTaskInfo info(action);
    info.SetWarningInfo(
        {Policy::kDlp, std::move(callback),
         base::BindOnce(
             &FilesPolicyNotificationManager::OnNonIOTaskWarningDialogClicked,
             weak_factory_.GetWeakPtr(), notification_id),
         FilesPolicyDialog::Info::Warn(FilesPolicyDialog::BlockReason::kDlp,
                                       warning_files)});
    non_io_tasks_.emplace(notification_id, std::move(info));

    std::vector<message_center::ButtonInfo> buttons = {
        message_center::ButtonInfo(GetCancelButton(NotificationType::kWarning)),
        message_center::ButtonInfo(GetOkButton(NotificationType::kWarning,
                                               action, warning_files.size(),
                                               /*always_show_review=*/false))};
    // The notification should stay visible until actioned upon.
    message_center::RichNotificationData optional_fields;
    optional_fields.never_timeout = true;
    auto notification = file_manager::CreateSystemNotification(
        notification_id,
        GetNotificationTitle(NotificationType::kWarning, action,
                             /*file_count=*/std::nullopt),
        GetNotificationMessage(
            NotificationType::kWarning, warning_files.size(),
            warning_files.begin()->BaseName().LossyDisplayName(), std::nullopt),
        base::MakeRefCounted<PolicyNotificationClickHandler>(base::BindOnce(
            &FilesPolicyNotificationManager::HandleDlpWarningNotificationClick,
            weak_factory_.GetWeakPtr(), notification_id)));
    notification->set_buttons(std::move(buttons));

    auto* profile = Profile::FromBrowserContext(context_);
    DCHECK(profile);
    NotificationDisplayServiceFactory::GetForProfile(profile)->Display(
        NotificationHandler::Type::TRANSIENT, *notification,
        /*metadata=*/nullptr);

    // Start warning timer.
    non_io_tasks_warning_timers_[notification_id] =
        std::make_unique<base::OneShotTimer>();
    non_io_tasks_warning_timers_[notification_id]->SetTaskRunner(task_runner_);
    non_io_tasks_warning_timers_[notification_id]->Start(
        FROM_HERE, kWarningTimeout,
        base::BindOnce(
            &FilesPolicyNotificationManager::OnNonIOTaskWarningTimedOut,
            weak_factory_.GetWeakPtr(), notification_id));
  } else {
    FilesPolicyDialog::CreateWarnDialog(
        std::move(callback), action, /*modal_parent=*/nullptr,
        FilesPolicyDialog::Info::Warn(FilesPolicyDialog::BlockReason::kDlp,
                                      warning_files),
        destination);
  }
}

void FilesPolicyNotificationManager::PauseIOTask(
    file_manager::io_task::IOTaskId task_id,
    WarningWithJustificationCallback callback,
    dlp::FileAction action,
    Policy warning_reason,
    FilesPolicyDialog::Info dialog_info) {
  auto* io_task_controller = GetIOTaskController(context_);
  if (!io_task_controller) {
    // Proceed because the IO task can't be paused.
    std::move(callback).Run(/*user_justification=*/std::nullopt,
                            /*should_proceed=*/true);
    return;
  }
  // Sometimes DLP checks are done before FilesPolicyNotificationManager is
  // lazily created, so the task is not tracked and the pausing won't happen. On
  // the other hand, the IO task may be aborted/canceled already so the info
  // saved may be not needed anymore.
  if (!HasIOTask(task_id)) {
    AddIOTask(task_id, action);
  }

  io_tasks_.at(task_id).SetWarningInfo(
      {warning_reason, std::move(callback),
       base::BindOnce(
           &FilesPolicyNotificationManager::OnIOTaskWarningDialogClicked,
           weak_factory_.GetWeakPtr(), task_id, warning_reason),
       std::move(dialog_info)});

  file_manager::io_task::PauseParams pause_params;
  pause_params.policy_params = file_manager::io_task::PolicyPauseParams(
      warning_reason,
      io_tasks_.at(task_id).GetWarningInfo()->dialog_info.GetFiles().size(),
      io_tasks_.at(task_id)
          .GetWarningInfo()
          ->dialog_info.GetFiles()
          .begin()
          ->file_path.BaseName()
          .value(),
      io_tasks_.at(task_id).GetWarningInfo()->dialog_info.HasCustomDetails());
  io_task_controller->Pause(task_id, std::move(pause_params));
  // Start warning timer.
  io_tasks_warning_timers_[task_id] = std::make_unique<base::OneShotTimer>();
  io_tasks_warning_timers_[task_id]->SetTaskRunner(task_runner_);
  io_tasks_warning_timers_[task_id]->Start(
      FROM_HERE, kWarningTimeout,
      base::BindOnce(&FilesPolicyNotificationManager::OnIOTaskWarningTimedOut,
                     weak_factory_.GetWeakPtr(), task_id));
}

void FilesPolicyNotificationManager::OnIOTaskAppLaunchTimedOut(
    file_manager::io_task::IOTaskId task_id) {
  if (pending_dialogs_.empty()) {
    return;
  }
  DCHECK(pending_dialogs_.front()->task_id == task_id);
  // Stop waiting for the Files App and fallback to system modal.
  data_controls::DlpBooleanHistogram(
      data_controls::dlp::kFilesAppOpenTimedOutUMA, /*value=*/true);
  ShowPendingDialog(/*modal_parent=*/nullptr);
}

void FilesPolicyNotificationManager::OnNonIOTaskAppLaunchTimedOut(
    std::string notification_id) {
  // If the notification id doesn't match the front element, we already showed
  // the dialog for this notification before timing out.
  if (pending_dialogs_.empty()) {
    return;
  }
  DCHECK(pending_dialogs_.front()->notification_id == notification_id);
  // Stop waiting for the Files App and fallback to system modal.
  data_controls::DlpBooleanHistogram(
      data_controls::dlp::kFilesAppOpenTimedOutUMA, /*value=*/true);
  ShowPendingDialog(/*modal_parent=*/nullptr);
}

void FilesPolicyNotificationManager::ShowPendingDialog(
    gfx::NativeWindow modal_parent) {
  if (pending_dialogs_.empty()) {
    return;
  }
  // Pop the dialog. This also stops the timer if it hasn't fired already.
  CHECK(pending_dialogs_.front()->dialog_callback);
  std::move(pending_dialogs_.front()->dialog_callback).Run(modal_parent);
  pending_dialogs_.pop();
  // If this was the last dialog, stop observing the browser list.
  if (pending_dialogs_.empty()) {
    BrowserList::RemoveObserver(this);
  }
}

void FilesPolicyNotificationManager::OnIOTaskWarningTimedOut(
    const file_manager::io_task::IOTaskId& task_id) {
  // Remove the timer.
  io_tasks_warning_timers_.erase(task_id);
  if (!HasIOTask(task_id) || !HasWarning(task_id)) {
    return;
  }

  // Close the warning dialog if there's any.
  io_tasks_.at(task_id).CloseWidget();

  data_controls::DlpHistogramEnumeration(
      data_controls::dlp::kFileActionWarnTimedOutUMA,
      io_tasks_.at(task_id).action());

  // Abort the IOtask. No need to run the warning callback here as it will be
  // called in OnIOTaskStatus when there's an update sent that the task
  // completed with error.
  auto* io_task_controller = GetIOTaskController(context_);
  io_task_controller->CompleteWithError(
      task_id, file_manager::io_task::PolicyError(
                   file_manager::io_task::PolicyErrorType::kDlpWarningTimeout));
}

void FilesPolicyNotificationManager::OnNonIOTaskWarningTimedOut(
    const std::string& notification_id) {
  // Remove the timer.
  non_io_tasks_warning_timers_.erase(notification_id);
  // Dismiss the notification if it's still shown.
  Dismiss(context_, notification_id);
  if (!HasWarning(notification_id)) {
    return;
  }

  // Close the warning dialog if there's any.
  non_io_tasks_.at(notification_id).CloseWidget();

  data_controls::DlpHistogramEnumeration(
      data_controls::dlp::kFileActionWarnTimedOutUMA,
      non_io_tasks_.at(notification_id).action());

  // Run the warning callback with false.
  std::move(
      non_io_tasks_.at(notification_id).GetWarningInfo()->warning_callback)
      .Run(/*user_justification=*/std::nullopt, /*should_proceed=*/false);

  ShowDlpWarningTimeoutNotification(non_io_tasks_.at(notification_id).action());

  non_io_tasks_.erase(notification_id);
}

}  // namespace policy