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

#include <set>
#include <utility>

#include "ash/constants/ash_switches.h"
#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/metrics/histogram_functions.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/metrics/app_platform_metrics.h"
#include "chrome/browser/ash/app_restore/app_restore_arc_task_handler.h"
#include "chrome/browser/ash/app_restore/arc_app_queue_restore_handler.h"
#include "chrome/browser/ash/app_restore/full_restore_service.h"
#include "chrome/browser/ash/crosapi/browser_util.h"
#include "chrome/browser/ash/floating_workspace/floating_workspace_util.h"
#include "chrome/browser/ash/login/session/user_session_manager.h"
#include "chrome/browser/ash/policy/scheduled_task_handler/reboot_notifications_scheduler.h"
#include "chrome/browser/prefs/session_startup_pref.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/sessions/exit_type_service.h"
#include "chrome/browser/sessions/session_restore.h"
#include "chrome/browser/sessions/session_service_log.h"
#include "chrome/browser/ui/startup/startup_tab.h"
#include "chrome/common/chrome_switches.h"
#include "components/app_constants/constants.h"
#include "components/app_restore/features.h"
#include "components/app_restore/full_restore_read_handler.h"
#include "components/app_restore/full_restore_save_handler.h"

// Enable VLOG level 1.
#undef ENABLED_VLOG_LEVEL
#define ENABLED_VLOG_LEVEL 1

namespace ash::full_restore {

namespace {

bool g_launch_browser_for_testing = false;

constexpr char kRestoredAppLaunchHistogramPrefix[] = "Apps.RestoredAppLaunch";
constexpr char kRestoreBrowserResultHistogramPrefix[] =
    "Apps.RestoreBrowserResult";
constexpr char kSessionRestoreExitResultPrefix[] =
    "Apps.SessionRestoreExitResult";
constexpr char kSessionRestoreWindowCountPrefix[] =
    "Apps.SessionRestoreWindowCount";
constexpr char kFullRestoreTabCountPrefix[] = "Apps.FullRestoreTabCount";

}  // namespace

FullRestoreAppLaunchHandler::FullRestoreAppLaunchHandler(
    Profile* profile,
    bool should_init_service)
    : AppLaunchHandler(profile), should_init_service_(should_init_service) {
  // 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(&FullRestoreAppLaunchHandler::OnGetRestoreData,
                     weak_ptr_factory_.GetWeakPtr()));
}

FullRestoreAppLaunchHandler::~FullRestoreAppLaunchHandler() = default;

// TODO: b/325616600 - Move early returns for floating workspace service checks
// logic out.
void FullRestoreAppLaunchHandler::LaunchBrowserWhenReady(
    bool first_run_full_restore) {
  if (floating_workspace_util::ShouldHandleRestartRestore()) {
    return;
  }

  if (g_launch_browser_for_testing ||
      base::CommandLine::ForCurrentProcess()->HasSwitch(
          switches::kForceLaunchBrowser)) {
    ForceLaunchBrowserForTesting();
    return;
  }

  if (first_run_full_restore) {
    // Observe AppRegistryCache to get the notification when the app is ready.
    if (apps::AppServiceProxyFactory::IsAppServiceAvailableForProfile(
            profile())) {
      auto* cache = &apps::AppServiceProxyFactory::GetForProfile(profile())
                         ->AppRegistryCache();
      ObserveCache(cache);

      for (const auto app_type : cache->InitializedAppTypes()) {
        OnAppTypeInitialized(app_type);
      }
    }

    LaunchBrowserForFirstRunFullRestore();
    return;
  }

  // If the restore data has been loaded, and the user has chosen to restore,
  // launch the browser.
  if (CanLaunchBrowser()) {
    LaunchBrowser();

    // OS Setting should be launched after browser to have OS setting window in
    // front.
    UserSessionManager::GetInstance()->PerformPostBrowserLaunchOOBEActions(
        profile());
    return;
  }

  UserSessionManager::GetInstance()->PerformPostBrowserLaunchOOBEActions(
      profile());

  // 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 FullRestoreAppLaunchHandler::SetShouldRestore() {
  should_restore_ = true;
  MaybePostRestore();
}

bool FullRestoreAppLaunchHandler::IsRestoreDataLoaded() {
  return restore_data() != nullptr;
}

void FullRestoreAppLaunchHandler::OnAppUpdate(const apps::AppUpdate& update) {
  // If the restore flag `should_restore_` is true, launch the app for
  // restoration.
  if (should_restore_)
    AppLaunchHandler::OnAppUpdate(update);
}

