// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/app_restore/arc_save_handler.h"
#include "base/containers/contains.h"
#include "base/ranges/algorithm.h"
#include "components/app_restore/app_launch_info.h"
#include "components/app_restore/app_restore_info.h"
#include "components/app_restore/app_restore_utils.h"
#include "components/app_restore/full_restore_save_handler.h"
#include "components/app_restore/window_info.h"
#include "components/app_restore/window_properties.h"
#include "ui/aura/window.h"
namespace full_restore {
namespace {
// Repeat timer interval between each checking that whether a task is created
// for each app launching.
constexpr base::TimeDelta kCheckCycleInterval = base::Seconds(600);
} // namespace
ArcSaveHandler::ArcSaveHandler(const base::FilePath& profile_path)
: profile_path_(profile_path) {}
ArcSaveHandler::~ArcSaveHandler() = default;
void ArcSaveHandler::SaveAppLaunchInfo(AppLaunchInfoPtr app_launch_info) {
DCHECK(app_launch_info->arc_session_id.has_value());
// Save |app_launch_info| to |session_id_to_app_launch_info_|, and wait for
// the ARC task to be created.
int32_t session_id = app_launch_info->arc_session_id.value();
// If the ghost window has been created for `session_id`, and the launch info
// hasn't been added yet, add `app_launch_info` to the restore data.
if (base::Contains(ghost_window_session_id_to_app_id_, session_id)) {
if (!FullRestoreSaveHandler::GetInstance()->HasAppRestoreData(
profile_path_, app_launch_info->app_id, session_id)) {
app_launch_info->window_id = session_id;
FullRestoreSaveHandler::GetInstance()->AddAppLaunchInfo(
profile_path_, std::move(app_launch_info));
// Go through `arc_window_candidates_`. If the window for `session_id` has
// been created, call OnAppLaunched to save the window info.
auto window_it = base::ranges::find(
arc_window_candidates_, session_id, [](aura::Window* window) {
return window->GetProperty(app_restore::kGhostWindowSessionIdKey);
});
if (window_it != arc_window_candidates_.end()) {
app_restore::AppRestoreInfo::GetInstance()->OnAppLaunched(*window_it);
arc_window_candidates_.erase(*window_it);
}
}
return;
}
session_id_to_app_launch_info_[session_id] =
std::make_pair(std::move(app_launch_info), base::TimeTicks::Now());
MaybeStartCheckTimer();
}
void ArcSaveHandler::ModifyWindowInfo(
const app_restore::WindowInfo& window_info) {
aura::Window* window = window_info.window;
int32_t task_id = window->GetProperty(app_restore::kWindowIdKey);
auto task_it = task_id_to_app_id_.find(task_id);
if (task_it != task_id_to_app_id_.end()) {
FullRestoreSaveHandler::GetInstance()->ModifyWindowInfo(
profile_path_, task_it->second, task_id, window_info);
return;
}
// For the ghost window, modify the window info with `session_id` as the
// window id.
int32_t session_id =
window->GetProperty(app_restore::kGhostWindowSessionIdKey);
auto it = ghost_window_session_id_to_app_id_.find(session_id);
if (it != ghost_window_session_id_to_app_id_.end()) {
FullRestoreSaveHandler::GetInstance()->ModifyWindowInfo(
profile_path_, it->second, session_id, window_info);
}
}
void ArcSaveHandler::OnWindowInitialized(aura::Window* window) {
int32_t task_id = window->GetProperty(app_restore::kWindowIdKey);
if (!base::Contains(task_id_to_app_id_, task_id)) {
// Check `session_id` to see whether this is a ghost window.
int32_t session_id =
window->GetProperty(app_restore::kGhostWindowSessionIdKey);
if (session_id < app_restore::kArcSessionIdOffsetForRestoredLaunching) {
// If the task hasn't been created, and this is not a ghost window, add
// `window` to `arc_window_candidates_` to wait for the task to be
// created.
arc_window_candidates_.insert(window);
return;
}
// Save `session_id` for the ghost window, to wait for the task created to
// replace the window id with the task id in the restore data.
const std::string* app_id = window->GetProperty(app_restore::kAppIdKey);
DCHECK(app_id);
ghost_window_session_id_to_app_id_[session_id] = *app_id;
auto it = session_id_to_app_launch_info_.find(session_id);
if (it == session_id_to_app_launch_info_.end()) {
arc_window_candidates_.insert(window);
return;
}
// If there is `app_launch_info`, add it to the restore data using
// `session_id` as `window_id`.
auto app_launch_info = std::move(it->second.first);
session_id_to_app_launch_info_.erase(it);
if (session_id_to_app_launch_info_.empty())
check_timer_.Stop();
app_launch_info->window_id = session_id;
FullRestoreSaveHandler::GetInstance()->AddAppLaunchInfo(
profile_path_, std::move(app_launch_info));
return;
}
// If the task has been created, call OnAppLaunched to save the window
// information.
app_restore::AppRestoreInfo::GetInstance()->OnAppLaunched(window);
}
void ArcSaveHandler::OnWindowDestroyed(aura::Window* window) {
arc_window_candidates_.erase(window);
int32_t task_id = window->GetProperty(app_restore::kWindowIdKey);
auto task_it = task_id_to_app_id_.find(task_id);
if (task_it != task_id_to_app_id_.end()) {
// Wait for the task to be destroyed to remove the full restore data for
// the task. Don't remove the window info, because it might affect the ghost
// window creating due to no window bounds. Send the window to background.
if (is_connection_ready_) {
FullRestoreSaveHandler::GetInstance()->SendWindowToBackground(
profile_path_, task_it->second, task_id);
}
return;
}
// If the ghost window has been created for `session_id`, remove
// `app_launch_info` from the restore data with `session_id` as the window id.
int32_t session_id =
window->GetProperty(app_restore::kGhostWindowSessionIdKey);
auto it = ghost_window_session_id_to_app_id_.find(session_id);
if (it != ghost_window_session_id_to_app_id_.end()) {
// For ghost windows, we don't need to wait for OnTaskDestroyed, so remove
// AppRestoreData for `session_id`.
FullRestoreSaveHandler::GetInstance()->RemoveAppRestoreData(
profile_path_, it->second, session_id);
ghost_window_session_id_to_app_id_.erase(it);
}
}
void ArcSaveHandler::OnTaskCreated(const std::string& app_id,
int32_t task_id,
int32_t session_id) {
auto it = session_id_to_app_launch_info_.find(session_id);
if (it == session_id_to_app_launch_info_.end()) {
auto session_it = ghost_window_session_id_to_app_id_.find(session_id);
if (session_it == ghost_window_session_id_to_app_id_.end())
return;
// For the ghost window, modify the window id from `session_id` to
// `task_id`.
FullRestoreSaveHandler::GetInstance()->ModifyWindowId(
profile_path_, session_it->second, session_id, task_id);
task_id_to_app_id_[task_id] = session_it->second;
ghost_window_session_id_to_app_id_.erase(session_it);
return;
}
auto app_launch_info = std::move(it->second.first);
task_id_to_app_id_[task_id] = app_launch_info->app_id;
session_id_to_app_launch_info_.erase(it);
if (session_id_to_app_launch_info_.empty())
check_timer_.Stop();
app_launch_info->window_id = task_id;
FullRestoreSaveHandler::GetInstance()->AddAppLaunchInfo(
profile_path_, std::move(app_launch_info));
// Go through |arc_window_candidates_|. If the window for |task_id| has been
// created, call OnAppLaunched to save the window info.
auto window_it = base::ranges::find(
arc_window_candidates_, task_id, [](aura::Window* window) {
return window->GetProperty(app_restore::kWindowIdKey);
});
if (window_it != arc_window_candidates_.end()) {
app_restore::AppRestoreInfo::GetInstance()->OnAppLaunched(*window_it);
arc_window_candidates_.erase(*window_it);
}
}
void ArcSaveHandler::OnTaskDestroyed(int32_t task_id) {
auto it = task_id_to_app_id_.find(task_id);
if (it == task_id_to_app_id_.end())
return;
FullRestoreSaveHandler::GetInstance()->RemoveAppRestoreData(
profile_path_, it->second, task_id);
task_id_to_app_id_.erase(task_id);
}
void ArcSaveHandler::OnArcPlayStoreEnabledChanged(bool enabled) {
if (!enabled)
task_id_to_app_id_.clear();
}
void ArcSaveHandler::OnTaskThemeColorUpdated(int32_t task_id,
uint32_t primary_color,
uint32_t status_bar_color) {
auto it = task_id_to_app_id_.find(task_id);
if (it == task_id_to_app_id_.end())
return;
FullRestoreSaveHandler::GetInstance()->ModifyThemeColor(
profile_path_, it->second, task_id, primary_color, status_bar_color);
}
int32_t ArcSaveHandler::GetArcSessionId() {
if (session_id_ >= app_restore::kArcSessionIdOffsetForRestoredLaunching) {
LOG(WARNING) << "ARC session id is too large: " << session_id_;
session_id_ = 0;
}
return ++session_id_;
}
std::string ArcSaveHandler::GetAppId(aura::Window* window) {
// First check |task_id_to_app_id_| to see if we can find app id there.
const int32_t task_id = window->GetProperty(app_restore::kWindowIdKey);
auto task_iter = task_id_to_app_id_.find(task_id);
if (task_iter != task_id_to_app_id_.end())
return task_iter->second;
// If not, try to search in |ghost_window_session_id_to_app_id_|.
const int32_t session_id =
window->GetProperty(app_restore::kGhostWindowSessionIdKey);
auto ghost_iter = ghost_window_session_id_to_app_id_.find(session_id);
return ghost_iter != ghost_window_session_id_to_app_id_.end()
? ghost_iter->second
: std::string();
}
void ArcSaveHandler::MaybeStartCheckTimer() {
if (!check_timer_.IsRunning()) {
check_timer_.Start(
FROM_HERE, kCheckCycleInterval,
base::BindRepeating(&ArcSaveHandler::CheckTasksForAppLaunching,
weak_factory_.GetWeakPtr()));
}
}
void ArcSaveHandler::CheckTasksForAppLaunching() {
std::set<int32_t> session_ids;
for (const auto& it : session_id_to_app_launch_info_) {
base::TimeDelta time_delta = base::TimeTicks::Now() - it.second.second;
if (time_delta > kCheckCycleInterval)
session_ids.insert(it.first);
}
for (auto id : session_ids)
session_id_to_app_launch_info_.erase(id);
if (session_id_to_app_launch_info_.empty())
check_timer_.Stop();
}
} // namespace full_restore