chromium/chrome/browser/ash/app_restore/arc_app_queue_restore_handler.cc

// 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 "chrome/browser/ash/app_restore/arc_app_queue_restore_handler.h"

#include <list>
#include <utility>
#include <vector>

#include "ash/components/arc/arc_util.h"
#include "ash/components/arc/metrics/arc_metrics_constants.h"
#include "ash/root_window_controller.h"
#include "ash/shell.h"
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/metrics/histogram_functions.h"
#include "base/system/sys_info.h"
#include "base/task/single_thread_task_runner.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/apps/app_service/metrics/app_platform_metrics.h"
#include "chrome/browser/ash/app_list/arc/arc_app_list_prefs.h"
#include "chrome/browser/ash/app_restore/app_restore_arc_task_handler.h"
#include "chrome/browser/ash/app_restore/arc_ghost_window_handler.h"
#include "chrome/browser/ash/app_restore/arc_window_utils.h"
#include "chrome/browser/ash/app_restore/full_restore_app_launch_handler.h"
#include "chrome/browser/ash/arc/arc_util.h"
#include "chrome/browser/ash/arc/session/arc_session_manager.h"
#include "chrome/browser/ash/arc/window_predictor/window_predictor_utils.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/browser_process_platform_part.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/sessions/exit_type_service.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/ash/components/system/scheduler_configuration_manager_base.h"
#include "chromeos/ash/services/cros_healthd/public/cpp/service_connection.h"
#include "chromeos/ash/services/cros_healthd/public/mojom/cros_healthd_probe.mojom.h"
#include "components/app_restore/app_launch_info.h"
#include "components/app_restore/app_restore_utils.h"
#include "components/app_restore/features.h"
#include "components/app_restore/full_restore_utils.h"
#include "components/app_restore/restore_data.h"
#include "components/app_restore/window_properties.h"
#include "components/exo/wm_helper.h"
#include "components/services/app_service/public/cpp/app_launch_util.h"
#include "components/services/app_service/public/cpp/app_types.h"
#include "components/services/app_service/public/cpp/intent.h"
#include "components/services/app_service/public/cpp/types_util.h"
#include "ui/display/display.h"
#include "ui/wm/public/activation_client.h"

namespace ash::app_restore {

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::Seconds(1);

// Delay between each app launching.
constexpr base::TimeDelta kAppLaunchDelay = base::Seconds(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 kRestoreArcAppStatesHistogram[] = "Apps.RestoreArcAppStates";

constexpr char kGhostWindowPopToArcHistogram[] = "Arc.LaunchedWithGhostWindow";

}  // namespace

ArcAppQueueRestoreHandler::ArcAppQueueRestoreHandler() {
  if (aura::Env::HasInstance())
    env_observer_.Observe(aura::Env::GetInstance());

  if (Shell::HasInstance() && Shell::Get()->GetPrimaryRootWindow()) {
    auto* activation_client =
        wm::GetActivationClient(Shell::Get()->GetPrimaryRootWindow());
    if (activation_client)
      activation_client->AddObserver(this);
  }

  auto* manager = GetSchedulerConfigurationManager();
  if (manager) {
    std::optional<std::pair<bool, size_t>> scheduler_configuration =
        manager->GetLastReply();
    if (scheduler_configuration) {
      // Logical CPU core number should consider system HyperThread status.
      should_apply_cpu_restirction_ =
          (base::SysInfo::NumberOfProcessors() -
           scheduler_configuration->second) <= kCpuRestrictCoresCondition;
    } else {
      // If the configuration not exist, add observer to receive configuration
      // update.
      manager->AddObserver(this);
    }
  }
}

ArcAppQueueRestoreHandler::~ArcAppQueueRestoreHandler() {
  if (Shell::HasInstance() && Shell::Get()->GetPrimaryRootWindow()) {
    auto* activation_client =
        wm::GetActivationClient(Shell::Get()->GetPrimaryRootWindow());
    if (activation_client)
      activation_client->RemoveObserver(this);
  }

  auto* manager = GetSchedulerConfigurationManager();
  if (manager)
    manager->RemoveObserver(this);
}

void ArcAppQueueRestoreHandler::RestoreArcApps(
    AppLaunchHandler* 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_ = AppRestoreArcTaskHandler::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 ArcAppQueueRestoreHandler::OnAppConnectionReady() {
  is_app_connection_ready_ = true;

  if (!HasRestoreData())
    return;

  base::UmaHistogramCounts100(kRestoredAppWindowCountHistogram,
                              windows_.size() + no_stack_windows_.size());

  // Receive the memory pressure level.
  if (ResourcedClient::Get() && !resourced_client_observer_.IsObserving())
    resourced_client_observer_.Observe(ResourcedClient::Get());

  // Receive the system CPU usage rate.
  if (!probe_service_ || !probe_service_.is_connected()) {
    cros_healthd::ServiceConnection::GetInstance()->BindProbeService(
        probe_service_.BindNewPipeAndPassReceiver());
    probe_service_.set_disconnect_handler(
        base::BindOnce(&ArcAppQueueRestoreHandler::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, this,
                               &ArcAppQueueRestoreHandler::StopRestore);
  }
}

void ArcAppQueueRestoreHandler::OnShelfReady() {
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(&ArcAppQueueRestoreHandler::PrepareLaunchApps,
                                weak_ptr_factory_.GetWeakPtr()));
}

void ArcAppQueueRestoreHandler::OnArcPlayStoreEnabledChanged(bool enabled) {
  if (enabled)
    return;

  StopRestore();

  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);
  }

