diff options
-rw-r--r-- | ash/ash.gyp | 1 | ||||
-rw-r--r-- | ash/wm/session_state_animator.cc | 106 | ||||
-rw-r--r-- | ash/wm/session_state_animator.h | 4 | ||||
-rw-r--r-- | ash/wm/session_state_controller.h | 2 | ||||
-rw-r--r-- | ash/wm/session_state_controller_impl2.h | 4 | ||||
-rw-r--r-- | ash/wm/session_state_controller_impl2_unittest.cc | 617 |
6 files changed, 701 insertions, 33 deletions
diff --git a/ash/ash.gyp b/ash/ash.gyp index a89f96b..53cdc29 100644 --- a/ash/ash.gyp +++ b/ash/ash.gyp @@ -561,6 +561,7 @@ 'wm/panel_layout_manager_unittest.cc', 'wm/power_button_controller_unittest.cc', 'wm/screen_dimmer_unittest.cc', + 'wm/session_state_controller_impl2_unittest.cc', 'wm/shelf_layout_manager_unittest.cc', 'wm/system_gesture_event_filter_unittest.cc', 'wm/system_modal_container_layout_manager_unittest.cc', diff --git a/ash/wm/session_state_animator.cc b/ash/wm/session_state_animator.cc index 12ddf33..b082c98 100644 --- a/ash/wm/session_state_animator.cc +++ b/ash/wm/session_state_animator.cc @@ -254,6 +254,72 @@ class CallbackAnimationObserver : public ui::LayerAnimationObserver { DISALLOW_COPY_AND_ASSIGN(CallbackAnimationObserver); }; + +bool IsLayerAnimated(ui::Layer* layer, + SessionStateAnimator::AnimationType type) { + switch (type) { + case SessionStateAnimator::ANIMATION_PARTIAL_CLOSE: + if (layer->GetTargetTransform() != GetSlowCloseTransform()) + return false; + break; + case SessionStateAnimator::ANIMATION_UNDO_PARTIAL_CLOSE: + if (layer->GetTargetTransform() != gfx::Transform()) + return false; + break; + case SessionStateAnimator::ANIMATION_FULL_CLOSE: + if (layer->GetTargetTransform() != GetFastCloseTransform() || + layer->GetTargetOpacity() > 0.0001) + return false; + break; + case SessionStateAnimator::ANIMATION_FADE_IN: + if (layer->GetTargetOpacity() < 0.9999) + return false; + break; + case SessionStateAnimator::ANIMATION_HIDE_IMMEDIATELY: + if (layer->GetTargetOpacity() > 0.0001) + return false; + break; + case SessionStateAnimator::ANIMATION_RESTORE: + if (layer->opacity() < 0.9999 || layer->transform() != gfx::Transform()) + return false; + break; + case SessionStateAnimator::ANIMATION_GRAYSCALE_BRIGHTNESS: + if ((layer->GetTargetBrightness() < 0.9999) || + (layer->GetTargetGrayscale() < 0.9999)) + return false; + break; + case SessionStateAnimator::ANIMATION_UNDO_GRAYSCALE_BRIGHTNESS: + if ((layer->GetTargetBrightness() > 0.0001) || + (layer->GetTargetGrayscale() > 0.0001)) + return false; + break; + case SessionStateAnimator::ANIMATION_DROP: + //ToDo(antim) : check other effects + if (layer->GetTargetOpacity() < 0.9999) + return false; + break; + //ToDo(antim) : check other effects + case SessionStateAnimator::ANIMATION_LIFT: + if (layer->GetTargetOpacity() > 0.0001) + return false; + break; + case SessionStateAnimator::ANIMATION_RAISE_TO_SCREEN: + //ToDo(antim) : check other effects + if (layer->GetTargetOpacity() < 0.9999) + return false; + break; + //ToDo(antim) : check other effects + case SessionStateAnimator::ANIMATION_LOWER_BELOW_SCREEN: + if (layer->GetTargetOpacity() > 0.0001) + return false; + break; + default: + NOTREACHED() << "Unhandled animation type " << type; + return false; + } + return true; +} + } // namespace bool SessionStateAnimator::TestApi::ContainersAreAnimated( @@ -264,41 +330,19 @@ bool SessionStateAnimator::TestApi::ContainersAreAnimated( it != containers.end(); ++it) { aura::Window* window = *it; ui::Layer* layer = window->layer(); - - switch (type) { - case ANIMATION_PARTIAL_CLOSE: - if (layer->GetTargetTransform() != GetSlowCloseTransform()) - return false; - break; - case ANIMATION_UNDO_PARTIAL_CLOSE: - if (layer->GetTargetTransform() != gfx::Transform()) - return false; - break; - case ANIMATION_FULL_CLOSE: - if (layer->GetTargetTransform() != GetFastCloseTransform() || - layer->GetTargetOpacity() > 0.0001) - return false; - break; - case ANIMATION_FADE_IN: - if (layer->GetTargetOpacity() < 0.9999) - return false; - break; - case ANIMATION_HIDE_IMMEDIATELY: - if (layer->GetTargetOpacity() > 0.0001) - return false; - break; - case ANIMATION_RESTORE: - if (layer->opacity() < 0.9999 || layer->transform() != gfx::Transform()) - return false; - break; - default: - NOTREACHED() << "Unhandled animation type " << type; - return false; - } + if (!IsLayerAnimated(layer, type)) + return false; } return true; } +bool SessionStateAnimator::TestApi::RootWindowIsAnimated(AnimationType type) + const { + aura::RootWindow* root_window = Shell::GetPrimaryRootWindow(); + ui::Layer* layer = root_window->layer(); + return IsLayerAnimated(layer, type); +} + const int SessionStateAnimator::kAllLockScreenContainersMask = SessionStateAnimator::LOCK_SCREEN_BACKGROUND | SessionStateAnimator::LOCK_SCREEN_CONTAINERS | diff --git a/ash/wm/session_state_animator.h b/ash/wm/session_state_animator.h index e46e2df..ed6ea45 100644 --- a/ash/wm/session_state_animator.h +++ b/ash/wm/session_state_animator.h @@ -110,6 +110,10 @@ class ASH_EXPORT SessionStateAnimator { // |container_mask| is a bitfield of a Container. bool ContainersAreAnimated(int container_mask, AnimationType type) const; + // Returns true if root window was last animated with |type| (probably; + // the analysis is fairly ad-hoc). + bool RootWindowIsAnimated(AnimationType type) const; + private: SessionStateAnimator* animator_; // not owned diff --git a/ash/wm/session_state_controller.h b/ash/wm/session_state_controller.h index 648e923..c457c70 100644 --- a/ash/wm/session_state_controller.h +++ b/ash/wm/session_state_controller.h @@ -29,6 +29,7 @@ namespace ash { namespace test { class PowerButtonControllerTest; +class SessionStateControllerImpl2Test; } // Performs system-related functions on behalf of SessionStateController. @@ -128,6 +129,7 @@ class ASH_EXPORT SessionStateController : public aura::RootWindowObserver, protected: friend class test::PowerButtonControllerTest; + friend class test::SessionStateControllerImpl2Test; scoped_ptr<internal::SessionStateAnimator> animator_; diff --git a/ash/wm/session_state_controller_impl2.h b/ash/wm/session_state_controller_impl2.h index f8535fb..d554679 100644 --- a/ash/wm/session_state_controller_impl2.h +++ b/ash/wm/session_state_controller_impl2.h @@ -27,7 +27,7 @@ class Layer; namespace ash { namespace test { -class PowerButtonControllerTest; +class SessionStateControllerImpl2Test; } // Displays onscreen animations and locks or suspends the system in response to @@ -118,7 +118,7 @@ class ASH_EXPORT SessionStateControllerImpl2 : public SessionStateController { virtual void SetLockScreenDisplayedCallback(base::Closure& callback) OVERRIDE; protected: - friend class test::PowerButtonControllerTest; + friend class test::SessionStateControllerImpl2Test; private: void RequestShutdownImpl(); diff --git a/ash/wm/session_state_controller_impl2_unittest.cc b/ash/wm/session_state_controller_impl2_unittest.cc new file mode 100644 index 0000000..9a2cd0d --- /dev/null +++ b/ash/wm/session_state_controller_impl2_unittest.cc @@ -0,0 +1,617 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ash/wm/session_state_controller_impl2.h" + +#include "ash/ash_switches.h" +#include "ash/shell.h" +#include "ash/test/ash_test_base.h" +#include "ash/test/test_shell_delegate.h" +#include "ash/wm/cursor_manager.h" +#include "ash/wm/power_button_controller.h" +#include "ash/wm/session_state_animator.h" +#include "ash/wm/session_state_controller.h" +#include "base/command_line.h" +#include "base/memory/scoped_ptr.h" +#include "base/time.h" +#include "ui/aura/env.h" +#include "ui/aura/root_window.h" +#include "ui/aura/test/event_generator.h" +#include "ui/gfx/rect.h" +#include "ui/gfx/size.h" + +namespace ash { +namespace test { +namespace { +bool cursor_visible() { + return ash::Shell::GetInstance()->cursor_manager()->cursor_visible(); +} + +void CheckCalledCallback(bool* flag) { + (*flag) = true; +} +} + +// Fake implementation of PowerButtonControllerDelegate that just logs requests +// to lock the screen and shut down the device. +class TestSessionStateControllerDelegate : + public SessionStateControllerDelegate { + public: + TestSessionStateControllerDelegate() + : num_lock_requests_(0), + num_shutdown_requests_(0) {} + + int num_lock_requests() const { return num_lock_requests_; } + int num_shutdown_requests() const { return num_shutdown_requests_; } + + // SessionStateControllerDelegate implementation. + virtual void RequestLockScreen() OVERRIDE { + num_lock_requests_++; + } + virtual void RequestShutdown() OVERRIDE { + num_shutdown_requests_++; + } + + private: + int num_lock_requests_; + int num_shutdown_requests_; + + DISALLOW_COPY_AND_ASSIGN(TestSessionStateControllerDelegate); +}; + +class SessionStateControllerImpl2Test : public AshTestBase { + public: + SessionStateControllerImpl2Test() : controller_(NULL), delegate_(NULL) {} + virtual ~SessionStateControllerImpl2Test() {} + + void SetUp() OVERRIDE { + CommandLine::ForCurrentProcess()->AppendSwitch( + ash::switches::kAshNewLockAnimationsEnabled); + + AshTestBase::SetUp(); + delegate_ = new TestSessionStateControllerDelegate; + controller_ = Shell::GetInstance()->power_button_controller(); + state_controller_ = static_cast<SessionStateControllerImpl2*>( + Shell::GetInstance()->session_state_controller()); + state_controller_->SetDelegate(delegate_); // transfers ownership + test_api_.reset( + new SessionStateControllerImpl2::TestApi(state_controller_)); + animator_api_.reset( + new internal::SessionStateAnimator::TestApi(state_controller_-> + animator_.get())); + shell_delegate_ = reinterpret_cast<TestShellDelegate*>( + ash::Shell::GetInstance()->delegate()); + } + + protected: + void GenerateMouseMoveEvent() { + aura::test::EventGenerator generator( + Shell::GetPrimaryRootWindow()); + generator.MoveMouseTo(10, 10); + } + + int NumShutdownRequests() { + return delegate_->num_shutdown_requests() + + shell_delegate_->num_exit_requests(); + } + + PowerButtonController* controller_; // not owned + SessionStateControllerImpl2* state_controller_; // not owned + TestSessionStateControllerDelegate* delegate_; // not owned + TestShellDelegate* shell_delegate_; // not owned + + scoped_ptr<SessionStateControllerImpl2::TestApi> test_api_; + scoped_ptr<internal::SessionStateAnimator::TestApi> animator_api_; + + private: + DISALLOW_COPY_AND_ASSIGN(SessionStateControllerImpl2Test); +}; + +// Test the lock-to-shutdown flow for non-Chrome-OS hardware that doesn't +// correctly report power button releases. We should lock immediately the first +// time the button is pressed and shut down when it's pressed from the locked +// state. +TEST_F(SessionStateControllerImpl2Test, LegacyLockAndShutDown) { + controller_->set_has_legacy_power_button_for_test(true); + state_controller_->OnLoginStateChanged(user::LOGGED_IN_USER); + state_controller_->OnLockStateChanged(false); + + // We should request that the screen be locked immediately after seeing the + // power button get pressed. + controller_->OnPowerButtonEvent(true, base::TimeTicks::Now()); + EXPECT_TRUE( + animator_api_->ContainersAreAnimated( + internal::SessionStateAnimator::NON_LOCK_SCREEN_CONTAINERS | + internal::SessionStateAnimator::LAUNCHER, + internal::SessionStateAnimator::ANIMATION_LIFT)); + + EXPECT_FALSE(test_api_->lock_timer_is_running()); + EXPECT_EQ(1, delegate_->num_lock_requests()); + + // Notify that we locked successfully. + state_controller_->OnStartingLock(); + + EXPECT_TRUE( + animator_api_->ContainersAreAnimated( + internal::SessionStateAnimator::NON_LOCK_SCREEN_CONTAINERS | + internal::SessionStateAnimator::LAUNCHER, + internal::SessionStateAnimator::ANIMATION_LIFT)); + EXPECT_TRUE( + animator_api_->ContainersAreAnimated( + internal::SessionStateAnimator::LOCK_SCREEN_CONTAINERS, + internal::SessionStateAnimator::ANIMATION_HIDE_IMMEDIATELY)); + + + // Notify that the lock window is visible. We should make it fade in. + state_controller_->OnLockStateChanged(true); + shell_delegate_->LockScreen(); + + EXPECT_TRUE( + animator_api_->ContainersAreAnimated( + internal::SessionStateAnimator::LOCK_SCREEN_CONTAINERS, + internal::SessionStateAnimator::ANIMATION_RAISE_TO_SCREEN)); + + // We shouldn't progress towards the shutdown state, however. + EXPECT_FALSE(test_api_->lock_to_shutdown_timer_is_running()); + EXPECT_FALSE(test_api_->shutdown_timer_is_running()); + controller_->OnPowerButtonEvent(false, base::TimeTicks::Now()); + + // Hold the button again and check that we start shutting down. + controller_->OnPowerButtonEvent(true, base::TimeTicks::Now()); + EXPECT_EQ(0, NumShutdownRequests()); + + EXPECT_TRUE( + animator_api_->RootWindowIsAnimated( + internal::SessionStateAnimator::ANIMATION_GRAYSCALE_BRIGHTNESS)); + // Make sure a mouse move event won't show the cursor. + GenerateMouseMoveEvent(); + EXPECT_FALSE(cursor_visible()); + EXPECT_TRUE(test_api_->real_shutdown_timer_is_running()); + test_api_->trigger_real_shutdown_timeout(); + EXPECT_EQ(1, NumShutdownRequests()); +} + +// Test that we start shutting down immediately if the power button is pressed +// while we're not logged in on an unofficial system. +TEST_F(SessionStateControllerImpl2Test, LegacyNotLoggedIn) { + controller_->set_has_legacy_power_button_for_test(true); + state_controller_->OnLoginStateChanged(user::LOGGED_IN_NONE); + state_controller_->OnLockStateChanged(false); + SetUserLoggedIn(false); + controller_->OnPowerButtonEvent(true, base::TimeTicks::Now()); + EXPECT_TRUE(test_api_->real_shutdown_timer_is_running()); +} + +// Test that we start shutting down immediately if the power button is pressed +// while we're logged in as a guest on an unofficial system. +TEST_F(SessionStateControllerImpl2Test, LegacyGuest) { + controller_->set_has_legacy_power_button_for_test(true); + state_controller_->OnLoginStateChanged(user::LOGGED_IN_GUEST); + state_controller_->OnLockStateChanged(false); + SetCanLockScreen(false); + controller_->OnPowerButtonEvent(true, base::TimeTicks::Now()); + EXPECT_TRUE(test_api_->real_shutdown_timer_is_running()); +} + +// When we hold the power button while the user isn't logged in, we should shut +// down the machine directly. +TEST_F(SessionStateControllerImpl2Test, ShutdownWhenNotLoggedIn) { + controller_->set_has_legacy_power_button_for_test(false); + state_controller_->OnLoginStateChanged(user::LOGGED_IN_NONE); + state_controller_->OnLockStateChanged(false); + SetUserLoggedIn(false); + + // Press the power button and check that we start the shutdown timer. + controller_->OnPowerButtonEvent(true, base::TimeTicks::Now()); + EXPECT_FALSE(test_api_->lock_timer_is_running()); + EXPECT_TRUE(test_api_->shutdown_timer_is_running()); + EXPECT_TRUE( + animator_api_->RootWindowIsAnimated( + internal::SessionStateAnimator::ANIMATION_GRAYSCALE_BRIGHTNESS)); + + // Release the power button before the shutdown timer fires. + controller_->OnPowerButtonEvent(false, base::TimeTicks::Now()); + EXPECT_FALSE(test_api_->shutdown_timer_is_running()); + EXPECT_TRUE( + animator_api_->RootWindowIsAnimated( + internal::SessionStateAnimator::ANIMATION_UNDO_GRAYSCALE_BRIGHTNESS)); + + // Press the button again and make the shutdown timeout fire this time. + // Check that we start the timer for actually requesting the shutdown. + controller_->OnPowerButtonEvent(true, base::TimeTicks::Now()); + EXPECT_TRUE(test_api_->shutdown_timer_is_running()); + test_api_->trigger_shutdown_timeout(); + EXPECT_TRUE(test_api_->real_shutdown_timer_is_running()); + EXPECT_EQ(0, NumShutdownRequests()); + EXPECT_TRUE( + animator_api_->RootWindowIsAnimated( + internal::SessionStateAnimator::ANIMATION_GRAYSCALE_BRIGHTNESS)); + + // When the timout fires, we should request a shutdown. + test_api_->trigger_real_shutdown_timeout(); + EXPECT_EQ(1, NumShutdownRequests()); +} + +// Test that we lock the screen and deal with unlocking correctly. +TEST_F(SessionStateControllerImpl2Test, LockAndUnlock) { + controller_->set_has_legacy_power_button_for_test(false); + state_controller_->OnLoginStateChanged(user::LOGGED_IN_USER); + state_controller_->OnLockStateChanged(false); + + // We should initially be showing the screen locker containers, since they + // also contain login-related windows that we want to show during the + // logging-in animation. + EXPECT_TRUE( + animator_api_->ContainersAreAnimated( + internal::SessionStateAnimator::LOCK_SCREEN_CONTAINERS, + internal::SessionStateAnimator::ANIMATION_RESTORE)); + + // Press the power button and check that the lock timer is started and that we + // start lifting the non-screen-locker containers. + controller_->OnPowerButtonEvent(true, base::TimeTicks::Now()); + EXPECT_TRUE(test_api_->lock_timer_is_running()); + EXPECT_FALSE(test_api_->shutdown_timer_is_running()); + EXPECT_TRUE( + animator_api_->ContainersAreAnimated( + internal::SessionStateAnimator::NON_LOCK_SCREEN_CONTAINERS | + internal::SessionStateAnimator::LAUNCHER, + internal::SessionStateAnimator::ANIMATION_LIFT)); + + // Release the button before the lock timer fires. + controller_->OnPowerButtonEvent(false, base::TimeTicks::Now()); + EXPECT_FALSE(test_api_->lock_timer_is_running()); + EXPECT_TRUE( + animator_api_->ContainersAreAnimated( + internal::SessionStateAnimator::NON_LOCK_SCREEN_CONTAINERS | + internal::SessionStateAnimator::LAUNCHER, + internal::SessionStateAnimator::ANIMATION_DROP)); + + // Press the button and fire the lock timer. We should request that the + // screen be locked, but we should still be in the lift animation. + controller_->OnPowerButtonEvent(true, base::TimeTicks::Now()); + EXPECT_TRUE(test_api_->lock_timer_is_running()); + EXPECT_EQ(0, delegate_->num_lock_requests()); + test_api_->trigger_lock_timeout(); + EXPECT_EQ(1, delegate_->num_lock_requests()); + EXPECT_TRUE( + animator_api_->ContainersAreAnimated( + internal::SessionStateAnimator::NON_LOCK_SCREEN_CONTAINERS | + internal::SessionStateAnimator::LAUNCHER, + internal::SessionStateAnimator::ANIMATION_LIFT)); + + // Notify that we locked successfully. + state_controller_->OnStartingLock(); + EXPECT_TRUE( + animator_api_->ContainersAreAnimated( + internal::SessionStateAnimator::NON_LOCK_SCREEN_CONTAINERS | + internal::SessionStateAnimator::LAUNCHER, + internal::SessionStateAnimator::ANIMATION_LIFT)); + EXPECT_TRUE( + animator_api_->ContainersAreAnimated( + internal::SessionStateAnimator::LOCK_SCREEN_CONTAINERS, + internal::SessionStateAnimator::ANIMATION_HIDE_IMMEDIATELY)); + + // Notify that the lock window is visible. We should make it raise in. + state_controller_->OnLockStateChanged(true); + shell_delegate_->LockScreen(); + EXPECT_TRUE( + animator_api_->ContainersAreAnimated( + internal::SessionStateAnimator::LOCK_SCREEN_CONTAINERS, + internal::SessionStateAnimator::ANIMATION_RAISE_TO_SCREEN)); + + // When we release the power button, the lock-to-shutdown timer should be + // stopped. + EXPECT_TRUE(test_api_->lock_to_shutdown_timer_is_running()); + controller_->OnPowerButtonEvent(false, base::TimeTicks::Now()); + EXPECT_FALSE(test_api_->lock_to_shutdown_timer_is_running()); + + // Notify that the screen has been unlocked. We should show the + // non-screen-locker windows. + state_controller_->OnLockStateChanged(false); + shell_delegate_->UnlockScreen(); + bool called = false; + base::Closure closure = base::Bind(&CheckCalledCallback, &called); + state_controller_->OnLockScreenHide(closure); + EXPECT_TRUE( + animator_api_->ContainersAreAnimated( + internal::SessionStateAnimator::LOCK_SCREEN_CONTAINERS, + internal::SessionStateAnimator::ANIMATION_LIFT)); + EXPECT_TRUE(called); + EXPECT_TRUE( + animator_api_->ContainersAreAnimated( + internal::SessionStateAnimator::NON_LOCK_SCREEN_CONTAINERS | + internal::SessionStateAnimator::LAUNCHER, + internal::SessionStateAnimator::ANIMATION_DROP)); +} + +// Hold the power button down from the unlocked state to eventual shutdown. +TEST_F(SessionStateControllerImpl2Test, LockToShutdown) { + controller_->set_has_legacy_power_button_for_test(false); + state_controller_->OnLoginStateChanged(user::LOGGED_IN_USER); + state_controller_->OnLockStateChanged(false); + + // Hold the power button and lock the screen. + controller_->OnPowerButtonEvent(true, base::TimeTicks::Now()); + EXPECT_TRUE(test_api_->lock_timer_is_running()); + test_api_->trigger_lock_timeout(); + state_controller_->OnStartingLock(); + state_controller_->OnLockStateChanged(true); + shell_delegate_->LockScreen(); + + // When the lock-to-shutdown timeout fires, we should start the shutdown + // timer. + EXPECT_TRUE(test_api_->lock_to_shutdown_timer_is_running()); + test_api_->trigger_lock_to_shutdown_timeout(); + EXPECT_TRUE(test_api_->shutdown_timer_is_running()); + EXPECT_TRUE( + animator_api_->ContainersAreAnimated( + internal::SessionStateAnimator::LOCK_SCREEN_CONTAINERS, + internal::SessionStateAnimator::ANIMATION_RAISE_TO_SCREEN)); + + // Fire the shutdown timeout and check that we request shutdown. + test_api_->trigger_shutdown_timeout(); + EXPECT_TRUE(test_api_->real_shutdown_timer_is_running()); + EXPECT_EQ(0, NumShutdownRequests()); + test_api_->trigger_real_shutdown_timeout(); + EXPECT_EQ(1, NumShutdownRequests()); +} + + +// Hold the power button down from the unlocked state to eventual shutdown, +// then release the button while system does locking. +TEST_F(SessionStateControllerImpl2Test, CancelLockToShutdown) { + controller_->set_has_legacy_power_button_for_test(false); + + state_controller_->OnLoginStateChanged(user::LOGGED_IN_USER); + state_controller_->OnLockStateChanged(false); + + // Hold the power button and lock the screen. + controller_->OnPowerButtonEvent(true, base::TimeTicks::Now()); + EXPECT_TRUE(test_api_->lock_timer_is_running()); + test_api_->trigger_lock_timeout(); + state_controller_->OnStartingLock(); + + // Power button is released while system attempts to lock. + controller_->OnPowerButtonEvent(false, base::TimeTicks::Now()); + state_controller_->OnLockStateChanged(true); + shell_delegate_->LockScreen(); + + EXPECT_FALSE(state_controller_->ShutdownRequested()); + EXPECT_FALSE(test_api_->lock_to_shutdown_timer_is_running()); + EXPECT_FALSE(test_api_->shutdown_timer_is_running()); +} + +// Test that we handle the case where lock requests are ignored. +TEST_F(SessionStateControllerImpl2Test, Lock) { + // We require animations to have a duration for this test. + ui::LayerAnimator::set_disable_animations_for_test(false); + + controller_->set_has_legacy_power_button_for_test(false); + state_controller_->OnLoginStateChanged(user::LOGGED_IN_USER); + state_controller_->OnLockStateChanged(false); + + // Hold the power button and lock the screen. + controller_->OnPowerButtonEvent(true, base::TimeTicks::Now()); + EXPECT_TRUE(test_api_->lock_timer_is_running()); + EXPECT_TRUE( + animator_api_->ContainersAreAnimated( + internal::SessionStateAnimator::NON_LOCK_SCREEN_CONTAINERS | + internal::SessionStateAnimator::LAUNCHER, + internal::SessionStateAnimator::ANIMATION_LIFT)); + test_api_->trigger_lock_timeout(); + EXPECT_EQ(1, delegate_->num_lock_requests()); + EXPECT_TRUE(test_api_->lock_fail_timer_is_running()); + + // We shouldn't start the lock-to-shutdown timer until the screen has actually + // been locked. + EXPECT_FALSE(test_api_->lock_to_shutdown_timer_is_running()); + + // Act as if the request timed out. We should restore the windows. + test_api_->trigger_lock_fail_timeout(); + EXPECT_TRUE( + animator_api_->ContainersAreAnimated( + internal::SessionStateAnimator::NON_LOCK_SCREEN_CONTAINERS | + internal::SessionStateAnimator::LAUNCHER, + internal::SessionStateAnimator::ANIMATION_DROP)); +} + +// Test the basic operation of the lock button. +TEST_F(SessionStateControllerImpl2Test, LockButtonBasic) { + controller_->set_has_legacy_power_button_for_test(false); + // The lock button shouldn't do anything if we aren't logged in. + state_controller_->OnLoginStateChanged(user::LOGGED_IN_NONE); + state_controller_->OnLockStateChanged(false); + SetUserLoggedIn(false); + controller_->OnLockButtonEvent(true, base::TimeTicks::Now()); + EXPECT_FALSE(test_api_->lock_timer_is_running()); + controller_->OnLockButtonEvent(false, base::TimeTicks::Now()); + EXPECT_EQ(0, delegate_->num_lock_requests()); + + // Ditto for when we're logged in as a guest. + state_controller_->OnLoginStateChanged(user::LOGGED_IN_GUEST); + SetUserLoggedIn(true); + SetCanLockScreen(false); + controller_->OnLockButtonEvent(true, base::TimeTicks::Now()); + EXPECT_FALSE(test_api_->lock_timer_is_running()); + controller_->OnLockButtonEvent(false, base::TimeTicks::Now()); + EXPECT_EQ(0, delegate_->num_lock_requests()); + + // If we're logged in as a regular user, we should start the lock timer and + // the pre-lock animation. + state_controller_->OnLoginStateChanged(user::LOGGED_IN_USER); + SetCanLockScreen(true); + controller_->OnLockButtonEvent(true, base::TimeTicks::Now()); + EXPECT_TRUE(test_api_->lock_timer_is_running()); + EXPECT_TRUE( + animator_api_->ContainersAreAnimated( + internal::SessionStateAnimator::NON_LOCK_SCREEN_CONTAINERS | + internal::SessionStateAnimator::LAUNCHER, + internal::SessionStateAnimator::ANIMATION_LIFT)); + + // If the button is released immediately, we shouldn't lock the screen. + controller_->OnLockButtonEvent(false, base::TimeTicks::Now()); + EXPECT_FALSE(test_api_->lock_timer_is_running()); + EXPECT_TRUE( + animator_api_->ContainersAreAnimated( + internal::SessionStateAnimator::NON_LOCK_SCREEN_CONTAINERS | + internal::SessionStateAnimator::LAUNCHER, + internal::SessionStateAnimator::ANIMATION_DROP)); + EXPECT_EQ(0, delegate_->num_lock_requests()); + + // Press the button again and let the lock timeout fire. We should request + // that the screen be locked. + controller_->OnLockButtonEvent(true, base::TimeTicks::Now()); + EXPECT_TRUE(test_api_->lock_timer_is_running()); + test_api_->trigger_lock_timeout(); + EXPECT_EQ(1, delegate_->num_lock_requests()); + + // Pressing the lock button while we have a pending lock request shouldn't do + // anything. + controller_->OnLockButtonEvent(false, base::TimeTicks::Now()); + controller_->OnLockButtonEvent(true, base::TimeTicks::Now()); + EXPECT_FALSE(test_api_->lock_timer_is_running()); + controller_->OnLockButtonEvent(false, base::TimeTicks::Now()); + + // Pressing the button also shouldn't do anything after the screen is locked. + state_controller_->OnStartingLock(); + state_controller_->OnLockStateChanged(true); + shell_delegate_->LockScreen(); + controller_->OnLockButtonEvent(true, base::TimeTicks::Now()); + EXPECT_FALSE(test_api_->lock_timer_is_running()); + controller_->OnLockButtonEvent(false, base::TimeTicks::Now()); +} + +// Test that the power button takes priority over the lock button. +TEST_F(SessionStateControllerImpl2Test, PowerButtonPreemptsLockButton) { + controller_->set_has_legacy_power_button_for_test(false); + state_controller_->OnLoginStateChanged(user::LOGGED_IN_USER); + state_controller_->OnLockStateChanged(false); + + // While the lock button is down, hold the power button. + controller_->OnLockButtonEvent(true, base::TimeTicks::Now()); + EXPECT_TRUE(test_api_->lock_timer_is_running()); + controller_->OnPowerButtonEvent(true, base::TimeTicks::Now()); + EXPECT_TRUE(test_api_->lock_timer_is_running()); + + // The lock timer shouldn't be stopped when the lock button is released. + controller_->OnLockButtonEvent(false, base::TimeTicks::Now()); + EXPECT_TRUE(test_api_->lock_timer_is_running()); + controller_->OnPowerButtonEvent(false, base::TimeTicks::Now()); + EXPECT_FALSE(test_api_->lock_timer_is_running()); + + // Now press the power button first and then the lock button. + controller_->OnPowerButtonEvent(true, base::TimeTicks::Now()); + EXPECT_TRUE(test_api_->lock_timer_is_running()); + controller_->OnLockButtonEvent(true, base::TimeTicks::Now()); + EXPECT_TRUE(test_api_->lock_timer_is_running()); + + // Releasing the power button should stop the lock timer. + controller_->OnPowerButtonEvent(false, base::TimeTicks::Now()); + EXPECT_FALSE(test_api_->lock_timer_is_running()); + controller_->OnLockButtonEvent(false, base::TimeTicks::Now()); + EXPECT_FALSE(test_api_->lock_timer_is_running()); +} + +// When the screen is locked without going through the usual power-button +// slow-close path (e.g. via the wrench menu), test that we still show the +// fast-close animation. +TEST_F(SessionStateControllerImpl2Test, LockWithoutButton) { + state_controller_->OnLoginStateChanged(user::LOGGED_IN_USER); + state_controller_->OnStartingLock(); + EXPECT_TRUE( + animator_api_->ContainersAreAnimated( + internal::SessionStateAnimator::NON_LOCK_SCREEN_CONTAINERS | + internal::SessionStateAnimator::LAUNCHER, + internal::SessionStateAnimator::ANIMATION_LIFT)); +} + +// When we hear that the process is exiting but we haven't had a chance to +// display an animation, we should just blank the screen. +TEST_F(SessionStateControllerImpl2Test, ShutdownWithoutButton) { + state_controller_->OnLoginStateChanged(user::LOGGED_IN_USER); + state_controller_->OnAppTerminating(); + EXPECT_TRUE( + animator_api_->RootWindowIsAnimated( + internal::SessionStateAnimator::ANIMATION_GRAYSCALE_BRIGHTNESS)); + GenerateMouseMoveEvent(); + EXPECT_FALSE(cursor_visible()); +} + +// Test that we display the fast-close animation and shut down when we get an +// outside request to shut down (e.g. from the login or lock screen). +TEST_F(SessionStateControllerImpl2Test, RequestShutdownFromLoginScreen) { + state_controller_->OnLoginStateChanged(user::LOGGED_IN_NONE); + state_controller_->OnLockStateChanged(false); + SetUserLoggedIn(false); + state_controller_->RequestShutdown(); + EXPECT_TRUE( + animator_api_->RootWindowIsAnimated( + internal::SessionStateAnimator::ANIMATION_GRAYSCALE_BRIGHTNESS)); + GenerateMouseMoveEvent(); + EXPECT_FALSE(cursor_visible()); + + EXPECT_EQ(0, NumShutdownRequests()); + EXPECT_TRUE(test_api_->real_shutdown_timer_is_running()); + test_api_->trigger_real_shutdown_timeout(); + EXPECT_EQ(1, NumShutdownRequests()); +} + +TEST_F(SessionStateControllerImpl2Test, RequestShutdownFromLockScreen) { + state_controller_->OnLoginStateChanged(user::LOGGED_IN_USER); + state_controller_->OnLockStateChanged(true); + shell_delegate_->LockScreen(); + state_controller_->RequestShutdown(); + EXPECT_TRUE( + animator_api_->RootWindowIsAnimated( + internal::SessionStateAnimator::ANIMATION_GRAYSCALE_BRIGHTNESS)); + GenerateMouseMoveEvent(); + EXPECT_FALSE(cursor_visible()); + + EXPECT_EQ(0, NumShutdownRequests()); + EXPECT_TRUE(test_api_->real_shutdown_timer_is_running()); + test_api_->trigger_real_shutdown_timeout(); + EXPECT_EQ(1, NumShutdownRequests()); +} + +TEST_F(SessionStateControllerImpl2Test, + RequestAndCancelShutdownFromLockScreen) { + state_controller_->OnLoginStateChanged(user::LOGGED_IN_USER); + state_controller_->OnLockStateChanged(true); + shell_delegate_->LockScreen(); + + // Press the power button and check that we start the shutdown timer. + controller_->OnPowerButtonEvent(true, base::TimeTicks::Now()); + EXPECT_FALSE(test_api_->lock_timer_is_running()); + EXPECT_TRUE(test_api_->shutdown_timer_is_running()); + EXPECT_TRUE( + animator_api_->RootWindowIsAnimated( + internal::SessionStateAnimator::ANIMATION_GRAYSCALE_BRIGHTNESS)); + + // Release the power button before the shutdown timer fires. + controller_->OnPowerButtonEvent(false, base::TimeTicks::Now()); + EXPECT_FALSE(test_api_->shutdown_timer_is_running()); + EXPECT_TRUE( + animator_api_->RootWindowIsAnimated( + internal::SessionStateAnimator::ANIMATION_UNDO_GRAYSCALE_BRIGHTNESS)); +} + +// Test that we ignore power button presses when the screen is turned off. +TEST_F(SessionStateControllerImpl2Test, IgnorePowerButtonIfScreenIsOff) { + state_controller_->OnLoginStateChanged(user::LOGGED_IN_USER); + + // When the screen brightness is at 0%, we shouldn't do anything in response + // to power button presses. + controller_->OnScreenBrightnessChanged(0.0); + controller_->OnPowerButtonEvent(true, base::TimeTicks::Now()); + EXPECT_FALSE(test_api_->lock_timer_is_running()); + + // After increasing the brightness to 10%, we should start the timer like + // usual. + controller_->OnScreenBrightnessChanged(10.0); + controller_->OnPowerButtonEvent(true, base::TimeTicks::Now()); + EXPECT_TRUE(test_api_->lock_timer_is_running()); +} + +} // namespace test +} // namespace ash |