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/accelerators/accelerator_controller.h"
8 #include "ash/accelerators/accelerator_table.h"
9 #include "ash/accelerometer/accelerometer_controller.h"
10 #include "ash/ash_switches.h"
11 #include "ash/display/display_manager.h"
12 #include "ash/shell.h"
13 #include "ash/wm/maximize_mode/maximize_mode_event_blocker.h"
14 #include "base/command_line.h"
15 #include "ui/base/accelerators/accelerator.h"
16 #include "ui/events/event.h"
17 #include "ui/events/event_handler.h"
18 #include "ui/events/keycodes/keyboard_codes.h"
19 #include "ui/gfx/vector3d_f.h"
25 // The hinge angle at which to enter maximize mode.
26 const float kEnterMaximizeModeAngle
= 200.0f
;
28 // The angle at which to exit maximize mode, this is specifically less than the
29 // angle to enter maximize mode to prevent rapid toggling when near the angle.
30 const float kExitMaximizeModeAngle
= 160.0f
;
32 // When the lid is fully open 360 degrees, the accelerometer readings can
33 // occasionally appear as though the lid is almost closed. If the lid appears
34 // near closed but the device is on we assume it is an erroneous reading from
35 // it being open 360 degrees.
36 const float kFullyOpenAngleErrorTolerance
= 20.0f
;
38 // When the device approaches vertical orientation (i.e. portrait orientation)
39 // the accelerometers for the base and lid approach the same values (i.e.
40 // gravity pointing in the direction of the hinge). When this happens we cannot
41 // compute the hinge angle reliably and must turn ignore accelerometer readings.
42 // This is the minimum acceleration perpendicular to the hinge under which to
43 // detect hinge angle.
44 const float kHingeAngleDetectionThreshold
= 0.25f
;
46 // The maximum deviation from the acceleration expected due to gravity under
47 // which to detect hinge angle and screen rotation.
48 const float kDeviationFromGravityThreshold
= 0.1f
;
50 // The maximum deviation between the magnitude of the two accelerometers under
51 // which to detect hinge angle and screen rotation. These accelerometers are
52 // attached to the same physical device and so should be under the same
54 const float kNoisyMagnitudeDeviation
= 0.1f
;
56 // The angle which the screen has to be rotated past before the display will
57 // rotate to match it (i.e. 45.0f is no stickiness).
58 const float kDisplayRotationStickyAngleDegrees
= 60.0f
;
60 // The minimum acceleration in a direction required to trigger screen rotation.
61 // This prevents rapid toggling of rotation when the device is near flat and
62 // there is very little screen aligned force on it.
63 const float kMinimumAccelerationScreenRotation
= 0.3f
;
65 const float kRadiansToDegrees
= 180.0f
/ 3.14159265f
;
67 // Returns the angle between |base| and |other| in degrees.
68 float AngleBetweenVectorsInDegrees(const gfx::Vector3dF
& base
,
69 const gfx::Vector3dF
& other
) {
70 return acos(gfx::DotProduct(base
, other
) /
71 base
.Length() / other
.Length()) * kRadiansToDegrees
;
74 // Returns the clockwise angle between |base| and |other| where |normal| is the
75 // normal of the virtual surface to measure clockwise according to.
76 float ClockwiseAngleBetweenVectorsInDegrees(const gfx::Vector3dF
& base
,
77 const gfx::Vector3dF
& other
,
78 const gfx::Vector3dF
& normal
) {
79 float angle
= AngleBetweenVectorsInDegrees(base
, other
);
80 gfx::Vector3dF
cross(base
);
82 // If the dot product of this cross product is normal, it means that the
83 // shortest angle between |base| and |other| was counterclockwise with respect
84 // to the surface represented by |normal| and this angle must be reversed.
85 if (gfx::DotProduct(cross
, normal
) > 0.0f
)
86 angle
= 360.0f
- angle
;
90 #if defined(OS_CHROMEOS)
92 // An event handler which listens for a volume down + power keypress and
93 // triggers a screenshot when this is seen.
94 class ScreenshotActionHandler
: public ui::EventHandler
{
96 ScreenshotActionHandler();
97 virtual ~ScreenshotActionHandler();
100 virtual void OnKeyEvent(ui::KeyEvent
* event
) OVERRIDE
;
103 bool volume_down_pressed_
;
105 DISALLOW_COPY_AND_ASSIGN(ScreenshotActionHandler
);
108 ScreenshotActionHandler::ScreenshotActionHandler()
109 : volume_down_pressed_(false) {
110 Shell::GetInstance()->PrependPreTargetHandler(this);
113 ScreenshotActionHandler::~ScreenshotActionHandler() {
114 Shell::GetInstance()->RemovePreTargetHandler(this);
117 void ScreenshotActionHandler::OnKeyEvent(ui::KeyEvent
* event
) {
118 if (event
->key_code() == ui::VKEY_VOLUME_DOWN
) {
119 volume_down_pressed_
= event
->type() == ui::ET_KEY_PRESSED
||
120 event
->type() == ui::ET_TRANSLATED_KEY_PRESS
;
121 } else if (volume_down_pressed_
&&
122 event
->key_code() == ui::VKEY_POWER
&&
123 event
->type() == ui::ET_KEY_PRESSED
) {
124 Shell::GetInstance()->accelerator_controller()->PerformAction(
125 ash::TAKE_SCREENSHOT
, ui::Accelerator());
129 #endif // OS_CHROMEOS
133 MaximizeModeController::MaximizeModeController()
134 : rotation_locked_(false),
135 have_seen_accelerometer_data_(false) {
136 Shell::GetInstance()->accelerometer_controller()->AddObserver(this);
139 MaximizeModeController::~MaximizeModeController() {
140 Shell::GetInstance()->accelerometer_controller()->RemoveObserver(this);
143 bool MaximizeModeController::CanEnterMaximizeMode() {
144 // If we have ever seen accelerometer data, then HandleHingeRotation may
145 // trigger maximize mode at some point in the future.
146 // The --enable-touch-view-testing switch can also mean that we may enter
148 return have_seen_accelerometer_data_
||
149 CommandLine::ForCurrentProcess()->HasSwitch(
150 switches::kAshEnableTouchViewTesting
);
153 void MaximizeModeController::OnAccelerometerUpdated(
154 const gfx::Vector3dF
& base
,
155 const gfx::Vector3dF
& lid
) {
156 have_seen_accelerometer_data_
= true;
158 // Ignore the reading if it appears unstable. The reading is considered
159 // unstable if it deviates too much from gravity and/or the magnitude of the
160 // reading from the lid differs too much from the reading from the base.
161 float base_magnitude
= base
.Length();
162 float lid_magnitude
= lid
.Length();
163 if (std::abs(base_magnitude
- lid_magnitude
) > kNoisyMagnitudeDeviation
||
164 std::abs(base_magnitude
- 1.0f
) > kDeviationFromGravityThreshold
||
165 std::abs(lid_magnitude
- 1.0f
) > kDeviationFromGravityThreshold
) {
169 // Responding to the hinge rotation can change the maximize mode state which
170 // affects screen rotation, so we handle hinge rotation first.
171 HandleHingeRotation(base
, lid
);
172 HandleScreenRotation(lid
);
175 void MaximizeModeController::HandleHingeRotation(const gfx::Vector3dF
& base
,
176 const gfx::Vector3dF
& lid
) {
177 static const gfx::Vector3dF
hinge_vector(0.0f
, 1.0f
, 0.0f
);
178 bool maximize_mode_engaged
=
179 Shell::GetInstance()->IsMaximizeModeWindowManagerEnabled();
180 // Ignore the component of acceleration parallel to the hinge for the purposes
181 // of hinge angle calculation.
182 gfx::Vector3dF
base_flattened(base
);
183 gfx::Vector3dF
lid_flattened(lid
);
184 base_flattened
.set_y(0.0f
);
185 lid_flattened
.set_y(0.0f
);
187 // As the hinge approaches a vertical angle, the base and lid accelerometers
188 // approach the same values making any angle calculations highly inaccurate.
189 // Bail out early when it is too close.
190 if (base_flattened
.Length() < kHingeAngleDetectionThreshold
||
191 lid_flattened
.Length() < kHingeAngleDetectionThreshold
) {
195 // Compute the angle between the base and the lid.
196 float angle
= ClockwiseAngleBetweenVectorsInDegrees(base_flattened
,
197 lid_flattened
, hinge_vector
);
199 // Toggle maximize mode on or off when corresponding thresholds are passed.
200 // TODO(flackr): Make MaximizeModeController own the MaximizeModeWindowManager
201 // such that observations of state changes occur after the change and shell
202 // has fewer states to track.
203 if (maximize_mode_engaged
&&
204 angle
> kFullyOpenAngleErrorTolerance
&&
205 angle
< kExitMaximizeModeAngle
) {
206 Shell::GetInstance()->EnableMaximizeModeWindowManager(false);
207 event_blocker_
.reset();
208 event_handler_
.reset();
209 } else if (!maximize_mode_engaged
&&
210 angle
> kEnterMaximizeModeAngle
) {
211 Shell::GetInstance()->EnableMaximizeModeWindowManager(true);
212 event_blocker_
.reset(new MaximizeModeEventBlocker
);
213 #if defined(OS_CHROMEOS)
214 event_handler_
.reset(new ScreenshotActionHandler
);
219 void MaximizeModeController::HandleScreenRotation(const gfx::Vector3dF
& lid
) {
220 bool maximize_mode_engaged
=
221 Shell::GetInstance()->IsMaximizeModeWindowManagerEnabled();
223 DisplayManager
* display_manager
=
224 Shell::GetInstance()->display_manager();
225 gfx::Display::Rotation current_rotation
= display_manager
->GetDisplayInfo(
226 gfx::Display::InternalDisplayId()).rotation();
228 // If maximize mode is not engaged, ensure the screen is not rotated and
229 // do not rotate to match the current device orientation.
230 if (!maximize_mode_engaged
) {
231 if (current_rotation
!= gfx::Display::ROTATE_0
) {
232 // TODO(flackr): Currently this will prevent setting a manual rotation on
233 // the screen of a device with an accelerometer, this should only set it
234 // back to ROTATE_0 if it was last set by the accelerometer.
235 // Also, SetDisplayRotation will save the setting to the local store,
236 // this should be stored in a way that we can distinguish what the
237 // rotation was set by.
238 display_manager
->SetDisplayRotation(gfx::Display::InternalDisplayId(),
239 gfx::Display::ROTATE_0
);
241 rotation_locked_
= false;
245 if (rotation_locked_
)
248 // After determining maximize mode state, determine if the screen should
250 gfx::Vector3dF
lid_flattened(lid
.x(), lid
.y(), 0.0f
);
251 float lid_flattened_length
= lid_flattened
.Length();
252 // When the lid is close to being flat, don't change rotation as it is too
253 // sensitive to slight movements.
254 if (lid_flattened_length
< kMinimumAccelerationScreenRotation
)
257 // The reference vector is the angle of gravity when the device is rotated
258 // clockwise by 45 degrees. Computing the angle between this vector and
259 // gravity we can easily determine the expected display rotation.
260 static gfx::Vector3dF
rotation_reference(-1.0f
, 1.0f
, 0.0f
);
262 // Set the down vector to match the expected direction of gravity given the
263 // last configured rotation. This is used to enforce a stickiness that the
264 // user must overcome to rotate the display and prevents frequent rotations
265 // when holding the device near 45 degrees.
266 gfx::Vector3dF
down(0.0f
, 0.0f
, 0.0f
);
267 if (current_rotation
== gfx::Display::ROTATE_0
)
269 else if (current_rotation
== gfx::Display::ROTATE_90
)
271 else if (current_rotation
== gfx::Display::ROTATE_180
)
276 // Don't rotate if the screen has not passed the threshold.
277 if (AngleBetweenVectorsInDegrees(down
, lid_flattened
) <
278 kDisplayRotationStickyAngleDegrees
) {
282 float angle
= ClockwiseAngleBetweenVectorsInDegrees(rotation_reference
,
283 lid_flattened
, gfx::Vector3dF(0.0f
, 0.0f
, -1.0f
));
285 gfx::Display::Rotation new_rotation
= gfx::Display::ROTATE_90
;
287 new_rotation
= gfx::Display::ROTATE_0
;
288 else if (angle
< 180.0f
)
289 new_rotation
= gfx::Display::ROTATE_270
;
290 else if (angle
< 270.0f
)
291 new_rotation
= gfx::Display::ROTATE_180
;
293 // When exiting maximize mode return rotation to 0. When entering, rotate to
294 // match screen orientation.
295 if (new_rotation
== gfx::Display::ROTATE_0
||
296 maximize_mode_engaged
) {
297 display_manager
->SetDisplayRotation(gfx::Display::InternalDisplayId(),