chromium/chrome/browser/ash/policy/skyvault/migration_notification_manager.cc

// Copyright 2024 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/skyvault/migration_notification_manager.h"

#include <map>
#include <memory>
#include <string>
#include <vector>

#include "ash/constants/notifier_catalogs.h"
#include "ash/public/cpp/notification_utils.h"
#include "base/callback_list.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/no_destructor.h"
#include "base/notreached.h"
#include "base/sequence_checker.h"
#include "chrome/browser/ash/file_manager/open_util.h"
#include "chrome/browser/ash/policy/skyvault/policy_utils.h"
#include "chrome/browser/ash/policy/skyvault/signin_notification_helper.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/profiles/profile_selections.h"
#include "chrome/browser/ui/webui/ash/skyvault/local_files_migration_dialog.h"
#include "components/strings/grit/components_strings.h"
#include "components/vector_icons/vector_icons.h"
#include "content/public/browser/browser_context.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/message_center/public/cpp/notification.h"

namespace policy::local_user_files {

namespace {

// Creates a notification with `kSkyvaultNotificationId`, `title` and `message`,
// that invokes `callback` when clicked on.
std::unique_ptr<message_center::Notification> CreateNotificationPtr(
    const std::u16string title,
    const std::u16string message,
    base::RepeatingCallback<void(std::optional<int>)> callback) {
  message_center::RichNotificationData optional_fields;
  optional_fields.never_timeout = true;
  return ash::CreateSystemNotificationPtr(
      message_center::NotificationType::NOTIFICATION_TYPE_SIMPLE,
      kSkyVaultMigrationNotificationId, title, message,
      /*display_source=*/std::u16string(), /*origin_url=*/GURL(),
      message_center::NotifierId(), optional_fields,
      base::MakeRefCounted<message_center::HandleNotificationClickDelegate>(
          callback),
      vector_icons::kBusinessIcon,
      message_center::SystemNotificationWarningLevel::NORMAL);
}

// Closes the notification with `kSkyVaultMigrationNotificationId`.
void CloseNotification(Profile* profile) {
  NotificationDisplayService::GetForProfile(profile)->Close(
      NotificationHandler::Type::TRANSIENT, kSkyVaultMigrationNotificationId);
}

// Closes the notification and, if the button is clicked, opens `path`.
void HandleNotificationClick(Profile* profile,
                             const base::FilePath& path,
                             std::optional<int> button) {
  if (button.has_value() && button == 0) {
    file_manager::util::ShowItemInFolder(profile, path, base::DoNothing());
  }
  CloseNotification(profile);
}

// Returns the translation string corresponding to `provider`.
std::u16string CloudProviderToString(CloudProvider provider) {
  switch (provider) {
    case CloudProvider::kGoogleDrive:
      return l10n_util::GetStringUTF16(
          IDS_POLICY_SKYVAULT_CLOUD_PROVIDER_GOOGLE_DRIVE);
    case CloudProvider::kOneDrive:
      return l10n_util::GetStringUTF16(
          IDS_POLICY_SKYVAULT_CLOUD_PROVIDER_ONEDRIVE);
    case CloudProvider::kNotSpecified:
      NOTREACHED();
  }
}

}  // namespace

MigrationNotificationManager::MigrationNotificationManager(
    content::BrowserContext* context)
    : context_(context) {}

MigrationNotificationManager::~MigrationNotificationManager() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}

void MigrationNotificationManager::ShowMigrationInfoDialog(
    CloudProvider provider,
    base::Time migration_start_time,
    base::OnceClosure migration_callback) {
  LocalFilesMigrationDialog::Show(provider, migration_start_time,
                                  std::move(migration_callback));
}

