blob: e9f05042a32aef96cf819f0500f40ef7fe0a1cd8 [file] [log] [blame]
// Copyright 2021 The Chromium Authors. All rights reserved.
// 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/desk_template_app_launch_handler.h"
#include <string>
#include "ash/wm/desks/desks_controller.h"
#include "base/notreached.h"
#include "base/threading/thread_task_runner_handle.h"
#include "chrome/browser/apps/app_service/app_service_proxy.h"
#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
#include "chrome/browser/ash/app_restore/full_restore_app_launch_handler.h"
#include "chrome/browser/ash/app_restore/full_restore_service.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/ash/shelf/chrome_shelf_controller_util.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_tabstrip.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/web_applications/system_web_app_ui_utils.h"
#include "chrome/browser/web_applications/system_web_apps/system_web_app_manager.h"
#include "chrome/browser/web_applications/web_app_provider.h"
#include "components/app_restore/restore_data.h"
#include "components/app_restore/window_info.h"
#include "extensions/common/extension.h"
namespace {
// The restore data owned by this class will clear after being set. This is a
// temporary estimate of how long it takes to launch apps.
constexpr base::TimeDelta kClearRestoreDataDuration = base::Seconds(5);
} // namespace
DeskTemplateAppLaunchHandler::DeskTemplateAppLaunchHandler(Profile* profile)
: ash::AppLaunchHandler(profile) {
app_restore::DeskTemplateReadHandler::GetInstance()->SetDelegate(this);
}
DeskTemplateAppLaunchHandler::~DeskTemplateAppLaunchHandler() {
app_restore::DeskTemplateReadHandler::GetInstance()->SetDelegate(nullptr);
}
void DeskTemplateAppLaunchHandler::SetRestoreDataAndLaunch(
std::unique_ptr<app_restore::RestoreData> new_restore_data) {
// Another desk template is underway.
// TODO(sammiequon): Checking for `restore_data_clone_` is temporary. We will
// want to use a better check of whether a desk template is underway. Perhaps
// removing entries from `restore_data_clone_` of launched apps and/or using
// individual shorter timeouts.
if (restore_data_clone_)
return;
set_restore_data(std::move(new_restore_data));
if (!HasRestoreData())
return;
restore_data_clone_ = restore_data()->Clone();
LaunchApps();
LaunchBrowsers();
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&DeskTemplateAppLaunchHandler::ClearRestoreDataClone,
weak_ptr_factory_.GetWeakPtr()),
kClearRestoreDataDuration);
}
std::unique_ptr<app_restore::WindowInfo>
DeskTemplateAppLaunchHandler::GetWindowInfo(int restore_window_id) {
if (!restore_data_clone_)
return nullptr;
// Try to find the window info associated with `restore_window_id`.
const app_restore::RestoreData::AppIdToLaunchList& launch_list =
restore_data_clone_->app_id_to_launch_list();
for (const auto& it : launch_list) {
const std::string& app_id = it.first;
const app_restore::AppRestoreData* app_restore_data =
restore_data_clone_->GetAppRestoreData(app_id, restore_window_id);
if (app_restore_data)
return app_restore_data->GetWindowInfo();
}
return nullptr;
}
int32_t DeskTemplateAppLaunchHandler::FetchRestoreWindowId(
const std::string& app_id) {
return restore_data_clone_ ? restore_data_clone_->FetchRestoreWindowId(app_id)
: 0;
}
bool DeskTemplateAppLaunchHandler::IsFullRestoreRunning() const {
ash::full_restore::FullRestoreService* full_restore_service =
ash::full_restore::FullRestoreService::GetForProfile(
const_cast<Profile*>(profile()));
if (!full_restore_service)
return false;
ash::full_restore::FullRestoreAppLaunchHandler*
full_restore_app_launch_handler =
full_restore_service->app_launch_handler();
DCHECK(full_restore_app_launch_handler);
base::TimeTicks full_restore_start_time =
full_restore_app_launch_handler->restore_start_time();
// Full restore has not started yet.
if (full_restore_start_time.is_null())
return false;
// We estimate that full restore is still running if it has been less than
// five seconds since it started.
return (base::TimeTicks::Now() - full_restore_start_time) <
kClearRestoreDataDuration;
}
bool DeskTemplateAppLaunchHandler::ShouldLaunchSystemWebAppOrChromeApp(
const std::string& app_id,
const app_restore::RestoreData::LaunchList& launch_list) {
// Find out if the app can have multiple instances. Apps that can have
// multiple instances are:
// 1) System web apps which can open multiple windows
// 2) Non platform app type Chrome apps
// TODO(crubg.com/1239089): Investigate if we can have a way to handle moving
// single instance windows without all these heuristics.
bool is_multi_instance_window = false;
// Check the app registry cache to see if the app is a system web app.
bool is_system_web_app = false;
apps::AppServiceProxyFactory::GetForProfile(profile())
->AppRegistryCache()
.ForOneApp(app_id, [&is_system_web_app](const apps::AppUpdate& update) {
if (update.AppType() == apps::mojom::AppType::kWeb ||
update.AppType() == apps::mojom::AppType::kSystemWeb) {
is_system_web_app = true;
}
});
// A SWA can handle multiple instances if it can open multiple windows.
if (is_system_web_app) {
absl::optional<web_app::SystemAppType> swa_type =
web_app::GetSystemWebAppTypeForAppId(profile(), app_id);
if (swa_type.has_value()) {
auto* system_app = web_app::WebAppProvider::GetForSystemWebApps(profile())
->system_web_app_manager()
.GetSystemApp(*swa_type);
DCHECK(system_app);
is_multi_instance_window = system_app->ShouldShowNewWindowMenuOption();
}
} else {
// Check the extensions registry to see if the app is a platform app. No
// need to do this check if the app is a system web app.
DCHECK(!is_multi_instance_window);
const extensions::Extension* extension =
GetExtensionForAppID(app_id, profile());
is_multi_instance_window = extension && !extension->is_platform_app();
}
// Do not try sending an existing window to the active desk and launch a new
// instance.
if (is_multi_instance_window)
return true;
return ash::DesksController::Get()->OnSingleInstanceAppLaunchingFromTemplate(
app_id, launch_list);
}
void DeskTemplateAppLaunchHandler::OnExtensionLaunching(
const std::string& app_id) {
if (restore_data_clone_)
restore_data_clone_->SetNextRestoreWindowIdForChromeApp(app_id);
}
base::WeakPtr<ash::AppLaunchHandler>
DeskTemplateAppLaunchHandler::GetWeakPtrAppLaunchHandler() {
return weak_ptr_factory_.GetWeakPtr();
}
void DeskTemplateAppLaunchHandler::LaunchBrowsers() {
DCHECK(restore_data());
const auto& launch_list = restore_data()->app_id_to_launch_list();
for (const auto& iter : launch_list) {
const std::string& app_id = iter.first;
if (app_id != extension_misc::kChromeAppId)
continue;
for (const auto& window_iter : iter.second) {
const std::unique_ptr<app_restore::AppRestoreData>& app_restore_data =
window_iter.second;
absl::optional<std::vector<GURL>> urls = app_restore_data->urls;
if (!urls || urls->empty())
continue;
const bool app_type_browser =
app_restore_data->app_type_browser.value_or(false);
const std::string app_name = app_restore_data->app_name.value_or(app_id);
const gfx::Rect current_bounds =
app_restore_data->current_bounds.value_or(gfx::Rect());
Browser::CreateParams create_params =
app_type_browser
? Browser::CreateParams::CreateForApp(app_name,
/*trusted_source=*/true,
current_bounds, profile(),
/*user_gesture=*/false)
: Browser::CreateParams(Browser::TYPE_NORMAL, profile(),
/*user_gesture=*/false);
create_params.restore_id = window_iter.first;
absl::optional<chromeos::WindowStateType> window_state_type(
app_restore_data->window_state_type);
if (window_state_type) {
create_params.initial_show_state =
chromeos::ToWindowShowState(*window_state_type);
}
if (!current_bounds.IsEmpty())
create_params.initial_bounds = current_bounds;
Browser* browser = Browser::Create(create_params);
absl::optional<int32_t> active_tab_index =
app_restore_data->active_tab_index;
for (int i = 0; i < urls->size(); i++) {
chrome::AddTabAt(
browser, urls->at(i), /*index=*/-1,
/*foreground=*/(active_tab_index && i == *active_tab_index));
}
// We need to handle minimized windows separately since unlike other
// window types, it's not shown.
if (window_state_type &&
*window_state_type == chromeos::WindowStateType::kMinimized) {
browser->window()->Minimize();
continue;
}
browser->window()->ShowInactive();
}
}
restore_data()->RemoveApp(extension_misc::kChromeAppId);
}
void DeskTemplateAppLaunchHandler::RecordRestoredAppLaunch(
apps::AppTypeName app_type_name) {
// TODO: Add UMA Histogram.
NOTIMPLEMENTED();
}
void DeskTemplateAppLaunchHandler::ClearRestoreDataClone() {
restore_data_clone_.reset();
}