Updating trunk VERSION from 2139.0 to 2140.0
[chromium-blink-merge.git] / ash / wm / maximize_mode / maximize_mode_controller.cc
blob6f655b28d9a6b2f82a7bbc26ec3d336c47dd42c8
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"
26 #if defined(USE_X11)
27 #include "ash/wm/maximize_mode/scoped_disable_internal_mouse_and_keyboard_x11.h"
28 #endif
30 #if defined(OS_CHROMEOS)
31 #include "chromeos/dbus/dbus_thread_manager.h"
32 #endif // OS_CHROMEOS
34 namespace ash {
36 namespace {
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
48 // vice versa).
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
74 // acceleration.
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
85 // 25 degree rise.
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);
104 cross.Cross(other);
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;
110 return 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 {
118 public:
119 ScreenshotActionHandler();
120 virtual ~ScreenshotActionHandler();
122 // ui::EventHandler:
123 virtual void OnKeyEvent(ui::KeyEvent* event) OVERRIDE;
125 private:
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
154 } // namespace
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)
184 return;
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
206 // maximize mode.
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;
235 LeaveMaximizeMode();
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) {
252 return;
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
263 // state.
264 Shell::GetInstance()->display_manager()->
265 RegisterDisplayRotationProperties(rotation_locked_, current_rotation_);
269 void MaximizeModeController::OnDisplayConfigurationChanged() {
270 if (ignore_display_configuration_updates_)
271 return;
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) {
288 if (open)
289 last_lid_open_time_ = time;
290 lid_is_closed_ = !open;
291 LeaveMaximizeMode();
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) {
320 return;
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.
332 if (is_angle_stable)
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) {
341 LeaveMaximizeMode();
342 } else if (!lid_is_closed_ && !maximize_mode_engaged &&
343 angle > kEnterMaximizeModeAngle &&
344 (is_angle_stable || !WasLidOpenedRecently())) {
345 EnterMaximizeMode();
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_)
356 return;
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
364 // be rotated.
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)
370 return;
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)
383 down.set_x(-1.0f);
384 else if (current_rotation == gfx::Display::ROTATE_90)
385 down.set_y(1.0f);
386 else if (current_rotation == gfx::Display::ROTATE_180)
387 down.set_x(1.0f);
388 else
389 down.set_y(-1.0f);
391 // Don't rotate if the screen has not passed the threshold.
392 if (AngleBetweenVectorsInDegrees(down, lid_flattened) <
393 kDisplayRotationStickyAngleDegrees) {
394 return;
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;
401 if (angle < 90.0f)
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(),
419 rotation);
422 void MaximizeModeController::EnterMaximizeMode() {
423 if (IsMaximizeModeWindowManagerEnabled())
424 return;
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);
432 #if defined(USE_X11)
433 event_blocker_.reset(new ScopedDisableInternalMouseAndKeyboardX11);
434 #endif
435 #if defined(OS_CHROMEOS)
436 event_handler_.reset(new ScreenshotActionHandler);
437 #endif
438 Shell::GetInstance()->display_controller()->AddObserver(this);
441 void MaximizeModeController::LeaveMaximizeMode() {
442 if (!IsMaximizeModeWindowManagerEnabled())
443 return;
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_);
451 if (!shutting_down_)
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;
477 } else {
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())
488 return;
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())
516 return false;
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) {
526 DCHECK(tick_clock_);
527 tick_clock_ = tick_clock.Pass();
530 } // namespace ash