void FullRestoreAppLaunchHandler::OnAppTypeInitialized(apps::AppType app_type) {
  if (app_type == apps::AppType::kChromeApp) {
    are_chrome_apps_initialized_ = true;
    return;
  }

  if (app_type != apps::AppType::kWeb)
    return;

  are_web_apps_initialized_ = true;

  // `are_web_apps_initialized_` is checked in MaybeStartSaveTimer to decide
  // whether start the save timer. So if web apps are ready, call
  // MaybeStartSaveTimer to start the save timer if possbile.
  MaybeStartSaveTimer();

  if (first_run_full_restore_) {
    LaunchBrowserForFirstRunFullRestore();
    return;
  }

  if (should_launch_browser_ && CanLaunchBrowser()) {
    LaunchBrowser();
    should_launch_browser_ = false;
  }
}

void FullRestoreAppLaunchHandler::OnGotSession(Profile* session_profile,
                                               bool for_app,
                                               int window_count) {
  if (session_profile != profile())
    return;

  if (for_app)
    browser_app_window_count_ = window_count;
  else
    browser_window_count_ = window_count;
}

void FullRestoreAppLaunchHandler::OnMojoDisconnected() {
  observation_.Reset();
}

void FullRestoreAppLaunchHandler::OnStateChanged() {
  if (crosapi::BrowserManager::Get()->IsRunning()) {
    observation_.Reset();
    if (!floating_workspace_util::ShouldHandleRestartRestore()) {
      VLOG(1) << "Full restore opens Lacros";
      crosapi::BrowserManager::Get()->OpenForFullRestore(
          /*skip_crash_restore=*/IsLastSessionExitTypeCrashed());
    }
  }
}

void FullRestoreAppLaunchHandler::ForceLaunchBrowserForTesting() {
  ::full_restore::AddChromeBrowserLaunchInfoForTesting(profile()->GetPath());
  UserSessionManager::GetInstance()->LaunchBrowser(profile());
  UserSessionManager::GetInstance()->PerformPostBrowserLaunchOOBEActions(
      profile());
}

void FullRestoreAppLaunchHandler::OnExtensionLaunching(
    const std::string& app_id) {
  ::full_restore::FullRestoreReadHandler::GetInstance()
      ->SetNextRestoreWindowIdForChromeApp(profile()->GetPath(), app_id);
}

base::WeakPtr<AppLaunchHandler>
FullRestoreAppLaunchHandler::GetWeakPtrAppLaunchHandler() {
  return weak_ptr_factory_.GetWeakPtr();
}

void FullRestoreAppLaunchHandler::OnGetRestoreData(
    std::unique_ptr<::app_restore::RestoreData> restore_data) {
  set_restore_data(std::move(restore_data));
  LogRestoreData();

  // FullRestoreAppLaunchHandler could be created multiple times in browser
  // tests, and used by the desk template. Only when it is created by
  // FullRestoreService, we need to init FullRestoreService.
  bool is_full_restore_shown = false;
  if (should_init_service_)
    FullRestoreService::GetForProfile(profile())->Init(is_full_restore_shown);

  policy::RebootNotificationsScheduler* reboot_notifications_scheduler =
      policy::RebootNotificationsScheduler::Get();
  if (reboot_notifications_scheduler) {
    reboot_notifications_scheduler->MaybeShowPostRebootNotification(
        !is_full_restore_shown);
  }
}

void FullRestoreAppLaunchHandler::MaybePostRestore() {
  MaybeStartSaveTimer();

  // 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::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(&FullRestoreAppLaunchHandler::MaybeRestore,
                                weak_ptr_factory_.GetWeakPtr()));
}

void FullRestoreAppLaunchHandler::MaybeRestore() {
  if (floating_workspace_util::ShouldHandleRestartRestore()) {
    return;
  }
  ::full_restore::FullRestoreReadHandler::GetInstance()->SetStartTimeForProfile(
      profile()->GetPath());
  ::full_restore::FullRestoreReadHandler::GetInstance()->SetCheckRestoreData(
      profile()->GetPath());

  if (should_launch_browser_ && CanLaunchBrowser()) {
    LaunchBrowser();
    should_launch_browser_ = false;
  }

  VLOG(1) << "Restore apps in " << profile()->GetPath();
  if (auto* arc_task_handler =
          app_restore::AppRestoreArcTaskHandler::GetForProfile(profile())) {
    arc_task_handler->GetFullRestoreArcAppQueueRestoreHandler()->RestoreArcApps(
        this);
  }

  MaybeRestoreLacros();

  LaunchApps();

  MaybeStartSaveTimer();
}

bool FullRestoreAppLaunchHandler::CanLaunchBrowser() {
  return should_restore_ && restore_data() &&
         (!restore_data()->HasAppTypeBrowser() || are_web_apps_initialized_);
}

