chromium/ash/wm/lock_state_controller.cc

// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "ash/wm/lock_state_controller.h"

#include <algorithm>
#include <string>
#include <utility>

#include "ash/accessibility/accessibility_controller.h"
#include "ash/app_list/app_list_controller_impl.h"
#include "ash/cancel_mode.h"
#include "ash/capture_mode/capture_mode_controller.h"
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/public/cpp/saved_desk_delegate.h"
#include "ash/public/cpp/shell_window_ids.h"
#include "ash/public/cpp/shutdown_controller.h"
#include "ash/root_window_controller.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "ash/shell_delegate.h"
#include "ash/utility/occlusion_tracker_pauser.h"
#include "ash/wallpaper/views/wallpaper_view.h"
#include "ash/wallpaper/views/wallpaper_widget_controller.h"
#include "ash/wallpaper/wallpaper_controller_impl.h"
#include "ash/wm/desks/desks_util.h"
#include "ash/wm/mru_window_tracker.h"
#include "ash/wm/overview/overview_controller.h"
#include "ash/wm/screen_pinning_controller.h"
#include "ash/wm/session_state_animator_impl.h"
#include "ash/wm/window_restore/informed_restore_constants.h"
#include "ash/wm/window_restore/window_restore_metrics.h"
#include "ash/wm/window_restore/window_restore_util.h"
#include "ash/wm/workspace/backdrop_controller.h"
#include "ash/wm/workspace/workspace_layout_manager.h"
#include "ash/wm/workspace_controller.h"
#include "base/debug/crash_logging.h"
#include "base/debug/dump_without_crashing.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/json/values_util.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/user_metrics.h"
#include "base/strings/string_util.h"
#include "base/system/sys_info.h"
#include "base/task/bind_post_task.h"
#include "base/task/current_thread.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/time/default_clock.h"
#include "base/time/time.h"
#include "chromeos/dbus/power/power_manager_client.h"
#include "ui/aura/window_tree_host.h"
#include "ui/compositor/layer.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/image/image_util.h"
#include "ui/snapshot/snapshot.h"
#include "ui/views/controls/menu/menu_controller.h"
#include "ui/views/widget/widget.h"
#include "ui/wm/core/compound_event_filter.h"
#include "ui/wm/core/cursor_manager.h"

#define UMA_HISTOGRAM_LOCK_TIMES(name, sample)                    \
  UMA_HISTOGRAM_CUSTOM_TIMES(name, sample, base::Milliseconds(1), \
                             base::Seconds(50), 100)

// TODO(b/228873153): Remove after figuring out the root cause of the bug
#undef ENABLED_VLOG_LEVEL
#define ENABLED_VLOG_LEVEL 1

