| // 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/chromeos/full_restore/app_launch_handler.h" |
| |
| #include <set> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/callback.h" |
| #include "base/command_line.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "chrome/browser/apps/app_service/app_platform_metrics.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/apps/app_service/browser_app_launcher.h" |
| #include "chrome/browser/ash/login/session/user_session_manager.h" |
| #include "chrome/browser/ash/profiles/profile_helper.h" |
| #include "chrome/browser/chromeos/full_restore/arc_window_utils.h" |
| #include "chrome/browser/chromeos/full_restore/full_restore_arc_task_handler.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/common/chrome_switches.h" |
| #include "components/full_restore/app_launch_info.h" |
| #include "components/full_restore/full_restore_read_handler.h" |
| #include "components/full_restore/full_restore_save_handler.h" |
| #include "components/full_restore/full_restore_utils.h" |
| #include "components/full_restore/restore_data.h" |
| #include "components/services/app_service/public/cpp/app_update.h" |
| #include "components/services/app_service/public/mojom/types.mojom.h" |
| #include "extensions/common/constants.h" |
| #include "ui/base/window_open_disposition.h" |
| |
| namespace chromeos { |
| namespace full_restore { |
| |
| namespace { |
| |
| bool g_launch_browser_for_testing = false; |
| |
| constexpr char kRestoredAppLaunchHistogramPrefix[] = "Apps.RestoredAppLaunch"; |
| constexpr char kArcGhostWindowLaunchHistogramPrefix[] = |
| "Apps.ArcGhostWindowLaunch"; |
| |
| // Returns apps::AppTypeName used for metrics. |
| apps::AppTypeName GetHistogrameAppType(apps::mojom::AppType app_type) { |
| switch (app_type) { |
| case apps::mojom::AppType::kUnknown: |
| return apps::AppTypeName::kUnknown; |
| case apps::mojom::AppType::kArc: |
| return apps::AppTypeName::kArc; |
| case apps::mojom::AppType::kBuiltIn: |
| case apps::mojom::AppType::kCrostini: |
| return apps::AppTypeName::kUnknown; |
| case apps::mojom::AppType::kExtension: |
| return apps::AppTypeName::kChromeApp; |
| case apps::mojom::AppType::kWeb: |
| return apps::AppTypeName::kWeb; |
| case apps::mojom::AppType::kMacOs: |
| case apps::mojom::AppType::kPluginVm: |
| case apps::mojom::AppType::kStandaloneBrowser: |
| case apps::mojom::AppType::kRemote: |
| case apps::mojom::AppType::kBorealis: |
| return apps::AppTypeName::kUnknown; |
| case apps::mojom::AppType::kSystemWeb: |
| return apps::AppTypeName::kSystemWeb; |
| } |
| } |
| |
| } // namespace |
| |
| AppLaunchHandler::AppLaunchHandler(Profile* profile) : profile_(profile) { |
| // FullRestoreReadHandler reads the full restore data from the full restore |
| // data file on a background task runner. |
| ::full_restore::FullRestoreReadHandler::GetInstance()->ReadFromFile( |
| profile_->GetPath(), base::BindOnce(&AppLaunchHandler::OnGetRestoreData, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| AppLaunchHandler::~AppLaunchHandler() = default; |
| |
| void AppLaunchHandler::OnAppUpdate(const apps::AppUpdate& update) { |
| // If the restore flag |should_restore_| is false, or the restore data has not |
| // been read yet, or the app is not ready, don't launch the app for the |
| // restoration. |
| if (!should_restore_ || !restore_data_ || !update.ReadinessChanged() || |
| update.Readiness() != apps::mojom::Readiness::kReady) { |
| return; |
| } |
| |
| // If there is no restore data or the launch list for the app is empty, don't |
| // launch the app. |
| const auto& app_id_to_launch_list = restore_data_->app_id_to_launch_list(); |
| if (app_id_to_launch_list.find(update.AppId()) == |
| app_id_to_launch_list.end()) { |
| return; |
| } |
| |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::BindOnce(&AppLaunchHandler::LaunchApp, |
| weak_ptr_factory_.GetWeakPtr(), |
| update.AppType(), update.AppId())); |
| } |
| |
| void AppLaunchHandler::OnAppRegistryCacheWillBeDestroyed( |
| apps::AppRegistryCache* cache) { |
| apps::AppRegistryCache::Observer::Observe(nullptr); |
| } |
| |
| void AppLaunchHandler::LaunchBrowserWhenReady() { |
| if (g_launch_browser_for_testing) { |
| ForceLaunchBrowserForTesting(); |
| return; |
| } |
| |
| // If the restore data has been loaded, and the user has chosen to restore, |
| // launch the browser. |
| if (should_restore_ && restore_data_) { |
| LaunchBrowser(); |
| return; |
| } |
| |
| // If the restore data hasn't been loaded, or the user hasn't chosen to |
| // restore, set should_launch_browser_ as true, and wait the restore data |
| // loaded, and the user selection, then we can launch the browser. |
| should_launch_browser_ = true; |
| } |
| |
| void AppLaunchHandler::SetShouldRestore() { |
| should_restore_ = true; |
| MaybePostRestore(); |
| } |
| |
| void AppLaunchHandler::ForceLaunchBrowserForTesting() { |
| UserSessionManager::GetInstance()->LaunchBrowser(profile_); |
| UserSessionManager::GetInstance()->MaybeLaunchSettings(profile_); |
| } |
| |
| void AppLaunchHandler::OnGetRestoreData( |
| std::unique_ptr<::full_restore::RestoreData> restore_data) { |
| restore_data_ = std::move(restore_data); |
| |
| if (ProfileHelper::Get()->GetUserByProfile(profile_) == |
| user_manager::UserManager::Get()->GetPrimaryUser()) { |
| ::full_restore::FullRestoreSaveHandler::GetInstance() |
| ->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()); |
| } |
| |
| MaybePostRestore(); |
| } |
| |
| void AppLaunchHandler::MaybePostRestore() { |
| // If the restore flag |should_restore_| is not true, or reading the restore |
| // data hasn't finished, don't restore. |
| if (!should_restore_ || !restore_data_) |
| return; |
| |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::BindOnce(&AppLaunchHandler::MaybeRestore, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void AppLaunchHandler::MaybeRestore() { |
| if (should_launch_browser_) { |
| LaunchBrowser(); |
| should_launch_browser_ = false; |
| } |
| |
| // If there is no launch list from the restore data, we don't need to handle |
| // the restoration. |
| const auto& launch_list = restore_data_->app_id_to_launch_list(); |
| if (launch_list.empty()) |
| return; |
| |
| // Observe AppRegistryCache to get the notification when the app is ready. |
| DCHECK( |
| apps::AppServiceProxyFactory::IsAppServiceAvailableForProfile(profile_)); |
| auto* cache = &apps::AppServiceProxyFactory::GetForProfile(profile_) |
| ->AppRegistryCache(); |
| Observe(cache); |
| |
| // Add the app to |app_ids| if there is a launch list from the restore data |
| // for the app. |
| std::set<std::string> app_ids; |
| cache->ForEachApp([&app_ids, &launch_list](const apps::AppUpdate& update) { |
| if (update.Readiness() == apps::mojom::Readiness::kReady && |
| launch_list.find(update.AppId()) != launch_list.end()) { |
| app_ids.insert(update.AppId()); |
| } |
| }); |
| |
| for (const auto& app_id : app_ids) |
| LaunchApp(cache->GetAppType(app_id), app_id); |
| } |
| |
| void AppLaunchHandler::LaunchBrowser() { |
| // If the browser is not launched before reboot, don't launch browser during |
| // the startup phase. |
| const auto& launch_list = restore_data_->app_id_to_launch_list(); |
| if (launch_list.find(extension_misc::kChromeAppId) == launch_list.end()) |
| return; |
| |
| RecordRestoredAppLaunch(apps::AppTypeName::kChromeBrowser); |
| |
| restore_data_->RemoveApp(extension_misc::kChromeAppId); |
| |
| if (profile_->GetLastSessionExitType() == Profile::EXIT_CRASHED) { |
| base::CommandLine::ForCurrentProcess()->AppendSwitch( |
| switches::kHideCrashRestoreBubble); |
| } |
| |
| // Modify the command line to restore browser sessions. |
| base::CommandLine::ForCurrentProcess()->AppendSwitch( |
| switches::kRestoreLastSession); |
| |
| UserSessionManager::GetInstance()->LaunchBrowser(profile_); |
| UserSessionManager::GetInstance()->MaybeLaunchSettings(profile_); |
| } |
| |
| void AppLaunchHandler::LaunchApp(apps::mojom::AppType app_type, |
| const std::string& app_id) { |
| DCHECK(restore_data_); |
| |
| // For the Chrome browser, the browser session restore is used to restore the |
| // web pages, so we don't need to launch the app. |
| if (app_id == extension_misc::kChromeAppId) { |
| return; |
| } |
| |
| const auto it = restore_data_->app_id_to_launch_list().find(app_id); |
| if (it == restore_data_->app_id_to_launch_list().end() || |
| it->second.empty()) { |
| restore_data_->RemoveApp(app_id); |
| return; |
| } |
| |
| switch (app_type) { |
| case apps::mojom::AppType::kArc: |
| LaunchArcApp(app_id, it->second); |
| break; |
| case apps::mojom::AppType::kExtension: |
| ::full_restore::FullRestoreReadHandler::GetInstance() |
| ->SetNextRestoreWindowIdForChromeApp(profile_->GetPath(), app_id); |
| // Deliberately fall through to apps::mojom::AppType::kWeb to launch the |
| // app. |
| FALLTHROUGH; |
| case apps::mojom::AppType::kWeb: |
| case apps::mojom::AppType::kSystemWeb: |
| LaunchSystemWebAppOrChromeApp(app_type, app_id, it->second); |
| break; |
| case apps::mojom::AppType::kBuiltIn: |
| case apps::mojom::AppType::kCrostini: |
| case apps::mojom::AppType::kPluginVm: |
| case apps::mojom::AppType::kUnknown: |
| case apps::mojom::AppType::kMacOs: |
| case apps::mojom::AppType::kStandaloneBrowser: |
| case apps::mojom::AppType::kRemote: |
| case apps::mojom::AppType::kBorealis: |
| NOTREACHED(); |
| break; |
| } |
| restore_data_->RemoveApp(app_id); |
| } |
| |
| void AppLaunchHandler::LaunchSystemWebAppOrChromeApp( |
| apps::mojom::AppType app_type, |
| const std::string& app_id, |
| const ::full_restore::RestoreData::LaunchList& launch_list) { |
| auto* launcher = apps::AppServiceProxyFactory::GetForProfile(profile_) |
| ->BrowserAppLauncher(); |
| if (!launcher) |
| return; |
| |
| for (const auto& it : launch_list) { |
| RecordRestoredAppLaunch(GetHistogrameAppType(app_type)); |
| |
| DCHECK(it.second->container.has_value()); |
| DCHECK(it.second->disposition.has_value()); |
| DCHECK(it.second->display_id.has_value()); |
| apps::mojom::IntentPtr intent; |
| apps::AppLaunchParams params( |
| app_id, |
| static_cast<apps::mojom::LaunchContainer>(it.second->container.value()), |
| static_cast<WindowOpenDisposition>(it.second->disposition.value()), |
| apps::mojom::AppLaunchSource::kSourceChromeInternal, |
| it.second->display_id.value(), |
| it.second->file_paths.has_value() ? it.second->file_paths.value() |
| : std::vector<base::FilePath>{}, |
| it.second->intent.has_value() ? it.second->intent.value() : intent); |
| params.restore_id = it.first; |
| launcher->LaunchAppWithParams(std::move(params)); |
| } |
| } |
| |
| void AppLaunchHandler::LaunchArcApp( |
| const std::string& app_id, |
| const ::full_restore::RestoreData::LaunchList& launch_list) { |
| auto* proxy = apps::AppServiceProxyFactory::GetForProfile(profile_); |
| DCHECK(proxy); |
| auto* arc_handler = FullRestoreArcTaskHandler::GetForProfile(profile_); |
| |
| for (const auto& it : launch_list) { |
| RecordRestoredAppLaunch(apps::AppTypeName::kArc); |
| |
| DCHECK(it.second->event_flag.has_value()); |
| |
| apps::mojom::WindowInfoPtr window_info = |
| HandleArcWindowInfo(it.second->GetAppWindowInfo()); |
| |
| // Set an ARC session id to find the restore window id based on the new |
| // created ARC task id in FullRestoreReadHandler. |
| int32_t arc_session_id = |
| ::full_restore::FullRestoreReadHandler::GetInstance() |
| ->GetArcSessionId(); |
| window_info->window_id = arc_session_id; |
| ::full_restore::FullRestoreReadHandler::GetInstance() |
| ->SetArcSessionIdForWindowId(arc_session_id, it.first); |
| |
| #if BUILDFLAG(ENABLE_WAYLAND_SERVER) |
| if (!window_info->bounds.is_null() && arc_handler && |
| arc_handler->window_handler()) { |
| RecordArcGhostWindowLaunch(/*is_arc_ghost_window=*/true); |
| arc_handler->window_handler()->LaunchArcGhostWindow( |
| app_id, arc_session_id, it.second.get()); |
| } else { |
| RecordArcGhostWindowLaunch(/*is_arc_ghost_window=*/false); |
| } |
| #endif |
| |
| if (it.second->intent.has_value()) { |
| proxy->LaunchAppWithIntent(app_id, it.second->event_flag.value(), |
| std::move(it.second->intent.value()), |
| apps::mojom::LaunchSource::kFromFullRestore, |
| std::move(window_info)); |
| } else { |
| proxy->Launch(app_id, it.second->event_flag.value(), |
| apps::mojom::LaunchSource::kFromFullRestore, |
| std::move(window_info)); |
| } |
| } |
| } |
| |
| void AppLaunchHandler::RecordRestoredAppLaunch( |
| apps::AppTypeName app_type_name) { |
| base::UmaHistogramEnumeration(kRestoredAppLaunchHistogramPrefix, |
| app_type_name); |
| } |
| |
| void AppLaunchHandler::RecordArcGhostWindowLaunch(bool is_arc_ghost_window) { |
| base::UmaHistogramBoolean(kArcGhostWindowLaunchHistogramPrefix, |
| is_arc_ghost_window); |
| } |
| |
| ScopedLaunchBrowserForTesting::ScopedLaunchBrowserForTesting() { |
| g_launch_browser_for_testing = true; |
| } |
| |
| ScopedLaunchBrowserForTesting::~ScopedLaunchBrowserForTesting() { |
| g_launch_browser_for_testing = false; |
| } |
| |
| } // namespace full_restore |
| } // namespace chromeos |