void FullRestoreAppLaunchHandler::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(app_constants::kChromeAppId) == launch_list.end())
    return;

  SessionRestore::AddObserver(this);

  VLOG(1) << "Restore browser for " << profile()->GetPath();
  RecordRestoredAppLaunch(apps::AppTypeName::kChromeBrowser);

  restore_data()->RemoveApp(app_constants::kChromeAppId);

  if (IsLastSessionExitTypeCrashed()) {
    base::CommandLine::ForCurrentProcess()->AppendSwitch(
        ::switches::kHideCrashRestoreBubble);
  }

  MaybeStartSaveTimer();

  if (!::full_restore::HasBrowser(profile()->GetPath())) {
    // If there is no normal browsers before reboot, call session restore to
    // restore app type browsers only.
    SessionRestore::RestoreSession(profile(), nullptr,
                                   SessionRestore::RESTORE_APPS, StartupTabs());
    SessionRestore::RemoveObserver(this);
    return;
  }

  // Modify the command line to restore browser sessions.
  base::CommandLine::ForCurrentProcess()->AppendSwitch(
      ::switches::kRestoreLastSession);

  UserSessionManager::GetInstance()->LaunchBrowser(profile());
  RecordLaunchBrowserResult();
  SessionRestore::RemoveObserver(this);
}

void FullRestoreAppLaunchHandler::LaunchBrowserForFirstRunFullRestore() {
  first_run_full_restore_ = true;

  // Wait for the web apps initialized. Because the app type in AppRegistryCache
  // is checked when save the browser window. If the app doesn't exist in
  // AppRegistryCache, the web app window can't be saved in the full restore
  // file, which could affect the restoration next time after reboot.
  if (!are_web_apps_initialized_)
    return;

  first_run_full_restore_ = false;

  UserSessionManager::GetInstance()->LaunchBrowser(profile());

  PrefService* prefs = profile()->GetPrefs();
  DCHECK(prefs);
  SessionStartupPref session_startup_pref =
      SessionStartupPref::GetStartupPref(prefs);

  // If the system is upgrading from a crash, the app type browser window can be
  // restored, so we don't need to call session restore to restore app type
  // browsers. If the session restore setting is not restore, we don't need to
  // restore app type browser neither.
  if (ExitTypeService::GetLastSessionExitType(profile()) !=
          ExitType::kCrashed &&
      !::full_restore::HasAppTypeBrowser(profile()->GetPath()) &&
      session_startup_pref.ShouldRestoreLastSession()) {
    StartupTabs startup_tabs;
    if (session_startup_pref.type == SessionStartupPref::LAST_AND_URLS)
      startup_tabs = session_startup_pref.ToStartupTabs();
    // Restore the app type browsers only when the web apps are ready.
    SessionRestore::RestoreSession(profile(), nullptr,
                                   SessionRestore::RESTORE_APPS, startup_tabs);
  }

  UserSessionManager::GetInstance()->PerformPostBrowserLaunchOOBEActions(
      profile());
}

void FullRestoreAppLaunchHandler::MaybeRestoreLacros() {
  if (!crosapi::browser_util::IsLacrosEnabled() ||
      !::full_restore::features::IsFullRestoreForLacrosEnabled()) {
    return;
  }

  // TODO(crbug.com/40194081):
  // 1. Modify the restore conditions, e.g. check web apps ready, etc.
  // 2. Handle the migration scenario, e.g. from flag disable to enable.
  // 3. Add metrics to check whether the Lacros is restored successfully.
  if (!base::Contains(restore_data()->app_id_to_launch_list(),
                      app_constants::kLacrosAppId)) {
    return;
  }

  restore_data()->RemoveApp(app_constants::kLacrosAppId);

  if (crosapi::BrowserManager::Get()->IsRunning()) {
    VLOG(1) << "Full restore opens Lacros";
    crosapi::BrowserManager::Get()->OpenForFullRestore(
        /*skip_crash_restore=*/IsLastSessionExitTypeCrashed());
    return;
  }

  if (!crosapi::BrowserManager::Get()->IsTerminated())
    observation_.Observe(crosapi::BrowserManager::Get());
}

void FullRestoreAppLaunchHandler::RecordRestoredAppLaunch(
    apps::AppTypeName app_type_name) {
  base::UmaHistogramEnumeration(kRestoredAppLaunchHistogramPrefix,
                                app_type_name);
}

