| // 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/ash/full_restore/arc_app_launch_handler.h" |
| |
| #include <utility> |
| #include <vector> |
| |
| #include "ash/shell.h" |
| #include "base/bind.h" |
| #include "base/callback.h" |
| #include "base/containers/contains.h" |
| #include "base/cpu.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/launch_utils.h" |
| #include "chrome/browser/ash/arc/arc_util.h" |
| #include "chrome/browser/ash/full_restore/arc_window_handler.h" |
| #include "chrome/browser/ash/full_restore/arc_window_utils.h" |
| #include "chrome/browser/ash/full_restore/full_restore_app_launch_handler.h" |
| #include "chrome/browser/ash/full_restore/full_restore_arc_task_handler.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/app_list/arc/arc_app_list_prefs.h" |
| #include "chrome/browser/ui/ash/shelf/arc_shelf_spinner_item_controller.h" |
| #include "chrome/browser/ui/ash/shelf/chrome_shelf_controller.h" |
| #include "chrome/browser/ui/ash/shelf/shelf_spinner_controller.h" |
| #include "chromeos/services/cros_healthd/public/cpp/service_connection.h" |
| #include "chromeos/services/cros_healthd/public/mojom/cros_healthd_probe.mojom.h" |
| #include "components/arc/arc_util.h" |
| #include "components/arc/metrics/arc_metrics_constants.h" |
| #include "components/full_restore/app_launch_info.h" |
| #include "components/full_restore/full_restore_read_handler.h" |
| #include "components/full_restore/full_restore_utils.h" |
| #include "components/full_restore/restore_data.h" |
| #include "components/services/app_service/public/cpp/types_util.h" |
| #include "components/services/app_service/public/mojom/types.mojom.h" |
| #include "ui/display/display.h" |
| #include "ui/wm/public/activation_client.h" |
| |
| namespace { |
| |
| // If the app launching condition doesn't match, e.g. the app is not ready, |
| // and after checking `kMaxCheckingNum` times, there is no improvement, move to |
| // the next window to launch. |
| constexpr int kMaxCheckingNum = 3; |
| |
| // Time interval between each checking for the app launching condition, e.g. the |
| // memory pressure level, or whether the app is ready. |
| constexpr base::TimeDelta kAppLaunchCheckingDelay = |
| base::TimeDelta::FromSeconds(1); |
| |
| // Delay between each app launching. |
| constexpr base::TimeDelta kAppLaunchDelay = base::TimeDelta::FromSeconds(3); |
| |
| constexpr int kCpuUsageRefreshIntervalInSeconds = 1; |
| |
| // Count CPU usage by average on last 6 seconds. |
| constexpr int kCpuUsageCountWindowLength = |
| 6 * kCpuUsageRefreshIntervalInSeconds; |
| |
| // Restrict ARC app launch if CPU usage over threshold. |
| constexpr int kCpuUsageThreshold = 90; |
| |
| // Apply CPU usage restrict if and only if the CPU cores not over |
| // |kCpuRestrictCoresCondition|. |
| constexpr int kCpuRestrictCoresCondition = 2; |
| |
| constexpr char kRestoredArcAppResultHistogram[] = "Apps.RestoreArcAppsResult"; |
| |
| constexpr char kArcGhostWindowLaunchHistogram[] = "Apps.ArcGhostWindowLaunch"; |
| |
| constexpr char kRestoreArcAppStates[] = "Apps.RestoreArcAppStates"; |
| |
| } // namespace |
| |
| namespace ash { |
| namespace full_restore { |
| |
| ArcAppLaunchHandler::ArcAppLaunchHandler() { |
| if (aura::Env::HasInstance()) |
| env_observer_.Observe(aura::Env::GetInstance()); |
| |
| if (ash::Shell::HasInstance() && ash::Shell::Get()->GetPrimaryRootWindow()) { |
| auto* activation_client = |
| wm::GetActivationClient(ash::Shell::Get()->GetPrimaryRootWindow()); |
| if (activation_client) |
| activation_client->AddObserver(this); |
| } |
| |
| // Get CPU cores. |
| base::CPU::TimeInState state; |
| if (base::CPU::GetTimeInState(state) && |
| state.size() <= kCpuRestrictCoresCondition) { |
| should_apply_cpu_restirction_ = true; |
| } |
| } |
| |
| ArcAppLaunchHandler::~ArcAppLaunchHandler() { |
| if (ash::Shell::HasInstance() && ash::Shell::Get()->GetPrimaryRootWindow()) { |
| auto* activation_client = |
| wm::GetActivationClient(ash::Shell::Get()->GetPrimaryRootWindow()); |
| if (activation_client) |
| activation_client->RemoveObserver(this); |
| } |
| } |
| |
| void ArcAppLaunchHandler::RestoreArcApps( |
| FullRestoreAppLaunchHandler* app_launch_handler) { |
| DCHECK(app_launch_handler); |
| handler_ = app_launch_handler; |
| |
| if (!arc::IsArcPlayStoreEnabledForProfile(handler_->profile_)) |
| return; |
| |
| DCHECK(apps::AppServiceProxyFactory::IsAppServiceAvailableForProfile( |
| handler_->profile_)); |
| |
| LoadRestoreData(); |
| if (app_ids_.empty()) { |
| base::UmaHistogramCounts100(kRestoredAppWindowCountHistogram, 0); |
| return; |
| } |
| |
| window_handler_ = FullRestoreArcTaskHandler::GetForProfile(handler_->profile_) |
| ->window_handler(); |
| |
| apps::AppRegistryCache& cache = |
| apps::AppServiceProxyFactory::GetForProfile(handler_->profile_) |
| ->AppRegistryCache(); |
| |
| // Observe AppRegistryCache to get the notification when the app is ready. |
| if (!app_registry_cache_observer_.IsObserving()) |
| app_registry_cache_observer_.Observe(&cache); |
| |
| if (is_shelf_ready_) |
| PrepareLaunchApps(); |
| |
| if (is_app_connection_ready_) |
| OnAppConnectionReady(); |
| } |
| |
| void ArcAppLaunchHandler::OnAppUpdate(const apps::AppUpdate& update) { |
| if (!update.ReadinessChanged() || |
| update.AppType() != apps::mojom::AppType::kArc) { |
| return; |
| } |
| |
| if (!apps_util::IsInstalled(update.Readiness())) { |
| RemoveWindowsForApp(update.AppId()); |
| return; |
| } |
| |
| // If the app is not ready, don't launch the app for the restoration. |
| if (update.Readiness() != apps::mojom::Readiness::kReady) |
| return; |
| |
| if (is_shelf_ready_ && base::Contains(app_ids_, update.AppId())) { |
| AddWindows(update.AppId()); |
| PrepareAppLaunching(update.AppId()); |
| } |
| } |
| |
| void ArcAppLaunchHandler::OnAppRegistryCacheWillBeDestroyed( |
| apps::AppRegistryCache* cache) { |
| apps::AppRegistryCache::Observer::Observe(nullptr); |
| } |
| |
| void ArcAppLaunchHandler::OnAppConnectionReady() { |
| is_app_connection_ready_ = true; |
| |
| if (!HasRestoreData()) |
| return; |
| |
| base::UmaHistogramCounts100(kRestoredAppWindowCountHistogram, |
| windows_.size() + no_stack_windows_.size()); |
| |
| // Receive the memory pressure level. |
| if (chromeos::ResourcedClient::Get() && |
| !resourced_client_observer_.IsObserving()) { |
| resourced_client_observer_.Observe(chromeos::ResourcedClient::Get()); |
| } |
| |
| // Receive the system CPU usage rate. |
| if (!probe_service_ || !probe_service_.is_connected()) { |
| cros_healthd::ServiceConnection::GetInstance()->GetProbeService( |
| probe_service_.BindNewPipeAndPassReceiver()); |
| probe_service_.set_disconnect_handler( |
| base::BindOnce(&ArcAppLaunchHandler::OnProbeServiceDisconnect, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| StartCpuUsageCount(); |
| |
| if (!app_launch_timer_) { |
| app_launch_timer_ = std::make_unique<base::RepeatingTimer>(); |
| MaybeReStartTimer(kAppLaunchCheckingDelay); |
| } |
| |
| if (!stop_restore_timer_) { |
| stop_restore_timer_ = std::make_unique<base::OneShotTimer>(); |
| stop_restore_timer_->Start(FROM_HERE, kStopRestoreDelay, |
| base::BindOnce(&ArcAppLaunchHandler::StopRestore, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| } |
| |
| void ArcAppLaunchHandler::OnShelfReady() { |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::BindOnce(&ArcAppLaunchHandler::PrepareLaunchApps, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void ArcAppLaunchHandler::OnArcPlayStoreEnabledChanged(bool enabled) { |
| if (enabled) |
| return; |
| |
| StopRestore(); |
| |
| #if BUILDFLAG(ENABLE_WAYLAND_SERVER) |
| if (window_handler_) { |
| std::set<int32_t> session_ids; |
| for (const auto& it : session_id_to_window_id_) |
| session_ids.insert(it.first); |
| for (auto session_id : session_ids) |
| window_handler_->CloseWindow(session_id); |
| } |
| #endif |
| |
| app_ids_.clear(); |
| windows_.clear(); |
| no_stack_windows_.clear(); |
| } |
| |
| void ArcAppLaunchHandler::LaunchApp(const std::string& app_id) { |
| if (!IsAppReady(app_id)) |
| return; |
| |
| DCHECK(handler_); |
| const auto it = handler_->restore_data_->app_id_to_launch_list().find(app_id); |
| if (it == handler_->restore_data_->app_id_to_launch_list().end()) |
| return; |
| |
| if (it->second.empty()) { |
| handler_->restore_data_->RemoveApp(app_id); |
| return; |
| } |
| |
| for (const auto& data_it : it->second) |
| LaunchApp(app_id, data_it.first); |
| |
| RemoveWindowsForApp(app_id); |
| } |
| |
| void ArcAppLaunchHandler::OnWindowActivated( |
| ::wm::ActivationChangeObserver::ActivationReason reason, |
| aura::Window* new_active, |
| aura::Window* old_active) { |
| const auto session_id = arc::GetWindowSessionId(new_active); |
| if (!session_id.has_value()) |
| return; |
| |
| const std::string* arc_app_id = |
| new_active->GetProperty(::full_restore::kAppIdKey); |
| if (!arc_app_id || arc_app_id->empty() || !IsAppReady(*arc_app_id)) |
| return; |
| |
| auto it = session_id_to_window_id_.find(session_id.value()); |
| if (it == session_id_to_window_id_.end()) |
| return; |
| |
| RemoveWindow(*arc_app_id, it->second); |
| LaunchApp(*arc_app_id, it->second); |
| } |
| |
| void ArcAppLaunchHandler::OnWindowInitialized(aura::Window* window) { |
| // An app window has type WINDOW_TYPE_NORMAL, a WindowDelegate and |
| // is a top level views widget. Tooltips, menus, and other kinds of transient |
| // windows that can't activate are filtered out. |
| if (window->GetType() != aura::client::WINDOW_TYPE_NORMAL || |
| !window->delegate()) |
| return; |
| views::Widget* widget = views::Widget::GetWidgetForNativeWindow(window); |
| if (!widget || !widget->is_top_level() || |
| !arc::GetWindowSessionId(window).has_value()) { |
| return; |
| } |
| |
| observed_windows_.AddObservation(window); |
| } |
| |
| void ArcAppLaunchHandler::OnWindowDestroying(aura::Window* window) { |
| DCHECK(observed_windows_.IsObservingSource(window)); |
| observed_windows_.RemoveObservation(window); |
| |
| const auto session_id = arc::GetWindowSessionId(window); |
| if (!session_id.has_value()) |
| return; |
| |
| auto it = session_id_to_window_id_.find(session_id.value()); |
| if (it == session_id_to_window_id_.end()) |
| return; |
| |
| auto window_id = it->second; |
| session_id_to_window_id_.erase(session_id.value()); |
| |
| const std::string* arc_app_id = |
| window->GetProperty(::full_restore::kAppIdKey); |
| if (!arc_app_id || arc_app_id->empty()) |
| return; |
| |
| RemoveWindow(*arc_app_id, window_id); |
| } |
| |
| void ArcAppLaunchHandler::LoadRestoreData() { |
| DCHECK(handler_); |
| for (const auto& it : handler_->restore_data_->app_id_to_launch_list()) |
| app_ids_.insert(it.first); |
| } |
| |
| void ArcAppLaunchHandler::AddWindows(const std::string& app_id) { |
| DCHECK(handler_); |
| auto it = handler_->restore_data_->app_id_to_launch_list().find(app_id); |
| for (const auto& data_it : it->second) { |
| if (data_it.second->activation_index.has_value()) { |
| windows_[data_it.second->activation_index.value()] = {app_id, |
| data_it.first}; |
| } else { |
| no_stack_windows_.push_back({app_id, data_it.first}); |
| } |
| } |
| } |
| |
| void ArcAppLaunchHandler::PrepareLaunchApps() { |
| is_shelf_ready_ = true; |
| |
| if (app_ids_.empty()) |
| return; |
| |
| apps::AppRegistryCache& cache = |
| apps::AppServiceProxyFactory::GetForProfile(handler_->profile_) |
| ->AppRegistryCache(); |
| |
| // 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, this](const apps::AppUpdate& update) { |
| if (update.Readiness() == apps::mojom::Readiness::kReady && |
| update.AppType() == apps::mojom::AppType::kArc && |
| base::Contains(app_ids_, update.AppId())) { |
| app_ids.insert(update.AppId()); |
| } |
| }); |
| |
| for (const auto& app_id : app_ids) { |
| AddWindows(app_id); |
| PrepareAppLaunching(app_id); |
| } |
| } |
| |
| void ArcAppLaunchHandler::PrepareAppLaunching(const std::string& app_id) { |
| DCHECK(handler_); |
| app_ids_.erase(app_id); |
| |
| const auto it = handler_->restore_data_->app_id_to_launch_list().find(app_id); |
| if (it == handler_->restore_data_->app_id_to_launch_list().end()) |
| return; |
| |
| if (it->second.empty()) { |
| handler_->restore_data_->RemoveApp(app_id); |
| return; |
| } |
| |
| for (const auto& data_it : it->second) { |
| handler_->RecordRestoredAppLaunch(apps::AppTypeName::kArc); |
| |
| DCHECK(data_it.second->event_flag.has_value()); |
| |
| // 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(); |
| ::full_restore::FullRestoreReadHandler::GetInstance() |
| ->SetArcSessionIdForWindowId(arc_session_id, data_it.first); |
| window_id_to_session_id_[data_it.first] = arc_session_id; |
| session_id_to_window_id_[arc_session_id] = data_it.first; |
| |
| bool launch_ghost_window = false; |
| #if BUILDFLAG(ENABLE_WAYLAND_SERVER) |
| if (window_handler_ && (data_it.second->bounds_in_root.has_value() || |
| data_it.second->current_bounds.has_value())) { |
| RecordArcGhostWindowLaunch(/*is_arc_ghost_window=*/true); |
| window_handler_->LaunchArcGhostWindow(app_id, arc_session_id, |
| data_it.second.get()); |
| launch_ghost_window = true; |
| } else { |
| RecordArcGhostWindowLaunch(/*is_arc_ghost_window=*/false); |
| } |
| #endif |
| |
| const auto& file_path = handler_->profile_->GetPath(); |
| int32_t event_flags = data_it.second->event_flag.value(); |
| int64_t display_id = data_it.second->display_id.has_value() |
| ? data_it.second->display_id.value() |
| : display::kInvalidDisplayId; |
| if (data_it.second->intent.has_value()) { |
| DCHECK(data_it.second->intent.value()); |
| ::full_restore::SaveAppLaunchInfo( |
| file_path, std::make_unique<::full_restore::AppLaunchInfo>( |
| app_id, event_flags, data_it.second->intent->Clone(), |
| arc_session_id, display_id)); |
| } else { |
| ::full_restore::SaveAppLaunchInfo( |
| file_path, std::make_unique<::full_restore::AppLaunchInfo>( |
| app_id, event_flags, arc_session_id, display_id)); |
| } |
| |
| if (launch_ghost_window) |
| continue; |
| |
| ChromeShelfController* chrome_controller = |
| ChromeShelfController::instance(); |
| // chrome_controller may be null in tests. |
| if (chrome_controller) { |
| apps::mojom::WindowInfoPtr window_info = apps::mojom::WindowInfo::New(); |
| window_info->window_id = arc_session_id; |
| chrome_controller->GetShelfSpinnerController()->AddSpinnerToShelf( |
| app_id, std::make_unique<ArcShelfSpinnerItemController>( |
| app_id, data_it.second->event_flag.value(), |
| arc::UserInteractionType::APP_STARTED_FROM_FULL_RESTORE, |
| apps::MakeArcWindowInfo(std::move(window_info)))); |
| } |
| } |
| } |
| |
| void ArcAppLaunchHandler::OnMemoryPressure( |
| chromeos::ResourcedClient::PressureLevel level, |
| uint64_t reclaim_target_kb) { |
| pressure_level_ = level; |
| } |
| |
| bool ArcAppLaunchHandler::HasRestoreData() { |
| return !(windows_.empty() && no_stack_windows_.empty() && |
| pending_windows_.empty()); |
| } |
| |
| bool ArcAppLaunchHandler::CanLaunchApp() { |
| // Checks CPU usage limiting and memory pressure, make sure it can |
| // be recorded for UMA statistic data. |
| bool isUnderCPUUsageLimiting = IsUnderCPUUsageLimiting(); |
| if (isUnderCPUUsageLimiting) |
| was_cpu_usage_limited_ = true; |
| bool isUnderMemoryPressure = IsUnderMemoryPressure(); |
| if (isUnderMemoryPressure) |
| was_memory_pressured_ = true; |
| |
| return !isUnderCPUUsageLimiting && !isUnderMemoryPressure; |
| } |
| |
| bool ArcAppLaunchHandler::IsUnderMemoryPressure() { |
| switch (pressure_level_) { |
| case chromeos::ResourcedClient::PressureLevel::NONE: |
| return false; |
| case chromeos::ResourcedClient::PressureLevel::MODERATE: |
| case chromeos::ResourcedClient::PressureLevel::CRITICAL: { |
| LOG(WARNING) |
| << "Stop restoring Arc apps due to memory pressure: " |
| << (pressure_level_ == |
| chromeos::ResourcedClient::PressureLevel::MODERATE |
| ? "MODERATE" |
| : "CRITICAL"); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| bool ArcAppLaunchHandler::IsUnderCPUUsageLimiting() { |
| if (should_apply_cpu_restirction_) { |
| int cpu_usage_rate = GetCpuUsageRate(); |
| if (cpu_usage_rate >= kCpuUsageThreshold) { |
| LOG(WARNING) << "CPU usage rate is too high to restore Arc apps: " |
| << cpu_usage_rate; |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| bool ArcAppLaunchHandler::IsAppReady(const std::string& app_id) { |
| ArcAppListPrefs* prefs = ArcAppListPrefs::Get(handler_->profile_); |
| if (!prefs) |
| return false; |
| |
| std::unique_ptr<ArcAppListPrefs::AppInfo> app_info = prefs->GetApp(app_id); |
| if (!app_info || app_info->suspended || !app_info->ready) |
| return false; |
| |
| return true; |
| } |
| |
| void ArcAppLaunchHandler::MaybeLaunchApp() { |
| // Check CanLaunchApp() first for record the system states. |
| if (CanLaunchApp() && !first_run_) { |
| MaybeReStartTimer(kAppLaunchCheckingDelay); |
| return; |
| } |
| |
| for (auto it = pending_windows_.begin(); it != pending_windows_.end(); ++it) { |
| if (IsAppReady(it->app_id)) { |
| LaunchApp(it->app_id, it->window_id); |
| pending_windows_.erase(it); |
| MaybeReStartTimer(kAppLaunchDelay); |
| return; |
| } |
| } |
| |
| if (!windows_.empty()) { |
| auto it = windows_.begin(); |
| if (IsAppReady(it->second.app_id)) { |
| launch_count_ = 0; |
| LaunchApp(it->second.app_id, it->second.window_id); |
| windows_.erase(it); |
| MaybeReStartTimer(kAppLaunchDelay); |
| } else { |
| ++launch_count_; |
| if (launch_count_ >= kMaxCheckingNum) { |
| pending_windows_.push_back({it->second.app_id, it->second.window_id}); |
| windows_.erase(it); |
| launch_count_ = 0; |
| } else if (launch_count_ == 1) { |
| MaybeReStartTimer(kAppLaunchCheckingDelay); |
| } |
| } |
| return; |
| } |
| |
| for (auto it = no_stack_windows_.begin(); it != no_stack_windows_.end(); |
| ++it) { |
| if (IsAppReady(it->app_id)) { |
| LaunchApp(it->app_id, it->window_id); |
| no_stack_windows_.erase(it); |
| MaybeReStartTimer(kAppLaunchDelay); |
| return; |
| } |
| } |
| } |
| |
| void ArcAppLaunchHandler::LaunchApp(const std::string& app_id, |
| int32_t window_id) { |
| DCHECK(handler_); |
| |
| const auto it = handler_->restore_data_->app_id_to_launch_list().find(app_id); |
| if (it == handler_->restore_data_->app_id_to_launch_list().end()) |
| return; |
| |
| if (it->second.empty()) { |
| handler_->restore_data_->RemoveApp(app_id); |
| return; |
| } |
| |
| const auto data_it = it->second.find(window_id); |
| if (data_it == it->second.end()) |
| return; |
| |
| first_run_ = false; |
| |
| auto* proxy = apps::AppServiceProxyFactory::GetForProfile(handler_->profile_); |
| DCHECK(proxy); |
| |
| DCHECK(data_it->second->event_flag.has_value()); |
| |
| apps::mojom::WindowInfoPtr window_info = |
| HandleArcWindowInfo(data_it->second->GetAppWindowInfo()); |
| const auto window_it = window_id_to_session_id_.find(window_id); |
| if (window_it != window_id_to_session_id_.end()) { |
| window_info->window_id = window_it->second; |
| window_id_to_session_id_.erase(window_it); |
| } else { |
| // 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, window_id); |
| window_id_to_session_id_[window_id] = arc_session_id; |
| } |
| |
| if (data_it->second->intent.has_value()) { |
| DCHECK(data_it->second->intent.value()); |
| proxy->LaunchAppWithIntent(app_id, data_it->second->event_flag.value(), |
| data_it->second->intent->Clone(), |
| apps::mojom::LaunchSource::kFromFullRestore, |
| std::move(window_info)); |
| } else { |
| proxy->Launch(app_id, data_it->second->event_flag.value(), |
| apps::mojom::LaunchSource::kFromFullRestore, |
| std::move(window_info)); |
| } |
| |
| if (!HasRestoreData()) |
| StopRestore(); |
| } |
| |
| void ArcAppLaunchHandler::RemoveWindowsForApp(const std::string& app_id) { |
| app_ids_.erase(app_id); |
| std::vector<int32_t> window_stacks; |
| for (auto& it : windows_) { |
| if (it.second.app_id == app_id) |
| window_stacks.push_back(it.first); |
| } |
| |
| for (auto window_stack : window_stacks) |
| windows_.erase(window_stack); |
| |
| std::vector<std::list<WindowInfo>::iterator> windows; |
| for (auto it = no_stack_windows_.begin(); it != no_stack_windows_.end(); |
| ++it) { |
| if (it->app_id == app_id) |
| windows.push_back(it); |
| } |
| |
| for (auto it : windows) |
| no_stack_windows_.erase(it); |
| windows.clear(); |
| |
| for (auto it = pending_windows_.begin(); it != pending_windows_.end(); ++it) { |
| if (it->app_id == app_id) |
| windows.push_back(it); |
| } |
| |
| for (auto it : windows) |
| pending_windows_.erase(it); |
| } |
| |
| void ArcAppLaunchHandler::RemoveWindow(const std::string& app_id, |
| int32_t window_id) { |
| for (auto& it : windows_) { |
| if (it.second.app_id == app_id && it.second.window_id == window_id) { |
| windows_.erase(it.first); |
| return; |
| } |
| } |
| |
| for (auto it = no_stack_windows_.begin(); it != no_stack_windows_.end(); |
| ++it) { |
| if (it->app_id == app_id && it->window_id == window_id) { |
| no_stack_windows_.erase(it); |
| return; |
| } |
| } |
| |
| for (auto it = pending_windows_.begin(); it != pending_windows_.end(); ++it) { |
| if (it->app_id == app_id && it->window_id == window_id) { |
| pending_windows_.erase(it); |
| return; |
| } |
| } |
| } |
| |
| void ArcAppLaunchHandler::MaybeReStartTimer(const base::TimeDelta& delay) { |
| DCHECK(app_launch_timer_); |
| |
| // If there is no window to be launched, stop the timer. |
| if (!HasRestoreData()) { |
| StopRestore(); |
| return; |
| } |
| |
| if (current_delay_ == delay) |
| return; |
| |
| // If the delay is changed, restart the timer. |
| if (app_launch_timer_->IsRunning()) |
| app_launch_timer_->Stop(); |
| |
| current_delay_ = delay; |
| |
| app_launch_timer_->Start( |
| FROM_HERE, current_delay_, |
| base::BindRepeating(&ArcAppLaunchHandler::MaybeLaunchApp, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void ArcAppLaunchHandler::StopRestore() { |
| if (app_launch_timer_ && app_launch_timer_->IsRunning()) |
| app_launch_timer_->Stop(); |
| app_launch_timer_.reset(); |
| |
| if (stop_restore_timer_ && stop_restore_timer_->IsRunning()) |
| stop_restore_timer_->Stop(); |
| stop_restore_timer_.reset(); |
| |
| StopCpuUsageCount(); |
| |
| RecordRestoreResult(); |
| } |
| |
| int ArcAppLaunchHandler::GetCpuUsageRate() { |
| uint64_t idle = 0, sum = 0; |
| for (const auto& tick : cpu_tick_window_) { |
| idle += tick.idle_time; |
| sum += tick.idle_time + tick.used_time; |
| } |
| |
| // Convert to xx% percentage. |
| return sum ? int(100 * (sum - idle) / sum) : 0; |
| } |
| |
| void ArcAppLaunchHandler::StartCpuUsageCount() { |
| cpu_tick_count_timer_.Start( |
| FROM_HERE, |
| base::TimeDelta::FromSeconds(kCpuUsageRefreshIntervalInSeconds), |
| base::BindRepeating(&ArcAppLaunchHandler::UpdateCpuUsage, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void ArcAppLaunchHandler::StopCpuUsageCount() { |
| cpu_tick_count_timer_.Stop(); |
| } |
| |
| void ArcAppLaunchHandler::UpdateCpuUsage() { |
| if (!probe_service_.is_connected()) |
| return; |
| probe_service_->ProbeTelemetryInfo( |
| {chromeos::cros_healthd::mojom::ProbeCategoryEnum::kCpu}, |
| base::BindOnce(&ArcAppLaunchHandler::OnCpuUsageUpdated, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void ArcAppLaunchHandler::OnCpuUsageUpdated( |
| chromeos::cros_healthd::mojom::TelemetryInfoPtr info_ptr) { |
| CpuTick tick; |
| // For simplicity, assume that device has only one physical CPU. |
| for (const auto& logical_cpu : |
| info_ptr->cpu_result->get_cpu_info()->physical_cpus[0]->logical_cpus) { |
| tick.idle_time += logical_cpu->idle_time_user_hz; |
| tick.used_time += |
| logical_cpu->user_time_user_hz + logical_cpu->system_time_user_hz; |
| } |
| |
| if (last_cpu_tick_.has_value()) |
| cpu_tick_window_.push_back(tick - last_cpu_tick_.value()); |
| last_cpu_tick_ = tick; |
| |
| // Sliding window for CPU usage count. |
| while (cpu_tick_window_.size() > kCpuUsageCountWindowLength) |
| cpu_tick_window_.pop_front(); |
| } |
| |
| void ArcAppLaunchHandler::OnProbeServiceDisconnect() { |
| probe_service_.reset(); |
| } |
| |
| void ArcAppLaunchHandler::RecordArcGhostWindowLaunch(bool is_arc_ghost_window) { |
| base::UmaHistogramBoolean(kArcGhostWindowLaunchHistogram, |
| is_arc_ghost_window); |
| } |
| |
| void ArcAppLaunchHandler::RecordRestoreResult() { |
| bool isFinished = !HasRestoreData(); |
| |
| base::UmaHistogramEnumeration( |
| kRestoredArcAppResultHistogram, |
| isFinished ? RestoreResult::kFinish : RestoreResult::kNotFinish); |
| |
| ArcRestoreState restore_state = ArcRestoreState::kFailedWithUnknown; |
| if (isFinished) { |
| if (was_cpu_usage_limited_ && was_memory_pressured_) |
| restore_state = |
| ArcRestoreState::kSuccessWithMemoryPressureAndCPUUsageRateLimiting; |
| else if (was_cpu_usage_limited_) |
| restore_state = ArcRestoreState::kSuccessWithCPUUsageRateLimiting; |
| else if (was_memory_pressured_) |
| restore_state = ArcRestoreState::kSuccessWithMemoryPressure; |
| else |
| restore_state = ArcRestoreState::kSuccess; |
| } else { |
| if (was_cpu_usage_limited_ && was_memory_pressured_) |
| restore_state = |
| ArcRestoreState::kFailedWithMemoryPressureAndCPUUsageRateLimiting; |
| else if (was_cpu_usage_limited_) |
| restore_state = ArcRestoreState::kFailedWithCPUUsageRateLimiting; |
| else if (was_memory_pressured_) |
| restore_state = ArcRestoreState::kFailedWithMemoryPressure; |
| // For other cases, mark the failed state as "unknown". |
| } |
| |
| base::UmaHistogramEnumeration(kRestoreArcAppStates, restore_state); |
| } |
| |
| } // namespace full_restore |
| } // namespace ash |