void MigrationNotificationManager::ShowMigrationProgressNotification(
    CloudProvider provider) {
  std::u16string provider_str = CloudProviderToString(provider);

  std::u16string title = base::ReplaceStringPlaceholders(
      l10n_util::GetStringUTF16(IDS_POLICY_SKYVAULT_MIGRATION_PROGRESS_TITLE),
      provider_str,
      /*offset=*/nullptr);
  std::u16string message = base::ReplaceStringPlaceholders(
      l10n_util::GetStringUTF16(IDS_POLICY_SKYVAULT_MIGRATION_PROGRESS_MESSAGE),
      provider_str,
      /*offset=*/nullptr);

  auto notification = CreateNotificationPtr(title, message,
                                            /*callback=*/base::DoNothing());

  NotificationDisplayService::GetForProfile(profile())->Display(
      NotificationHandler::Type::TRANSIENT, *notification,
      /*metadata=*/nullptr);
}

void MigrationNotificationManager::ShowMigrationCompletedNotification(
    CloudProvider provider,
    const base::FilePath& destination_path) {
  std::u16string provider_str = CloudProviderToString(provider);
  std::u16string folder_name = destination_path.BaseName().AsUTF16Unsafe();

  std::u16string title = base::ReplaceStringPlaceholders(
      l10n_util::GetStringUTF16(IDS_POLICY_SKYVAULT_MIGRATION_COMPLETED_TITLE),
      {folder_name, provider_str},
      /*offsets=*/nullptr);
  std::u16string message = base::ReplaceStringPlaceholders(
      l10n_util::GetStringUTF16(
          IDS_POLICY_SKYVAULT_MIGRATION_COMPLETED_MESSAGE),
      provider_str,
      /*offset=*/nullptr);
  std::u16string button = base::ReplaceStringPlaceholders(
      l10n_util::GetStringUTF16(IDS_POLICY_SKYVAULT_MIGRATION_COMPLETED_BUTTON),
      provider_str,
      /*offset=*/nullptr);

  auto notification =
      CreateNotificationPtr(title, message,
                            base::BindRepeating(&HandleNotificationClick,
                                                profile(), destination_path));
  notification->set_buttons({message_center::ButtonInfo(button)});

  NotificationDisplayService::GetForProfile(profile())->Display(
      NotificationHandler::Type::TRANSIENT, *notification,
      /*metadata=*/nullptr);
}

void MigrationNotificationManager::ShowMigrationErrorNotification(
    CloudProvider provider,
    const base::FilePath& destination_path,
    std::map<base::FilePath, MigrationUploadError> errors) {
  // TODO(aidazolic): Pass error log path.
  const base::FilePath error_log_path = base::FilePath();

  std::u16string provider_str = CloudProviderToString(provider);

  std::u16string folder_name = destination_path.BaseName().AsUTF16Unsafe();
  std::u16string title = base::ReplaceStringPlaceholders(
      l10n_util::GetStringUTF16(IDS_POLICY_SKYVAULT_MIGRATION_ERROR_TITLE),
      provider_str,
      /*offset=*/nullptr);
  std::u16string message = base::ReplaceStringPlaceholders(
      l10n_util::GetStringUTF16(IDS_POLICY_SKYVAULT_MIGRATION_ERROR_MESSAGE),
      {folder_name, provider_str},
      /*offsets=*/nullptr);
  std::u16string button =
      l10n_util::GetStringUTF16(IDS_POLICY_SKYVAULT_MIGRATION_ERROR_BUTTON);

  auto notification = CreateNotificationPtr(
      title, message,
      base::BindRepeating(&HandleNotificationClick, profile(), error_log_path));
  notification->set_buttons({message_center::ButtonInfo(button)});

  NotificationDisplayService::GetForProfile(profile())->Display(
      NotificationHandler::Type::TRANSIENT, *notification,
      /*metadata=*/nullptr);
}