void FullRestoreAppLaunchHandler::RecordLaunchBrowserResult() {
  RestoreTabResult result = RestoreTabResult::kNoTabs;

  int window_count = 0;
  int tab_count = 0;
  std::list<SessionServiceEvent> events = GetSessionServiceEvents(profile());
  if (!events.empty()) {
    auto it = events.back();
    if (it.type == SessionServiceEventLogType::kRestore) {
      window_count = it.data.restore.window_count;
      tab_count = it.data.exit.tab_count;
      if (tab_count > 0)
        result = RestoreTabResult::kHasTabs;
    } else {
      result = RestoreTabResult::kError;
      window_count = -1;
      tab_count = -1;
    }
  }

  VLOG(1) << "Browser is restored (windows=" << window_count
          << " tabs=" << tab_count << ").";
  base::UmaHistogramEnumeration(kRestoreBrowserResultHistogramPrefix, result);
  base::UmaHistogramCounts100(kFullRestoreTabCountPrefix, tab_count);

  if (result != RestoreTabResult::kNoTabs)
    return;

  SessionRestoreExitResult session_restore_exit =
      SessionRestoreExitResult::kNoExit;
  for (auto iter = events.rbegin(); iter != events.rend(); ++iter) {
    if (iter->type != SessionServiceEventLogType::kStart)
      continue;

    ++iter;
    if (iter != events.rend() &&
        iter->type == SessionServiceEventLogType::kExit) {
      bool is_first_session_service = iter->data.exit.is_first_session_service;
      bool did_schedule_command = iter->data.exit.did_schedule_command;
      if (is_first_session_service) {
        session_restore_exit =
            did_schedule_command
                ? SessionRestoreExitResult::kIsFirstServiceDidSchedule
                : SessionRestoreExitResult::kIsFirstServiceNoSchedule;
      } else {
        session_restore_exit =
            did_schedule_command
                ? SessionRestoreExitResult::kNotFirstServiceDidSchedule
                : SessionRestoreExitResult::kNotFirstServiceNoSchedule;
      }
    }
    break;
  }

  base::UmaHistogramEnumeration(kSessionRestoreExitResultPrefix,
                                session_restore_exit);

  SessionRestoreWindowCount restored_window_count;
  if (browser_app_window_count_ != 0) {
    restored_window_count =
        browser_window_count_ == 0
            ? SessionRestoreWindowCount::kHasAppWindowNoNormalWindow
            : SessionRestoreWindowCount::kHasAppWindowHasNormalWindow;
  } else {
    restored_window_count =
        browser_window_count_ == 0
            ? SessionRestoreWindowCount::kNoWindow
            : SessionRestoreWindowCount::kNoAppWindowHasNormalWindow;
  }
  base::UmaHistogramEnumeration(kSessionRestoreWindowCountPrefix,
                                restored_window_count);
}

void FullRestoreAppLaunchHandler::LogRestoreData() {
  if (!restore_data() || restore_data()->app_id_to_launch_list().empty()) {
    VLOG(1) << "There is no restore data from " << profile()->GetPath();
    return;
  }

  int arc_app_count = 0;
  int other_app_count = 0;
  for (const auto& it : restore_data()->app_id_to_launch_list()) {
    if (it.first == app_constants::kChromeAppId || it.second.empty())
      continue;

    if (it.second.begin()->second->event_flag.has_value()) {
      ++arc_app_count;
      continue;
    }

    ++other_app_count;
  }

  VLOG(1) << "There is restore data: Browser("
          << (::full_restore::HasAppTypeBrowser(profile()->GetPath())
                  ? " has app type browser "
                  : " no app type browser")
          << ","
          << (::full_restore::HasBrowser(profile()->GetPath())
                  ? " has normal browser "
                  : " no normal ")
          << ") ARC(" << arc_app_count << ") other apps(" << other_app_count
          << ") in " << profile()->GetPath();
}

void FullRestoreAppLaunchHandler::MaybeStartSaveTimer() {
  if (!should_restore_) {
    // FullRestoreService is responsible to handle all non restore processes.
    return;
  }

  if (!restore_data() || restore_data()->app_id_to_launch_list().empty()) {
    // If there is no restore data, start the timer.
    ::full_restore::FullRestoreSaveHandler::GetInstance()->AllowSave();
    return;
  }

  if (base::Contains(restore_data()->app_id_to_launch_list(),
                     app_constants::kChromeAppId)) {
    // If the browser hasn't been restored yet, Wait for the browser
    // restoration. LaunchBrowser will call this function again to start the
    // save timer after restore the browser sessions.
    return;
  }

  // If both web apps and chrome apps has finished the initialization, start the
  // timer.
  if (are_chrome_apps_initialized_ && are_web_apps_initialized_)
    ::full_restore::FullRestoreSaveHandler::GetInstance()->AllowSave();
}

bool FullRestoreAppLaunchHandler::IsLastSessionExitTypeCrashed() {
  return ExitTypeService::GetLastSessionExitType(profile()) ==
         ExitType::kCrashed;
}

ScopedLaunchBrowserForTesting::ScopedLaunchBrowserForTesting() {
  g_launch_browser_for_testing = true;
}

ScopedLaunchBrowserForTesting::~ScopedLaunchBrowserForTesting() {
  g_launch_browser_for_testing = false;
}

}  // namespace ash::full_restore