chromium/chrome/browser/ui/ash/device_scheduled_reboot/reboot_notification_controller.cc

// Copyright 2022 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/ui/ash/device_scheduled_reboot/reboot_notification_controller.h"

#include <memory>

#include "ash/public/cpp/notification_utils.h"
#include "ash/public/cpp/shell_window_ids.h"
#include "ash/shell.h"
#include "base/functional/callback_helpers.h"
#include "base/i18n/time_formatting.h"
#include "base/memory/ptr_util.h"
#include "base/strings/strcat.h"
#include "chrome/browser/notifications/notification_display_service.h"
#include "chrome/browser/notifications/notification_handler.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "components/strings/grit/components_strings.h"
#include "components/user_manager/user_manager.h"
#include "components/vector_icons/vector_icons.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/message_center/public/cpp/notification.h"

namespace ash {

// Id of the pending reboot notification
const char kPendingRebootNotificationId[] =
    "ash.device_scheduled_reboot_pending_notification";

// Id of the post reboot notification
const char kPostRebootNotificationId[] =
    "ash.device_scheduled_reboot_post_reboot_notification";
}  // namespace ash

RebootNotificationController::RebootNotificationController() = default;

RebootNotificationController::~RebootNotificationController() = default;

void RebootNotificationController::MaybeShowPendingRebootNotification(
    const base::Time& reboot_time,
    base::RepeatingClosure reboot_callback) {
  if (!ShouldNotifyUser())
    return;
  notification_callback_ = std::move(reboot_callback);
  std::u16string reboot_title =
      l10n_util::GetStringUTF16(IDS_POLICY_DEVICE_SCHEDULED_REBOOT_TITLE);
  std::u16string reboot_message =
      l10n_util::GetStringFUTF16(IDS_POLICY_DEVICE_SCHEDULED_REBOOT_MESSAGE,
                                 base::TimeFormatTimeOfDay(reboot_time),
                                 base::TimeFormatShortDate(reboot_time));

  message_center::RichNotificationData notification_data;
  notification_data.pinned = true;
  notification_data.buttons.emplace_back(
      l10n_util::GetStringUTF16(IDS_POLICY_REBOOT_BUTTON));
  scoped_refptr<message_center::NotificationDelegate> delegate =
      base::MakeRefCounted<message_center::HandleNotificationClickDelegate>(
          base::BindRepeating(
              &RebootNotificationController::HandleNotificationClick,
              weak_ptr_factory_.GetWeakPtr()));

  ShowNotification(ash::kPendingRebootNotificationId, reboot_title,
                   reboot_message, notification_data, delegate);
}

void RebootNotificationController::MaybeShowPendingRebootDialog(
    const base::Time& reboot_time,
    base::OnceClosure reboot_callback) {
  if (!ShouldNotifyUser())
    return;

  gfx::NativeView parent =
      ash::Shell::GetContainer(ash::Shell::GetRootWindowForNewWindows(),
                               ash::kShellWindowId_SystemModalContainer);
  // Closes old dialog if it was active at the moment and shows a new dialog
  // notifying the user about the reboot.
  scheduled_reboot_dialog_ = std::make_unique<ScheduledRebootDialog>(
      reboot_time, parent, std::move(reboot_callback));
}

void RebootNotificationController::MaybeShowPostRebootNotification() const {
  if (!ShouldNotifyUser())
    return;
  std::u16string title =
      l10n_util::GetStringUTF16(IDS_POLICY_DEVICE_POST_REBOOT_TITLE);
  scoped_refptr<message_center::NotificationDelegate> delegate =
      base::MakeRefCounted<message_center::HandleNotificationClickDelegate>(
          base::BindRepeating(
              &RebootNotificationController::HandleNotificationClick,
              weak_ptr_factory_.GetWeakPtr()));
  ShowNotification(ash::kPostRebootNotificationId, title, std::u16string(),
                   message_center::RichNotificationData(), delegate);
}

void RebootNotificationController::CloseRebootNotification() const {
  if (!ShouldNotifyUser())
    return;
  NotificationDisplayService* notification_display_service =
      NotificationDisplayService::GetForProfile(
          ProfileManager::GetActiveUserProfile());
  notification_display_service->Close(NotificationHandler::Type::TRANSIENT,
                                      ash::kPendingRebootNotificationId);
}

void RebootNotificationController::CloseRebootDialog() {
  if (scheduled_reboot_dialog_) {
    scheduled_reboot_dialog_.reset();
  }
}

void RebootNotificationController::ShowNotification(
    const std::string& id,
    const std::u16string& title,
    const std::u16string& message,
    const message_center::RichNotificationData& data,
    scoped_refptr<message_center::NotificationDelegate> delegate) const {
  // Create notification.
  message_center::Notification notification = ash::CreateSystemNotification(
      message_center::NOTIFICATION_TYPE_SIMPLE, id, title, message,
      std::u16string(), GURL(), message_center::NotifierId(), data, delegate,
      vector_icons::kBusinessIcon,
      message_center::SystemNotificationWarningLevel::NORMAL);

  NotificationDisplayService* notification_display_service =
      NotificationDisplayService::GetForProfile(
          ProfileManager::GetActiveUserProfile());
  // Close old notification.
  notification_display_service->Close(NotificationHandler::Type::TRANSIENT, id);
  // Display new notification.
  notification_display_service->Display(NotificationHandler::Type::TRANSIENT,
                                        notification,
                                        /*metadata=*/nullptr);
}

bool RebootNotificationController::ShouldNotifyUser() const {
  return (user_manager::UserManager::IsInitialized() &&
          user_manager::UserManager::Get()->IsUserLoggedIn() &&
          !user_manager::UserManager::Get()->IsLoggedInAsAnyKioskApp());
}

void RebootNotificationController::HandleNotificationClick(
    std::optional<int> button_index) const {
  // Only request restart when the button is clicked, i.e. ignore the clicks
  // on the body of the notification.
  if (!button_index)
    return;
  notification_callback_.Run();
}