namespace ash {

namespace {

// ASan/TSan/MSan instrument each memory access. This may slow the execution
// down significantly.
#if defined(MEMORY_SANITIZER)
// For MSan the slowdown depends heavily on the value of msan_track_origins GYP
// flag. The multiplier below corresponds to msan_track_origins=1.
constexpr int kTimeoutMultiplier = 6;
#elif defined(ADDRESS_SANITIZER) || defined(THREAD_SANITIZER)
constexpr int kTimeoutMultiplier = 2;
#else
constexpr int kTimeoutMultiplier = 1;
#endif

constexpr int kMaxShutdownSoundDurationMs = 1500;

// Amount of time to wait for our lock requests to be honored before giving up.
constexpr base::TimeDelta kLockFailTimeout =
    base::Seconds(8 * kTimeoutMultiplier);

// Amount of time to wait for our post lock animation before giving up.
constexpr base::TimeDelta kPostLockFailTimeout =
    base::Seconds(2 * kTimeoutMultiplier);

// Additional time to wait after starting the fast-close shutdown animation
// before actually requesting shutdown, to give the animation time to finish.
constexpr base::TimeDelta kShutdownRequestDelay = base::Milliseconds(50);

// Amount of time to wait after starting to take the informed restore
// screenshot. The task will be stopped if it takes longer than this time
// duration.
constexpr base::TimeDelta kTakeScreenshotFailTimeout = base::Milliseconds(800);

// Records the given `duration` to the given `pref_name` so it can be recorded
// as an UMA metric on the next startup.
void SaveInformedRestoreScreenshotDuration(PrefService* local_state,
                                           const std::string& pref_name,
                                           base::TimeDelta duration) {
  if (!local_state) {
    return;
  }

  local_state->SetTimeDelta(pref_name, duration);
}

// Encodes and saves the given `image` to `file_path`.
void EncodeAndSaveImage(const base::FilePath& file_path, gfx::Image image) {
  CHECK(!base::CurrentUIThread::IsSet());
  if (image.IsEmpty()) {
    base::DeleteFile(file_path);
    return;
  }

  // The width of the resized informed restore image will be fixed and then the
  // height of it will be calculated based on the aspect ratio of the original
  // informed restore image. The resized informed restore image will be saved to
  // disk, decoded and shown with this size directly inside the informed restore
  // dialog later as well.
  const float aspect_ratio = static_cast<float>(image.Height()) / image.Width();
  const int resized_image_height =
      aspect_ratio * informed_restore::kPreviewContainerWidth;
  const auto resized_image = gfx::ResizedImage(
      image, gfx::Size(informed_restore::kPreviewContainerWidth,
                       resized_image_height));
  auto png_bytes = resized_image.As1xPNGBytes();
  auto raw_data = base::make_span(png_bytes->data(), png_bytes->size());
  if (!base::WriteFile(file_path, raw_data)) {
    LOG(ERROR) << "Failed to write informed restore image to "
               << file_path.MaybeAsASCII();
  }
}

// If the given `for_test_callback` is valid, `callback` will be modified
// to be a new callback that runs the original `callback` and then runs
// `for_test_callback` after the former finishes.
// `base::BindPostTask()` is used to guarantee that when `for_test_callback`
// is invoked, it runs on the same thread of the call site (even if `callback`
// is posted to run on a different thread).
// Note that `for_test_callback` will be empty after this function returns.
template <typename Callback>
void MaybeAppendTestCallback(Callback& callback,
                             base::OnceClosure& for_test_callback) {
  if (for_test_callback) {
    callback = std::move(callback).Then(
        base::BindPostTask(base::SingleThreadTaskRunner::GetCurrentDefault(),
                           std::move(for_test_callback)));
  }
}

// Deletes any existing informed restore image if we should change the session
// state without taking the screenshot, then no stale screenshot will be shown
// after the session state changes.
void DeleteInformedRestoreImage(base::OnceClosure& for_test_callback,
                                const base::FilePath& file_path) {
  auto delete_image_cb =
      base::BindOnce(base::IgnoreResult(&base::DeleteFile), file_path);
  MaybeAppendTestCallback(delete_image_cb, for_test_callback);
  base::ThreadPool::PostTask(FROM_HERE,
                             {base::MayBlock(), base::TaskPriority::HIGHEST,
                              base::TaskShutdownBehavior::BLOCK_SHUTDOWN},
                             std::move(delete_image_cb));
}

// TODO(minch): Check whether the screenshot should be taken in kiosk mode.
// Returns true if the informed restore screenshot should be taken on session
// state changes.
bool ShouldTakeInformedRestoreScreenshot() {
  auto* shell = Shell::Get();
  // Do not take the informed restore screenshot if it is in overview mode, lock
  // screen, home launcher or pinned mode.
  if (shell->overview_controller()->InOverviewSession()) {
    RecordScreenshotOnShutdownStatus(
        ScreenshotOnShutdownStatus::kFailedInOverview);
    return false;
  }
  auto* session_controller = shell->session_controller();
  if (session_controller->IsScreenLocked()) {
    RecordScreenshotOnShutdownStatus(
        ScreenshotOnShutdownStatus::kFailedInLockScreen);
    return false;
  }
  if (session_controller->IsUserGuest() ||
      session_controller->IsUserPublicAccount()) {
    RecordScreenshotOnShutdownStatus(
        ScreenshotOnShutdownStatus::kFailedInGuestOrPublicUserSession);
    return false;
  }
  if (shell->app_list_controller()->IsHomeScreenVisible()) {
    RecordScreenshotOnShutdownStatus(
        ScreenshotOnShutdownStatus::kFailedInHomeLauncher);
    return false;
  }
  if (shell->screen_pinning_controller()->IsPinned()) {
    RecordScreenshotOnShutdownStatus(
        ScreenshotOnShutdownStatus::kFailedInPinnedMode);
    return false;
  }

  bool has_regular_unminimized_window = false;
  for (aura::Window* window :
       shell->mru_window_tracker()->BuildMruWindowList(kActiveDesk)) {
    const bool is_non_regular_profile_window =
        !shell->saved_desk_delegate()->IsWindowPersistable(window);
    const bool is_minimized = WindowState::Get(window)->IsMinimized();
    // Do not take the screenshot if there is an incognito ash browser window or
    // a lacros window with the non-regular profile.
    if (!is_minimized && is_non_regular_profile_window) {
      RecordScreenshotOnShutdownStatus(
          ScreenshotOnShutdownStatus::kFailedWithIncognito);
      return false;
    }
    has_regular_unminimized_window |=
        !is_non_regular_profile_window && !is_minimized;
  }

  // Take the screenshot if there are unminimized non-incognito windows inside
  // the active desk. Both the float and the always on top window will be
  // counted.
  if (!has_regular_unminimized_window) {
    RecordScreenshotOnShutdownStatus(
        ScreenshotOnShutdownStatus::kFailedWithNoWindows);
  }
  return has_regular_unminimized_window;
}

// Hide the cursor and lock the cursor as well if `lock` is true.
void HideAndMaybeLockCursor(bool lock) {
  Shell* shell = Shell::Get();
  if (auto* cursor_manager = shell->cursor_manager(); cursor_manager) {
    // Hide cursor, but let it reappear if the mouse moves.
    cursor_manager->HideCursor();
    if (lock) {
      cursor_manager->LockCursor();
    }
  }
}

}  // namespace

// static
const int LockStateController::kPreLockContainersMask =
    SessionStateAnimator::NON_LOCK_SCREEN_CONTAINERS |
    SessionStateAnimator::SHELF;

LockStateController::LockStateController(
    ShutdownController* shutdown_controller,
    PrefService* local_state)
    : animator_(new SessionStateAnimatorImpl()),
      shutdown_controller_(shutdown_controller),
      scoped_session_observer_(this),
      local_state_(local_state) {
  DCHECK(shutdown_controller_);
  Shell::GetPrimaryRootWindow()->GetHost()->AddObserver(this);
  // |local_state_| could be null in tests.
  if (local_state_) {
    // If kLoginShutdownTimestampPrefName is registered, check the last recorded
    // login shutdown timestamp in local state prefs, in case device was shut
    // down using shelf button.
    auto* login_shutdown_timestamp_pref =
        local_state_->FindPreference(prefs::kLoginShutdownTimestampPrefName);
    if (login_shutdown_timestamp_pref &&
        !login_shutdown_timestamp_pref->IsDefaultValue()) {
      base::Time last_recorded_login_shutdown_timestamp =
          base::ValueToTime(login_shutdown_timestamp_pref->GetValue()).value();
      base::TimeDelta duration = base::DefaultClock::GetInstance()->Now() -
                                 last_recorded_login_shutdown_timestamp;
      // Report time delta even if it exceeds histogram limit, to better
      // understand fraction of users using the feature.
      base::UmaHistogramLongTimes(
          "Ash.Shelf.ShutdownConfirmationBubble.TimeToNextBoot."
          "LoginShutdownToPowerUpDuration",
          duration);

      // Reset to the default value after the value is recorded.
      local_state_->ClearPref(prefs::kLoginShutdownTimestampPrefName);
    }
  }
}

LockStateController::~LockStateController() {
  Shell::GetPrimaryRootWindow()->GetHost()->RemoveObserver(this);
}

// static
void LockStateController::RegisterPrefs(PrefRegistrySimple* registry) {
  registry->RegisterTimePref(prefs::kLoginShutdownTimestampPrefName,
                             base::Time());
  registry->RegisterTimeDeltaPref(
      prefs::kInformedRestoreScreenshotTakenDuration, base::TimeDelta());
  registry->RegisterTimeDeltaPref(
      prefs::kInformedRestoreScreenshotEncodeAndSaveDuration,
      base::TimeDelta());
}

void LockStateController::AddObserver(LockStateObserver* observer) {
  observers_.AddObserver(observer);
}

void LockStateController::RemoveObserver(LockStateObserver* observer) {
  observers_.RemoveObserver(observer);
}

void LockStateController::StartLockAnimation() {
  if (animating_lock_) {
    return;
  }

  views::MenuController* active_menu_controller =
      views::MenuController::GetActiveInstance();

  if (active_menu_controller) {
    // TODO(http://b/328064674): Please remove the below crash keys once the
    // the crash is fixed. It seems after post lock animation finished there
    // is active menu. This check is moved to the StartLockAnimation, since it
    // seems the check in the post lock animation is too late.

    views::Widget* owner = active_menu_controller->owner();
    SCOPED_CRASH_KEY_STRING256("LockStateController", "StartLockAnimation",
                               owner ? owner->GetName() : "ownerless");
    CHECK(false);
  }

  animating_lock_ = true;
  StoreUnlockedProperties();
  VLOG(1) << "StartLockAnimation";
  PreLockAnimation(SessionStateAnimator::ANIMATION_SPEED_UNDOABLE, true);
  DispatchCancelMode();
  OnLockStateEvent(LockStateObserver::EVENT_PRELOCK_ANIMATION_STARTED);
}

void LockStateController::LockWithoutAnimation() {
  VLOG(1) << "LockWithoutAnimation : "
          << "animating_unlock_: " << static_cast<int>(animating_unlock_)
          << ", animating_lock_: " << static_cast<int>(animating_lock_);
  if (animating_unlock_) {
    CancelUnlockAnimation();
    // One would expect a call to
    // `Shell::Get()->session_controller()->LockScreen()` at this point,
    // however, when execution reaches here, if:
    //
    // We were running the animations started as part of
    // StartUnlockAnimationBeforeLockUIDestroyed, `session_manager` still
    // considers the screen to be locked, as we've only executed the part of the
    // animations done before the lock screen UI is destroyed.
    //
    // We were running the animations started as part of
    // StartUnlockAnimationAfterLockUIDestroyed, `session_manager` would
    // consider the session to be unlocked, and thus we lock it again as part of
    // UnlockAnimationAfterLockUIDestroyedFinished.
    return;
  }
  if (animating_lock_)
    return;
  animating_lock_ = true;
  post_lock_immediate_animation_ = true;
  animator_->StartAnimation(kPreLockContainersMask,
                            SessionStateAnimator::ANIMATION_HIDE_IMMEDIATELY,
                            SessionStateAnimator::ANIMATION_SPEED_IMMEDIATE);
  OnLockStateEvent(LockStateObserver::EVENT_LOCK_ANIMATION_STARTED);
  Shell::Get()->session_controller()->LockScreen();
}

bool LockStateController::LockRequested() {
  return lock_fail_timer_.IsRunning();
}

void LockStateController::CancelLockAnimation() {
  VLOG(1) << "CancelLockAnimation";
  animating_lock_ = false;
  Shell::Get()->wallpaper_controller()->RestoreWallpaperBlurForLockState(
      saved_blur_);
  auto next_animation_starter =
      base::BindOnce(&LockStateController::LockAnimationCancelled,
                     weak_ptr_factory_.GetWeakPtr());
  SessionStateAnimator::AnimationSequence* animation_sequence =
      animator_->BeginAnimationSequence(std::move(next_animation_starter));

  animation_sequence->StartAnimation(
      SessionStateAnimator::NON_LOCK_SCREEN_CONTAINERS,
      SessionStateAnimator::ANIMATION_UNDO_LIFT,
      SessionStateAnimator::ANIMATION_SPEED_UNDO_MOVE_WINDOWS);
  animation_sequence->StartAnimation(
      SessionStateAnimator::SHELF, SessionStateAnimator::ANIMATION_FADE_IN,
      SessionStateAnimator::ANIMATION_SPEED_UNDO_MOVE_WINDOWS);
  AnimateWallpaperHidingIfNecessary(
      SessionStateAnimator::ANIMATION_SPEED_UNDO_MOVE_WINDOWS,
      animation_sequence);

  animation_sequence->EndSequence();
}

void LockStateController::OnUnlockAnimationBeforeLockUIDestroyedFinished() {
  if (pb_pressed_during_unlock_) {
    // Power button was pressed during the unlock animation and
    // CancelUnlockAnimation was called, restore UI elements to previous state
    // immediately.
    animator_->StartAnimation(SessionStateAnimator::SHELF,
                              SessionStateAnimator::ANIMATION_FADE_IN,
                              SessionStateAnimator::ANIMATION_SPEED_IMMEDIATE);
    animator_->StartAnimation(SessionStateAnimator::LOCK_SCREEN_CONTAINERS,
                              SessionStateAnimator::ANIMATION_UNDO_LIFT,
                              SessionStateAnimator::ANIMATION_SPEED_IMMEDIATE);
    // We aborted, so we are not animating anymore.
    animating_unlock_ = false;
  }
  std::move(start_unlock_callback_).Run(pb_pressed_during_unlock_);
  pb_pressed_during_unlock_ = false;
}

void LockStateController::OnLockScreenHide(
    SessionStateAnimator::AnimationCallback callback) {
  start_unlock_callback_ = std::move(callback);
  StartUnlockAnimationBeforeLockUIDestroyed(base::BindOnce(
      &LockStateController::OnUnlockAnimationBeforeLockUIDestroyedFinished,
      weak_ptr_factory_.GetWeakPtr()));
}

void LockStateController::SetLockScreenDisplayedCallback(
    base::OnceClosure callback) {
  DCHECK(lock_screen_displayed_callback_.is_null());
  if (system_is_locked_ && !animating_lock_)
    std::move(callback).Run();
  else
    lock_screen_displayed_callback_ = std::move(callback);
}

void LockStateController::RequestShutdown(ShutdownReason reason) {
  if (shutting_down_) {
    return;
  }

  shutting_down_ = true;
  shutdown_reason_ = reason;
  shutdown_canceled_ = false;

  if (reason == ShutdownReason::LOGIN_SHUT_DOWN_BUTTON) {
    base::Time now_timestamp = base::DefaultClock::GetInstance()->Now();
    local_state_->SetTime(prefs::kLoginShutdownTimestampPrefName,
                          now_timestamp);
  }

  HideAndMaybeLockCursor(/*lock=*/true);
  if (features::IsForestFeatureEnabled()) {
    SessionStateChangeWithInformedRestore(RequestedSessionState::kShutdown);
  } else {
    StartSessionStateChange(RequestedSessionState::kShutdown);
  }
}

void LockStateController::RequestCancelableShutdown(ShutdownReason reason) {
  shutdown_reason_ = reason;
  shutdown_canceled_ = false;

  HideAndMaybeLockCursor(/*lock=*/false);
  if (features::IsForestFeatureEnabled()) {
    SessionStateChangeWithInformedRestore(
        RequestedSessionState::kCancelableShutdown);
  } else {
    StartSessionStateChange(RequestedSessionState::kCancelableShutdown);
  }
}

bool LockStateController::ShutdownRequested() const {
  return shutting_down_;
}

bool LockStateController::MaybeCancelShutdownAnimation() {
  if (ShutdownRequested()) {
    return false;
  }

  animator_->StartAnimation(
      SessionStateAnimator::ROOT_CONTAINER,
      SessionStateAnimator::ANIMATION_UNDO_GRAYSCALE_BRIGHTNESS,
      SessionStateAnimator::ANIMATION_SPEED_REVERT_SHUTDOWN);
  shutdown_canceled_ = true;
  if (features::IsForestFeatureEnabled()) {
    // Shutdown maybe canceled before or after image saved. So we need to delete
    // both here and `OnImageSaved`.
    DeleteInformedRestoreImage(informed_restore_image_callback_for_test_,
                               GetInformedRestoreImagePath());
  }
  cancelable_shutdown_timer_.Stop();
  return true;
}

void LockStateController::RequestRestart(
    power_manager::RequestRestartReason reason,
    const std::string& description) {
  if (features::IsForestFeatureEnabled()) {
    HideAndMaybeLockCursor(/*lock=*/false);
    restart_callback_ =
        base::BindOnce(&LockStateController::DoRestart, base::Unretained(this),
                       reason, description);
    SessionStateChangeWithInformedRestore(RequestedSessionState::kRestart);
  } else {
    chromeos::PowerManagerClient::Get()->RequestRestart(reason, description);
  }
}

void LockStateController::RequestSignOut() {
  if (features::IsForestFeatureEnabled()) {
    SessionStateChangeWithInformedRestore(RequestedSessionState::kSignOut);
  } else {
    Shell::Get()->session_controller()->RequestSignOut();
  }
}

void LockStateController::OnHostCloseRequested(aura::WindowTreeHost* host) {
  Shell::Get()->session_controller()->RequestSignOut();
}

void LockStateController::OnChromeTerminating() {
  // If we hear that Chrome is exiting but didn't request it ourselves, all we
  // can really hope for is that we'll have time to clear the screen.
  // This is also the case when the user signs off.
  if (!shutting_down_) {
    shutting_down_ = true;
    HideAndMaybeLockCursor(/*lock=*/true);
    animator_->StartAnimation(SessionStateAnimator::kAllNonRootContainersMask,
                              SessionStateAnimator::ANIMATION_HIDE_IMMEDIATELY,
                              SessionStateAnimator::ANIMATION_SPEED_IMMEDIATE);
  }
}

void LockStateController::OnLockStateChanged(bool locked) {
  // Unpause if lock animations didn't start and ends in 3 seconds.
  constexpr base::TimeDelta kPauseTimeout = base::Seconds(3);

  DCHECK((lock_fail_timer_.IsRunning() && lock_duration_timer_ != nullptr) ||
         (!lock_fail_timer_.IsRunning() && lock_duration_timer_ == nullptr));
  VLOG(1) << "OnLockStateChanged called with locked: " << locked
          << ", shutting_down_: " << shutting_down_
          << ", system_is_locked_: " << system_is_locked_
          << ", lock_fail_timer_.IsRunning(): " << lock_fail_timer_.IsRunning()
          << ", animating_unlock_: " << static_cast<int>(animating_unlock_)
          << ", animating_lock_: " << static_cast<int>(animating_lock_);

  if (shutting_down_ || (system_is_locked_ == locked))
    return;

  system_is_locked_ = locked;

  Shell::Get()->occlusion_tracker_pauser()->PauseUntilAnimationsEnd(
      kPauseTimeout);

  if (locked) {
    StartPostLockAnimation();

    lock_fail_timer_.Stop();

    if (lock_duration_timer_) {
      UMA_HISTOGRAM_LOCK_TIMES("Ash.WindowManager.Lock.Success",
                               lock_duration_timer_->Elapsed());
      lock_duration_timer_.reset();
    }
  } else {
    StartUnlockAnimationAfterLockUIDestroyed();
  }
}

void LockStateController::CancelUnlockAnimation() {
  VLOG(1) << "CancelUnlockAnimation";
  pb_pressed_during_unlock_ = true;
}

void LockStateController::OnLockFailTimeout() {
  UMA_HISTOGRAM_LOCK_TIMES("Ash.WindowManager.Lock.Timeout",
                           lock_duration_timer_->Elapsed());
  lock_duration_timer_.reset();
  DCHECK(!system_is_locked_);

  // b/228873153: Here we use `LOG(ERROR)` instead of `LOG(FATAL)` because it
  // seems like certain users are hitting this timeout causing chrome to crash
  // and be restarted from session manager without `--login-manager`
  LOG(ERROR) << "Screen lock took too long; Signing out";
  base::debug::DumpWithoutCrashing();
  Shell::Get()->session_controller()->RequestSignOut();
}

void LockStateController::PreLockAnimation(
    SessionStateAnimator::AnimationSpeed speed,
    bool request_lock_on_completion) {
  VLOG(1) << "PreLockAnimation";
  saved_blur_ = Shell::GetPrimaryRootWindowController()
                    ->wallpaper_widget_controller()
                    ->GetWallpaperBlur();
  Shell::Get()->wallpaper_controller()->UpdateWallpaperBlurForLockState(true);
  auto next_animation_starter = base::BindOnce(
      &LockStateController::PreLockAnimationFinished,
      weak_ptr_factory_.GetWeakPtr(), request_lock_on_completion);
  SessionStateAnimator::AnimationSequence* animation_sequence =
      animator_->BeginAnimationSequence(std::move(next_animation_starter));

  animation_sequence->StartAnimation(
      SessionStateAnimator::NON_LOCK_SCREEN_CONTAINERS,
      SessionStateAnimator::ANIMATION_LIFT, speed);
  animation_sequence->StartAnimation(SessionStateAnimator::SHELF,
                                     SessionStateAnimator::ANIMATION_FADE_OUT,
                                     speed);
  // Hide the screen locker containers so we can raise them later.
  animator_->StartAnimation(SessionStateAnimator::LOCK_SCREEN_CONTAINERS,
                            SessionStateAnimator::ANIMATION_HIDE_IMMEDIATELY,
                            SessionStateAnimator::ANIMATION_SPEED_IMMEDIATE);
  AnimateWallpaperAppearanceIfNecessary(speed, animation_sequence);

  animation_sequence->EndSequence();
}

void LockStateController::StartPostLockAnimation() {
  VLOG(1) << "StartPostLockAnimation";
  auto next_animation_starter =
      base::BindOnce(&LockStateController::PostLockAnimationFinished,
                     weak_ptr_factory_.GetWeakPtr());
  SessionStateAnimator::AnimationSequence* animation_sequence =
      animator_->BeginAnimationSequence(std::move(next_animation_starter));

  animation_sequence->StartAnimation(
      SessionStateAnimator::LOCK_SCREEN_CONTAINERS,
      SessionStateAnimator::ANIMATION_RAISE_TO_SCREEN,
      post_lock_immediate_animation_
          ? SessionStateAnimator::ANIMATION_SPEED_IMMEDIATE
          : SessionStateAnimator::ANIMATION_SPEED_MOVE_WINDOWS);
  // Show the lock screen shelf. This is a no-op if views-based shelf is
  // disabled, since shelf is in NonLockScreenContainersContainer.
  animation_sequence->StartAnimation(
      SessionStateAnimator::SHELF, SessionStateAnimator::ANIMATION_FADE_IN,
      post_lock_immediate_animation_
          ? SessionStateAnimator::ANIMATION_SPEED_IMMEDIATE
          : SessionStateAnimator::ANIMATION_SPEED_MOVE_WINDOWS);
  animation_sequence->EndSequence();
  post_lock_fail_timer_.Start(FROM_HERE, kPostLockFailTimeout, this,
                              &LockStateController::OnPostLockFailTimeout);
}

void LockStateController::StartUnlockAnimationBeforeLockUIDestroyed(
    base::OnceClosure callback) {
  VLOG(1) << "StartUnlockAnimationBeforeLockUIDestroyed";
  animating_unlock_ = true;
  // Hide the lock screen shelf. This is a no-op if views-based shelf is
  // disabled, since shelf is in NonLockScreenContainersContainer.
  animator_->StartAnimation(SessionStateAnimator::SHELF,
                            SessionStateAnimator::ANIMATION_FADE_OUT,
                            SessionStateAnimator::ANIMATION_SPEED_MOVE_WINDOWS);
  animator_->StartAnimationWithCallback(
      SessionStateAnimator::LOCK_SCREEN_CONTAINERS,
      SessionStateAnimator::ANIMATION_LIFT,
      SessionStateAnimator::ANIMATION_SPEED_MOVE_WINDOWS, std::move(callback));
  animator_->StartAnimation(SessionStateAnimator::NON_LOCK_SCREEN_CONTAINERS,
                            SessionStateAnimator::ANIMATION_COPY_LAYER,
                            SessionStateAnimator::ANIMATION_SPEED_IMMEDIATE);
}

void LockStateController::StartUnlockAnimationAfterLockUIDestroyed() {
  VLOG(1) << "StartUnlockAnimationAfterLockUIDestroyed";
  auto next_animation_starter = base::BindOnce(
      &LockStateController::UnlockAnimationAfterLockUIDestroyedFinished,
      weak_ptr_factory_.GetWeakPtr());
  SessionStateAnimator::AnimationSequence* animation_sequence =
      animator_->BeginAnimationSequence(std::move(next_animation_starter));

  animation_sequence->StartAnimation(
      SessionStateAnimator::NON_LOCK_SCREEN_CONTAINERS,
      SessionStateAnimator::ANIMATION_DROP,
      SessionStateAnimator::ANIMATION_SPEED_MOVE_WINDOWS);
  animation_sequence->StartAnimation(
      SessionStateAnimator::SHELF, SessionStateAnimator::ANIMATION_FADE_IN,
      SessionStateAnimator::ANIMATION_SPEED_MOVE_WINDOWS);
  AnimateWallpaperHidingIfNecessary(
      SessionStateAnimator::ANIMATION_SPEED_MOVE_WINDOWS, animation_sequence);
  animation_sequence->EndSequence();
}

void LockStateController::LockAnimationCancelled(bool aborted) {
  VLOG(1) << "LockAnimationCancelled: aborted=" << aborted;
  RestoreUnlockedProperties();
}

void LockStateController::PreLockAnimationFinished(bool request_lock,
                                                   bool aborted) {
  VLOG(1) << "PreLockAnimationFinished: aborted=" << aborted;
  // Aborted in this stage means the locking animation was cancelled by
  // `CancelLockAnimation()`, triggered by releasing a lock button before
  // finishing animation.
  if (aborted)
    return;

  // Don't do anything (including starting the lock-fail timer) if the screen
  // was already locked while the animation was going.
  if (system_is_locked_) {
    DCHECK(!request_lock) << "Got request to lock already-locked system "
                          << "at completion of pre-lock animation";
    return;
  }

  if (request_lock) {
    base::RecordAction(base::UserMetricsAction("Accel_LockScreen_LockButton"));
    Shell::Get()->session_controller()->LockScreen();
  }

  VLOG(1) << "b/228873153 : Starting lock fail timer";
  lock_fail_timer_.Start(FROM_HERE, kLockFailTimeout, this,
                         &LockStateController::OnLockFailTimeout);

  lock_duration_timer_ = std::make_unique<base::ElapsedTimer>();
}

void LockStateController::OnPostLockFailTimeout() {
  VLOG(1) << "OnPostLockFailTimeout";
  PostLockAnimationFinished(true);
}

void LockStateController::PostLockAnimationFinished(bool aborted) {
  VLOG(1) << "PostLockAnimationFinished: aborted=" << aborted;
  if (!animating_lock_)
    return;
  animating_lock_ = false;
  post_lock_immediate_animation_ = false;
  post_lock_fail_timer_.Stop();
  OnLockStateEvent(LockStateObserver::EVENT_LOCK_ANIMATION_FINISHED);
  if (!lock_screen_displayed_callback_.is_null())
    std::move(lock_screen_displayed_callback_).Run();
}

void LockStateController::UnlockAnimationAfterLockUIDestroyedFinished(
    bool aborted) {
  VLOG(1) << "UnlockAnimationAfterLockUIDestroyedFinished: aborted=" << aborted;
  animating_unlock_ = false;
  if (pb_pressed_during_unlock_) {
    Shell::Get()->session_controller()->LockScreen();
    pb_pressed_during_unlock_ = false;
  } else {
    Shell::Get()->wallpaper_controller()->UpdateWallpaperBlurForLockState(
        false);
    RestoreUnlockedProperties();
  }
}

void LockStateController::StoreUnlockedProperties() {
  if (!unlocked_properties_) {
    unlocked_properties_ = std::make_unique<UnlockedStateProperties>();
    unlocked_properties_->wallpaper_is_hidden = animator_->IsWallpaperHidden();
  }
  if (unlocked_properties_->wallpaper_is_hidden) {
    // Hide wallpaper so that it can be animated later.
    animator_->StartAnimation(SessionStateAnimator::WALLPAPER,
                              SessionStateAnimator::ANIMATION_HIDE_IMMEDIATELY,
                              SessionStateAnimator::ANIMATION_SPEED_IMMEDIATE);
    animator_->ShowWallpaper();
  }
}

void LockStateController::RestoreUnlockedProperties() {
  if (!unlocked_properties_)
    return;
  if (unlocked_properties_->wallpaper_is_hidden) {
    animator_->HideWallpaper();
    // Restore wallpaper visibility.
    animator_->StartAnimation(SessionStateAnimator::WALLPAPER,
                              SessionStateAnimator::ANIMATION_FADE_IN,
                              SessionStateAnimator::ANIMATION_SPEED_IMMEDIATE);
  }
  unlocked_properties_.reset();
}

void LockStateController::AnimateWallpaperAppearanceIfNecessary(
    SessionStateAnimator::AnimationSpeed speed,
    SessionStateAnimator::AnimationSequence* animation_sequence) {
  if (unlocked_properties_.get() && unlocked_properties_->wallpaper_is_hidden) {
    animation_sequence->StartAnimation(SessionStateAnimator::WALLPAPER,
                                       SessionStateAnimator::ANIMATION_FADE_IN,
                                       speed);
  }
}

void LockStateController::AnimateWallpaperHidingIfNecessary(
    SessionStateAnimator::AnimationSpeed speed,
    SessionStateAnimator::AnimationSequence* animation_sequence) {
  if (unlocked_properties_.get() && unlocked_properties_->wallpaper_is_hidden) {
    animation_sequence->StartAnimation(SessionStateAnimator::WALLPAPER,
                                       SessionStateAnimator::ANIMATION_FADE_OUT,
                                       speed);
  }
}

void LockStateController::OnLockStateEvent(LockStateObserver::EventType event) {
  if (shutting_down_)
    return;

  for (auto& observer : observers_)
    observer.OnLockStateEvent(event);
}

void LockStateController::StartPreShutdownAnimationTimer() {
  cancelable_shutdown_timer_.Stop();
  cancelable_shutdown_timer_.Start(
      FROM_HERE,
      animator_->GetDuration(SessionStateAnimator::ANIMATION_SPEED_SHUTDOWN),
      this, &LockStateController::OnPreShutdownAnimationTimeout);
}

void LockStateController::OnPreShutdownAnimationTimeout() {
  VLOG(1) << "OnPreShutdownAnimationTimeout";
  shutting_down_ = true;

  HideAndMaybeLockCursor(/*lock=*/false);
  StartSessionStateChangeTimer(/*with_animation_time=*/false,
                               RequestedSessionState::kCancelableShutdown);
}

void LockStateController::StartSessionStateChangeTimer(
    bool with_animation_time,
    RequestedSessionState requested_session_state) {
  base::TimeDelta duration = kShutdownRequestDelay;
  if (with_animation_time) {
    duration +=
        animator_->GetDuration(SessionStateAnimator::ANIMATION_SPEED_SHUTDOWN);
  }
  // Play and get shutdown sound duration from chrome in |sound_duration|. And
  // start real shutdown after a delay of |duration|.
  if (requested_session_state == RequestedSessionState::kShutdown ||
      requested_session_state == RequestedSessionState::kCancelableShutdown) {
    base::TimeDelta sound_duration =
        std::min(Shell::Get()->accessibility_controller()->PlayShutdownSound(),
                 base::Milliseconds(kMaxShutdownSoundDurationMs));
    duration = std::max(duration, sound_duration);
  }
  session_state_change_timer_.Start(
      FROM_HERE, duration,
      base::BindOnce(&LockStateController::OnSessionStateChangeTimeout,
                     base::Unretained(this), requested_session_state));
}

void LockStateController::OnSessionStateChangeTimeout(
    RequestedSessionState requested_session_state) {
  if (requested_session_state == RequestedSessionState::kShutdown ||
      requested_session_state == RequestedSessionState::kCancelableShutdown) {
    VLOG(1) << "OnSessionStateChangeTimeout with shutdown requested";
  }
  switch (requested_session_state) {
    case RequestedSessionState::kShutdown:
    case RequestedSessionState::kCancelableShutdown:
      DCHECK(shutting_down_);
      DCHECK(shutdown_reason_);
      shutdown_controller_->ShutDownOrReboot(*shutdown_reason_);
      break;
    case RequestedSessionState::kRestart:
      std::move(restart_callback_).Run();
      break;
    case RequestedSessionState::kSignOut:
      Shell::Get()->session_controller()->RequestSignOut();
      break;
  }
}

void LockStateController::SessionStateChangeWithInformedRestore(
    RequestedSessionState requested_session_state) {
  const base::FilePath file_path = GetInformedRestoreImagePath();

  if (!ShouldTakeInformedRestoreScreenshot()) {
    DeleteInformedRestoreImage(informed_restore_image_callback_for_test_,
                               file_path);
    StartSessionStateChange(requested_session_state);
    return;
  }

  // Check if there are any content currently on the screen that are restricted
  // by DLP.
  CaptureModeController::Get()->CheckScreenCaptureDlpRestrictions(
      base::BindOnce(
          &LockStateController::OnDlpRestrictionCheckedAtScreenCapture,
          weak_ptr_factory_.GetWeakPtr(), requested_session_state, file_path));
}

void LockStateController::OnDlpRestrictionCheckedAtScreenCapture(
    RequestedSessionState requested_session_state,
    const base::FilePath& file_path,
    bool proceed) {
  if (!proceed) {
    RecordScreenshotOnShutdownStatus(ScreenshotOnShutdownStatus::kFailedOnDLP);
    StartSessionStateChange(requested_session_state);
    return;
  }

  // TODO(b/319921650): Finalize the expected behavior on multi-display.
  auto* root = Shell::GetRootWindowForNewWindows();

  // Create a new layer that mirrors the painted wallpaper view layer. Adds it
  // to be the bottom-most child of the shutdown screenshot container layer,
  // which is the parent of the active desk container also the container that we
  // are going to take the informed restore screenshot. With this,
  // 1) wallpaper will be included in the screenshot besides the content of the
  //    active desk.
  // 2) screenshot will be taken on the whole desktop instead of the specific
  //    area with windows. This guarantees the windows' relative position inside
  //    the desktop.
  auto* wallpaper_layer = RootWindowController::ForWindow(root)
                              ->wallpaper_widget_controller()
                              ->wallpaper_view()
                              ->layer();
  CHECK(wallpaper_layer && wallpaper_layer->children().empty());
  mirror_wallpaper_layer_ = wallpaper_layer->Mirror();

  auto* informed_restore_screenshot_container =
      root->GetChildById(kShellWindowId_ShutdownScreenshotContainer);
  auto* shutdown_screenshot_layer =
      informed_restore_screenshot_container->layer();
  shutdown_screenshot_layer->Add(mirror_wallpaper_layer_.get());
  shutdown_screenshot_layer->StackAtBottom(mirror_wallpaper_layer_.get());

  if (!disable_screenshot_timeout_for_test_) {
    // Trigger the `take_screenshot_fail_timer_` and start taking the screenshot
    // at the same time. If the timer timeouts before receiving the screenshot,
    // shutdown process will be triggered without the screenshot.
    take_screenshot_fail_timer_.Start(
        FROM_HERE, kTakeScreenshotFailTimeout,
        base::BindOnce(&LockStateController::OnTakeScreenshotFailTimeout,
                       base::Unretained(this), requested_session_state));
  }

  if (auto* workspace_controller = GetWorkspaceController(
          desks_util::GetActiveDeskContainerForRoot(root))) {
    if (BackdropController* backdrop_controller =
            workspace_controller->layout_manager()->backdrop_controller()) {
      backdrop_controller->HideOnTakingInformedRestoreScreenshot();
    }
  }

  // Take the screenshot on the shutdown screenshot container, thus the float
  // and the always on top windows will be included in the screenshot as well.
  ui::GrabWindowSnapshot(
      informed_restore_screenshot_container,
      /*source_rect=*/
      gfx::Rect(informed_restore_screenshot_container->bounds().size()),
      base::BindOnce(&LockStateController::OnInformedRestoreImageTaken,
                     weak_ptr_factory_.GetWeakPtr(), requested_session_state,
                     file_path, base::TimeTicks::Now()));
}

void LockStateController::StartSessionStateChange(
    RequestedSessionState requested_session_state) {
  if (requested_session_state == RequestedSessionState::kCancelableShutdown &&
      shutdown_canceled_) {
    return;
  }

  animator_->StartAnimation(
      SessionStateAnimator::ROOT_CONTAINER,
      SessionStateAnimator::ANIMATION_GRAYSCALE_BRIGHTNESS,
      SessionStateAnimator::ANIMATION_SPEED_SHUTDOWN);

  if (requested_session_state == RequestedSessionState::kCancelableShutdown) {
    StartPreShutdownAnimationTimer();
  } else {
    StartSessionStateChangeTimer(/*with_animation_time=*/true,
                                 requested_session_state);
  }
}

void LockStateController::OnTakeScreenshotFailTimeout(
    RequestedSessionState requested_session_state) {
  SaveInformedRestoreScreenshotDuration(
      local_state_, prefs::kInformedRestoreScreenshotTakenDuration,
      kTakeScreenshotFailTimeout);
  RecordScreenshotOnShutdownStatus(
      ScreenshotOnShutdownStatus::kFailedOnTakingScreenshotTimeout);
  mirror_wallpaper_layer_.reset();
  DeleteInformedRestoreImage(informed_restore_image_callback_for_test_,
                             GetInformedRestoreImagePath());
  StartSessionStateChange(requested_session_state);
}

void LockStateController::OnInformedRestoreImageTaken(
    RequestedSessionState requested_session_state,
    const base::FilePath& file_path,
    base::TimeTicks start_time,
    gfx::Image informed_restore_image) {
  // Do not proceed if the `take_screenshot_fail_timer_` is stopped, which means
  // taking screenshot process took too long and the shutdown process has been
  // triggered without the informed restore image.
  if (!disable_screenshot_timeout_for_test_ &&
      !take_screenshot_fail_timer_.IsRunning()) {
    return;
  }

  take_screenshot_fail_timer_.Stop();
  SaveInformedRestoreScreenshotDuration(
      local_state_, prefs::kInformedRestoreScreenshotTakenDuration,
      base::TimeTicks::Now() - start_time);

  mirror_wallpaper_layer_.reset();

  base::ThreadPool::PostTaskAndReply(
      FROM_HERE,
      {base::MayBlock(), base::TaskPriority::HIGHEST,
       base::TaskShutdownBehavior::BLOCK_SHUTDOWN},
      base::BindOnce(&EncodeAndSaveImage, file_path,
                     std::move(informed_restore_image)),
      base::BindOnce(&LockStateController::OnInformedRestoreImageSaved,
                     weak_ptr_factory_.GetWeakPtr(), base::TimeTicks::Now(),
                     file_path));

  StartSessionStateChange(requested_session_state);
}

void LockStateController::OnInformedRestoreImageSaved(
    base::TimeTicks start_time,
    const base::FilePath& file_path) {
  SaveInformedRestoreScreenshotDuration(
      local_state_, prefs::kInformedRestoreScreenshotEncodeAndSaveDuration,
      // This duration includes the time waiting for the `ThreadPool` to start
      // running the task, also the time that the UI thread waits to get the
      // reply from the `ThreadPool`.
      base::TimeTicks::Now() - start_time);
  RecordScreenshotOnShutdownStatus(ScreenshotOnShutdownStatus::kSucceeded);
  if (shutdown_canceled_) {
    DeleteInformedRestoreImage(informed_restore_image_callback_for_test_,
                               file_path);
  }
  if (informed_restore_image_callback_for_test_) {
    std::move(informed_restore_image_callback_for_test_).Run();
  }
}

void LockStateController::DoRestart(power_manager::RequestRestartReason reason,
                                    const std::string& description) {
  chromeos::PowerManagerClient::Get()->RequestRestart(reason, description);
}

}  // namespace ash