  app_ids_.clear();
  windows_.clear();
  no_stack_windows_.clear();
}

void ArcAppQueueRestoreHandler::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;

  const auto& launch_list = it->second;
  if (launch_list.empty()) {
    handler_->restore_data()->RemoveApp(app_id);
    return;
  }

  for (const auto& [window_id, _] : launch_list)
    LaunchAppWindow(app_id, window_id);

  RemoveWindowsForApp(app_id);
}

bool ArcAppQueueRestoreHandler::IsAppPendingRestore(
    const std::string& app_id) const {
  return base::Contains(app_ids_, app_id);
}

void ArcAppQueueRestoreHandler::OnAppUpdate(const apps::AppUpdate& update) {
  if (!update.ReadinessChanged() || update.AppType() != apps::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::Readiness::kReady)
    return;

  if (is_shelf_ready_ && base::Contains(app_ids_, update.AppId())) {
    AddWindows(update.AppId());
    PrepareAppLaunching(update.AppId());
  }
}

void ArcAppQueueRestoreHandler::OnAppRegistryCacheWillBeDestroyed(
    apps::AppRegistryCache* cache) {
  app_registry_cache_observer_.Reset();
}

void ArcAppQueueRestoreHandler::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;

  auto it = session_id_to_window_id_.find(session_id.value());
  if (it == session_id_to_window_id_.end())
    return;

  const std::string* arc_app_id =
      new_active->GetProperty(::app_restore::kAppIdKey);
  if (!arc_app_id || arc_app_id->empty() || !IsAppReady(*arc_app_id))
    return;

  auto window_id = it->second;
  RemoveWindow(*arc_app_id, window_id);
  LaunchAppWindow(*arc_app_id, window_id);
}

void ArcAppQueueRestoreHandler::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 ArcAppQueueRestoreHandler::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(::app_restore::kAppIdKey);
  if (!arc_app_id || arc_app_id->empty())
    return;

  RemoveWindow(*arc_app_id, window_id);
}

void ArcAppQueueRestoreHandler::OnConfigurationSet(bool success,
                                                   size_t num_cores_disabled) {
  // Logical CPU core number should consider system HyperThread status.
  should_apply_cpu_restirction_ =
      (base::SysInfo::NumberOfProcessors() - num_cores_disabled) <=
      kCpuRestrictCoresCondition;
  auto* manager = GetSchedulerConfigurationManager();
  if (manager)
    manager->RemoveObserver(this);
}

void ArcAppQueueRestoreHandler::LoadRestoreData() {
  DCHECK(handler_);
  for (const auto& it : handler_->restore_data()->app_id_to_launch_list())
    app_ids_.insert(it.first);
}

