// 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/ash/app_restore/full_restore_service.h"
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/constants/ash_switches.h"
#include "ash/constants/notifier_catalogs.h"
#include "ash/glanceables/post_login_glanceables_metrics_recorder.h"
#include "ash/metrics/login_unlock_throughput_recorder.h"
#include "ash/public/cpp/notification_utils.h"
#include "ash/public/cpp/session/session_controller.h"
#include "ash/shell.h"
#include "ash/webui/settings/public/constants/routes.mojom.h"
#include "ash/webui/settings/public/constants/setting.mojom-shared.h"
#include "ash/wm/desks/templates/saved_desk_controller.h"
#include "ash/wm/window_restore/informed_restore_controller.h"
#include "ash/wm/window_restore/window_restore_metrics.h"
#include "ash/wm/window_restore/window_restore_util.h"
#include "base/barrier_callback.h"
#include "base/command_line.h"
#include "base/metrics/histogram_functions.h"
#include "base/ranges/algorithm.h"
#include "base/strings/string_util.h"
#include "base/trace_event/trace_event.h"
#include "base/version_info/version_info.h"
#include "chrome/app/vector_icons/vector_icons.h"
#include "chrome/browser/ash/app_restore/app_restore_arc_task_handler.h"
#include "chrome/browser/ash/app_restore/full_restore_app_launch_handler.h"
#include "chrome/browser/ash/app_restore/full_restore_data_handler.h"
#include "chrome/browser/ash/app_restore/full_restore_prefs.h"
#include "chrome/browser/ash/app_restore/full_restore_service_factory.h"
#include "chrome/browser/ash/app_restore/new_user_restore_pref_handler.h"
#include "chrome/browser/ash/crosapi/browser_util.h"
#include "chrome/browser/ash/crosapi/crosapi_ash.h"
#include "chrome/browser/ash/crosapi/crosapi_manager.h"
#include "chrome/browser/ash/policy/scheduled_task_handler/reboot_notifications_scheduler.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/chromeos/full_restore/full_restore_util.h"
#include "chrome/browser/first_run/first_run.h"
#include "chrome/browser/lifetime/termination_notification.h"
#include "chrome/browser/notifications/notification_display_service.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/sessions/app_session_service_factory.h"
#include "chrome/browser/sessions/session_service_factory.h"
#include "chrome/browser/ui/settings_window_manager_chromeos.h"
#include "chrome/browser/web_applications/web_app_id_constants.h"
#include "chrome/browser/web_applications/web_app_utils.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/grit/branded_strings.h"
#include "chrome/grit/generated_resources.h"
#include "components/account_id/account_id.h"
#include "components/app_constants/constants.h"
#include "components/app_restore/app_restore_data.h"
#include "components/app_restore/app_restore_info.h"
#include "components/app_restore/app_restore_utils.h"
#include "components/app_restore/features.h"
#include "components/app_restore/full_restore_save_handler.h"
#include "components/app_restore/full_restore_utils.h"
#include "components/app_restore/restore_data.h"
#include "components/app_restore/window_info.h"
#include "components/prefs/pref_service.h"
#include "components/strings/grit/components_strings.h"
#include "components/user_manager/user.h"
#include "components/user_manager/user_manager.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/chromeos/devicetype_utils.h"
#include "ui/message_center/public/cpp/notification.h"
// Enable VLOG level 1.
#undef ENABLED_VLOG_LEVEL
#define ENABLED_VLOG_LEVEL 1
namespace ash::full_restore {
namespace {
// This flag forces full session restore on startup regardless of potential
// non-clean shutdown. It could be used in tests to ignore crashes on shutdown.
constexpr char kForceFullRestoreAndSessionRestoreAfterCrash[] =
"force-full-restore-and-session-restore-after-crash";
constexpr char kRestoreSettingHistogramName[] = "Apps.RestoreSetting";
constexpr char kRestoreInitSettingHistogramName[] = "Apps.RestoreInitSetting";
constexpr char kFullRestoreWindowCountHistogramName[] =
"Apps.FullRestoreWindowCount2";
// If the reboot occurred due to DeviceScheduledRebootPolicy, change the title
// to notify the user that the device was rebooted by the administrator.
int GetRestoreNotificationTitleId(Profile* profile) {
if (policy::RebootNotificationsScheduler::ShouldShowPostRebootNotification(
profile)) {
return IDS_POLICY_DEVICE_POST_REBOOT_TITLE;
}
return IDS_RESTORE_NOTIFICATION_TITLE;
}
// Returns true if `profile` is the primary user profile.
bool IsPrimaryUser(Profile* profile) {
return ProfileHelper::Get()->GetUserByProfile(profile) ==
user_manager::UserManager::Get()->GetPrimaryUser();
}
// Will (maybe) initiate an auto launch of an admin template.
void MaybeInitiateAdminTemplateAutoLaunch() {
// The controller is available if the admin template feature is enabled.
if (auto* saved_desk_controller = ash::SavedDeskController::Get()) {
saved_desk_controller->InitiateAdminTemplateAutoLaunch(base::DoNothing());
}
}
// Collects window id and app id of normal browser windows.
// Note that this collects both Lacros and Ash browser windows.
std::vector<LoginUnlockThroughputRecorder::RestoreWindowID>
CollectRestoreIDsForNormalBrowserWindows(
::app_restore::RestoreData* restore_data) {
if (!restore_data || restore_data->app_id_to_launch_list().empty()) {
return {};
}
std::vector<LoginUnlockThroughputRecorder::RestoreWindowID> app_restore_ids;
for (const auto& [app_id, launch_list] :
restore_data->app_id_to_launch_list()) {
const bool is_browser = app_id == app_constants::kChromeAppId ||
app_id == app_constants::kLacrosAppId;
// We are only interested in Ash or Lacros browsers.
if (!is_browser) {
continue;
}
for (const auto& [window_id, app_restore_data] : launch_list) {
if (app_id == app_constants::kChromeAppId) {
// Ignore app type browsers.
const bool app_type_browser =
app_restore_data->browser_extra_info.app_type_browser.value_or(
false);
if (app_type_browser) {
continue;
}
}
app_restore_ids.emplace_back(window_id, app_id);
}
}
return app_restore_ids;
}
} // namespace
bool g_restore_for_testing = true;
const char kRestoreForCrashNotificationId[] = "restore_for_crash_notification";
const char kRestoreNotificationId[] = "restore_notification";
const char kRestoreNotificationHistogramName[] = "Apps.RestoreNotification";
const char kRestoreForCrashNotificationHistogramName[] =
"Apps.RestoreForCrashNotification";
bool MaybeCreateFullRestoreServiceForLacros() {
// Full restore for Lacros depends on BrowserAppInstanceRegistry to save and
// restore Lacros windows, so check the web apps crosapi flag to make sure
// BrowserAppInstanceRegistry is created.
if (!::full_restore::features::IsFullRestoreForLacrosEnabled() ||
!web_app::IsWebAppsCrosapiEnabled()) {
return false;
}
const user_manager::User* user =
user_manager::UserManager::Get()->GetPrimaryUser();
DCHECK(user);
Profile* profile = ProfileHelper::Get()->GetProfileByUser(user);
DCHECK(profile);
// Lacros can be launched at the very early stage during the system startup
// phase. So create FullRestoreService to construct LacrosWindowHandler to
// observe BrowserAppInstanceRegistry for Lacros windows before the first
// Lacros window is created, to avoid missing any Lacros windows.
return FullRestoreService::GetForProfile(profile);
}
class DelegateImpl : public FullRestoreService::Delegate {
public:
DelegateImpl() = default;
DelegateImpl(const DelegateImpl&) = delete;
DelegateImpl& operator=(const DelegateImpl&) = delete;
~DelegateImpl() override = default;
void MaybeStartInformedRestoreOverviewSession(
std::unique_ptr<InformedRestoreContentsData> contents_data) override {
// A unit test that does not override this default delegate may not have ash
// shell.
if (Shell::HasInstance()) {
CHECK(Shell::Get()->informed_restore_controller());
Shell::Get()
->informed_restore_controller()
->MaybeStartInformedRestoreSession(std::move(contents_data));
}
}
void MaybeEndInformedRestoreOverviewSession() override {
// A unit test that does not override this default delegate may not have ash
// shell.
if (Shell::HasInstance()) {
CHECK(Shell::Get()->informed_restore_controller());
Shell::Get()
->informed_restore_controller()
->MaybeEndInformedRestoreSession();
}
}
InformedRestoreContentsData* GetInformedRestoreContentData() override {
if (Shell::HasInstance()) {
CHECK(Shell::Get()->informed_restore_controller());
return Shell::Get()->informed_restore_controller()->contents_data();
}
return nullptr;
}
void OnInformedRestoreContentsDataUpdated() override {
if (Shell::HasInstance()) {
CHECK(Shell::Get()->informed_restore_controller());
Shell::Get()->informed_restore_controller()->OnContentsDataUpdated();
}
}
};
// static
FullRestoreService* FullRestoreService::GetForProfile(Profile* profile) {
TRACE_EVENT0("ui", "FullRestoreService::GetForProfile");
return static_cast<FullRestoreService*>(
FullRestoreServiceFactory::GetInstance()->GetForProfile(profile));
}
// static
void FullRestoreService::MaybeCloseNotification(Profile* profile) {
auto* full_restore_service = FullRestoreService::GetForProfile(profile);
if (full_restore_service)
full_restore_service->MaybeCloseNotification();
}
FullRestoreService::FullRestoreService(Profile* profile)
: profile_(profile),
app_launch_handler_(std::make_unique<FullRestoreAppLaunchHandler>(
profile_,
/*should_init_service=*/true)),
restore_data_handler_(std::make_unique<FullRestoreDataHandler>(profile_)),
delegate_(std::make_unique<DelegateImpl>()) {
on_app_terminating_subscription_ =
browser_shutdown::AddAppTerminatingCallback(base::BindOnce(
&FullRestoreService::OnAppTerminating, base::Unretained(this)));
auto* full_restore_save_handler =
::full_restore::FullRestoreSaveHandler::GetInstance();
full_restore_save_handler->InsertIgnoreApplicationId(
web_app::kOsFeedbackAppId);
PrefService* prefs = profile_->GetPrefs();
DCHECK(prefs);
pref_change_registrar_.Init(prefs);
pref_change_registrar_.Add(
prefs::kRestoreAppsAndPagesPrefName,
base::BindRepeating(&FullRestoreService::OnPreferenceChanged,
weak_ptr_factory_.GetWeakPtr()));
const user_manager::User* user =
ProfileHelper::Get()->GetUserByProfile(profile_);
if (user) {
::app_restore::AppRestoreInfo::GetInstance()->SetRestorePref(
user->GetAccountId(), CanPerformRestore(prefs));
}
// Set profile path before init the restore process to create
// FullRestoreSaveHandler to observe restore windows.
if (IsPrimaryUser(profile_)) {
full_restore_save_handler->SetPrimaryProfilePath(profile_->GetPath());
// In Multi-Profile mode, only set for the primary user. For other users,
// active profile path is set when switch users.
::full_restore::SetActiveProfilePath(profile_->GetPath());
can_be_inited_ = CanBeInited();
}
if (!HasRestorePref(prefs) && HasSessionStartupPref(prefs)) {
// If there is no full restore pref, but there is a session restore setting,
// set the first run flag to maintain the previous behavior for the first
// time running the full restore feature when migrate to the full restore
// release. Restore browsers and web apps by the browser session restore.
first_run_full_restore_ = true;
SetDefaultRestorePrefIfNecessary(prefs);
full_restore_save_handler->AllowSave();
VLOG(1) << "No restore pref! First time to run full restore."
<< profile_->GetPath();
}
// In some unit tests, there may not be a shell instance and session
// controller.
if (auto* session_controller = SessionController::Get()) {
session_controller->AddObserver(this);
}
}
FullRestoreService::~FullRestoreService() {
if (auto* session_controller = SessionController::Get()) {
session_controller->RemoveObserver(this);
}
}
void FullRestoreService::Init(bool& show_notification) {
// If it is the first time to migrate to the full restore release, we don't
// have other restore data, so we don't need to consider restoration.
if (first_run_full_restore_)
return;
// If the user of `profile_` is not the primary user, and hasn't been the
// active user yet, we don't need to consider restoration to prevent the
// restored windows are written to the active user's profile path.
if (!can_be_inited_)
return;
// If the restore data has not been loaded, wait for it. For test cases,
// `app_launch_handler_` might be reset as null because test cases might be
// finished before Init is called, so check `app_launch_handler_` to prevent
// crash for test cases.
if (!app_launch_handler_ || !app_launch_handler_->IsRestoreDataLoaded())
return;
if (is_shut_down_)
return;
PrefService* prefs = profile_->GetPrefs();
DCHECK(prefs);
// Determine whether we should show the update string. Crash takes priority
// over update but we do the computations to store the pref for the next
// session here first. The pref may not be registered in certain unit tests.
bool is_update = false;
if (features::IsForestFeatureEnabled() &&
prefs->HasPrefPath(prefs::kInformedRestoreLastVersion)) {
const base::Version old_version(
prefs->GetString(prefs::kInformedRestoreLastVersion));
const base::Version current_version = version_info::GetVersion();
prefs->SetString(prefs::kInformedRestoreLastVersion,
current_version.GetString());
is_update = old_version.IsValid() && current_version > old_version;
}
// If the system crashed before reboot, show the restore notification.
if (ExitTypeService::GetLastSessionExitType(profile_) == ExitType::kCrashed) {
if (!HasRestorePref(prefs))
SetDefaultRestorePrefIfNecessary(prefs);
MaybeShowRestoreNotification(
InformedRestoreContentsData::DialogType::kCrash, show_notification);
return;
}
// If either OS pref setting nor Chrome pref setting exist, that means we
// don't have restore data, so we don't need to consider restoration, and call
// NewUserRestorePrefHandler to set OS pref setting.
if (!HasRestorePref(prefs) && !HasSessionStartupPref(prefs)) {
new_user_pref_handler_ =
std::make_unique<NewUserRestorePrefHandler>(profile_);
::full_restore::FullRestoreSaveHandler::GetInstance()->AllowSave();
MaybeInitiateAdminTemplateAutoLaunch();
return;
}
RestoreOption restore_pref = static_cast<RestoreOption>(
prefs->GetInteger(prefs::kRestoreAppsAndPagesPrefName));
base::UmaHistogramEnumeration(kRestoreInitSettingHistogramName, restore_pref);
::app_restore::RestoreData* restore_data =
app_launch_handler_->restore_data();
// Record the window count from the full restore file, unless the option is do
// not restore.
if (restore_pref != RestoreOption::kDoNotRestore) {
if (!restore_data) {
base::UmaHistogramCounts100(kFullRestoreWindowCountHistogramName, 0);
} else {
auto [window_count, tab_count, total_count] =
::app_restore::GetWindowAndTabCount(*restore_data);
base::UmaHistogramCounts100(kFullRestoreWindowCountHistogramName,
window_count);
}
}
// LoginUnlockThroughputRecorder needs to track when session
// restore is done. Here we notify it of the set of normal browser windows.
if (ProfileHelper::IsPrimaryProfile(profile_) && Shell::HasInstance() &&
Shell::Get()->login_unlock_throughput_recorder()) {
Shell::Get()
->login_unlock_throughput_recorder()
->FullSessionRestoreDataLoaded(
CollectRestoreIDsForNormalBrowserWindows(restore_data),
/*restore_automatically=*/restore_pref == RestoreOption::kAlways);
}
switch (restore_pref) {
case RestoreOption::kAlways: {
Restore();
break;
}
case RestoreOption::kAskEveryTime: {
const auto dialog_type =
is_update ? InformedRestoreContentsData::DialogType::kUpdate
: InformedRestoreContentsData::DialogType::kNormal;
MaybeShowRestoreNotification(dialog_type, show_notification);
MaybeInitiateAdminTemplateAutoLaunch();
break;
}
case RestoreOption::kDoNotRestore: {
if (features::IsForestFeatureEnabled()) {
MaybeShowInformedRestoreOnboarding(/*restore_on=*/false);
}
::full_restore::FullRestoreSaveHandler::GetInstance()->AllowSave();
MaybeInitiateAdminTemplateAutoLaunch();
return;
}
}
}
void FullRestoreService::OnTransitionedToNewActiveUser(Profile* profile) {
const bool already_initialized = can_be_inited_;
if (profile_ != profile || already_initialized)
return;
can_be_inited_ = true;
bool show_notification = false;
Init(show_notification);
}
void FullRestoreService::LaunchBrowserWhenReady() {
TRACE_EVENT0("ui", "FullRestoreService::LaunchBrowserWhenReady");
if (!g_restore_for_testing || !app_launch_handler_)
return;
app_launch_handler_->LaunchBrowserWhenReady(first_run_full_restore_);
}
void FullRestoreService::MaybeCloseNotification(bool allow_save) {
close_notification_ = true;
VLOG(1) << "The full restore notification is closed for "
<< profile_->GetPath();
// The crash notification creates a crash lock for the browser session
// restore. So if the notification has been closed and the system is no longer
// crash, clear `crashed_lock_`. Otherwise, the crash flag might not be
// cleared, and the crash notification might be shown again after the normal
// shutdown process.
crashed_lock_.reset();
if (notification_ && !is_shut_down_) {
NotificationDisplayService::GetForProfile(profile_)->Close(
NotificationHandler::Type::TRANSIENT, notification_->id());
accelerator_controller_observer_.Reset();
}
if (allow_save) {
// If the user launches an app or clicks the cancel button, start the save
// timer.
::full_restore::FullRestoreSaveHandler::GetInstance()->AllowSave();
}
}
void FullRestoreService::Restore() {
if (app_launch_handler_)
app_launch_handler_->SetShouldRestore();
}
void FullRestoreService::Close(bool by_user) {
if (!skip_notification_histogram_) {
RecordRestoreAction(
notification_->id(),
by_user ? RestoreAction::kCloseByUser : RestoreAction::kCloseNotByUser);
}
notification_ = nullptr;
if (by_user) {
// If the user closes the notification, start the save timer. If it is not
// closed by the user, the restore button might be clicked, then we need to
// wait for the restore finish to start the save timer.
::full_restore::FullRestoreSaveHandler::GetInstance()->AllowSave();
}
}
void FullRestoreService::Click(const std::optional<int>& button_index,
const std::optional<std::u16string>& reply) {
DCHECK(notification_);
skip_notification_histogram_ = true;
if (!button_index.has_value() ||
button_index.value() ==
static_cast<int>(RestoreNotificationButtonIndex::kRestore)) {
VLOG(1) << "The restore notification is clicked for "
<< profile_->GetPath();
// Restore if the user clicks the notification body.
RecordRestoreAction(notification_->id(), RestoreAction::kRestore);
Restore();
// If the user selects restore, don't start the save timer. Wait for the
// restore finish.
MaybeCloseNotification(/*allow_save=*/false);
return;
}
if (notification_->id() == kRestoreNotificationId) {
// Show the 'On Startup' OS setting page if the user clicks the settings
// button of the restore notification.
ash::features::IsOsSettingsRevampWayfindingEnabled()
? chrome::SettingsWindowManager::GetInstance()->ShowOSSettings(
profile_,
chromeos::settings::mojom::kSystemPreferencesSectionPath,
chromeos::settings::mojom::Setting::kRestoreAppsAndPages)
: chrome::SettingsWindowManager::GetInstance()->ShowOSSettings(
profile_, chromeos::settings::mojom::kAppsSectionPath);
return;
}
VLOG(1) << "The crash restore notification is canceled for "
<< profile_->GetPath();
// Close the crash notification if the user clicks the cancel button of the
// crash notification.
RecordRestoreAction(notification_->id(), RestoreAction::kCancel);
MaybeCloseNotification();
}
void FullRestoreService::OnActionPerformed(AcceleratorAction action) {
switch (action) {
case AcceleratorAction::kNewIncognitoWindow:
case AcceleratorAction::kNewTab:
case AcceleratorAction::kNewWindow:
case AcceleratorAction::kOpenCrosh:
case AcceleratorAction::kOpenDiagnostics:
case AcceleratorAction::kRestoreTab:
MaybeCloseNotification();
return;
default:
return;
}
}
void FullRestoreService::OnAcceleratorControllerWillBeDestroyed(
AcceleratorController* controller) {
accelerator_controller_observer_.Reset();
}
void FullRestoreService::OnSessionStateChanged(
session_manager::SessionState state) {
if (!contents_data_) {
return;
}
// Start post-login session right after signing in.
if (state == session_manager::SessionState::ACTIVE) {
delegate_->MaybeStartInformedRestoreOverviewSession(
std::move(contents_data_));
}
}
void FullRestoreService::SetAppLaunchHandlerForTesting(
std::unique_ptr<FullRestoreAppLaunchHandler> app_launch_handler) {
app_launch_handler_ = std::move(app_launch_handler);
}
void FullRestoreService::Shutdown() {
is_shut_down_ = true;
}
bool FullRestoreService::CanBeInited() const {
auto* user_manager = user_manager::UserManager::Get();
DCHECK(user_manager);
DCHECK(user_manager->GetActiveUser());
// For non-primary user, wait for `OnTransitionedToNewActiveUser`.
auto* user = ProfileHelper::Get()->GetUserByProfile(profile_);
if (user != user_manager->GetPrimaryUser()) {
VLOG(1) << "Can't init full restore service for non_primary user."
<< profile_->GetPath();
return false;
}
// Check the command line to decide whether this is the restart case.
// `kLoginManager` means starting Chrome with login/oobe screen, not the
// restart process. For the restart process, `kLoginUser` should be in the
// command line.
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
DCHECK(command_line);
if (command_line->HasSwitch(switches::kLoginManager) ||
!command_line->HasSwitch(switches::kLoginUser)) {
return true;
}
// When the system restarts, and the active user in the previous session is
// not the primary user, don't init, but wait for the transition to the last
// active user.
const auto& last_session_active_account_id =
user_manager->GetLastSessionActiveAccountId();
if (last_session_active_account_id.is_valid() &&
AccountId::FromUserEmail(user->GetAccountId().GetUserEmail()) !=
last_session_active_account_id) {
VLOG(1) << "Can't init full restore service for non-active primary user."
<< profile_->GetPath();
return false;
}
return true;
}
void FullRestoreService::InitInformedRestoreContentsData(
InformedRestoreContentsData::DialogType dialog_type) {
CHECK(app_launch_handler_->HasRestoreData());
contents_data_ = std::make_unique<InformedRestoreContentsData>();
contents_data_->dialog_type = dialog_type;
contents_data_->restore_callback = base::BindOnce(
&FullRestoreService::OnDialogRestore, weak_ptr_factory_.GetWeakPtr());
contents_data_->cancel_callback = base::BindOnce(
&FullRestoreService::OnDialogCancel, weak_ptr_factory_.GetWeakPtr());
// Contains per-window app data to be sorted and and added to
// `contents_data_`.
struct WindowAppData {
int window_id;
std::string app_id;
raw_ptr<::app_restore::AppRestoreData> app_restore_data;
};
// Retrieve app id's from `restore_data`. There can be multiple entries with
// the same app id, these denote different windows.
auto* restore_data = app_launch_handler_->restore_data();
std::vector<WindowAppData> complete_window_list;
for (const auto& [app_id, launch_list] :
restore_data->app_id_to_launch_list()) {
for (const std::pair<const int,
std::unique_ptr<::app_restore::AppRestoreData>>&
id_data_pair : launch_list) {
complete_window_list.emplace_back(id_data_pair.first, app_id,
id_data_pair.second.get());
}
}
// Sort the windows based on their activation index (more recent windows
// have a lower index). Windows without an activation index can be placed at
// the end.
base::ranges::sort(complete_window_list, [](const WindowAppData& element_a,
const WindowAppData& element_b) {
return element_a.app_restore_data->window_info.activation_index.value_or(
INT_MAX) <
element_b.app_restore_data->window_info.activation_index.value_or(
INT_MAX);
});
for (auto info : complete_window_list) {
const std::string stored_title =
base::UTF16ToUTF8(info.app_restore_data->window_info.app_title.value_or(
std::u16string()));
contents_data_->apps_infos.emplace_back(info.app_id, stored_title,
info.window_id);
}
}
void FullRestoreService::MaybeShowRestoreNotification(
InformedRestoreContentsData::DialogType dialog_type,
bool& show_notification) {
if (!app_launch_handler_) {
return;
}
// Do not show the notification if we have no restore data.
if (!features::IsForestFeatureEnabled() &&
!app_launch_handler_->HasRestoreData()) {
return;
}
// Do not show the notification if it is the first run or the notification is
// being closed.
if (::first_run::IsChromeFirstRun() || close_notification_) {
return;
}
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
kForceFullRestoreAndSessionRestoreAfterCrash)) {
LOG(WARNING) << "Full session restore was forced by a debug flag.";
Restore();
return;
}
const bool last_session_crashed =
dialog_type == InformedRestoreContentsData::DialogType::kCrash;
const std::string id = last_session_crashed ? kRestoreForCrashNotificationId
: kRestoreNotificationId;
if (!app_launch_handler_->HasRestoreData()) {
CHECK(features::IsForestFeatureEnabled());
MaybeShowInformedRestoreOnboarding(/*restore_on=*/true);
return;
}
CHECK(app_launch_handler_->HasRestoreData());
// If the system is restored from crash, create the crash lock for the browser
// session restore to help set the browser saving flag.
ExitTypeService* exit_type_service =
ExitTypeService::GetInstanceForProfile(profile_);
if (last_session_crashed && exit_type_service) {
crashed_lock_ = exit_type_service->CreateCrashedLock();
}
if (Shell::HasInstance()) {
Shell::Get()
->post_login_glanceables_metrics_reporter()
->RecordPostLoginFullRestoreShown();
}
if (features::IsForestFeatureEnabled()) {
CHECK(delegate_);
InitInformedRestoreContentsData(dialog_type);
if (crosapi::browser_util::IsLacrosEnabled()) {
crosapi::CrosapiManager::Get()
->crosapi_ash()
->full_restore_ash()
->GetSessionInformation(
base::BindOnce(&FullRestoreService::OnGotAllSessionsLacros,
weak_ptr_factory_.GetWeakPtr()));
} else {
// Retrieves session service data from browser and app browsers, which
// will be used to display favicons and tab titles.
SessionServiceBase* service =
SessionServiceFactory::GetForProfileForSessionRestore(profile_);
SessionServiceBase* app_service =
AppSessionServiceFactory::GetForProfileForSessionRestore(profile_);
if (service && app_service) {
auto barrier = base::BarrierCallback<SessionWindows>(
/*num_callbacks=*/2u, /*done_callback=*/base::BindOnce(
&FullRestoreService::OnGotAllSessionsAsh,
weak_ptr_factory_.GetWeakPtr()));
service->GetLastSession(
base::BindOnce(&FullRestoreService::OnGotSessionAsh,
weak_ptr_factory_.GetWeakPtr(), barrier));
app_service->GetLastSession(
base::BindOnce(&FullRestoreService::OnGotSessionAsh,
weak_ptr_factory_.GetWeakPtr(), barrier));
} else {
OnGotAllSessionsAsh(/*all_session_windows=*/{});
}
}
// Set to true as we might want to show the post reboot notification.
show_notification = true;
return;
}
// For forest, we will handle closing the dialog on the ash side.
if (auto* accelerator_controller = AcceleratorController::Get()) {
CHECK(!accelerator_controller_observer_.IsObserving());
accelerator_controller_observer_.Observe(accelerator_controller);
}
message_center::RichNotificationData notification_data;
message_center::ButtonInfo restore_button(
l10n_util::GetStringUTF16(IDS_RESTORE_NOTIFICATION_RESTORE_BUTTON));
notification_data.buttons.push_back(restore_button);
int button_id;
if (id == kRestoreForCrashNotificationId)
button_id = IDS_RESTORE_NOTIFICATION_CANCEL_BUTTON;
else
button_id = IDS_RESTORE_NOTIFICATION_SETTINGS_BUTTON;
message_center::ButtonInfo cancel_button(
l10n_util::GetStringUTF16(button_id));
notification_data.buttons.push_back(cancel_button);
std::u16string title;
if (id == kRestoreForCrashNotificationId) {
title = l10n_util::GetStringFUTF16(IDS_RESTORE_CRASH_NOTIFICATION_TITLE,
ui::GetChromeOSDeviceName());
VLOG(1) << "Show the restore notification for crash for "
<< profile_->GetPath();
} else {
title = l10n_util::GetStringUTF16(GetRestoreNotificationTitleId(profile_));
VLOG(1) << "Show the restore notification for the normal startup for "
<< profile_->GetPath();
}
int message_id;
if (id == kRestoreForCrashNotificationId)
message_id = IDS_RESTORE_CRASH_NOTIFICATION_MESSAGE;
else
message_id = IDS_RESTORE_NOTIFICATION_MESSAGE;
notification_ = CreateSystemNotificationPtr(
message_center::NOTIFICATION_TYPE_SIMPLE, id, title,
l10n_util::GetStringUTF16(message_id),
l10n_util::GetStringUTF16(IDS_RESTORE_NOTIFICATION_DISPLAY_SOURCE),
GURL(),
message_center::NotifierId(message_center::NotifierType::SYSTEM_COMPONENT,
id, NotificationCatalogName::kFullRestore),
notification_data,
base::MakeRefCounted<message_center::ThunkNotificationDelegate>(
weak_ptr_factory_.GetWeakPtr()),
kFullRestoreNotificationIcon,
message_center::SystemNotificationWarningLevel::NORMAL);
notification_->set_priority(message_center::SYSTEM_PRIORITY);
auto* notification_display_service =
NotificationDisplayService::GetForProfile(profile_);
DCHECK(notification_display_service);
notification_display_service->Display(NotificationHandler::Type::TRANSIENT,
*notification_,
/*metadata=*/nullptr);
base::UmaHistogramBoolean(kFullRestoreNotificationHistogram, true);
show_notification = true;
}
void FullRestoreService::RecordRestoreAction(const std::string& notification_id,
RestoreAction restore_action) {
base::UmaHistogramEnumeration(notification_id == kRestoreNotificationId
? kRestoreNotificationHistogramName
: kRestoreForCrashNotificationHistogramName,
restore_action);
}
void FullRestoreService::OnPreferenceChanged(const std::string& pref_name) {
DCHECK_EQ(pref_name, prefs::kRestoreAppsAndPagesPrefName);
RestoreOption restore_option = static_cast<RestoreOption>(
profile_->GetPrefs()->GetInteger(prefs::kRestoreAppsAndPagesPrefName));
base::UmaHistogramEnumeration(kRestoreSettingHistogramName, restore_option);
const user_manager::User* user =
ProfileHelper::Get()->GetUserByProfile(profile_);
if (user) {
::app_restore::AppRestoreInfo::GetInstance()->SetRestorePref(
user->GetAccountId(), CanPerformRestore(profile_->GetPrefs()));
}
}
void FullRestoreService::OnAppTerminating() {
if (auto* arc_task_handler =
app_restore::AppRestoreArcTaskHandler::GetForProfile(profile_)) {
arc_task_handler->Shutdown();
}
app_launch_handler_.reset();
::full_restore::FullRestoreSaveHandler::GetInstance()->SetShutDown();
}
void FullRestoreService::OnDialogRestore() {
VLOG(1) << "The restore button is clicked for " << profile_->GetPath();
Restore();
delegate_->MaybeEndInformedRestoreOverviewSession();
}
void FullRestoreService::OnDialogCancel() {
::full_restore::FullRestoreSaveHandler::GetInstance()->AllowSave();
delegate_->MaybeEndInformedRestoreOverviewSession();
}
void FullRestoreService::OnGotSessionAsh(
base::OnceCallback<void(SessionWindows)> callback,
SessionWindows session_windows,
SessionID active_window_id,
bool read_error) {
std::move(callback).Run(std::move(session_windows));
}
void FullRestoreService::OnGotAllSessionsAsh(
const std::vector<SessionWindows>& all_session_windows) {
// Place all the session windows in map so we don't have to do so many O(n)
// lookups below. Note that this has the additional overhead of creating the
// full_restore.mojom struct. This is so we can share more code with Lacros,
// which is the final goal.
SessionWindowsMap session_windows_map;
for (const SessionWindows& session_windows : all_session_windows) {
for (const std::unique_ptr<sessions::SessionWindow>& session_window :
session_windows) {
session_windows_map.emplace(
session_window->window_id.id(),
::full_restore::ToSessionWindowPtr(*session_window,
/*lacros_profile_id=*/0));
}
}
OnSessionInformationReceived(session_windows_map);
}
void FullRestoreService::OnGotAllSessionsLacros(
std::vector<crosapi::mojom::SessionWindowPtr> all_session_windows) {
// Place all the session windows in map so we don't have to do so many O(n)
// lookups below.
SessionWindowsMap session_windows_map;
for (const crosapi::mojom::SessionWindowPtr& session_window :
all_session_windows) {
session_windows_map.emplace(session_window->window_id,
session_window->Clone());
}
OnSessionInformationReceived(session_windows_map);
}
void FullRestoreService::OnSessionInformationReceived(
const SessionWindowsMap& session_windows_map) {
auto* contents_data = contents_data_
? contents_data_.get()
: delegate_->GetInformedRestoreContentData();
CHECK(contents_data);
bool content_updated = false;
for (auto& info : contents_data->apps_infos) {
const std::string app_id = info.app_id;
const int window_id = info.window_id;
// For non browsers, the app id and title is sufficient for the UI we want
// to display.
if (app_id != app_constants::kChromeAppId &&
app_id != app_constants::kLacrosAppId) {
continue;
}
// Find the `crosapi::mojom::SessionWindow` associated with `window_id` if
// it exists.
auto it = session_windows_map.find(window_id);
crosapi::mojom::SessionWindow* session_window =
it == session_windows_map.end() ? nullptr : it->second.get();
// Default to using the app id if we cannot find the associated window for
// whatever reason.
if (!session_window) {
continue;
}
content_updated = true;
// App browsers app ID is the same as regular chrome browsers. To get the
// correct icon and title from the app service, we need to find the app
// name and remove the "_crx_", then use that result.
const std::string app_name = session_window->app_name;
if (!app_name.empty()) {
const std::string new_app_id =
::app_restore::GetAppIdFromAppName(app_name);
if (!new_app_id.empty()) {
info.app_id = new_app_id;
}
continue;
}
info = InformedRestoreContentsData::AppInfo(
app_id, session_window->active_tab_title, window_id,
session_window->urls, session_window->tab_count,
session_window->profile_id);
}
// Start the post-login session if not yet and pass the contents data to
// post-login controller.
if (contents_data_) {
delegate_->MaybeStartInformedRestoreOverviewSession(
std::move(contents_data_));
return;
}
// Notify the contents data updated when the data was sent to informed dialog
// and there are items updated.
if (!contents_data_ && content_updated) {
delegate_->OnInformedRestoreContentsDataUpdated();
}
}
void FullRestoreService::MaybeShowInformedRestoreOnboarding(bool restore_on) {
if (Shell::HasInstance() && !profile_->IsNewProfile() &&
!base::CommandLine::ForCurrentProcess()->HasSwitch(
::switches::kNoFirstRun)) {
CHECK(Shell::Get()->informed_restore_controller());
Shell::Get()
->informed_restore_controller()
->MaybeShowInformedRestoreOnboarding(restore_on);
}
}
ScopedRestoreForTesting::ScopedRestoreForTesting() {
g_restore_for_testing = false;
}
ScopedRestoreForTesting::~ScopedRestoreForTesting() {
g_restore_for_testing = true;
}
} // namespace ash::full_restore