blob: b917d9690bd0dfc8d5690b8d389c210673fc242f [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/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