void ArcAppQueueRestoreHandler::AddWindows(const std::string& app_id) {
  DCHECK(handler_);
  auto it = handler_->restore_data()->app_id_to_launch_list().find(app_id);
  DCHECK(it != handler_->restore_data()->app_id_to_launch_list().end());
  const auto& launch_list = it->second;
  for (const auto& [window_id, app_restore_data] : launch_list) {
    if (app_restore_data->window_info.activation_index.has_value()) {
      windows_[app_restore_data->window_info.activation_index.value()] = {
          app_id, window_id};
    } else {
      no_stack_windows_.push_back({app_id, window_id});
    }
  }
}

void ArcAppQueueRestoreHandler::PrepareLaunchApps() {
  is_shelf_ready_ = true;

  // Explicit check if the root window controller initialized. b/321719023
  if (RootWindowController::root_window_controllers().empty()) {
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
        FROM_HERE,
        base::BindOnce(&ArcAppQueueRestoreHandler::PrepareLaunchApps,
                       weak_ptr_factory_.GetWeakPtr()),
        kAppLaunchCheckingDelay);
    return;
  }

  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::Readiness::kReady &&
        update.AppType() == apps::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 ArcAppQueueRestoreHandler::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;

  const auto& launch_list = it->second;
  if (launch_list.empty()) {
    handler_->restore_data()->RemoveApp(app_id);
    return;
  }

  // Activate ARC in case still not active.
  arc::ArcSessionManager::Get()->AllowActivation(
      arc::ArcSessionManager::AllowActivationReason::kRestoreApps);

  for (const auto& [window_id, app_restore_data] : launch_list) {
    handler_->RecordRestoredAppLaunch(apps::AppTypeName::kArc);

    DCHECK(app_restore_data->event_flag.has_value());

    // Set an ARC session id to find the restore window id based on the newly
    // created ARC task id. Note that the desk template launch ID must be set
    // first, if available.
    const int32_t arc_session_id = ::app_restore::CreateArcSessionId();
    if (desk_template_launch_id_ != 0) {
      ::app_restore::SetDeskTemplateLaunchIdForArcSessionId(
          arc_session_id, desk_template_launch_id_);
    }
    ::app_restore::SetArcSessionIdForWindowId(arc_session_id, window_id);
    window_id_to_session_id_[window_id] = arc_session_id;
    session_id_to_window_id_[arc_session_id] = window_id;

    bool launch_ghost_window = false;
    if (window_handler_ &&
        arc::CanLaunchGhostWindowByRestoreData(*app_restore_data) &&
        window_handler_->LaunchArcGhostWindow(app_id, arc_session_id,
                                              app_restore_data.get())) {
      launch_ghost_window = true;

      // Update ARC app states immediately, since the app states may already
      // changed from original state.
      ArcAppListPrefs* prefs = ArcAppListPrefs::Get(handler_->profile());
      if (prefs) {
        auto app_info = prefs->GetApp(app_id);
        if (app_info)
          window_handler_->OnAppStatesUpdate(app_id, app_info->ready,
                                             app_info->need_fixup);
      }
    }
    RecordArcGhostWindowLaunch(launch_ghost_window);

    const auto& file_path = handler_->profile()->GetPath();
    int32_t event_flags = app_restore_data->event_flag.value();
    int64_t display_id = app_restore_data->display_id.has_value()
                             ? app_restore_data->display_id.value()
                             : display::kInvalidDisplayId;
    if (app_restore_data->intent) {
      ::full_restore::SaveAppLaunchInfo(
          file_path, std::make_unique<::app_restore::AppLaunchInfo>(
                         app_id, event_flags, app_restore_data->intent->Clone(),
                         arc_session_id, display_id));
    } else {
      ::full_restore::SaveAppLaunchInfo(
          file_path, std::make_unique<::app_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::WindowInfoPtr window_info = std::make_unique<apps::WindowInfo>();
      window_info->window_id = arc_session_id;
      chrome_controller->GetShelfSpinnerController()->AddSpinnerToShelf(
          app_id, std::make_unique<ArcShelfSpinnerItemController>(
                      app_id, nullptr, app_restore_data->event_flag.value(),
                      arc::UserInteractionType::APP_STARTED_FROM_FULL_RESTORE,
                      apps::MakeArcWindowInfo(std::move(window_info))));
    }
  }
}

