Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / ash / wm / maximize_mode / maximize_mode_controller.cc
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.
4
5 #include "ash/wm/maximize_mode/maximize_mode_controller.h"
6
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/keycodes/keyboard_codes.h"
23 #include "ui/gfx/vector3d_f.h"
24
25 #if defined(USE_X11)
26 #include "ash/wm/maximize_mode/scoped_disable_internal_mouse_and_keyboard_x11.h"
27 #endif
28
29 #if defined(OS_CHROMEOS)
30 #include "chromeos/dbus/dbus_thread_manager.h"
31 #endif  // OS_CHROMEOS
32
33 namespace ash {
34
35 namespace {
36
37 // The hinge angle at which to enter maximize mode.
38 const float kEnterMaximizeModeAngle = 200.0f;
39
40 // The angle at which to exit maximize mode, this is specifically less than the
41 // angle to enter maximize mode to prevent rapid toggling when near the angle.
42 const float kExitMaximizeModeAngle = 160.0f;
43
44 // Defines a range for which accelerometer readings are considered accurate.
45 // When the lid is near open (or near closed) the accelerometer readings may be
46 // inaccurate and a lid that is fully open may appear to be near closed (and
47 // vice versa).
48 const float kMinStableAngle = 20.0f;
49 const float kMaxStableAngle = 340.0f;
50
51 // The time duration to consider the lid to be recently opened.
52 // This is used to prevent entering maximize mode if an erroneous accelerometer
53 // reading makes the lid appear to be fully open when the user is opening the
54 // lid from a closed position.
55 const base::TimeDelta kLidRecentlyOpenedDuration =
56     base::TimeDelta::FromSeconds(2);
57
58 // The mean acceleration due to gravity on Earth in m/s^2.
59 const float kMeanGravity = 9.80665f;
60
61 // When the device approaches vertical orientation (i.e. portrait orientation)
62 // the accelerometers for the base and lid approach the same values (i.e.
63 // gravity pointing in the direction of the hinge). When this happens we cannot
64 // compute the hinge angle reliably and must turn ignore accelerometer readings.
65 // This is the minimum acceleration perpendicular to the hinge under which to
66 // detect hinge angle in m/s^2.
67 const float kHingeAngleDetectionThreshold = 2.5f;
68
69 // The maximum deviation from the acceleration expected due to gravity under
70 // which to detect hinge angle and screen rotation in m/s^2
71 const float kDeviationFromGravityThreshold = 1.0f;
72
73 // The maximum deviation between the magnitude of the two accelerometers under
74 // which to detect hinge angle and screen rotation in m/s^2. These
75 // accelerometers are attached to the same physical device and so should be
76 // under the same acceleration.
77 const float kNoisyMagnitudeDeviation = 1.0f;
78
79 // The angle which the screen has to be rotated past before the display will
80 // rotate to match it (i.e. 45.0f is no stickiness).
81 const float kDisplayRotationStickyAngleDegrees = 60.0f;
82
83 // The minimum acceleration in m/s^2 in a direction required to trigger screen
84 // rotation. This prevents rapid toggling of rotation when the device is near
85 // flat and there is very little screen aligned force on it. The value is
86 // effectively the sine of the rise angle required times the acceleration due
87 // to gravity, with the current value requiring at least a 25 degree rise.
88 const float kMinimumAccelerationScreenRotation = 4.2f;
89
90 const float kRadiansToDegrees = 180.0f / 3.14159265f;
91
92 // Returns the angle between |base| and |other| in degrees.
93 float AngleBetweenVectorsInDegrees(const gfx::Vector3dF& base,
94                                  const gfx::Vector3dF& other) {
95   return acos(gfx::DotProduct(base, other) /
96               base.Length() / other.Length()) * kRadiansToDegrees;
97 }
98
99 // Returns the clockwise angle between |base| and |other| where |normal| is the
100 // normal of the virtual surface to measure clockwise according to.
101 float ClockwiseAngleBetweenVectorsInDegrees(const gfx::Vector3dF& base,
102                                             const gfx::Vector3dF& other,
103                                             const gfx::Vector3dF& normal) {
104   float angle = AngleBetweenVectorsInDegrees(base, other);
105   gfx::Vector3dF cross(base);
106   cross.Cross(other);
107   // If the dot product of this cross product is normal, it means that the
108   // shortest angle between |base| and |other| was counterclockwise with respect
109   // to the surface represented by |normal| and this angle must be reversed.
110   if (gfx::DotProduct(cross, normal) > 0.0f)
111     angle = 360.0f - angle;
112   return angle;
113 }
114
115 }  // namespace
116
117 MaximizeModeController::MaximizeModeController()
118     : rotation_locked_(false),
119       have_seen_accelerometer_data_(false),
120       ignore_display_configuration_updates_(false),
121       shutting_down_(false),
122       user_rotation_(gfx::Display::ROTATE_0),
123       last_touchview_transition_time_(base::Time::Now()),
124       tick_clock_(new base::DefaultTickClock()),
125       lid_is_closed_(false) {
126   Shell::GetInstance()->accelerometer_controller()->AddObserver(this);
127   Shell::GetInstance()->AddShellObserver(this);
128 #if defined(OS_CHROMEOS)
129   chromeos::DBusThreadManager::Get()->
130       GetPowerManagerClient()->AddObserver(this);
131 #endif  // OS_CHROMEOS
132 }
133
134 MaximizeModeController::~MaximizeModeController() {
135   Shell::GetInstance()->RemoveShellObserver(this);
136   Shell::GetInstance()->accelerometer_controller()->RemoveObserver(this);
137 #if defined(OS_CHROMEOS)
138   chromeos::DBusThreadManager::Get()->
139       GetPowerManagerClient()->RemoveObserver(this);
140 #endif  // OS_CHROMEOS
141 }
142
143 void MaximizeModeController::SetRotationLocked(bool rotation_locked) {
144   if (rotation_locked_ == rotation_locked)
145     return;
146   base::AutoReset<bool> auto_ignore_display_configuration_updates(
147       &ignore_display_configuration_updates_, true);
148   rotation_locked_ = rotation_locked;
149   Shell::GetInstance()->display_manager()->
150       RegisterDisplayRotationProperties(rotation_locked_, current_rotation_);
151   FOR_EACH_OBSERVER(Observer, observers_,
152                     OnRotationLockChanged(rotation_locked_));
153 }
154
155 void MaximizeModeController::AddObserver(Observer* observer) {
156   observers_.AddObserver(observer);
157 }
158
159 void MaximizeModeController::RemoveObserver(Observer* observer) {
160   observers_.RemoveObserver(observer);
161 }
162
163 bool MaximizeModeController::CanEnterMaximizeMode() {
164   // If we have ever seen accelerometer data, then HandleHingeRotation may
165   // trigger maximize mode at some point in the future.
166   // The --enable-touch-view-testing switch can also mean that we may enter
167   // maximize mode.
168   return have_seen_accelerometer_data_ ||
169          CommandLine::ForCurrentProcess()->HasSwitch(
170              switches::kAshEnableTouchViewTesting);
171 }
172
173 void MaximizeModeController::EnableMaximizeModeWindowManager(bool enable) {
174   if (enable && !maximize_mode_window_manager_.get()) {
175     maximize_mode_window_manager_.reset(new MaximizeModeWindowManager());
176     // TODO(jonross): Move the maximize mode notifications from ShellObserver
177     // to MaximizeModeController::Observer
178     Shell::GetInstance()->OnMaximizeModeStarted();
179   } else if (!enable && maximize_mode_window_manager_.get()) {
180     maximize_mode_window_manager_.reset();
181     Shell::GetInstance()->OnMaximizeModeEnded();
182   }
183 }
184
185 bool MaximizeModeController::IsMaximizeModeWindowManagerEnabled() const {
186   return maximize_mode_window_manager_.get() != NULL;
187 }
188
189 void MaximizeModeController::AddWindow(aura::Window* window) {
190   if (IsMaximizeModeWindowManagerEnabled())
191     maximize_mode_window_manager_->AddWindow(window);
192 }
193
194 void MaximizeModeController::Shutdown() {
195   shutting_down_ = true;
196   LeaveMaximizeMode();
197 }
198
199 void MaximizeModeController::OnAccelerometerUpdated(
200     const ui::AccelerometerUpdate& update) {
201   bool first_accelerometer_update = !have_seen_accelerometer_data_;
202   have_seen_accelerometer_data_ = true;
203
204   // Ignore the reading if it appears unstable. The reading is considered
205   // unstable if it deviates too much from gravity and/or the magnitude of the
206   // reading from the lid differs too much from the reading from the base.
207   float base_magnitude =
208       update.has(ui::ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD) ?
209       update.get(ui::ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD).Length() :
210       0.0f;
211   float lid_magnitude = update.has(ui::ACCELEROMETER_SOURCE_SCREEN) ?
212       update.get(ui::ACCELEROMETER_SOURCE_SCREEN).Length() : 0.0f;
213   bool lid_stable = update.has(ui::ACCELEROMETER_SOURCE_SCREEN) &&
214       std::abs(lid_magnitude - kMeanGravity) <= kDeviationFromGravityThreshold;
215   bool base_angle_stable = lid_stable &&
216       update.has(ui::ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD) &&
217       std::abs(base_magnitude - lid_magnitude) <= kNoisyMagnitudeDeviation &&
218       std::abs(base_magnitude - kMeanGravity) <= kDeviationFromGravityThreshold;
219
220   if (base_angle_stable) {
221     // Responding to the hinge rotation can change the maximize mode state which
222     // affects screen rotation, so we handle hinge rotation first.
223     HandleHingeRotation(
224         update.get(ui::ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD),
225         update.get(ui::ACCELEROMETER_SOURCE_SCREEN));
226   }
227   if (lid_stable)
228     HandleScreenRotation(update.get(ui::ACCELEROMETER_SOURCE_SCREEN));
229
230   if (first_accelerometer_update) {
231     // On the first accelerometer update we will know if we have entered
232     // maximize mode or not. Update the preferences to reflect the current
233     // state.
234     Shell::GetInstance()->display_manager()->
235         RegisterDisplayRotationProperties(rotation_locked_, current_rotation_);
236   }
237 }
238
239 void MaximizeModeController::OnDisplayConfigurationChanged() {
240   if (ignore_display_configuration_updates_)
241     return;
242   DisplayManager* display_manager = Shell::GetInstance()->display_manager();
243   gfx::Display::Rotation user_rotation = display_manager->
244       GetDisplayInfo(gfx::Display::InternalDisplayId()).rotation();
245   if (user_rotation != current_rotation_) {
246     // A user may change other display configuration settings. When the user
247     // does change the rotation setting, then lock rotation to prevent the
248     // accelerometer from erasing their change.
249     SetRotationLocked(true);
250     user_rotation_ = user_rotation;
251     current_rotation_ = user_rotation;
252   }
253 }
254
255 #if defined(OS_CHROMEOS)
256 void MaximizeModeController::LidEventReceived(bool open,
257                                               const base::TimeTicks& time) {
258   if (open)
259     last_lid_open_time_ = time;
260   lid_is_closed_ = !open;
261   LeaveMaximizeMode();
262 }
263
264 void MaximizeModeController::SuspendImminent() {
265   RecordTouchViewStateTransition();
266 }
267
268 void MaximizeModeController::SuspendDone(
269     const base::TimeDelta& sleep_duration) {
270   last_touchview_transition_time_ = base::Time::Now();
271 }
272 #endif  // OS_CHROMEOS
273
274 void MaximizeModeController::HandleHingeRotation(const gfx::Vector3dF& base,
275                                                  const gfx::Vector3dF& lid) {
276   static const gfx::Vector3dF hinge_vector(1.0f, 0.0f, 0.0f);
277   bool maximize_mode_engaged = IsMaximizeModeWindowManagerEnabled();
278   // Ignore the component of acceleration parallel to the hinge for the purposes
279   // of hinge angle calculation.
280   gfx::Vector3dF base_flattened(base);
281   gfx::Vector3dF lid_flattened(lid);
282   base_flattened.set_x(0.0f);
283   lid_flattened.set_x(0.0f);
284
285   // As the hinge approaches a vertical angle, the base and lid accelerometers
286   // approach the same values making any angle calculations highly inaccurate.
287   // Bail out early when it is too close.
288   if (base_flattened.Length() < kHingeAngleDetectionThreshold ||
289       lid_flattened.Length() < kHingeAngleDetectionThreshold) {
290     return;
291   }
292
293   // Compute the angle between the base and the lid.
294   float lid_angle = 180.0f - ClockwiseAngleBetweenVectorsInDegrees(
295       base_flattened, lid_flattened, hinge_vector);
296   if (lid_angle < 0.0f)
297     lid_angle += 360.0f;
298
299   bool is_angle_stable = lid_angle >= kMinStableAngle &&
300                          lid_angle <= kMaxStableAngle;
301
302   // Clear the last_lid_open_time_ for a stable reading so that there is less
303   // chance of a delay if the lid is moved from the close state to the fully
304   // open state very quickly.
305   if (is_angle_stable)
306     last_lid_open_time_ = base::TimeTicks();
307
308   // Toggle maximize mode on or off when corresponding thresholds are passed.
309   // TODO(flackr): Make MaximizeModeController own the MaximizeModeWindowManager
310   // such that observations of state changes occur after the change and shell
311   // has fewer states to track.
312   if (maximize_mode_engaged && is_angle_stable &&
313       lid_angle <= kExitMaximizeModeAngle) {
314     LeaveMaximizeMode();
315   } else if (!lid_is_closed_ && !maximize_mode_engaged &&
316              lid_angle >= kEnterMaximizeModeAngle &&
317              (is_angle_stable || !WasLidOpenedRecently())) {
318     EnterMaximizeMode();
319   }
320 }
321
322 void MaximizeModeController::HandleScreenRotation(const gfx::Vector3dF& lid) {
323   bool maximize_mode_engaged = IsMaximizeModeWindowManagerEnabled();
324
325   // TODO(jonross): track the updated rotation angle even when locked. So that
326   // when rotation lock is removed the accelerometer rotation can be applied
327   // without waiting for the next update.
328   if (!maximize_mode_engaged || rotation_locked_)
329     return;
330
331   DisplayManager* display_manager =
332       Shell::GetInstance()->display_manager();
333   gfx::Display::Rotation current_rotation = display_manager->GetDisplayInfo(
334       gfx::Display::InternalDisplayId()).rotation();
335
336   // After determining maximize mode state, determine if the screen should
337   // be rotated.
338   gfx::Vector3dF lid_flattened(lid.x(), lid.y(), 0.0f);
339   float lid_flattened_length = lid_flattened.Length();
340   // When the lid is close to being flat, don't change rotation as it is too
341   // sensitive to slight movements.
342   if (lid_flattened_length < kMinimumAccelerationScreenRotation)
343     return;
344
345   // The reference vector is the angle of gravity when the device is rotated
346   // clockwise by 45 degrees. Computing the angle between this vector and
347   // gravity we can easily determine the expected display rotation.
348   static const gfx::Vector3dF rotation_reference(-1.0f, -1.0f, 0.0f);
349
350   // Set the down vector to match the expected direction of gravity given the
351   // last configured rotation. This is used to enforce a stickiness that the
352   // user must overcome to rotate the display and prevents frequent rotations
353   // when holding the device near 45 degrees.
354   gfx::Vector3dF down(0.0f, 0.0f, 0.0f);
355   if (current_rotation == gfx::Display::ROTATE_0)
356     down.set_y(-1.0f);
357   else if (current_rotation == gfx::Display::ROTATE_90)
358     down.set_x(-1.0f);
359   else if (current_rotation == gfx::Display::ROTATE_180)
360     down.set_y(1.0f);
361   else
362     down.set_x(1.0f);
363
364   // Don't rotate if the screen has not passed the threshold.
365   if (AngleBetweenVectorsInDegrees(down, lid_flattened) <
366       kDisplayRotationStickyAngleDegrees) {
367     return;
368   }
369
370   float angle = ClockwiseAngleBetweenVectorsInDegrees(rotation_reference,
371       lid_flattened, gfx::Vector3dF(0.0f, 0.0f, -1.0f));
372
373   gfx::Display::Rotation new_rotation = gfx::Display::ROTATE_90;
374   if (angle < 90.0f)
375     new_rotation = gfx::Display::ROTATE_0;
376   else if (angle < 180.0f)
377     new_rotation = gfx::Display::ROTATE_270;
378   else if (angle < 270.0f)
379     new_rotation = gfx::Display::ROTATE_180;
380
381   if (new_rotation != current_rotation)
382     SetDisplayRotation(display_manager, new_rotation);
383 }
384
385 void MaximizeModeController::SetDisplayRotation(
386     DisplayManager* display_manager,
387     gfx::Display::Rotation rotation) {
388   base::AutoReset<bool> auto_ignore_display_configuration_updates(
389       &ignore_display_configuration_updates_, true);
390   current_rotation_ = rotation;
391   display_manager->SetDisplayRotation(gfx::Display::InternalDisplayId(),
392                                       rotation);
393 }
394
395 void MaximizeModeController::EnterMaximizeMode() {
396   if (IsMaximizeModeWindowManagerEnabled())
397     return;
398   DisplayManager* display_manager = Shell::GetInstance()->display_manager();
399   if (display_manager->HasInternalDisplay()) {
400     current_rotation_ = user_rotation_ = display_manager->
401         GetDisplayInfo(gfx::Display::InternalDisplayId()).rotation();
402     LoadDisplayRotationProperties();
403   }
404   EnableMaximizeModeWindowManager(true);
405 #if defined(USE_X11)
406   event_blocker_.reset(new ScopedDisableInternalMouseAndKeyboardX11);
407 #endif
408   Shell::GetInstance()->display_controller()->AddObserver(this);
409 }
410
411 void MaximizeModeController::LeaveMaximizeMode() {
412   if (!IsMaximizeModeWindowManagerEnabled())
413     return;
414   DisplayManager* display_manager = Shell::GetInstance()->display_manager();
415   if (display_manager->HasInternalDisplay()) {
416     gfx::Display::Rotation current_rotation = display_manager->
417         GetDisplayInfo(gfx::Display::InternalDisplayId()).rotation();
418     if (current_rotation != user_rotation_)
419       SetDisplayRotation(display_manager, user_rotation_);
420   }
421   if (!shutting_down_)
422     SetRotationLocked(false);
423   EnableMaximizeModeWindowManager(false);
424   event_blocker_.reset();
425   Shell::GetInstance()->display_controller()->RemoveObserver(this);
426 }
427
428 // Called after maximize mode has started, windows might still animate though.
429 void MaximizeModeController::OnMaximizeModeStarted() {
430   RecordTouchViewStateTransition();
431 }
432
433 // Called after maximize mode has ended, windows might still be returning to
434 // their original position.
435 void MaximizeModeController::OnMaximizeModeEnded() {
436   RecordTouchViewStateTransition();
437 }
438
439 void MaximizeModeController::RecordTouchViewStateTransition() {
440   if (CanEnterMaximizeMode()) {
441     base::Time current_time = base::Time::Now();
442     base::TimeDelta delta = current_time - last_touchview_transition_time_;
443     if (IsMaximizeModeWindowManagerEnabled()) {
444       UMA_HISTOGRAM_LONG_TIMES("Ash.TouchView.TouchViewInactive", delta);
445       total_non_touchview_time_ += delta;
446     } else {
447       UMA_HISTOGRAM_LONG_TIMES("Ash.TouchView.TouchViewActive", delta);
448       total_touchview_time_ += delta;
449     }
450     last_touchview_transition_time_ = current_time;
451   }
452 }
453
454 void MaximizeModeController::LoadDisplayRotationProperties() {
455   DisplayManager* display_manager = Shell::GetInstance()->display_manager();
456   if (!display_manager->registered_internal_display_rotation_lock())
457     return;
458
459   SetDisplayRotation(display_manager,
460                      display_manager->registered_internal_display_rotation());
461   SetRotationLocked(true);
462 }
463
464 void MaximizeModeController::OnAppTerminating() {
465   if (CanEnterMaximizeMode()) {
466     RecordTouchViewStateTransition();
467     UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchView.TouchViewActiveTotal",
468         total_touchview_time_.InMinutes(),
469         1, base::TimeDelta::FromDays(7).InMinutes(), 50);
470     UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchView.TouchViewInactiveTotal",
471         total_non_touchview_time_.InMinutes(),
472         1, base::TimeDelta::FromDays(7).InMinutes(), 50);
473     base::TimeDelta total_runtime = total_touchview_time_ +
474         total_non_touchview_time_;
475     if (total_runtime.InSeconds() > 0) {
476       UMA_HISTOGRAM_PERCENTAGE("Ash.TouchView.TouchViewActivePercentage",
477           100 * total_touchview_time_.InSeconds() / total_runtime.InSeconds());
478     }
479   }
480   Shell::GetInstance()->display_controller()->RemoveObserver(this);
481 }
482
483 bool MaximizeModeController::WasLidOpenedRecently() const {
484   if (last_lid_open_time_.is_null())
485     return false;
486
487   base::TimeTicks now = tick_clock_->NowTicks();
488   DCHECK(now >= last_lid_open_time_);
489   base::TimeDelta elapsed_time = now - last_lid_open_time_;
490   return elapsed_time <= kLidRecentlyOpenedDuration;
491 }
492
493 void MaximizeModeController::SetTickClockForTest(
494     scoped_ptr<base::TickClock> tick_clock) {
495   DCHECK(tick_clock_);
496   tick_clock_ = tick_clock.Pass();
497 }
498
499 }  // namespace ash