chromium/ash/system/power/power_event_observer_unittest.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.

#include "ash/system/power/power_event_observer.h"

#include <memory>

#include "ash/display/projecting_observer.h"
#include "ash/root_window_controller.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "ash/system/power/power_event_observer_test_api.h"
#include "ash/test/ash_test_base.h"
#include "ash/wallpaper/views/wallpaper_widget_controller.h"
#include "ash/wm/lock_state_controller.h"
#include "ash/wm/lock_state_controller_test_api.h"
#include "ash/wm/test/test_session_state_animator.h"
#include "base/memory/raw_ptr.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/time/time.h"
#include "chromeos/ash/components/feature_usage/feature_usage_metrics.h"
#include "chromeos/dbus/power/fake_power_manager_client.h"
#include "chromeos/dbus/power_manager/suspend.pb.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "ui/aura/window.h"
#include "ui/aura/window_tree_host.h"
#include "ui/compositor/compositor.h"
#include "ui/compositor/scoped_animation_duration_scale_mode.h"
#include "ui/display/manager/test/fake_display_snapshot.h"

namespace ash {

class PowerEventObserverTest : public AshTestBase {
 public:
  PowerEventObserverTest() = default;

  PowerEventObserverTest(const PowerEventObserverTest&) = delete;
  PowerEventObserverTest& operator=(const PowerEventObserverTest&) = delete;

  ~PowerEventObserverTest() override = default;

  // AshTestBase:
  void SetUp() override {
    AshTestBase::SetUp();
    observer_ = Shell::Get()->power_event_observer();
  }

  void TearDown() override { AshTestBase::TearDown(); }

 protected:
  int GetNumVisibleCompositors() {
    int result = 0;
    for (aura::Window* window : Shell::GetAllRootWindows()) {
      if (window->GetHost()->compositor()->IsVisible())
        ++result;
    }

    return result;
  }

  bool GetLockedState() {
    // LockScreen is an async mojo call.
    GetSessionControllerClient()->FlushForTest();
    return Shell::Get()->session_controller()->IsScreenLocked();
  }