void ArcAppQueueRestoreHandler::OnMemoryPressure(
    ResourcedClient::PressureLevel level,
    memory_pressure::ReclaimTarget) {
  pressure_level_ = level;
}

bool ArcAppQueueRestoreHandler::HasRestoreData() {
  return !(windows_.empty() && no_stack_windows_.empty() &&
           pending_windows_.empty());
}

bool ArcAppQueueRestoreHandler::CanLaunchApp() {
  // Checks CPU usage limiting and memory pressure, make sure it can
  // be recorded for UMA statistic data.
  bool is_under_cpu_usage_limiting = IsUnderCPUUsageLimiting();
  if (is_under_cpu_usage_limiting)
    was_cpu_usage_limited_ = true;
  bool is_under_memory_pressure = IsUnderMemoryPressure();
  if (is_under_memory_pressure)
    was_memory_pressured_ = true;
  bool is_root_window_controller_initialized =
      !RootWindowController::root_window_controllers().empty();
  return !is_under_cpu_usage_limiting && !is_under_memory_pressure &&
         is_root_window_controller_initialized;
}

bool ArcAppQueueRestoreHandler::IsUnderMemoryPressure() {
  switch (pressure_level_) {
    case ResourcedClient::PressureLevel::NONE:
      return false;
    case ResourcedClient::PressureLevel::MODERATE:
    case ResourcedClient::PressureLevel::CRITICAL: {
      LOG(WARNING) << "Stop restoring Arc apps due to memory pressure: "
                   << (pressure_level_ ==
                               ResourcedClient::PressureLevel::MODERATE
                           ? "MODERATE"
                           : "CRITICAL");
      return true;
    }
  }
  return false;
}

