1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "ash/wm/maximize_mode/maximize_mode_controller.h"
7 #include "ash/accelerometer/accelerometer_controller.h"
8 #include "ash/display/display_manager.h"
10 #include "ash/wm/maximize_mode/maximize_mode_event_blocker.h"
11 #include "ui/gfx/vector3d_f.h"
17 // The hinge angle at which to enter maximize mode.
18 const float kEnterMaximizeModeAngle
= 200.0f
;
20 // The angle at which to exit maximize mode, this is specifically less than the
21 // angle to enter maximize mode to prevent rapid toggling when near the angle.
22 const float kExitMaximizeModeAngle
= 160.0f
;
24 // When the lid is fully open 360 degrees, the accelerometer readings can
25 // occasionally appear as though the lid is almost closed. If the lid appears
26 // near closed but the device is on we assume it is an erroneous reading from
27 // it being open 360 degrees.
28 const float kFullyOpenAngleErrorTolerance
= 20.0f
;
30 // When the device approaches vertical orientation (i.e. portrait orientation)
31 // the accelerometers for the base and lid approach the same values (i.e.
32 // gravity pointing in the direction of the hinge). When this happens we cannot
33 // compute the hinge angle reliably and must turn ignore accelerometer readings.
34 // This is the minimum acceleration perpendicular to the hinge under which to
35 // detect hinge angle.
36 const float kHingeAngleDetectionThreshold
= 0.25f
;
38 // The maximum deviation from the acceleration expected due to gravity under
39 // which to detect hinge angle and screen rotation.
40 const float kDeviationFromGravityThreshold
= 0.1f
;
42 // The maximum deviation between the magnitude of the two accelerometers under
43 // which to detect hinge angle and screen rotation. These accelerometers are
44 // attached to the same physical device and so should be under the same
46 const float kNoisyMagnitudeDeviation
= 0.1f
;
48 // The angle which the screen has to be rotated past before the display will
49 // rotate to match it (i.e. 45.0f is no stickiness).
50 const float kDisplayRotationStickyAngleDegrees
= 60.0f
;
52 // The minimum acceleration in a direction required to trigger screen rotation.
53 // This prevents rapid toggling of rotation when the device is near flat and
54 // there is very little screen aligned force on it.
55 const float kMinimumAccelerationScreenRotation
= 0.3f
;
57 const float kRadiansToDegrees
= 180.0f
/ 3.14159265f
;
59 // Returns the angle between |base| and |other| in degrees.
60 float AngleBetweenVectorsInDegrees(const gfx::Vector3dF
& base
,
61 const gfx::Vector3dF
& other
) {
62 return acos(gfx::DotProduct(base
, other
) /
63 base
.Length() / other
.Length()) * kRadiansToDegrees
;
66 // Returns the clockwise angle between |base| and |other| where |normal| is the
67 // normal of the virtual surface to measure clockwise according to.
68 float ClockwiseAngleBetweenVectorsInDegrees(const gfx::Vector3dF
& base
,
69 const gfx::Vector3dF
& other
,
70 const gfx::Vector3dF
& normal
) {
71 float angle
= AngleBetweenVectorsInDegrees(base
, other
);
72 gfx::Vector3dF
cross(base
);
74 // If the dot product of this cross product is normal, it means that the
75 // shortest angle between |base| and |other| was counterclockwise with respect
76 // to the surface represented by |normal| and this angle must be reversed.
77 if (gfx::DotProduct(cross
, normal
) > 0.0f
)
78 angle
= 360.0f
- angle
;
84 MaximizeModeController::MaximizeModeController()
85 : rotation_locked_(false) {
86 Shell::GetInstance()->accelerometer_controller()->AddObserver(this);
89 MaximizeModeController::~MaximizeModeController() {
90 Shell::GetInstance()->accelerometer_controller()->RemoveObserver(this);
93 void MaximizeModeController::OnAccelerometerUpdated(
94 const gfx::Vector3dF
& base
,
95 const gfx::Vector3dF
& lid
) {
96 // Ignore the reading if it appears unstable. The reading is considered
97 // unstable if it deviates too much from gravity and/or the magnitude of the
98 // reading from the lid differs too much from the reading from the base.
99 float base_magnitude
= base
.Length();
100 float lid_magnitude
= lid
.Length();
101 if (std::abs(base_magnitude
- lid_magnitude
) > kNoisyMagnitudeDeviation
||
102 std::abs(base_magnitude
- 1.0f
) > kDeviationFromGravityThreshold
||
103 std::abs(lid_magnitude
- 1.0f
) > kDeviationFromGravityThreshold
) {
107 // Responding to the hinge rotation can change the maximize mode state which
108 // affects screen rotation, so we handle hinge rotation first.
109 HandleHingeRotation(base
, lid
);
110 HandleScreenRotation(lid
);
113 void MaximizeModeController::HandleHingeRotation(const gfx::Vector3dF
& base
,
114 const gfx::Vector3dF
& lid
) {
115 static const gfx::Vector3dF
hinge_vector(0.0f
, 1.0f
, 0.0f
);
116 bool maximize_mode_engaged
=
117 Shell::GetInstance()->IsMaximizeModeWindowManagerEnabled();
118 // Ignore the component of acceleration parallel to the hinge for the purposes
119 // of hinge angle calculation.
120 gfx::Vector3dF
base_flattened(base
);
121 gfx::Vector3dF
lid_flattened(lid
);
122 base_flattened
.set_y(0.0f
);
123 lid_flattened
.set_y(0.0f
);
125 // As the hinge approaches a vertical angle, the base and lid accelerometers
126 // approach the same values making any angle calculations highly inaccurate.
127 // Bail out early when it is too close.
128 if (base_flattened
.Length() < kHingeAngleDetectionThreshold
||
129 lid_flattened
.Length() < kHingeAngleDetectionThreshold
) {
133 // Compute the angle between the base and the lid.
134 float angle
= ClockwiseAngleBetweenVectorsInDegrees(base_flattened
,
135 lid_flattened
, hinge_vector
);
137 // Toggle maximize mode on or off when corresponding thresholds are passed.
138 // TODO(flackr): Make MaximizeModeController own the MaximizeModeWindowManager
139 // such that observations of state changes occur after the change and shell
140 // has fewer states to track.
141 if (maximize_mode_engaged
&&
142 angle
> kFullyOpenAngleErrorTolerance
&&
143 angle
< kExitMaximizeModeAngle
) {
144 Shell::GetInstance()->EnableMaximizeModeWindowManager(false);
145 event_blocker_
.reset();
146 } else if (!maximize_mode_engaged
&&
147 angle
> kEnterMaximizeModeAngle
) {
148 Shell::GetInstance()->EnableMaximizeModeWindowManager(true);
149 event_blocker_
.reset(new MaximizeModeEventBlocker
);
153 void MaximizeModeController::HandleScreenRotation(const gfx::Vector3dF
& lid
) {
154 bool maximize_mode_engaged
=
155 Shell::GetInstance()->IsMaximizeModeWindowManagerEnabled();
157 DisplayManager
* display_manager
=
158 Shell::GetInstance()->display_manager();
159 gfx::Display::Rotation current_rotation
= display_manager
->GetDisplayInfo(
160 gfx::Display::InternalDisplayId()).rotation();
162 // If maximize mode is not engaged, ensure the screen is not rotated and
163 // do not rotate to match the current device orientation.
164 if (!maximize_mode_engaged
) {
165 if (current_rotation
!= gfx::Display::ROTATE_0
) {
166 // TODO(flackr): Currently this will prevent setting a manual rotation on
167 // the screen of a device with an accelerometer, this should only set it
168 // back to ROTATE_0 if it was last set by the accelerometer.
169 // Also, SetDisplayRotation will save the setting to the local store,
170 // this should be stored in a way that we can distinguish what the
171 // rotation was set by.
172 display_manager
->SetDisplayRotation(gfx::Display::InternalDisplayId(),
173 gfx::Display::ROTATE_0
);
175 rotation_locked_
= false;
179 if (rotation_locked_
)
182 // After determining maximize mode state, determine if the screen should
184 gfx::Vector3dF
lid_flattened(lid
.x(), lid
.y(), 0.0f
);
185 float lid_flattened_length
= lid_flattened
.Length();
186 // When the lid is close to being flat, don't change rotation as it is too
187 // sensitive to slight movements.
188 if (lid_flattened_length
< kMinimumAccelerationScreenRotation
)
191 // The reference vector is the angle of gravity when the device is rotated
192 // clockwise by 45 degrees. Computing the angle between this vector and
193 // gravity we can easily determine the expected display rotation.
194 static gfx::Vector3dF
rotation_reference(-1.0f
, 1.0f
, 0.0f
);
196 // Set the down vector to match the expected direction of gravity given the
197 // last configured rotation. This is used to enforce a stickiness that the
198 // user must overcome to rotate the display and prevents frequent rotations
199 // when holding the device near 45 degrees.
200 gfx::Vector3dF
down(0.0f
, 0.0f
, 0.0f
);
201 if (current_rotation
== gfx::Display::ROTATE_0
)
203 else if (current_rotation
== gfx::Display::ROTATE_90
)
205 else if (current_rotation
== gfx::Display::ROTATE_180
)
210 // Don't rotate if the screen has not passed the threshold.
211 if (AngleBetweenVectorsInDegrees(down
, lid_flattened
) <
212 kDisplayRotationStickyAngleDegrees
) {
216 float angle
= ClockwiseAngleBetweenVectorsInDegrees(rotation_reference
,
217 lid_flattened
, gfx::Vector3dF(0.0f
, 0.0f
, -1.0f
));
219 gfx::Display::Rotation new_rotation
= gfx::Display::ROTATE_90
;
221 new_rotation
= gfx::Display::ROTATE_0
;
222 else if (angle
< 180.0f
)
223 new_rotation
= gfx::Display::ROTATE_270
;
224 else if (angle
< 270.0f
)
225 new_rotation
= gfx::Display::ROTATE_180
;
227 // When exiting maximize mode return rotation to 0. When entering, rotate to
228 // match screen orientation.
229 if (new_rotation
== gfx::Display::ROTATE_0
||
230 maximize_mode_engaged
) {
231 display_manager
->SetDisplayRotation(gfx::Display::InternalDisplayId(),