Upstream version 9.37.195.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_event_blocker.h"
14 #include "ash/wm/maximize_mode/maximize_mode_window_manager.h"
15 #include "base/auto_reset.h"
16 #include "base/command_line.h"
17 #include "base/metrics/histogram.h"
18 #include "ui/base/accelerators/accelerator.h"
19 #include "ui/events/event.h"
20 #include "ui/events/event_handler.h"
21 #include "ui/events/keycodes/keyboard_codes.h"
22 #include "ui/gfx/vector3d_f.h"
23
24 namespace ash {
25
26 namespace {
27
28 // The hinge angle at which to enter maximize mode.
29 const float kEnterMaximizeModeAngle = 200.0f;
30
31 // The angle at which to exit maximize mode, this is specifically less than the
32 // angle to enter maximize mode to prevent rapid toggling when near the angle.
33 const float kExitMaximizeModeAngle = 160.0f;
34
35 // When the lid is fully open 360 degrees, the accelerometer readings can
36 // occasionally appear as though the lid is almost closed. If the lid appears
37 // near closed but the device is on we assume it is an erroneous reading from
38 // it being open 360 degrees.
39 const float kFullyOpenAngleErrorTolerance = 20.0f;
40
41 // When the device approaches vertical orientation (i.e. portrait orientation)
42 // the accelerometers for the base and lid approach the same values (i.e.
43 // gravity pointing in the direction of the hinge). When this happens we cannot
44 // compute the hinge angle reliably and must turn ignore accelerometer readings.
45 // This is the minimum acceleration perpendicular to the hinge under which to
46 // detect hinge angle.
47 const float kHingeAngleDetectionThreshold = 0.25f;
48
49 // The maximum deviation from the acceleration expected due to gravity under
50 // which to detect hinge angle and screen rotation.
51 const float kDeviationFromGravityThreshold = 0.1f;
52
53 // The maximum deviation between the magnitude of the two accelerometers under
54 // which to detect hinge angle and screen rotation. These accelerometers are
55 // attached to the same physical device and so should be under the same
56 // acceleration.
57 const float kNoisyMagnitudeDeviation = 0.1f;
58
59 // The angle which the screen has to be rotated past before the display will
60 // rotate to match it (i.e. 45.0f is no stickiness).
61 const float kDisplayRotationStickyAngleDegrees = 60.0f;
62
63 // The minimum acceleration in a direction required to trigger screen rotation.
64 // This prevents rapid toggling of rotation when the device is near flat and
65 // there is very little screen aligned force on it. The value is effectively the
66 // sine of the rise angle required, with the current value requiring at least a
67 // 25 degree rise.
68 const float kMinimumAccelerationScreenRotation = 0.42f;
69
70 const float kRadiansToDegrees = 180.0f / 3.14159265f;
71
72 // Returns the angle between |base| and |other| in degrees.
73 float AngleBetweenVectorsInDegrees(const gfx::Vector3dF& base,
74                                  const gfx::Vector3dF& other) {
75   return acos(gfx::DotProduct(base, other) /
76               base.Length() / other.Length()) * kRadiansToDegrees;
77 }
78
79 // Returns the clockwise angle between |base| and |other| where |normal| is the
80 // normal of the virtual surface to measure clockwise according to.
81 float ClockwiseAngleBetweenVectorsInDegrees(const gfx::Vector3dF& base,
82                                             const gfx::Vector3dF& other,
83                                             const gfx::Vector3dF& normal) {
84   float angle = AngleBetweenVectorsInDegrees(base, other);
85   gfx::Vector3dF cross(base);
86   cross.Cross(other);
87   // If the dot product of this cross product is normal, it means that the
88   // shortest angle between |base| and |other| was counterclockwise with respect
89   // to the surface represented by |normal| and this angle must be reversed.
90   if (gfx::DotProduct(cross, normal) > 0.0f)
91     angle = 360.0f - angle;
92   return angle;
93 }
94
95 #if defined(OS_CHROMEOS)
96
97 // An event handler which listens for a volume down + power keypress and
98 // triggers a screenshot when this is seen.
99 class ScreenshotActionHandler : public ui::EventHandler {
100  public:
101   ScreenshotActionHandler();
102   virtual ~ScreenshotActionHandler();
103
104   // ui::EventHandler:
105   virtual void OnKeyEvent(ui::KeyEvent* event) OVERRIDE;
106
107  private:
108   bool volume_down_pressed_;
109
110   DISALLOW_COPY_AND_ASSIGN(ScreenshotActionHandler);
111 };
112
113 ScreenshotActionHandler::ScreenshotActionHandler()
114     : volume_down_pressed_(false) {
115   Shell::GetInstance()->PrependPreTargetHandler(this);
116 }
117
118 ScreenshotActionHandler::~ScreenshotActionHandler() {
119   Shell::GetInstance()->RemovePreTargetHandler(this);
120 }
121
122 void ScreenshotActionHandler::OnKeyEvent(ui::KeyEvent* event) {
123   if (event->key_code() == ui::VKEY_VOLUME_DOWN) {
124     volume_down_pressed_ = event->type() == ui::ET_KEY_PRESSED ||
125                            event->type() == ui::ET_TRANSLATED_KEY_PRESS;
126   } else if (volume_down_pressed_ &&
127              event->key_code() == ui::VKEY_POWER &&
128              event->type() == ui::ET_KEY_PRESSED) {
129     Shell::GetInstance()->accelerator_controller()->PerformAction(
130         ash::TAKE_SCREENSHOT, ui::Accelerator());
131   }
132 }
133
134 #endif  // OS_CHROMEOS
135
136 }  // namespace
137
138 MaximizeModeController::MaximizeModeController()
139     : rotation_locked_(false),
140       have_seen_accelerometer_data_(false),
141       in_set_screen_rotation_(false),
142       user_rotation_(gfx::Display::ROTATE_0),
143       last_touchview_transition_time_(base::Time::Now()) {
144   Shell::GetInstance()->accelerometer_controller()->AddObserver(this);
145   Shell::GetInstance()->AddShellObserver(this);
146 }
147
148 MaximizeModeController::~MaximizeModeController() {
149   Shell::GetInstance()->RemoveShellObserver(this);
150   Shell::GetInstance()->accelerometer_controller()->RemoveObserver(this);
151 }
152
153 void MaximizeModeController::SetRotationLocked(bool rotation_locked) {
154   if (rotation_locked_ == rotation_locked)
155     return;
156   rotation_locked_ = rotation_locked;
157   FOR_EACH_OBSERVER(Observer, observers_,
158                     OnRotationLockChanged(rotation_locked_));
159 }
160
161 void MaximizeModeController::AddObserver(Observer* observer) {
162   observers_.AddObserver(observer);
163 }
164
165 void MaximizeModeController::RemoveObserver(Observer* observer) {
166   observers_.RemoveObserver(observer);
167 }
168
169 bool MaximizeModeController::CanEnterMaximizeMode() {
170   // If we have ever seen accelerometer data, then HandleHingeRotation may
171   // trigger maximize mode at some point in the future.
172   // The --enable-touch-view-testing switch can also mean that we may enter
173   // maximize mode.
174   return have_seen_accelerometer_data_ ||
175          CommandLine::ForCurrentProcess()->HasSwitch(
176              switches::kAshEnableTouchViewTesting);
177 }
178
179 void MaximizeModeController::EnableMaximizeModeWindowManager(bool enable) {
180   if (enable && !maximize_mode_window_manager_.get()) {
181     maximize_mode_window_manager_.reset(new MaximizeModeWindowManager());
182     // TODO(jonross): Move the maximize mode notifications from ShellObserver
183     // to MaximizeModeController::Observer
184     Shell::GetInstance()->OnMaximizeModeStarted();
185   } else if (!enable && maximize_mode_window_manager_.get()) {
186     maximize_mode_window_manager_.reset();
187     Shell::GetInstance()->OnMaximizeModeEnded();
188   }
189 }
190
191 bool MaximizeModeController::IsMaximizeModeWindowManagerEnabled() const {
192   return maximize_mode_window_manager_.get() != NULL;
193 }
194
195 void MaximizeModeController::AddWindow(aura::Window* window) {
196   if (IsMaximizeModeWindowManagerEnabled())
197     maximize_mode_window_manager_->AddWindow(window);
198 }
199
200 void MaximizeModeController::Shutdown() {
201   maximize_mode_window_manager_.reset();
202   Shell::GetInstance()->OnMaximizeModeEnded();
203 }
204
205 void MaximizeModeController::OnAccelerometerUpdated(
206     const gfx::Vector3dF& base,
207     const gfx::Vector3dF& lid) {
208   have_seen_accelerometer_data_ = true;
209
210   // Ignore the reading if it appears unstable. The reading is considered
211   // unstable if it deviates too much from gravity and/or the magnitude of the
212   // reading from the lid differs too much from the reading from the base.
213   float base_magnitude = base.Length();
214   float lid_magnitude = lid.Length();
215   if (std::abs(base_magnitude - lid_magnitude) > kNoisyMagnitudeDeviation ||
216       std::abs(base_magnitude - 1.0f) > kDeviationFromGravityThreshold ||
217       std::abs(lid_magnitude - 1.0f) > kDeviationFromGravityThreshold) {
218       return;
219   }
220
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(base, lid);
224   HandleScreenRotation(lid);
225 }
226
227 void MaximizeModeController::OnDisplayConfigurationChanged() {
228   if (in_set_screen_rotation_)
229     return;
230   DisplayManager* display_manager = Shell::GetInstance()->display_manager();
231   gfx::Display::Rotation user_rotation = display_manager->
232       GetDisplayInfo(gfx::Display::InternalDisplayId()).rotation();
233   if (user_rotation != current_rotation_) {
234     // A user may change other display configuration settings. When the user
235     // does change the rotation setting, then lock rotation to prevent the
236     // accelerometer from erasing their change.
237     SetRotationLocked(true);
238     user_rotation_ = user_rotation;
239     current_rotation_ = user_rotation;
240   }
241 }
242
243 void MaximizeModeController::HandleHingeRotation(const gfx::Vector3dF& base,
244                                                  const gfx::Vector3dF& lid) {
245   static const gfx::Vector3dF hinge_vector(0.0f, 1.0f, 0.0f);
246   bool maximize_mode_engaged = IsMaximizeModeWindowManagerEnabled();
247   // Ignore the component of acceleration parallel to the hinge for the purposes
248   // of hinge angle calculation.
249   gfx::Vector3dF base_flattened(base);
250   gfx::Vector3dF lid_flattened(lid);
251   base_flattened.set_y(0.0f);
252   lid_flattened.set_y(0.0f);
253
254   // As the hinge approaches a vertical angle, the base and lid accelerometers
255   // approach the same values making any angle calculations highly inaccurate.
256   // Bail out early when it is too close.
257   if (base_flattened.Length() < kHingeAngleDetectionThreshold ||
258       lid_flattened.Length() < kHingeAngleDetectionThreshold) {
259     return;
260   }
261
262   // Compute the angle between the base and the lid.
263   float angle = ClockwiseAngleBetweenVectorsInDegrees(base_flattened,
264       lid_flattened, hinge_vector);
265
266   // Toggle maximize mode on or off when corresponding thresholds are passed.
267   // TODO(flackr): Make MaximizeModeController own the MaximizeModeWindowManager
268   // such that observations of state changes occur after the change and shell
269   // has fewer states to track.
270   if (maximize_mode_engaged &&
271       angle > kFullyOpenAngleErrorTolerance &&
272       angle < kExitMaximizeModeAngle) {
273     LeaveMaximizeMode();
274   } else if (!maximize_mode_engaged &&
275       angle > kEnterMaximizeModeAngle) {
276     EnterMaximizeMode();
277   }
278 }
279
280 void MaximizeModeController::HandleScreenRotation(const gfx::Vector3dF& lid) {
281   bool maximize_mode_engaged = IsMaximizeModeWindowManagerEnabled();
282
283   // TODO(jonross): track the updated rotation angle even when locked. So that
284   // when rotation lock is removed the accelerometer rotation can be applied
285   // without waiting for the next update.
286   if (!maximize_mode_engaged || rotation_locked_)
287     return;
288
289   DisplayManager* display_manager =
290       Shell::GetInstance()->display_manager();
291   gfx::Display::Rotation current_rotation = display_manager->GetDisplayInfo(
292       gfx::Display::InternalDisplayId()).rotation();
293
294   // After determining maximize mode state, determine if the screen should
295   // be rotated.
296   gfx::Vector3dF lid_flattened(lid.x(), lid.y(), 0.0f);
297   float lid_flattened_length = lid_flattened.Length();
298   // When the lid is close to being flat, don't change rotation as it is too
299   // sensitive to slight movements.
300   if (lid_flattened_length < kMinimumAccelerationScreenRotation)
301     return;
302
303   // The reference vector is the angle of gravity when the device is rotated
304   // clockwise by 45 degrees. Computing the angle between this vector and
305   // gravity we can easily determine the expected display rotation.
306   static gfx::Vector3dF rotation_reference(-1.0f, 1.0f, 0.0f);
307
308   // Set the down vector to match the expected direction of gravity given the
309   // last configured rotation. This is used to enforce a stickiness that the
310   // user must overcome to rotate the display and prevents frequent rotations
311   // when holding the device near 45 degrees.
312   gfx::Vector3dF down(0.0f, 0.0f, 0.0f);
313   if (current_rotation == gfx::Display::ROTATE_0)
314     down.set_x(-1.0f);
315   else if (current_rotation == gfx::Display::ROTATE_90)
316     down.set_y(1.0f);
317   else if (current_rotation == gfx::Display::ROTATE_180)
318     down.set_x(1.0f);
319   else
320     down.set_y(-1.0f);
321
322   // Don't rotate if the screen has not passed the threshold.
323   if (AngleBetweenVectorsInDegrees(down, lid_flattened) <
324       kDisplayRotationStickyAngleDegrees) {
325     return;
326   }
327
328   float angle = ClockwiseAngleBetweenVectorsInDegrees(rotation_reference,
329       lid_flattened, gfx::Vector3dF(0.0f, 0.0f, -1.0f));
330
331   gfx::Display::Rotation new_rotation = gfx::Display::ROTATE_90;
332   if (angle < 90.0f)
333     new_rotation = gfx::Display::ROTATE_0;
334   else if (angle < 180.0f)
335     new_rotation = gfx::Display::ROTATE_270;
336   else if (angle < 270.0f)
337     new_rotation = gfx::Display::ROTATE_180;
338
339   if (new_rotation != current_rotation)
340     SetDisplayRotation(display_manager, new_rotation);
341 }
342
343 void MaximizeModeController::SetDisplayRotation(
344     DisplayManager* display_manager,
345     gfx::Display::Rotation rotation) {
346   base::AutoReset<bool> auto_in_set_screen_rotation(
347       &in_set_screen_rotation_, true);
348   current_rotation_ = rotation;
349   display_manager->SetDisplayRotation(gfx::Display::InternalDisplayId(),
350                                       rotation);
351 }
352
353 void MaximizeModeController::EnterMaximizeMode() {
354   DisplayManager* display_manager = Shell::GetInstance()->display_manager();
355   current_rotation_ = user_rotation_ = display_manager->
356       GetDisplayInfo(gfx::Display::InternalDisplayId()).rotation();
357   EnableMaximizeModeWindowManager(true);
358   event_blocker_.reset(new MaximizeModeEventBlocker);
359 #if defined(OS_CHROMEOS)
360   event_handler_.reset(new ScreenshotActionHandler);
361 #endif
362   Shell::GetInstance()->display_controller()->AddObserver(this);
363 }
364
365 void MaximizeModeController::LeaveMaximizeMode() {
366   DisplayManager* display_manager = Shell::GetInstance()->display_manager();
367   gfx::Display::Rotation current_rotation = display_manager->
368       GetDisplayInfo(gfx::Display::InternalDisplayId()).rotation();
369   if (current_rotation != user_rotation_)
370     SetDisplayRotation(display_manager, user_rotation_);
371   rotation_locked_ = false;
372   EnableMaximizeModeWindowManager(false);
373   event_blocker_.reset();
374   event_handler_.reset();
375 }
376
377 void MaximizeModeController::OnSuspend() {
378   RecordTouchViewStateTransition();
379 }
380
381 void MaximizeModeController::OnResume() {
382   last_touchview_transition_time_ = base::Time::Now();
383 }
384
385 // Called after maximize mode has started, windows might still animate though.
386 void MaximizeModeController::OnMaximizeModeStarted() {
387   RecordTouchViewStateTransition();
388 }
389
390 // Called after maximize mode has ended, windows might still be returning to
391 // their original position.
392 void MaximizeModeController::OnMaximizeModeEnded() {
393   RecordTouchViewStateTransition();
394 }
395
396 void MaximizeModeController::RecordTouchViewStateTransition() {
397   if (CanEnterMaximizeMode()) {
398     base::Time current_time = base::Time::Now();
399     base::TimeDelta delta = current_time - last_touchview_transition_time_;
400     if (IsMaximizeModeWindowManagerEnabled()) {
401       UMA_HISTOGRAM_LONG_TIMES("Ash.TouchView.TouchViewInactive", delta);
402       total_non_touchview_time_ += delta;
403     } else {
404       UMA_HISTOGRAM_LONG_TIMES("Ash.TouchView.TouchViewActive", delta);
405       total_touchview_time_ += delta;
406     }
407     last_touchview_transition_time_ = current_time;
408   }
409 }
410
411 void MaximizeModeController::OnAppTerminating() {
412   if (CanEnterMaximizeMode()) {
413     RecordTouchViewStateTransition();
414     UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchView.TouchViewActiveTotal",
415         total_touchview_time_.InMinutes(),
416         1, base::TimeDelta::FromDays(7).InMinutes(), 50);
417     UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchView.TouchViewInactiveTotal",
418         total_non_touchview_time_.InMinutes(),
419         1, base::TimeDelta::FromDays(7).InMinutes(), 50);
420     base::TimeDelta total_runtime = total_touchview_time_ +
421         total_non_touchview_time_;
422     if (total_runtime.InSeconds() > 0) {
423       UMA_HISTOGRAM_PERCENTAGE("Ash.TouchView.TouchViewActivePercentage",
424           100 * total_touchview_time_.InSeconds() / total_runtime.InSeconds());
425     }
426   }
427   Shell::GetInstance()->display_controller()->RemoveObserver(this);
428 }
429
430 }  // namespace ash