void MigrationNotificationManager::ShowConfigurationErrorNotification(
    CloudProvider provider) {
  std::u16string provider_str = CloudProviderToString(provider);

  std::u16string title = base::ReplaceStringPlaceholders(
      l10n_util::GetStringUTF16(
          IDS_POLICY_SKYVAULT_MIGRATION_CONFIG_ERROR_TITLE),
      provider_str,
      /*offset=*/nullptr);
  std::u16string message = base::ReplaceStringPlaceholders(
      l10n_util::GetStringUTF16(
          IDS_POLICY_SKYVAULT_MIGRATION_CONFIG_ERROR_MESSAGE),
      provider_str,
      /*offset=*/nullptr);

  auto notification = CreateNotificationPtr(title, message,
                                            /*callback=*/base::DoNothing());

  NotificationDisplayService::GetForProfile(profile())->Display(
      NotificationHandler::Type::TRANSIENT, *notification,
      /*metadata=*/nullptr);
}

base::CallbackListSubscription
MigrationNotificationManager::ShowOneDriveSignInNotification(
    SignInCallback callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (sign_in_callbacks_.empty()) {
    policy::skyvault_ui_utils::ShowSignInNotification(
        Profile::FromBrowserContext(context_), /*id=*/0,
        ash::cloud_upload::OdfsSkyvaultUploader::FileType::kMigration,
        /*file_name=*/"",
        base::BindOnce(&MigrationNotificationManager::OnSignInResponse,
                       weak_factory_.GetWeakPtr()));
  }

  // sign_in_callback_subscriptions_.emplace_back(
  return sign_in_callbacks_.Add(std::move(callback));
}

void MigrationNotificationManager::CloseAll() {
  // TODO(b/349097807): Potential race condition. When migration stopping is
  // fully implemented, make sure this runs after uploads were already stopped
  // (otherwise upload might fail before it's cancelled) and/or post this to
  // same sequence & fail new requests that come in (if closing exactly when an
  // upload job was getting paused for sign in).
  CloseNotification(profile());
  CloseDialog();
}

void MigrationNotificationManager::CloseDialog() {
  LocalFilesMigrationDialog* dialog = LocalFilesMigrationDialog::GetDialog();
  if (dialog) {
    dialog->Close();
  }
}

Profile* MigrationNotificationManager::profile() {
  return Profile::FromBrowserContext(context_);
}

void MigrationNotificationManager::OnSignInResponse(base::File::Error error) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (error == base::File::Error::FILE_OK) {
    // This is only reached for OneDrive.
    ShowMigrationProgressNotification(CloudProvider::kOneDrive);
  }
  // If there was an error, the notification will be shown when migration fails.
  sign_in_callbacks_.Notify(error);
}

// static
MigrationNotificationManagerFactory*
MigrationNotificationManagerFactory::GetInstance() {
  static base::NoDestructor<MigrationNotificationManagerFactory> factory;
  return factory.get();
}

MigrationNotificationManager*
MigrationNotificationManagerFactory::GetForBrowserContext(
    content::BrowserContext* context) {
  return static_cast<MigrationNotificationManager*>(
      GetInstance()->GetServiceForBrowserContext(context, /*create=*/true));
}

MigrationNotificationManagerFactory::MigrationNotificationManagerFactory()
    : ProfileKeyedServiceFactory(
          "MigrationNotificationManager",
          ProfileSelections::Builder()
              .WithRegular(ProfileSelection::kOriginalOnly)
              // TODO(crbug.com/41488885): Check if this service is needed for
              // Ash Internals.
              .WithAshInternals(ProfileSelection::kOriginalOnly)
              .Build()) {
  DependsOn(NotificationDisplayServiceFactory::GetInstance());
}

MigrationNotificationManagerFactory::~MigrationNotificationManagerFactory() =
    default;

bool MigrationNotificationManagerFactory::ServiceIsNULLWhileTesting() const {
  return true;
}

std::unique_ptr<KeyedService>
MigrationNotificationManagerFactory::BuildServiceInstanceForBrowserContext(
    content::BrowserContext* context) const {
  return std::make_unique<MigrationNotificationManager>(context);
}

}  // namespace policy::local_user_files