diff options
author | flackr@chromium.org <flackr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-04-11 23:39:12 +0000 |
---|---|---|
committer | flackr@chromium.org <flackr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-04-11 23:39:12 +0000 |
commit | 504ec34cba59dd49ca83b70dae3a41b714add4da (patch) | |
tree | 668b4aced42b587135c108ada436849cf6b3897d | |
parent | 48e375ec3121a731dcf3198fbf2ed25c0b3c217b (diff) | |
download | chromium_src-504ec34cba59dd49ca83b70dae3a41b714add4da.zip chromium_src-504ec34cba59dd49ca83b70dae3a41b714add4da.tar.gz chromium_src-504ec34cba59dd49ca83b70dae3a41b714add4da.tar.bz2 |
Merge 261692 "Auto rotate on lid rotation changes."
> Auto rotate on lid rotation changes.
>
> Based on patch to read the accelerometer https://codereview.chromium.org/200643005, this listens to accelerometer updates and rotates the screen.
>
> BUG=342907
> TEST=MaximizeModeControllerTest.AccelerometerRotation, MaximizeModeControllerTest.AccelerometerIgnoresLowAngles, MaximizeModeControllerTest.AccelerometerSticky, MaximizeModeControllerTest.RotationOnlyInMaximizeMode
> TEST=Rotate device, ash should rotate to match device orientation.
>
> Review URL: https://codereview.chromium.org/196413017
TBR=flackr@chromium.org
Review URL: https://codereview.chromium.org/228663010
git-svn-id: svn://svn.chromium.org/chrome/branches/1916/src@263409 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | ash/wm/maximize_mode/maximize_mode_controller.cc | 104 | ||||
-rw-r--r-- | ash/wm/maximize_mode/maximize_mode_controller.h | 11 | ||||
-rw-r--r-- | ash/wm/maximize_mode/maximize_mode_controller_unittest.cc | 124 |
3 files changed, 235 insertions, 4 deletions
diff --git a/ash/wm/maximize_mode/maximize_mode_controller.cc b/ash/wm/maximize_mode/maximize_mode_controller.cc index 34fc1ff..d9e3668 100644 --- a/ash/wm/maximize_mode/maximize_mode_controller.cc +++ b/ash/wm/maximize_mode/maximize_mode_controller.cc @@ -5,6 +5,7 @@ #include "ash/wm/maximize_mode/maximize_mode_controller.h" #include "ash/accelerometer/accelerometer_controller.h" +#include "ash/display/display_manager.h" #include "ash/shell.h" #include "ui/gfx/vector3d_f.h" @@ -33,6 +34,15 @@ const float kFullyOpenAngleErrorTolerance = 10.0f; // angle. const float kHingeAxisAlignedThreshold = 15.0f; +// The angle which the screen has to be rotated past before the display will +// rotate to match it (i.e. 45.0f is no stickiness). +const float kDisplayRotationStickyAngleDegrees = 60.0f; + +// The minimum acceleration in a direction required to trigger screen rotation. +// This prevents rapid toggling of rotation when the device is near flat and +// there is very little screen aligned force on it. +const float kMinimumAccelerationScreenRotation = 0.3f; + const float kRadiansToDegrees = 180.0f / 3.14159265f; // Returns the angle between |base| and |other| in degrees. @@ -71,15 +81,26 @@ MaximizeModeController::~MaximizeModeController() { void MaximizeModeController::OnAccelerometerUpdated( const gfx::Vector3dF& base, const gfx::Vector3dF& lid) { + // Responding to the hinge rotation can change the maximize mode state which + // affects screen rotation, so we handle hinge rotation first. + HandleHingeRotation(base, lid); + HandleScreenRotation(lid); +} + +void MaximizeModeController::HandleHingeRotation(const gfx::Vector3dF& base, + const gfx::Vector3dF& lid) { static const gfx::Vector3dF hinge_vector(0.0f, 1.0f, 0.0f); + bool maximize_mode_engaged = + Shell::GetInstance()->IsMaximizeModeWindowManagerEnabled(); // As the hinge approaches a vertical angle, the base and lid accelerometers // approach the same values making any angle calculations highly inaccurate. // Bail out early when it is too close. float hinge_angle = AngleBetweenVectorsInDegrees(base, hinge_vector); if (hinge_angle < kHingeAxisAlignedThreshold || - hinge_angle > 180.0f - kHingeAxisAlignedThreshold) + hinge_angle > 180.0f - kHingeAxisAlignedThreshold) { return; + } // Compute the angle between the base and the lid. float angle = ClockwiseAngleBetweenVectorsInDegrees(base, lid, hinge_vector); @@ -88,8 +109,6 @@ void MaximizeModeController::OnAccelerometerUpdated( // TODO(flackr): Make MaximizeModeController own the MaximizeModeWindowManager // such that observations of state changes occur after the change and shell // has fewer states to track. - bool maximize_mode_engaged = - Shell::GetInstance()->IsMaximizeModeWindowManagerEnabled(); if (maximize_mode_engaged && angle > kFullyOpenAngleErrorTolerance && angle < kExitMaximizeModeAngle) { @@ -100,4 +119,83 @@ void MaximizeModeController::OnAccelerometerUpdated( } } +void MaximizeModeController::HandleScreenRotation(const gfx::Vector3dF& lid) { + bool maximize_mode_engaged = + Shell::GetInstance()->IsMaximizeModeWindowManagerEnabled(); + + internal::DisplayManager* display_manager = + Shell::GetInstance()->display_manager(); + gfx::Display::Rotation current_rotation = display_manager->GetDisplayInfo( + gfx::Display::InternalDisplayId()).rotation(); + + // If maximize mode is not engaged, ensure the screen is not rotated and + // do not rotate to match the current device orientation. + if (!maximize_mode_engaged) { + if (current_rotation != gfx::Display::ROTATE_0) { + // TODO(flackr): Currently this will prevent setting a manual rotation on + // the screen of a device with an accelerometer, this should only set it + // back to ROTATE_0 if it was last set by the accelerometer. + // Also, SetDisplayRotation will save the setting to the local store, + // this should be stored in a way that we can distinguish what the + // rotation was set by. + display_manager->SetDisplayRotation(gfx::Display::InternalDisplayId(), + gfx::Display::ROTATE_0); + } + return; + } + + // After determining maximize mode state, determine if the screen should + // be rotated. + gfx::Vector3dF lid_flattened(lid.x(), lid.y(), 0.0f); + float lid_flattened_length = lid_flattened.Length(); + // When the lid is close to being flat, don't change rotation as it is too + // sensitive to slight movements. + if (lid_flattened_length < kMinimumAccelerationScreenRotation) + return; + + // The reference vector is the angle of gravity when the device is rotated + // clockwise by 45 degrees. Computing the angle between this vector and + // gravity we can easily determine the expected display rotation. + static gfx::Vector3dF rotation_reference(-1.0f, 1.0f, 0.0f); + + // Set the down vector to match the expected direction of gravity given the + // last configured rotation. This is used to enforce a stickiness that the + // user must overcome to rotate the display and prevents frequent rotations + // when holding the device near 45 degrees. + gfx::Vector3dF down(0.0f, 0.0f, 0.0f); + if (current_rotation == gfx::Display::ROTATE_0) + down.set_x(-1.0f); + else if (current_rotation == gfx::Display::ROTATE_90) + down.set_y(1.0f); + else if (current_rotation == gfx::Display::ROTATE_180) + down.set_x(1.0f); + else + down.set_y(-1.0f); + + // Don't rotate if the screen has not passed the threshold. + if (AngleBetweenVectorsInDegrees(down, lid_flattened) < + kDisplayRotationStickyAngleDegrees) { + return; + } + + float angle = ClockwiseAngleBetweenVectorsInDegrees(rotation_reference, + lid_flattened, gfx::Vector3dF(0.0f, 0.0f, -1.0f)); + + gfx::Display::Rotation new_rotation = gfx::Display::ROTATE_90; + if (angle < 90.0f) + new_rotation = gfx::Display::ROTATE_0; + else if (angle < 180.0f) + new_rotation = gfx::Display::ROTATE_270; + else if (angle < 270.0f) + new_rotation = gfx::Display::ROTATE_180; + + // When exiting maximize mode return rotation to 0. When entering, rotate to + // match screen orientation. + if (new_rotation == gfx::Display::ROTATE_0 || + maximize_mode_engaged) { + display_manager->SetDisplayRotation(gfx::Display::InternalDisplayId(), + new_rotation); + } +} + } // namespace ash diff --git a/ash/wm/maximize_mode/maximize_mode_controller.h b/ash/wm/maximize_mode/maximize_mode_controller.h index f9919fa..0ef1b9f 100644 --- a/ash/wm/maximize_mode/maximize_mode_controller.h +++ b/ash/wm/maximize_mode/maximize_mode_controller.h @@ -12,7 +12,7 @@ namespace ash { // MaximizeModeController listens to accelerometer events and automatically // enters and exits maximize mode when the lid is opened beyond the triggering -// angle. +// angle and rotates the display to match the device when in maximize mode. class MaximizeModeController : public AccelerometerObserver { public: MaximizeModeController(); @@ -21,6 +21,15 @@ class MaximizeModeController : public AccelerometerObserver { virtual void OnAccelerometerUpdated(const gfx::Vector3dF& base, const gfx::Vector3dF& lid) OVERRIDE; private: + // Detect hinge rotation from |base| and |lid| accelerometers and + // automatically start / stop maximize mode. + void HandleHingeRotation(const gfx::Vector3dF& base, + const gfx::Vector3dF& lid); + + // Detect screen rotation from |lid| accelerometer and automatically rotate + // screen. + void HandleScreenRotation(const gfx::Vector3dF& lid); + DISALLOW_COPY_AND_ASSIGN(MaximizeModeController); }; diff --git a/ash/wm/maximize_mode/maximize_mode_controller_unittest.cc b/ash/wm/maximize_mode/maximize_mode_controller_unittest.cc index ee3afc4..3237524 100644 --- a/ash/wm/maximize_mode/maximize_mode_controller_unittest.cc +++ b/ash/wm/maximize_mode/maximize_mode_controller_unittest.cc @@ -5,12 +5,20 @@ #include "ash/wm/maximize_mode/maximize_mode_controller.h" #include "ash/accelerometer/accelerometer_controller.h" +#include "ash/display/display_manager.h" #include "ash/shell.h" #include "ash/test/ash_test_base.h" +#include "ash/test/display_manager_test_api.h" #include "ui/gfx/vector3d_f.h" namespace ash { +namespace { + +const float kDegreesToRadians = 3.14159265f / 180.0f; + +} // namespace + class MaximizeModeControllerTest : public test::AshTestBase { public: MaximizeModeControllerTest() {} @@ -20,6 +28,11 @@ class MaximizeModeControllerTest : public test::AshTestBase { test::AshTestBase::SetUp(); Shell::GetInstance()->accelerometer_controller()->RemoveObserver( maximize_mode_controller()); + + // Set the first display to be the internal display for the accelerometer + // screen rotation tests. + test::DisplayManagerTestApi(Shell::GetInstance()->display_manager()). + SetFirstDisplayAsInternalDisplay(); } virtual void TearDown() OVERRIDE { @@ -41,6 +54,11 @@ class MaximizeModeControllerTest : public test::AshTestBase { return Shell::GetInstance()->IsMaximizeModeWindowManagerEnabled(); } + gfx::Display::Rotation GetInternalDisplayRotation() const { + return Shell::GetInstance()->display_manager()->GetDisplayInfo( + gfx::Display::InternalDisplayId()).rotation(); + } + private: DISALLOW_COPY_AND_ASSIGN(MaximizeModeControllerTest); }; @@ -108,4 +126,110 @@ TEST_F(MaximizeModeControllerTest, HingeAligned) { EXPECT_TRUE(IsMaximizeModeStarted()); } +// Tests that accelerometer readings in each of the screen angles will trigger +// a rotation of the internal display. +TEST_F(MaximizeModeControllerTest, DisplayRotation) { + // Trigger maximize mode by opening to 270. + TriggerAccelerometerUpdate(gfx::Vector3dF(0.0f, 0.0f, -1.0f), + gfx::Vector3dF(-1.0f, 0.0f, 0.0f)); + ASSERT_TRUE(IsMaximizeModeStarted()); + + // Now test rotating in all directions. + TriggerAccelerometerUpdate(gfx::Vector3dF(0.0f, 1.0f, 0.0f), + gfx::Vector3dF(0.0f, 1.0f, 0.0f)); + EXPECT_EQ(gfx::Display::ROTATE_90, GetInternalDisplayRotation()); + TriggerAccelerometerUpdate(gfx::Vector3dF(1.0f, 0.0f, 0.0f), + gfx::Vector3dF(1.0f, 0.0f, 0.0f)); + EXPECT_EQ(gfx::Display::ROTATE_180, GetInternalDisplayRotation()); + TriggerAccelerometerUpdate(gfx::Vector3dF(0.0f, -1.0f, 0.0f), + gfx::Vector3dF(0.0f, -1.0f, 0.0f)); + EXPECT_EQ(gfx::Display::ROTATE_270, GetInternalDisplayRotation()); + TriggerAccelerometerUpdate(gfx::Vector3dF(-1.0f, 0.0f, 0.0f), + gfx::Vector3dF(-1.0f, 0.0f, 0.0f)); + EXPECT_EQ(gfx::Display::ROTATE_0, GetInternalDisplayRotation()); +} + +// Tests that low angles are ignored by the accelerometer (i.e. when the device +// is almost laying flat). +TEST_F(MaximizeModeControllerTest, RotationIgnoresLowAngles) { + // Trigger maximize mode by opening to 270. + TriggerAccelerometerUpdate(gfx::Vector3dF(0.0f, 0.0f, -1.0f), + gfx::Vector3dF(-1.0f, 0.0f, 0.0f)); + ASSERT_TRUE(IsMaximizeModeStarted()); + + TriggerAccelerometerUpdate(gfx::Vector3dF(-1.0f, 0.0f, -1.0f), + gfx::Vector3dF(-1.0f, 0.0f, -1.0f)); + EXPECT_EQ(gfx::Display::ROTATE_0, GetInternalDisplayRotation()); + TriggerAccelerometerUpdate(gfx::Vector3dF(0.0f, 0.2f, -1.0f), + gfx::Vector3dF(0.0f, 0.2f, -1.0f)); + EXPECT_EQ(gfx::Display::ROTATE_0, GetInternalDisplayRotation()); + TriggerAccelerometerUpdate(gfx::Vector3dF(0.2f, 0.0f, -1.0f), + gfx::Vector3dF(0.2f, 0.0f, -1.0f)); + EXPECT_EQ(gfx::Display::ROTATE_0, GetInternalDisplayRotation()); + TriggerAccelerometerUpdate(gfx::Vector3dF(0.0f, -0.2f, -1.0f), + gfx::Vector3dF(0.0f, -0.2f, -1.0f)); + EXPECT_EQ(gfx::Display::ROTATE_0, GetInternalDisplayRotation()); + TriggerAccelerometerUpdate(gfx::Vector3dF(-0.2f, 0.0f, -1.0f), + gfx::Vector3dF(-0.2f, 0.0f, -1.0f)); + EXPECT_EQ(gfx::Display::ROTATE_0, GetInternalDisplayRotation()); +} + +// Tests that the display will stick to the current orientation beyond the +// halfway point, preventing frequent updates back and forth. +TEST_F(MaximizeModeControllerTest, RotationSticky) { + // Trigger maximize mode by opening to 270. + TriggerAccelerometerUpdate(gfx::Vector3dF(0.0f, 0.0f, -1.0f), + gfx::Vector3dF(-1.0f, 0.0f, 0.0f)); + ASSERT_TRUE(IsMaximizeModeStarted()); + + gfx::Vector3dF gravity(-1.0f, 0.0f, 0.0f); + TriggerAccelerometerUpdate(gravity, gravity); + EXPECT_EQ(gfx::Display::ROTATE_0, GetInternalDisplayRotation()); + + // Turn past half-way point to next direction and rotation should remain + // the same. + float degrees = 50.0; + gravity.set_x(-cos(degrees * kDegreesToRadians)); + gravity.set_y(sin(degrees * kDegreesToRadians)); + TriggerAccelerometerUpdate(gravity, gravity); + EXPECT_EQ(gfx::Display::ROTATE_0, GetInternalDisplayRotation()); + + // Turn more and the screen should rotate. + degrees = 70.0; + gravity.set_x(-cos(degrees * kDegreesToRadians)); + gravity.set_y(sin(degrees * kDegreesToRadians)); + TriggerAccelerometerUpdate(gravity, gravity); + EXPECT_EQ(gfx::Display::ROTATE_90, GetInternalDisplayRotation()); + + // Turn back just beyond the half-way point and the new rotation should + // still be in effect. + degrees = 40.0; + gravity.set_x(-cos(degrees * kDegreesToRadians)); + gravity.set_y(sin(degrees * kDegreesToRadians)); + TriggerAccelerometerUpdate(gravity, gravity); + EXPECT_EQ(gfx::Display::ROTATE_90, GetInternalDisplayRotation()); +} + +// Tests that the screen only rotates when maximize mode is engaged, and will +// return to the standard orientation on exiting maximize mode. +TEST_F(MaximizeModeControllerTest, RotationOnlyInMaximizeMode) { + // Rotate on side with lid only open 90 degrees. + TriggerAccelerometerUpdate(gfx::Vector3dF(0.0f, 0.8f, 0.3f), + gfx::Vector3dF(-0.3f, 0.8f, 0.0f)); + ASSERT_FALSE(IsMaximizeModeStarted()); + EXPECT_EQ(gfx::Display::ROTATE_0, GetInternalDisplayRotation()); + + // Open lid, screen should now rotate to match orientation. + TriggerAccelerometerUpdate(gfx::Vector3dF(0.0f, 0.8f, -0.3f), + gfx::Vector3dF(-0.3f, 0.8f, 0.0f)); + ASSERT_TRUE(IsMaximizeModeStarted()); + EXPECT_EQ(gfx::Display::ROTATE_90, GetInternalDisplayRotation()); + + // Close lid back to 90, screen should rotate back. + TriggerAccelerometerUpdate(gfx::Vector3dF(0.0f, 0.8f, 0.3f), + gfx::Vector3dF(-0.3f, 0.8f, 0.0f)); + ASSERT_FALSE(IsMaximizeModeStarted()); + EXPECT_EQ(gfx::Display::ROTATE_0, GetInternalDisplayRotation()); +} + } // namespace ash |