  raw_ptr<PowerEventObserver, DanglingUntriaged> observer_ = nullptr;
};

TEST_F(PowerEventObserverTest, LockBeforeSuspend) {
  chromeos::FakePowerManagerClient* client =
      chromeos::FakePowerManagerClient::Get();
  ASSERT_EQ(0, client->num_pending_suspend_readiness_callbacks());

  // Check that the observer requests a suspend-readiness callback when it hears
  // that the system is about to suspend.
  SetCanLockScreen(true);
  SetShouldLockScreenAutomatically(true);
  observer_->SuspendImminent(power_manager::SuspendImminent_Reason_OTHER);
  EXPECT_EQ(1, client->num_pending_suspend_readiness_callbacks());

  // It should run the callback when it hears that the screen is locked and the
  // lock screen animations have completed.
  BlockUserSession(BLOCKED_BY_LOCK_SCREEN);

  PowerEventObserverTestApi test_api(observer_);

  ui::Compositor* compositor =
      Shell::GetPrimaryRootWindow()->GetHost()->compositor();

  test_api.CompositingDidCommit(compositor);
  observer_->OnLockAnimationsComplete();

  // Verify that CompositingStarted and CompositingAckDeprecated observed before
  // CompositingDidCommit are ignored.
  test_api.CompositingStarted(compositor);
  test_api.CompositingAckDeprecated(compositor);
  EXPECT_EQ(1, client->num_pending_suspend_readiness_callbacks());
  EXPECT_EQ(1, GetNumVisibleCompositors());

  // Suspend should remain delayed after first compositing cycle ends.
  test_api.CompositeFrame(compositor);
  EXPECT_EQ(1, client->num_pending_suspend_readiness_callbacks());
  EXPECT_EQ(1, GetNumVisibleCompositors());

  test_api.CompositingDidCommit(compositor);
  test_api.CompositingStarted(compositor);
  EXPECT_EQ(1, client->num_pending_suspend_readiness_callbacks());
  EXPECT_EQ(1, GetNumVisibleCompositors());

  test_api.CompositingAckDeprecated(compositor);
  EXPECT_EQ(0, client->num_pending_suspend_readiness_callbacks());
  EXPECT_EQ(0, GetNumVisibleCompositors());

  // If the system is already locked, no callback should be requested.
  observer_->SuspendDoneEx(power_manager::SuspendDone());
  EXPECT_EQ(1, GetNumVisibleCompositors());
  UnblockUserSession();
  BlockUserSession(BLOCKED_BY_LOCK_SCREEN);

  // Notify that lock animation is complete.
  observer_->OnLockAnimationsComplete();

  // Wait for a compositing after lock animation completes before suspending.
  // In this case compositors should be made invisible immediately
  test_api.CompositeFrame(compositor);
  test_api.CompositeFrame(compositor);

  observer_->SuspendImminent(power_manager::SuspendImminent_Reason_OTHER);
  EXPECT_EQ(0, client->num_pending_suspend_readiness_callbacks());
  EXPECT_EQ(0, GetNumVisibleCompositors());

  // It also shouldn't request a callback if it isn't instructed to lock the
  // screen.
  observer_->SuspendDoneEx(power_manager::SuspendDone());
  UnblockUserSession();
  SetShouldLockScreenAutomatically(false);
  EXPECT_EQ(1, GetNumVisibleCompositors());
  observer_->SuspendImminent(power_manager::SuspendImminent_Reason_OTHER);
  EXPECT_EQ(0, client->num_pending_suspend_readiness_callbacks());
  EXPECT_EQ(0, GetNumVisibleCompositors());
}

TEST_F(PowerEventObserverTest, SetInvisibleBeforeSuspend) {
  // Tests that all the Compositors are marked invisible before a suspend
  // request when the screen is not supposed to be locked before a suspend.
  EXPECT_EQ(1, GetNumVisibleCompositors());

  observer_->SuspendImminent(power_manager::SuspendImminent_Reason_OTHER);
  EXPECT_EQ(0, GetNumVisibleCompositors());
  observer_->SuspendDoneEx(power_manager::SuspendDone());

  // Tests that all the Compositors are marked invisible _after_ the screen lock
  // animations have completed.
  SetCanLockScreen(true);
  SetShouldLockScreenAutomatically(true);

  observer_->SuspendImminent(power_manager::SuspendImminent_Reason_OTHER);
  EXPECT_EQ(1, GetNumVisibleCompositors());

  BlockUserSession(BLOCKED_BY_LOCK_SCREEN);
  EXPECT_EQ(1, GetNumVisibleCompositors());

  observer_->OnLockAnimationsComplete();

  EXPECT_EQ(1, GetNumVisibleCompositors());
  ASSERT_TRUE(PowerEventObserverTestApi(observer_)
                  .SimulateCompositorsReadyForSuspend());
  EXPECT_EQ(0, GetNumVisibleCompositors());

  observer_->SuspendDoneEx(power_manager::SuspendDone());
  EXPECT_EQ(1, GetNumVisibleCompositors());
}

TEST_F(PowerEventObserverTest, CanceledSuspend) {
  // Tests that the Compositors are not marked invisible if a suspend is
  // canceled or the system resumes before the lock screen is ready.
  SetCanLockScreen(true);
  SetShouldLockScreenAutomatically(true);
  observer_->SuspendImminent(power_manager::SuspendImminent_Reason_OTHER);
  EXPECT_EQ(1, GetNumVisibleCompositors());

  observer_->SuspendDoneEx(power_manager::SuspendDone());
  BlockUserSession(BLOCKED_BY_LOCK_SCREEN);
  observer_->OnLockAnimationsComplete();
  EXPECT_EQ(1, GetNumVisibleCompositors());
}

TEST_F(PowerEventObserverTest, DelayResuspendForLockAnimations) {
  // Tests that the following order of events is handled correctly:
  //
  // - A suspend request is started.
  // - The screen is locked.
  // - The suspend request is canceled.
  // - Another suspend request is started.
  // - The screen lock animations complete.
  // - The screen lock UI changes get composited
  //
  // In this case, the observer should block the second suspend request until
  // the screen lock compositing is done.
  SetCanLockScreen(true);
  SetShouldLockScreenAutomatically(true);

  chromeos::FakePowerManagerClient* client =
      chromeos::FakePowerManagerClient::Get();
  observer_->SuspendImminent(power_manager::SuspendImminent_Reason_OTHER);
  EXPECT_EQ(1, client->num_pending_suspend_readiness_callbacks());

  BlockUserSession(BLOCKED_BY_LOCK_SCREEN);
  observer_->SuspendDoneEx(power_manager::SuspendDone());
  observer_->SuspendImminent(power_manager::SuspendImminent_Reason_OTHER);

  // The expected number of suspend readiness callbacks is 2 because the
  // observer has not run the callback that it got from the first suspend
  // request.  The real PowerManagerClient would reset its internal counter in
  // this situation but the stub client is not that smart.
  EXPECT_EQ(2, client->num_pending_suspend_readiness_callbacks());

  observer_->OnLockAnimationsComplete();
  EXPECT_EQ(2, client->num_pending_suspend_readiness_callbacks());
  EXPECT_EQ(1, GetNumVisibleCompositors());

  ASSERT_TRUE(PowerEventObserverTestApi(observer_)
                  .SimulateCompositorsReadyForSuspend());
  EXPECT_EQ(1, client->num_pending_suspend_readiness_callbacks());
  EXPECT_EQ(0, GetNumVisibleCompositors());
}

// Tests that device suspend is delayed for screen lock until the screen lock
// changes are composited for all root windows.
TEST_F(PowerEventObserverTest, DelaySuspendForCompositing_MultiDisplay) {
  SetCanLockScreen(true);
  SetShouldLockScreenAutomatically(true);

  UpdateDisplay("200x100,300x200");

  chromeos::FakePowerManagerClient* client =
      chromeos::FakePowerManagerClient::Get();
  observer_->SuspendImminent(power_manager::SuspendImminent_Reason_OTHER);
  EXPECT_EQ(1, client->num_pending_suspend_readiness_callbacks());

  aura::Window::Windows windows = Shell::GetAllRootWindows();
  ASSERT_EQ(2u, windows.size());

  ui::Compositor* primary_compositor = windows[0]->GetHost()->compositor();
  ui::Compositor* secondary_compositor = windows[1]->GetHost()->compositor();
  ASSERT_EQ(2, GetNumVisibleCompositors());
  EXPECT_EQ(1, client->num_pending_suspend_readiness_callbacks());

  PowerEventObserverTestApi test_api(observer_);

  // Simulate a commit before lock animations complete, and verify associated
  // compositing ends are ignored.
  test_api.CompositingDidCommit(secondary_compositor);
  observer_->OnLockAnimationsComplete();

  test_api.CompositingStarted(secondary_compositor);
  test_api.CompositingAckDeprecated(secondary_compositor);

  EXPECT_EQ(1, client->num_pending_suspend_readiness_callbacks());
  EXPECT_EQ(2, GetNumVisibleCompositors());

  test_api.CompositeFrame(primary_compositor);
  test_api.CompositeFrame(primary_compositor);

  test_api.CompositeFrame(secondary_compositor);

  // Even though compositing for one display is done, changes to compositor
  // visibility, and suspend readiness state should be delayed until compositing
  // for the other display finishes.
  EXPECT_EQ(1, client->num_pending_suspend_readiness_callbacks());
  EXPECT_EQ(2, GetNumVisibleCompositors());

  test_api.CompositeFrame(secondary_compositor);
  EXPECT_EQ(0, client->num_pending_suspend_readiness_callbacks());
  EXPECT_EQ(0, GetNumVisibleCompositors());
}

TEST_F(PowerEventObserverTest,
       DISABLED_DelaySuspendForCompositing_PendingDisplayRemoved) {
  SetCanLockScreen(true);
  SetShouldLockScreenAutomatically(true);

  UpdateDisplay("200x100,300x200");

  chromeos::FakePowerManagerClient* client =
      chromeos::FakePowerManagerClient::Get();
  observer_->SuspendImminent(power_manager::SuspendImminent_Reason_OTHER);
  EXPECT_EQ(1, client->num_pending_suspend_readiness_callbacks());

  aura::Window::Windows windows = Shell::GetAllRootWindows();
  ASSERT_EQ(2u, windows.size());

  ui::Compositor* primary_compositor = windows[0]->GetHost()->compositor();
  ASSERT_EQ(2, GetNumVisibleCompositors());
  EXPECT_EQ(1, client->num_pending_suspend_readiness_callbacks());
  observer_->OnLockAnimationsComplete();

  PowerEventObserverTestApi test_api(observer_);

  test_api.CompositeFrame(primary_compositor);
  test_api.CompositeFrame(primary_compositor);

  // Even though compositing for one display is done, changes to compositor
  // visibility, and suspend readiness state should be delayed until compositing
  // for the other display finishes.
  EXPECT_EQ(1, client->num_pending_suspend_readiness_callbacks());
  EXPECT_EQ(2, GetNumVisibleCompositors());

  // Remove the second display, and verify the remaining compositor is hidden
  // at this point.
  UpdateDisplay("200x100");
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(0, client->num_pending_suspend_readiness_callbacks());
  EXPECT_EQ(0, GetNumVisibleCompositors());
}

TEST_F(PowerEventObserverTest, CompositorNotVisibleAtLockAnimationsComplete) {
  SetCanLockScreen(true);
  SetShouldLockScreenAutomatically(true);

  chromeos::FakePowerManagerClient* client =
      chromeos::FakePowerManagerClient::Get();
  observer_->SuspendImminent(power_manager::SuspendImminent_Reason_OTHER);
  EXPECT_EQ(1, client->num_pending_suspend_readiness_callbacks());

  Shell::GetPrimaryRootWindow()->GetHost()->compositor()->SetVisible(false);

  observer_->OnLockAnimationsComplete();
  EXPECT_EQ(1, client->num_pending_suspend_readiness_callbacks());
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(0, client->num_pending_suspend_readiness_callbacks());
  EXPECT_EQ(0, GetNumVisibleCompositors());
}

// Tests that for suspend imminent induced locking screen, locking animations
// are immediate.
TEST_F(PowerEventObserverTest, ImmediateLockAnimations) {
  TestSessionStateAnimator* test_animator = new TestSessionStateAnimator;
  LockStateController* lock_state_controller =
      Shell::Get()->lock_state_controller();
  lock_state_controller->set_animator_for_test(test_animator);
  LockStateControllerTestApi lock_state_test_api(lock_state_controller);
  SetCanLockScreen(true);
  SetShouldLockScreenAutomatically(true);
  ASSERT_FALSE(GetLockedState());

  observer_->SuspendImminent(power_manager::SuspendImminent_Reason_OTHER);
  // Tests that locking animation starts.
  EXPECT_TRUE(lock_state_test_api.is_animating_lock());

  // Tests that we have two active animation containers for pre-lock animation,
  // which are non lock screen containers and shelf container.
  EXPECT_EQ(2u, test_animator->GetAnimationCount());
  test_animator->AreContainersAnimated(
      LockStateController::kPreLockContainersMask,
      SessionStateAnimator::ANIMATION_HIDE_IMMEDIATELY);
  // Tests that after finishing immediate animation, we have no active
  // animations left.
  test_animator->Advance(test_animator->GetDuration(
      SessionStateAnimator::ANIMATION_SPEED_IMMEDIATE));
  EXPECT_EQ(0u, test_animator->GetAnimationCount());

  // Flushes locking screen async request to start post-lock animation.
  EXPECT_TRUE(GetLockedState());
  EXPECT_TRUE(lock_state_test_api.is_animating_lock());
  // Tests that we have two active animation container for post-lock animation,
  // which are lock screen containers and shelf container.
  EXPECT_EQ(2u, test_animator->GetAnimationCount());
  test_animator->AreContainersAnimated(
      SessionStateAnimator::LOCK_SCREEN_CONTAINERS,
      SessionStateAnimator::ANIMATION_RAISE_TO_SCREEN);
  test_animator->AreContainersAnimated(SessionStateAnimator::SHELF,
                                       SessionStateAnimator::ANIMATION_FADE_IN);
  // Tests that after finishing immediate animation, we have no active
  // animations left. Also checks that animation ends.
  test_animator->Advance(test_animator->GetDuration(
      SessionStateAnimator::ANIMATION_SPEED_IMMEDIATE));
  EXPECT_EQ(0u, test_animator->GetAnimationCount());
  EXPECT_FALSE(lock_state_test_api.is_animating_lock());
}

// Tests that displays will not be considered ready to suspend until the
// animated wallpaper change finishes (if the wallpaper is being animated to
// another wallpaper after the screen is locked).
// Flaky: https://crbug.com/1293178
TEST_F(PowerEventObserverTest,
       DISABLED_DisplaysNotReadyForSuspendUntilWallpaperAnimationEnds) {
  chromeos::FakePowerManagerClient* client =
      chromeos::FakePowerManagerClient::Get();
  ASSERT_EQ(0, client->num_pending_suspend_readiness_callbacks());

  SetCanLockScreen(true);
  SetShouldLockScreenAutomatically(true);

  // Set up animation state so wallpaper widget animations are not ended on
  // their creation.
  ui::ScopedAnimationDurationScaleMode test_duration_mode(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);

  // Lock screen - this is expected to start wallpaper change (e.g. to a
  // widget with a blurred wallpaper).
  BlockUserSession(BLOCKED_BY_LOCK_SCREEN);
  observer_->OnLockAnimationsComplete();

  WallpaperWidgetController* wallpaper_widget_controller =
      Shell::GetPrimaryRootWindowController()->wallpaper_widget_controller();
  // Assert that the wallpaper is being animated here - otherwise the test will
  // not work.
  ASSERT_TRUE(wallpaper_widget_controller->IsAnimating());

  ui::Compositor* compositor =
      Shell::GetPrimaryRootWindow()->GetHost()->compositor();
  PowerEventObserverTestApi test_api(observer_);

  // Simulate a single frame getting composited before the wallpaper animation
  // is done - this frame is expected to be ignored by power event observer's
  // compositing state observer.
  test_api.CompositeFrame(compositor);

  // Simulate wallpaper animation finishing - for the purpose of this test,
  // before suspend begins.
  wallpaper_widget_controller->StopAnimating();

  // Expect that two compositing cycles are completed before suspend continues,
  // and displays get suspended.
  test_api.CompositeFrame(compositor);
  observer_->SuspendImminent(power_manager::SuspendImminent_Reason_OTHER);
  EXPECT_EQ(1, client->num_pending_suspend_readiness_callbacks());
  EXPECT_EQ(1, GetNumVisibleCompositors());

  test_api.CompositeFrame(compositor);
  EXPECT_EQ(0, client->num_pending_suspend_readiness_callbacks());
  EXPECT_EQ(0, GetNumVisibleCompositors());
}

// Tests that animated wallpaper changes will be finished immediately when
// suspend starts (if the screen was locked when suspend started).
TEST_F(PowerEventObserverTest, EndWallpaperAnimationOnSuspendWhileLocked) {
  chromeos::FakePowerManagerClient* client =
      chromeos::FakePowerManagerClient::Get();
  ASSERT_EQ(0, client->num_pending_suspend_readiness_callbacks());

  SetCanLockScreen(true);
  SetShouldLockScreenAutomatically(true);

  // Set up animation state so wallpaper widget animations are not ended on
  // their creation.
  ui::ScopedAnimationDurationScaleMode test_duration_mode(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);

  // Lock screen - this is expected to start wallpaper change (e.g. to a
  // widget with a blurred wallpaper).
  BlockUserSession(BLOCKED_BY_LOCK_SCREEN);
  observer_->OnLockAnimationsComplete();

  // Wallpaper animation should be stopped immediately on suspend.
  observer_->SuspendImminent(power_manager::SuspendImminent_Reason_OTHER);

  WallpaperWidgetController* wallpaper_widget_controller =
      Shell::GetPrimaryRootWindowController()->wallpaper_widget_controller();
  EXPECT_FALSE(wallpaper_widget_controller->IsAnimating());

  ui::Compositor* compositor =
      Shell::GetPrimaryRootWindow()->GetHost()->compositor();
  PowerEventObserverTestApi test_api(observer_);

  // Expect that two compositing cycles are completed before suspend continues,
  // and displays get suspended.
  test_api.CompositeFrame(compositor);
  EXPECT_EQ(1, client->num_pending_suspend_readiness_callbacks());
  EXPECT_EQ(1, GetNumVisibleCompositors());

  test_api.CompositeFrame(compositor);
  EXPECT_EQ(0, client->num_pending_suspend_readiness_callbacks());
  EXPECT_EQ(0, GetNumVisibleCompositors());
}

// Tests that animated wallpaper changes will be finished immediately when
// suspend starts (if the screen lock started before suspend).
TEST_F(PowerEventObserverTest, EndWallpaperAnimationOnSuspendWhileLocking) {
  chromeos::FakePowerManagerClient* client =
      chromeos::FakePowerManagerClient::Get();
  ASSERT_EQ(0, client->num_pending_suspend_readiness_callbacks());

  SetCanLockScreen(true);
  SetShouldLockScreenAutomatically(true);

  // Set up animation state so wallpaper widget animations are not ended on
  // their creation.
  ui::ScopedAnimationDurationScaleMode test_duration_mode(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);

  // Lock screen - this is expected to start wallpaper change (e.g. to a
  // widget with a blurred wallpaper).
  BlockUserSession(BLOCKED_BY_LOCK_SCREEN);

  // If suspend starts, wallpaper animation should be stopped after screen lock
  // completes.
  observer_->SuspendImminent(power_manager::SuspendImminent_Reason_OTHER);
  observer_->OnLockAnimationsComplete();

  WallpaperWidgetController* wallpaper_widget_controller =
      Shell::GetPrimaryRootWindowController()->wallpaper_widget_controller();
  EXPECT_FALSE(wallpaper_widget_controller->IsAnimating());

  ui::Compositor* compositor =
      Shell::GetPrimaryRootWindow()->GetHost()->compositor();
  PowerEventObserverTestApi test_api(observer_);

  // Expect that two compositing cycles are completed before suspend continues,
  // and displays get suspended.
  test_api.CompositeFrame(compositor);
  EXPECT_EQ(1, client->num_pending_suspend_readiness_callbacks());
  EXPECT_EQ(1, GetNumVisibleCompositors());

  test_api.CompositeFrame(compositor);
  EXPECT_EQ(0, client->num_pending_suspend_readiness_callbacks());
  EXPECT_EQ(0, GetNumVisibleCompositors());
}

// Tests that animated wallpaper changes will be finished immediately when
// suspend starts and causes a screen lock.
TEST_F(PowerEventObserverTest, EndWallpaperAnimationAfterLockDueToSuspend) {
  chromeos::FakePowerManagerClient* client =
      chromeos::FakePowerManagerClient::Get();
  ASSERT_EQ(0, client->num_pending_suspend_readiness_callbacks());

  SetCanLockScreen(true);
  SetShouldLockScreenAutomatically(true);

  // Set up animation state so wallpaper widget animations are not ended on
  // their creation.
  ui::ScopedAnimationDurationScaleMode test_duration_mode(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);

  // Start suspend (which should start screen lock) - verify that wallpaper is
  // not animating after the screen lock animations are reported as complete.
  observer_->SuspendImminent(power_manager::SuspendImminent_Reason_OTHER);
  observer_->OnLockAnimationsComplete();

  WallpaperWidgetController* wallpaper_widget_controller =
      Shell::GetPrimaryRootWindowController()->wallpaper_widget_controller();
  EXPECT_FALSE(wallpaper_widget_controller->IsAnimating());

  ui::Compositor* compositor =
      Shell::GetPrimaryRootWindow()->GetHost()->compositor();
  PowerEventObserverTestApi test_api(observer_);

  // Expect that two compositing cycles are completed before suspend continues,
  // and displays get suspended.
  test_api.CompositeFrame(compositor);
  EXPECT_EQ(1, client->num_pending_suspend_readiness_callbacks());
  EXPECT_EQ(1, GetNumVisibleCompositors());

  test_api.CompositeFrame(compositor);
  EXPECT_EQ(0, client->num_pending_suspend_readiness_callbacks());
  EXPECT_EQ(0, GetNumVisibleCompositors());
}

// Tests that removing a display while power event observer is waiting for the
// wallpaper animation does not cause suspend to hang.
TEST_F(PowerEventObserverTest, DisplayRemovedDuringWallpaperAnimation) {
  chromeos::FakePowerManagerClient* client =
      chromeos::FakePowerManagerClient::Get();
  ASSERT_EQ(0, client->num_pending_suspend_readiness_callbacks());

  SetCanLockScreen(true);
  SetShouldLockScreenAutomatically(true);

  UpdateDisplay("200x100,300x200");

  // Set up animation state so wallpaper widget animations are not ended on
  // their creation.
  ui::ScopedAnimationDurationScaleMode test_duration_mode(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);

  // Lock screen - this is expected to start wallpaper change (e.g. to a
  // widget with a blurred wallpaper).
  BlockUserSession(BLOCKED_BY_LOCK_SCREEN);
  observer_->OnLockAnimationsComplete();

  // Remove a display before wallpaper animation ends.
  UpdateDisplay("200x100");
  base::RunLoop().RunUntilIdle();

  // Start suspend and verify the suspend proceeds when the primary window's
  // compositors go through two compositing cycles.
  observer_->SuspendImminent(power_manager::SuspendImminent_Reason_OTHER);

  ui::Compositor* compositor =
      Shell::GetPrimaryRootWindow()->GetHost()->compositor();
  PowerEventObserverTestApi test_api(observer_);

  // Expect that two compositing cycles are completed before suspend continues,
  // and displays get suspended.
  test_api.CompositeFrame(compositor);
  test_api.CompositeFrame(compositor);
  EXPECT_EQ(0, client->num_pending_suspend_readiness_callbacks());
  EXPECT_EQ(0, GetNumVisibleCompositors());
}

TEST_F(PowerEventObserverTest, LockOnLidClose) {
  // Screen should not lock if values not set.
  SetCanLockScreen(false);
  SetShouldLockScreenAutomatically(false);
  observer_->LidEventReceived(chromeos::PowerManagerClient::LidState::CLOSED,
                              base::TimeTicks::Now());
  EXPECT_FALSE(GetLockedState());

  SetCanLockScreen(false);
  SetShouldLockScreenAutomatically(true);
  observer_->LidEventReceived(chromeos::PowerManagerClient::LidState::CLOSED,
                              base::TimeTicks::Now());
  EXPECT_FALSE(GetLockedState());

  SetCanLockScreen(true);
  SetShouldLockScreenAutomatically(false);
  observer_->LidEventReceived(chromeos::PowerManagerClient::LidState::CLOSED,
                              base::TimeTicks::Now());
  EXPECT_FALSE(GetLockedState());

  // Screen should only lock on CLOSED event.
  SetCanLockScreen(true);
  SetShouldLockScreenAutomatically(true);
  observer_->LidEventReceived(chromeos::PowerManagerClient::LidState::OPEN,
                              base::TimeTicks::Now());
  EXPECT_FALSE(GetLockedState());
  observer_->LidEventReceived(chromeos::PowerManagerClient::LidState::CLOSED,
                              base::TimeTicks::Now());
  EXPECT_TRUE(GetLockedState());
}

TEST_F(PowerEventObserverTest, LockOnLidCloseWhenDocked) {
  std::unique_ptr<display::DisplaySnapshot> internal_display =
      display::FakeDisplaySnapshot::Builder()
          .SetId(123)
          .SetNativeMode(gfx::Size(1024, 768))
          .SetType(display::DISPLAY_CONNECTION_TYPE_INTERNAL)
          .Build();

  std::unique_ptr<display::DisplaySnapshot> external_display =
      display::FakeDisplaySnapshot::Builder()
          .SetId(456)
          .SetNativeMode(gfx::Size(1024, 768))
          .SetType(display::DISPLAY_CONNECTION_TYPE_VGA)
          .Build();

  auto set_docked = [&](bool docked) {
    std::vector<raw_ptr<display::DisplaySnapshot, VectorExperimental>> displays(
        {internal_display.get()});
    if (docked) {
      displays.push_back(external_display.get());
    }
    Shell::Get()->projecting_observer()->OnDisplayConfigurationChanged(
        displays);
  };

  SetCanLockScreen(true);
  SetShouldLockScreenAutomatically(true);

  // Closing lid should not lock when projecting.
  set_docked(true);
  observer_->LidEventReceived(chromeos::PowerManagerClient::LidState::CLOSED,
                              base::TimeTicks::Now());
  EXPECT_FALSE(GetLockedState());

  // Opening lid, then disconnect display should not lock.
  observer_->LidEventReceived(chromeos::PowerManagerClient::LidState::OPEN,
                              base::TimeTicks::Now());
  set_docked(false);
  EXPECT_FALSE(GetLockedState());

  // Closing lid while projecting, then removing display should lock.
  set_docked(true);
  observer_->LidEventReceived(chromeos::PowerManagerClient::LidState::CLOSED,
                              base::TimeTicks::Now());
  EXPECT_FALSE(GetLockedState());
  set_docked(false);
  EXPECT_TRUE(GetLockedState());
}

class LockOnSuspendUsageTest : public PowerEventObserverTest {
 public:
  LockOnSuspendUsageTest() { set_start_session(false); }
};

TEST_F(LockOnSuspendUsageTest, LockOnSuspendUsage) {
  SetCanLockScreen(true);
  SetShouldLockScreenAutomatically(true);

  SimulateNewUserFirstLogin("[email protected]");
  PowerEventObserverTestApi test_api(observer_);
  ASSERT_TRUE(test_api.TrackingLockOnSuspendUsage());

  base::HistogramTester histogram_tester;
  observer_->SuspendImminent(power_manager::SuspendImminent_Reason_OTHER);
  EXPECT_THAT(
      histogram_tester.GetAllSamples("ChromeOS.FeatureUsage.LockOnSuspend"),
      ::testing::ElementsAre(
          base::Bucket(
              static_cast<int>(
                  feature_usage::FeatureUsageMetrics::Event::kEligible),
              1),
          base::Bucket(static_cast<int>(
                           feature_usage::FeatureUsageMetrics::Event::kEnabled),
                       1),
          base::Bucket(
              static_cast<int>(
                  feature_usage::FeatureUsageMetrics::Event::kUsedWithSuccess),
              1)));
}

// TODO(crbug.com/40898491): Test is failing on "Linux ChromiumOS MSan Tests".
#if defined(MEMORY_SANITIZER)
#define MAYBE_No_ShouldLockScreenAutomatically \
  DISABLED_No_ShouldLockScreenAutomatically
#else
#define MAYBE_No_ShouldLockScreenAutomatically No_ShouldLockScreenAutomatically
#endif
TEST_F(LockOnSuspendUsageTest, MAYBE_No_ShouldLockScreenAutomatically) {
  SetCanLockScreen(true);
  SetShouldLockScreenAutomatically(false);

  SimulateNewUserFirstLogin("[email protected]");
  PowerEventObserverTestApi test_api(observer_);
  ASSERT_TRUE(test_api.TrackingLockOnSuspendUsage());

  base::HistogramTester histogram_tester;
  observer_->SuspendImminent(power_manager::SuspendImminent_Reason_OTHER);
  histogram_tester.ExpectTotalCount("ChromeOS.FeatureUsage.LockOnSuspend", 0);
}

TEST_F(LockOnSuspendUsageTest, No_CanLockScreen) {
  SetCanLockScreen(false);
  SimulateNewUserFirstLogin("[email protected]");
  PowerEventObserverTestApi test_api(observer_);
  ASSERT_FALSE(test_api.TrackingLockOnSuspendUsage());
}

}  // namespace ash