bool ArcAppQueueRestoreHandler::IsUnderCPUUsageLimiting() {
  // If the CPU HyperThread info has not updated from CrOS, enable cpu usage
  // limiting as default behavior.
  if (should_apply_cpu_restirction_.value_or(true)) {
    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 ArcAppQueueRestoreHandler::IsAppReady(const std::string& app_id) {
  DCHECK(handler_);
  ArcAppListPrefs* prefs = ArcAppListPrefs::Get(handler_->profile());
  if (!prefs)
    return false;

  return prefs->IsAbleToBeLaunched(app_id);
}

void ArcAppQueueRestoreHandler::MaybeLaunchApp() {
  // Check CanLaunchApp() first for record the system states.
  if (!CanLaunchApp() && !first_run_) {
    MaybeReStartTimer(kAppLaunchCheckingDelay);
    return;
  }

  const auto find_ready_window = [this](const std::list<WindowInfo>& l) {
    return std::find_if(l.begin(), l.end(), [this](const WindowInfo& info) {
      return IsAppReady(info.app_id);
    });
  };

  if (const auto it = find_ready_window(pending_windows_);
      it != pending_windows_.end()) {
    const WindowInfo info = *it;
    LaunchAppWindow(info.app_id, info.window_id);
    MaybeReStartTimer(kAppLaunchDelay);
    std::erase(pending_windows_, info);
    return;
  }

  if (!windows_.empty()) {
    auto it = windows_.begin();
    if (IsAppReady(it->second.app_id)) {
      launch_count_ = 0;
      LaunchAppWindow(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;
  }

  if (auto it = find_ready_window(no_stack_windows_);
      it != no_stack_windows_.end()) {
    const WindowInfo info = *it;
    LaunchAppWindow(info.app_id, info.window_id);
    MaybeReStartTimer(kAppLaunchDelay);
    std::erase(no_stack_windows_, info);
  }
}

void ArcAppQueueRestoreHandler::LaunchAppWindow(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;

  const auto& launch_list = it->second;
  if (launch_list.empty()) {
    handler_->restore_data()->RemoveApp(app_id);
    return;
  }

  const auto data_it = launch_list.find(window_id);
  if (data_it == launch_list.end())
    return;
  const auto& app_restore_data = data_it->second;

  first_run_ = false;

  auto* proxy =
      apps::AppServiceProxyFactory::GetForProfile(handler_->profile());
  DCHECK(proxy);

  DCHECK(app_restore_data->event_flag.has_value());

  apps::WindowInfoPtr window_info =
      full_restore::HandleArcWindowInfo(app_restore_data->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 newly
    // created ARC task id.
    const int32_t arc_session_id = ::app_restore::CreateArcSessionId();
    window_info->window_id = arc_session_id;
    ::app_restore::SetArcSessionIdForWindowId(arc_session_id, window_id);
    window_id_to_session_id_[window_id] = arc_session_id;
  }

  if (app_restore_data->intent) {
    proxy->LaunchAppWithIntent(app_id, app_restore_data->event_flag.value(),
                               app_restore_data->intent->Clone(),
                               apps::LaunchSource::kFromFullRestore,
                               std::move(window_info), base::DoNothing());
  } else {
    proxy->Launch(app_id, app_restore_data->event_flag.value(),
                  apps::LaunchSource::kFromFullRestore, std::move(window_info));
  }

  if (!HasRestoreData())
    StopRestore();
}

void ArcAppQueueRestoreHandler::RemoveWindowsForApp(const std::string& app_id) {
  app_ids_.erase(app_id);
  std::vector<int32_t> window_stacks;
  for (const auto& [window_stack, window_info] : windows_) {
    if (window_info.app_id == app_id)
      window_stacks.push_back(window_stack);
  }

  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 ArcAppQueueRestoreHandler::RemoveWindow(const std::string& app_id,
                                             int32_t window_id) {
  for (auto& [window_stack, window_info] : windows_) {
    if (window_info.app_id == app_id && window_info.window_id == window_id) {
      windows_.erase(window_stack);
      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 ArcAppQueueRestoreHandler::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_, this,
                           &ArcAppQueueRestoreHandler::MaybeLaunchApp);
}

void ArcAppQueueRestoreHandler::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();

  auto* manager = GetSchedulerConfigurationManager();
  if (manager)
    manager->RemoveObserver(this);

  StopCpuUsageCount();

  RecordRestoreResult();
}

int ArcAppQueueRestoreHandler::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 ArcAppQueueRestoreHandler::StartCpuUsageCount() {
  cpu_tick_count_timer_.Start(FROM_HERE,
                              base::Seconds(kCpuUsageRefreshIntervalInSeconds),
                              this, &ArcAppQueueRestoreHandler::UpdateCpuUsage);
}

void ArcAppQueueRestoreHandler::StopCpuUsageCount() {
  probe_service_.reset();
  cpu_tick_count_timer_.Stop();
}

void ArcAppQueueRestoreHandler::UpdateCpuUsage() {
  if (!probe_service_ || !probe_service_.is_connected())
    return;
  probe_service_->ProbeTelemetryInfo(
      {cros_healthd::mojom::ProbeCategoryEnum::kCpu},
      base::BindOnce(&ArcAppQueueRestoreHandler::OnCpuUsageUpdated,
                     weak_ptr_factory_.GetWeakPtr()));
}

void ArcAppQueueRestoreHandler::OnCpuUsageUpdated(
    cros_healthd::mojom::TelemetryInfoPtr info_ptr) {
  // May be null in tests.
  if (info_ptr.is_null() || info_ptr->cpu_result.is_null() ||
      info_ptr->cpu_result->get_cpu_info().is_null()) {
    return;
  }

  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 ArcAppQueueRestoreHandler::OnProbeServiceDisconnect() {
  probe_service_.reset();
}

void ArcAppQueueRestoreHandler::RecordArcGhostWindowLaunch(
    bool is_arc_ghost_window) {
  base::UmaHistogramBoolean(kArcGhostWindowLaunchHistogram,
                            is_arc_ghost_window);
}

void ArcAppQueueRestoreHandler::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(kRestoreArcAppStatesHistogram, restore_state);

  if (window_handler_) {
    base::UmaHistogramCounts100(kGhostWindowPopToArcHistogram,
                                window_handler_->ghost_window_pop_count());
  }
}

SchedulerConfigurationManager*
ArcAppQueueRestoreHandler::GetSchedulerConfigurationManager() {
  if (!g_browser_process || !g_browser_process->platform_part())
    return nullptr;
  return g_browser_process->platform_part()->scheduler_configuration_manager();
}

}  // namespace ash::app_restore