blob: 43ff64008fa3ad85c86e4b43ba35816440e44aa7 [file] [log] [blame]
// 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/new_window_delegate.h"
#include "ash/public/cpp/notification_utils.h"
#include "base/callback_list.h"
#include "base/feature_list.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/no_destructor.h"
#include "base/notreached.h"
#include "base/sequence_checker.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.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 "chrome/common/chrome_features.h"
#include "components/strings/grit/components_strings.h"
#include "components/vector_icons/vector_icons.h"
#include "content/public/browser/browser_context.h"
#include "net/base/filename_util.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 =
base::DoNothing()) {
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) {
NotificationDisplayServiceFactory::GetForProfile(profile)->Close(
NotificationHandler::Type::TRANSIENT, kSkyVaultMigrationNotificationId);
}
// Closes the notification and, if the button is clicked, opens `path` in the
// Files App.
void HandleCompletedNotificationClick(Profile* profile,
const base::FilePath& path,
std::optional<int> button) {
if (button.has_value() && button == 0) {
file_manager::util::ShowItemInFolder(profile, path, base::DoNothing());
ash::NewWindowDelegate::GetInstance()->OpenUrl(
net::FilePathToFileURL(path),
ash::NewWindowDelegate::OpenUrlFrom::kUserInteraction,
ash::NewWindowDelegate::Disposition::kNewForegroundTab);
}
CloseNotification(profile);
}
// Closes the notification and, if the button is clicked, opens `path` in the
// browser.
void HandleErrorNotificationClick(Profile* profile,
const base::FilePath& path,
std::optional<int> button) {
if (button.has_value() && button == 0) {
ash::NewWindowDelegate::GetInstance()->OpenUrl(
net::FilePathToFileURL(path),
ash::NewWindowDelegate::OpenUrlFrom::kUserInteraction,
ash::NewWindowDelegate::Disposition::kNewForegroundTab);
}
CloseNotification(profile);
}
// Returns the translation string corresponding to `destination`.
std::u16string CloudProviderToString(MigrationDestination destination) {
switch (destination) {
case MigrationDestination::kGoogleDrive:
return l10n_util::GetStringUTF16(
IDS_POLICY_SKYVAULT_CLOUD_PROVIDER_GOOGLE_DRIVE);
case MigrationDestination::kOneDrive:
return l10n_util::GetStringUTF16(
IDS_POLICY_SKYVAULT_CLOUD_PROVIDER_ONEDRIVE);
case MigrationDestination::kNotSpecified:
case MigrationDestination::kDelete:
NOTREACHED();
}
}
} // namespace
MigrationNotificationManager::MigrationNotificationManager(
content::BrowserContext* context)
: context_(context) {}
MigrationNotificationManager::~MigrationNotificationManager() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
void MigrationNotificationManager::ShowMigrationInfoDialog(
MigrationDestination destination,
base::Time migration_start_time,
base::OnceClosure migration_callback) {
if (destination == MigrationDestination::kDelete &&
!base::FeatureList::IsEnabled(features::kSkyVaultV3)) {
LOG(ERROR) << "Destination set to MigrationDestination::kDelete, but the "
"flag is disabled; ignoring.";
return;
}
LocalFilesMigrationDialog::Show(destination, migration_start_time,
std::move(migration_callback));
}
void MigrationNotificationManager::ShowMigrationProgressNotification(
MigrationDestination destination) {
DCHECK(IsCloudDestination(destination));
std::u16string provider_str = CloudProviderToString(destination);
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);
NotificationDisplayServiceFactory::GetForProfile(profile())->Display(
NotificationHandler::Type::TRANSIENT, *notification,
/*metadata=*/nullptr);
}
void MigrationNotificationManager::ShowMigrationCompletedNotification(
MigrationDestination destination,
const base::FilePath& destination_path) {
DCHECK(IsCloudDestination(destination));
std::u16string provider_str = CloudProviderToString(destination);
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(&HandleCompletedNotificationClick, profile(),
destination_path));
notification->set_buttons({message_center::ButtonInfo(button)});
NotificationDisplayServiceFactory::GetForProfile(profile())->Display(
NotificationHandler::Type::TRANSIENT, *notification,
/*metadata=*/nullptr);
}
void MigrationNotificationManager::ShowDeletionCompletedNotification() {
std::u16string title =
l10n_util::GetStringUTF16(IDS_POLICY_SKYVAULT_DELETION_COMPLETED_TITLE);
std::u16string message =
l10n_util::GetStringUTF16(IDS_POLICY_SKYVAULT_DELETION_COMPLETED_MESSAGE);
auto notification = CreateNotificationPtr(title, message);
NotificationDisplayServiceFactory::GetForProfile(profile())->Display(
NotificationHandler::Type::TRANSIENT, *notification,
/*metadata=*/nullptr);
}
void MigrationNotificationManager::ShowMigrationErrorNotification(
MigrationDestination destination,
const std::string& folder_name,
const base::FilePath& error_log_path) {
DCHECK(!error_log_path.empty());
DCHECK(IsCloudDestination(destination));
std::u16string provider_str = CloudProviderToString(destination);
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),
{base::UTF8ToUTF16(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(&HandleErrorNotificationClick,
profile(), error_log_path));
notification->set_buttons({message_center::ButtonInfo(button)});
NotificationDisplayServiceFactory::GetForProfile(profile())->Display(
NotificationHandler::Type::TRANSIENT, *notification,
/*metadata=*/nullptr);
}
void MigrationNotificationManager::ShowConfigurationErrorNotification(
MigrationDestination destination) {
DCHECK(IsCloudDestination(destination));
std::u16string provider_str = CloudProviderToString(destination);
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());
NotificationDisplayServiceFactory::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,
UploadTrigger::kMigration,
/*file_path=*/base::FilePath(),
base::BindOnce(&MigrationNotificationManager::OnSignInResponse,
weak_factory_.GetWeakPtr()));
}
// sign_in_callback_subscriptions_.emplace_back(
return sign_in_callbacks_.Add(std::move(callback));
}
void MigrationNotificationManager::CloseNotifications() {
// 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());
}
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(MigrationDestination::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