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_window_manager.h"
14 #include "ash/wm/maximize_mode/scoped_disable_internal_mouse_and_keyboard.h"
15 #include "base/auto_reset.h"
16 #include "base/command_line.h"
17 #include "base/metrics/histogram.h"
18 #include "base/time/default_tick_clock.h"
19 #include "base/time/tick_clock.h"
20 #include "ui/base/accelerators/accelerator.h"
21 #include "ui/events/event.h"
22 #include "ui/events/event_handler.h"
23 #include "ui/events/keycodes/keyboard_codes.h"
24 #include "ui/gfx/vector3d_f.h"
27 #include "ash/wm/maximize_mode/scoped_disable_internal_mouse_and_keyboard_x11.h"
30 #if defined(OS_CHROMEOS)
31 #include "chromeos/dbus/dbus_thread_manager.h"
38 // The hinge angle at which to enter maximize mode.
39 const float kEnterMaximizeModeAngle
= 200.0f
;
41 // The angle at which to exit maximize mode, this is specifically less than the
42 // angle to enter maximize mode to prevent rapid toggling when near the angle.
43 const float kExitMaximizeModeAngle
= 160.0f
;
45 // Defines a range for which accelerometer readings are considered accurate.
46 // When the lid is near open (or near closed) the accelerometer readings may be
47 // inaccurate and a lid that is fully open may appear to be near closed (and
49 const float kMinStableAngle
= 20.0f
;
50 const float kMaxStableAngle
= 340.0f
;
52 // The time duration to consider the lid to be recently opened.
53 // This is used to prevent entering maximize mode if an erroneous accelerometer
54 // reading makes the lid appear to be fully open when the user is opening the
55 // lid from a closed position.
56 const base::TimeDelta kLidRecentlyOpenedDuration
=
57 base::TimeDelta::FromSeconds(2);
59 // When the device approaches vertical orientation (i.e. portrait orientation)
60 // the accelerometers for the base and lid approach the same values (i.e.
61 // gravity pointing in the direction of the hinge). When this happens we cannot
62 // compute the hinge angle reliably and must turn ignore accelerometer readings.
63 // This is the minimum acceleration perpendicular to the hinge under which to
64 // detect hinge angle.
65 const float kHingeAngleDetectionThreshold
= 0.25f
;
67 // The maximum deviation from the acceleration expected due to gravity under
68 // which to detect hinge angle and screen rotation.
69 const float kDeviationFromGravityThreshold
= 0.1f
;
71 // The maximum deviation between the magnitude of the two accelerometers under
72 // which to detect hinge angle and screen rotation. These accelerometers are
73 // attached to the same physical device and so should be under the same
75 const float kNoisyMagnitudeDeviation
= 0.1f
;
77 // The angle which the screen has to be rotated past before the display will
78 // rotate to match it (i.e. 45.0f is no stickiness).
79 const float kDisplayRotationStickyAngleDegrees
= 60.0f
;
81 // The minimum acceleration in a direction required to trigger screen rotation.
82 // This prevents rapid toggling of rotation when the device is near flat and
83 // there is very little screen aligned force on it. The value is effectively the
84 // sine of the rise angle required, with the current value requiring at least a
86 const float kMinimumAccelerationScreenRotation
= 0.42f
;
88 const float kRadiansToDegrees
= 180.0f
/ 3.14159265f
;
90 // Returns the angle between |base| and |other| in degrees.
91 float AngleBetweenVectorsInDegrees(const gfx::Vector3dF
& base
,
92 const gfx::Vector3dF
& other
) {
93 return acos(gfx::DotProduct(base
, other
) /
94 base
.Length() / other
.Length()) * kRadiansToDegrees
;
97 // Returns the clockwise angle between |base| and |other| where |normal| is the
98 // normal of the virtual surface to measure clockwise according to.
99 float ClockwiseAngleBetweenVectorsInDegrees(const gfx::Vector3dF
& base
,
100 const gfx::Vector3dF
& other
,
101 const gfx::Vector3dF
& normal
) {
102 float angle
= AngleBetweenVectorsInDegrees(base
, other
);
103 gfx::Vector3dF
cross(base
);
105 // If the dot product of this cross product is normal, it means that the
106 // shortest angle between |base| and |other| was counterclockwise with respect
107 // to the surface represented by |normal| and this angle must be reversed.
108 if (gfx::DotProduct(cross
, normal
) > 0.0f
)
109 angle
= 360.0f
- angle
;
113 #if defined(OS_CHROMEOS)
115 // An event handler which listens for a volume down + power keypress and
116 // triggers a screenshot when this is seen.
117 class ScreenshotActionHandler
: public ui::EventHandler
{
119 ScreenshotActionHandler();
120 virtual ~ScreenshotActionHandler();
123 virtual void OnKeyEvent(ui::KeyEvent
* event
) OVERRIDE
;
126 bool volume_down_pressed_
;
128 DISALLOW_COPY_AND_ASSIGN(ScreenshotActionHandler
);
131 ScreenshotActionHandler::ScreenshotActionHandler()
132 : volume_down_pressed_(false) {
133 Shell::GetInstance()->PrependPreTargetHandler(this);
136 ScreenshotActionHandler::~ScreenshotActionHandler() {
137 Shell::GetInstance()->RemovePreTargetHandler(this);
140 void ScreenshotActionHandler::OnKeyEvent(ui::KeyEvent
* event
) {
141 if (event
->key_code() == ui::VKEY_VOLUME_DOWN
) {
142 volume_down_pressed_
= event
->type() == ui::ET_KEY_PRESSED
||
143 event
->type() == ui::ET_TRANSLATED_KEY_PRESS
;
144 } else if (volume_down_pressed_
&&
145 event
->key_code() == ui::VKEY_POWER
&&
146 event
->type() == ui::ET_KEY_PRESSED
) {
147 Shell::GetInstance()->accelerator_controller()->PerformAction(
148 ash::TAKE_SCREENSHOT
, ui::Accelerator());
152 #endif // OS_CHROMEOS
156 MaximizeModeController::MaximizeModeController()
157 : rotation_locked_(false),
158 have_seen_accelerometer_data_(false),
159 ignore_display_configuration_updates_(false),
160 shutting_down_(false),
161 user_rotation_(gfx::Display::ROTATE_0
),
162 last_touchview_transition_time_(base::Time::Now()),
163 tick_clock_(new base::DefaultTickClock()),
164 lid_is_closed_(false) {
165 Shell::GetInstance()->accelerometer_controller()->AddObserver(this);
166 Shell::GetInstance()->AddShellObserver(this);
167 #if defined(OS_CHROMEOS)
168 chromeos::DBusThreadManager::Get()->
169 GetPowerManagerClient()->AddObserver(this);
170 #endif // OS_CHROMEOS
173 MaximizeModeController::~MaximizeModeController() {
174 Shell::GetInstance()->RemoveShellObserver(this);
175 Shell::GetInstance()->accelerometer_controller()->RemoveObserver(this);
176 #if defined(OS_CHROMEOS)
177 chromeos::DBusThreadManager::Get()->
178 GetPowerManagerClient()->RemoveObserver(this);
179 #endif // OS_CHROMEOS
182 void MaximizeModeController::SetRotationLocked(bool rotation_locked
) {
183 if (rotation_locked_
== rotation_locked
)
185 base::AutoReset
<bool> auto_ignore_display_configuration_updates(
186 &ignore_display_configuration_updates_
, true);
187 rotation_locked_
= rotation_locked
;
188 Shell::GetInstance()->display_manager()->
189 RegisterDisplayRotationProperties(rotation_locked_
, current_rotation_
);
190 FOR_EACH_OBSERVER(Observer
, observers_
,
191 OnRotationLockChanged(rotation_locked_
));
194 void MaximizeModeController::AddObserver(Observer
* observer
) {
195 observers_
.AddObserver(observer
);
198 void MaximizeModeController::RemoveObserver(Observer
* observer
) {
199 observers_
.RemoveObserver(observer
);
202 bool MaximizeModeController::CanEnterMaximizeMode() {
203 // If we have ever seen accelerometer data, then HandleHingeRotation may
204 // trigger maximize mode at some point in the future.
205 // The --enable-touch-view-testing switch can also mean that we may enter
207 return have_seen_accelerometer_data_
||
208 CommandLine::ForCurrentProcess()->HasSwitch(
209 switches::kAshEnableTouchViewTesting
);
212 void MaximizeModeController::EnableMaximizeModeWindowManager(bool enable
) {
213 if (enable
&& !maximize_mode_window_manager_
.get()) {
214 maximize_mode_window_manager_
.reset(new MaximizeModeWindowManager());
215 // TODO(jonross): Move the maximize mode notifications from ShellObserver
216 // to MaximizeModeController::Observer
217 Shell::GetInstance()->OnMaximizeModeStarted();
218 } else if (!enable
&& maximize_mode_window_manager_
.get()) {
219 maximize_mode_window_manager_
.reset();
220 Shell::GetInstance()->OnMaximizeModeEnded();
224 bool MaximizeModeController::IsMaximizeModeWindowManagerEnabled() const {
225 return maximize_mode_window_manager_
.get() != NULL
;
228 void MaximizeModeController::AddWindow(aura::Window
* window
) {
229 if (IsMaximizeModeWindowManagerEnabled())
230 maximize_mode_window_manager_
->AddWindow(window
);
233 void MaximizeModeController::Shutdown() {
234 shutting_down_
= true;
238 void MaximizeModeController::OnAccelerometerUpdated(
239 const gfx::Vector3dF
& base
,
240 const gfx::Vector3dF
& lid
) {
241 bool first_accelerometer_update
= !have_seen_accelerometer_data_
;
242 have_seen_accelerometer_data_
= true;
244 // Ignore the reading if it appears unstable. The reading is considered
245 // unstable if it deviates too much from gravity and/or the magnitude of the
246 // reading from the lid differs too much from the reading from the base.
247 float base_magnitude
= base
.Length();
248 float lid_magnitude
= lid
.Length();
249 if (std::abs(base_magnitude
- lid_magnitude
) > kNoisyMagnitudeDeviation
||
250 std::abs(base_magnitude
- 1.0f
) > kDeviationFromGravityThreshold
||
251 std::abs(lid_magnitude
- 1.0f
) > kDeviationFromGravityThreshold
) {
255 // Responding to the hinge rotation can change the maximize mode state which
256 // affects screen rotation, so we handle hinge rotation first.
257 HandleHingeRotation(base
, lid
);
258 HandleScreenRotation(lid
);
260 if (first_accelerometer_update
) {
261 // On the first accelerometer update we will know if we have entered
262 // maximize mode or not. Update the preferences to reflect the current
264 Shell::GetInstance()->display_manager()->
265 RegisterDisplayRotationProperties(rotation_locked_
, current_rotation_
);
269 void MaximizeModeController::OnDisplayConfigurationChanged() {
270 if (ignore_display_configuration_updates_
)
272 DisplayManager
* display_manager
= Shell::GetInstance()->display_manager();
273 gfx::Display::Rotation user_rotation
= display_manager
->
274 GetDisplayInfo(gfx::Display::InternalDisplayId()).rotation();
275 if (user_rotation
!= current_rotation_
) {
276 // A user may change other display configuration settings. When the user
277 // does change the rotation setting, then lock rotation to prevent the
278 // accelerometer from erasing their change.
279 SetRotationLocked(true);
280 user_rotation_
= user_rotation
;
281 current_rotation_
= user_rotation
;
285 #if defined(OS_CHROMEOS)
286 void MaximizeModeController::LidEventReceived(bool open
,
287 const base::TimeTicks
& time
) {
289 last_lid_open_time_
= time
;
290 lid_is_closed_
= !open
;
294 void MaximizeModeController::SuspendImminent() {
295 RecordTouchViewStateTransition();
298 void MaximizeModeController::SuspendDone(
299 const base::TimeDelta
& sleep_duration
) {
300 last_touchview_transition_time_
= base::Time::Now();
302 #endif // OS_CHROMEOS
304 void MaximizeModeController::HandleHingeRotation(const gfx::Vector3dF
& base
,
305 const gfx::Vector3dF
& lid
) {
306 static const gfx::Vector3dF
hinge_vector(0.0f
, 1.0f
, 0.0f
);
307 bool maximize_mode_engaged
= IsMaximizeModeWindowManagerEnabled();
308 // Ignore the component of acceleration parallel to the hinge for the purposes
309 // of hinge angle calculation.
310 gfx::Vector3dF
base_flattened(base
);
311 gfx::Vector3dF
lid_flattened(lid
);
312 base_flattened
.set_y(0.0f
);
313 lid_flattened
.set_y(0.0f
);
315 // As the hinge approaches a vertical angle, the base and lid accelerometers
316 // approach the same values making any angle calculations highly inaccurate.
317 // Bail out early when it is too close.
318 if (base_flattened
.Length() < kHingeAngleDetectionThreshold
||
319 lid_flattened
.Length() < kHingeAngleDetectionThreshold
) {
323 // Compute the angle between the base and the lid.
324 float angle
= ClockwiseAngleBetweenVectorsInDegrees(base_flattened
,
325 lid_flattened
, hinge_vector
);
327 bool is_angle_stable
= angle
> kMinStableAngle
&& angle
< kMaxStableAngle
;
329 // Clear the last_lid_open_time_ for a stable reading so that there is less
330 // chance of a delay if the lid is moved from the close state to the fully
331 // open state very quickly.
333 last_lid_open_time_
= base::TimeTicks();
335 // Toggle maximize mode on or off when corresponding thresholds are passed.
336 // TODO(flackr): Make MaximizeModeController own the MaximizeModeWindowManager
337 // such that observations of state changes occur after the change and shell
338 // has fewer states to track.
339 if (maximize_mode_engaged
&& is_angle_stable
&&
340 angle
< kExitMaximizeModeAngle
) {
342 } else if (!lid_is_closed_
&& !maximize_mode_engaged
&&
343 angle
> kEnterMaximizeModeAngle
&&
344 (is_angle_stable
|| !WasLidOpenedRecently())) {
349 void MaximizeModeController::HandleScreenRotation(const gfx::Vector3dF
& lid
) {
350 bool maximize_mode_engaged
= IsMaximizeModeWindowManagerEnabled();
352 // TODO(jonross): track the updated rotation angle even when locked. So that
353 // when rotation lock is removed the accelerometer rotation can be applied
354 // without waiting for the next update.
355 if (!maximize_mode_engaged
|| rotation_locked_
)
358 DisplayManager
* display_manager
=
359 Shell::GetInstance()->display_manager();
360 gfx::Display::Rotation current_rotation
= display_manager
->GetDisplayInfo(
361 gfx::Display::InternalDisplayId()).rotation();
363 // After determining maximize mode state, determine if the screen should
365 gfx::Vector3dF
lid_flattened(lid
.x(), lid
.y(), 0.0f
);
366 float lid_flattened_length
= lid_flattened
.Length();
367 // When the lid is close to being flat, don't change rotation as it is too
368 // sensitive to slight movements.
369 if (lid_flattened_length
< kMinimumAccelerationScreenRotation
)
372 // The reference vector is the angle of gravity when the device is rotated
373 // clockwise by 45 degrees. Computing the angle between this vector and
374 // gravity we can easily determine the expected display rotation.
375 static gfx::Vector3dF
rotation_reference(-1.0f
, 1.0f
, 0.0f
);
377 // Set the down vector to match the expected direction of gravity given the
378 // last configured rotation. This is used to enforce a stickiness that the
379 // user must overcome to rotate the display and prevents frequent rotations
380 // when holding the device near 45 degrees.
381 gfx::Vector3dF
down(0.0f
, 0.0f
, 0.0f
);
382 if (current_rotation
== gfx::Display::ROTATE_0
)
384 else if (current_rotation
== gfx::Display::ROTATE_90
)
386 else if (current_rotation
== gfx::Display::ROTATE_180
)
391 // Don't rotate if the screen has not passed the threshold.
392 if (AngleBetweenVectorsInDegrees(down
, lid_flattened
) <
393 kDisplayRotationStickyAngleDegrees
) {
397 float angle
= ClockwiseAngleBetweenVectorsInDegrees(rotation_reference
,
398 lid_flattened
, gfx::Vector3dF(0.0f
, 0.0f
, -1.0f
));
400 gfx::Display::Rotation new_rotation
= gfx::Display::ROTATE_90
;
402 new_rotation
= gfx::Display::ROTATE_0
;
403 else if (angle
< 180.0f
)
404 new_rotation
= gfx::Display::ROTATE_270
;
405 else if (angle
< 270.0f
)
406 new_rotation
= gfx::Display::ROTATE_180
;
408 if (new_rotation
!= current_rotation
)
409 SetDisplayRotation(display_manager
, new_rotation
);
412 void MaximizeModeController::SetDisplayRotation(
413 DisplayManager
* display_manager
,
414 gfx::Display::Rotation rotation
) {
415 base::AutoReset
<bool> auto_ignore_display_configuration_updates(
416 &ignore_display_configuration_updates_
, true);
417 current_rotation_
= rotation
;
418 display_manager
->SetDisplayRotation(gfx::Display::InternalDisplayId(),
422 void MaximizeModeController::EnterMaximizeMode() {
423 if (IsMaximizeModeWindowManagerEnabled())
425 DisplayManager
* display_manager
= Shell::GetInstance()->display_manager();
426 if (display_manager
->HasInternalDisplay()) {
427 current_rotation_
= user_rotation_
= display_manager
->
428 GetDisplayInfo(gfx::Display::InternalDisplayId()).rotation();
429 LoadDisplayRotationProperties();
431 EnableMaximizeModeWindowManager(true);
433 event_blocker_
.reset(new ScopedDisableInternalMouseAndKeyboardX11
);
435 #if defined(OS_CHROMEOS)
436 event_handler_
.reset(new ScreenshotActionHandler
);
438 Shell::GetInstance()->display_controller()->AddObserver(this);
441 void MaximizeModeController::LeaveMaximizeMode() {
442 if (!IsMaximizeModeWindowManagerEnabled())
444 DisplayManager
* display_manager
= Shell::GetInstance()->display_manager();
445 if (display_manager
->HasInternalDisplay()) {
446 gfx::Display::Rotation current_rotation
= display_manager
->
447 GetDisplayInfo(gfx::Display::InternalDisplayId()).rotation();
448 if (current_rotation
!= user_rotation_
)
449 SetDisplayRotation(display_manager
, user_rotation_
);
452 SetRotationLocked(false);
453 EnableMaximizeModeWindowManager(false);
454 event_blocker_
.reset();
455 event_handler_
.reset();
456 Shell::GetInstance()->display_controller()->RemoveObserver(this);
459 // Called after maximize mode has started, windows might still animate though.
460 void MaximizeModeController::OnMaximizeModeStarted() {
461 RecordTouchViewStateTransition();
464 // Called after maximize mode has ended, windows might still be returning to
465 // their original position.
466 void MaximizeModeController::OnMaximizeModeEnded() {
467 RecordTouchViewStateTransition();
470 void MaximizeModeController::RecordTouchViewStateTransition() {
471 if (CanEnterMaximizeMode()) {
472 base::Time current_time
= base::Time::Now();
473 base::TimeDelta delta
= current_time
- last_touchview_transition_time_
;
474 if (IsMaximizeModeWindowManagerEnabled()) {
475 UMA_HISTOGRAM_LONG_TIMES("Ash.TouchView.TouchViewInactive", delta
);
476 total_non_touchview_time_
+= delta
;
478 UMA_HISTOGRAM_LONG_TIMES("Ash.TouchView.TouchViewActive", delta
);
479 total_touchview_time_
+= delta
;
481 last_touchview_transition_time_
= current_time
;
485 void MaximizeModeController::LoadDisplayRotationProperties() {
486 DisplayManager
* display_manager
= Shell::GetInstance()->display_manager();
487 if (!display_manager
->registered_internal_display_rotation_lock())
490 SetDisplayRotation(display_manager
,
491 display_manager
->registered_internal_display_rotation());
492 SetRotationLocked(true);
495 void MaximizeModeController::OnAppTerminating() {
496 if (CanEnterMaximizeMode()) {
497 RecordTouchViewStateTransition();
498 UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchView.TouchViewActiveTotal",
499 total_touchview_time_
.InMinutes(),
500 1, base::TimeDelta::FromDays(7).InMinutes(), 50);
501 UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchView.TouchViewInactiveTotal",
502 total_non_touchview_time_
.InMinutes(),
503 1, base::TimeDelta::FromDays(7).InMinutes(), 50);
504 base::TimeDelta total_runtime
= total_touchview_time_
+
505 total_non_touchview_time_
;
506 if (total_runtime
.InSeconds() > 0) {
507 UMA_HISTOGRAM_PERCENTAGE("Ash.TouchView.TouchViewActivePercentage",
508 100 * total_touchview_time_
.InSeconds() / total_runtime
.InSeconds());
511 Shell::GetInstance()->display_controller()->RemoveObserver(this);
514 bool MaximizeModeController::WasLidOpenedRecently() const {
515 if (last_lid_open_time_
.is_null())
518 base::TimeTicks now
= tick_clock_
->NowTicks();
519 DCHECK(now
>= last_lid_open_time_
);
520 base::TimeDelta elapsed_time
= now
- last_lid_open_time_
;
521 return elapsed_time
<= kLidRecentlyOpenedDuration
;
524 void MaximizeModeController::SetTickClockForTest(
525 scoped_ptr
<base::TickClock
> tick_clock
) {
527 tick_clock_
= tick_clock
.Pass();