Upstream version 7.36.149.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/accelerometer/accelerometer_controller.h"
8 #include "ash/display/display_manager.h"
9 #include "ash/shell.h"
10 #include "ash/wm/maximize_mode/maximize_mode_event_blocker.h"
11 #include "ui/gfx/vector3d_f.h"
12
13 namespace ash {
14
15 namespace {
16
17 // The hinge angle at which to enter maximize mode.
18 const float kEnterMaximizeModeAngle = 200.0f;
19
20 // The angle at which to exit maximize mode, this is specifically less than the
21 // angle to enter maximize mode to prevent rapid toggling when near the angle.
22 const float kExitMaximizeModeAngle = 160.0f;
23
24 // When the lid is fully open 360 degrees, the accelerometer readings can
25 // occasionally appear as though the lid is almost closed. If the lid appears
26 // near closed but the device is on we assume it is an erroneous reading from
27 // it being open 360 degrees.
28 const float kFullyOpenAngleErrorTolerance = 20.0f;
29
30 // When the device approaches vertical orientation (i.e. portrait orientation)
31 // the accelerometers for the base and lid approach the same values (i.e.
32 // gravity pointing in the direction of the hinge). When this happens we cannot
33 // compute the hinge angle reliably and must turn ignore accelerometer readings.
34 // This is the minimum acceleration perpendicular to the hinge under which to
35 // detect hinge angle.
36 const float kHingeAngleDetectionThreshold = 0.25f;
37
38 // The maximum deviation from the acceleration expected due to gravity under
39 // which to detect hinge angle and screen rotation.
40 const float kDeviationFromGravityThreshold = 0.1f;
41
42 // The maximum deviation between the magnitude of the two accelerometers under
43 // which to detect hinge angle and screen rotation. These accelerometers are
44 // attached to the same physical device and so should be under the same
45 // acceleration.
46 const float kNoisyMagnitudeDeviation = 0.1f;
47
48 // The angle which the screen has to be rotated past before the display will
49 // rotate to match it (i.e. 45.0f is no stickiness).
50 const float kDisplayRotationStickyAngleDegrees = 60.0f;
51
52 // The minimum acceleration in a direction required to trigger screen rotation.
53 // This prevents rapid toggling of rotation when the device is near flat and
54 // there is very little screen aligned force on it.
55 const float kMinimumAccelerationScreenRotation = 0.3f;
56
57 const float kRadiansToDegrees = 180.0f / 3.14159265f;
58
59 // Returns the angle between |base| and |other| in degrees.
60 float AngleBetweenVectorsInDegrees(const gfx::Vector3dF& base,
61                                  const gfx::Vector3dF& other) {
62   return acos(gfx::DotProduct(base, other) /
63               base.Length() / other.Length()) * kRadiansToDegrees;
64 }
65
66 // Returns the clockwise angle between |base| and |other| where |normal| is the
67 // normal of the virtual surface to measure clockwise according to.
68 float ClockwiseAngleBetweenVectorsInDegrees(const gfx::Vector3dF& base,
69                                             const gfx::Vector3dF& other,
70                                             const gfx::Vector3dF& normal) {
71   float angle = AngleBetweenVectorsInDegrees(base, other);
72   gfx::Vector3dF cross(base);
73   cross.Cross(other);
74   // If the dot product of this cross product is normal, it means that the
75   // shortest angle between |base| and |other| was counterclockwise with respect
76   // to the surface represented by |normal| and this angle must be reversed.
77   if (gfx::DotProduct(cross, normal) > 0.0f)
78     angle = 360.0f - angle;
79   return angle;
80 }
81
82 }  // namespace
83
84 MaximizeModeController::MaximizeModeController()
85     : rotation_locked_(false) {
86   Shell::GetInstance()->accelerometer_controller()->AddObserver(this);
87 }
88
89 MaximizeModeController::~MaximizeModeController() {
90   Shell::GetInstance()->accelerometer_controller()->RemoveObserver(this);
91 }
92
93 void MaximizeModeController::OnAccelerometerUpdated(
94     const gfx::Vector3dF& base,
95     const gfx::Vector3dF& lid) {
96   // Ignore the reading if it appears unstable. The reading is considered
97   // unstable if it deviates too much from gravity and/or the magnitude of the
98   // reading from the lid differs too much from the reading from the base.
99   float base_magnitude = base.Length();
100   float lid_magnitude = lid.Length();
101   if (std::abs(base_magnitude - lid_magnitude) > kNoisyMagnitudeDeviation ||
102       std::abs(base_magnitude - 1.0f) > kDeviationFromGravityThreshold ||
103       std::abs(lid_magnitude - 1.0f) > kDeviationFromGravityThreshold) {
104       return;
105   }
106
107   // Responding to the hinge rotation can change the maximize mode state which
108   // affects screen rotation, so we handle hinge rotation first.
109   HandleHingeRotation(base, lid);
110   HandleScreenRotation(lid);
111 }
112
113 void MaximizeModeController::HandleHingeRotation(const gfx::Vector3dF& base,
114                                                  const gfx::Vector3dF& lid) {
115   static const gfx::Vector3dF hinge_vector(0.0f, 1.0f, 0.0f);
116   bool maximize_mode_engaged =
117       Shell::GetInstance()->IsMaximizeModeWindowManagerEnabled();
118   // Ignore the component of acceleration parallel to the hinge for the purposes
119   // of hinge angle calculation.
120   gfx::Vector3dF base_flattened(base);
121   gfx::Vector3dF lid_flattened(lid);
122   base_flattened.set_y(0.0f);
123   lid_flattened.set_y(0.0f);
124
125   // As the hinge approaches a vertical angle, the base and lid accelerometers
126   // approach the same values making any angle calculations highly inaccurate.
127   // Bail out early when it is too close.
128   if (base_flattened.Length() < kHingeAngleDetectionThreshold ||
129       lid_flattened.Length() < kHingeAngleDetectionThreshold) {
130     return;
131   }
132
133   // Compute the angle between the base and the lid.
134   float angle = ClockwiseAngleBetweenVectorsInDegrees(base_flattened,
135       lid_flattened, hinge_vector);
136
137   // Toggle maximize mode on or off when corresponding thresholds are passed.
138   // TODO(flackr): Make MaximizeModeController own the MaximizeModeWindowManager
139   // such that observations of state changes occur after the change and shell
140   // has fewer states to track.
141   if (maximize_mode_engaged &&
142       angle > kFullyOpenAngleErrorTolerance &&
143       angle < kExitMaximizeModeAngle) {
144     Shell::GetInstance()->EnableMaximizeModeWindowManager(false);
145     event_blocker_.reset();
146   } else if (!maximize_mode_engaged &&
147       angle > kEnterMaximizeModeAngle) {
148     Shell::GetInstance()->EnableMaximizeModeWindowManager(true);
149     event_blocker_.reset(new MaximizeModeEventBlocker);
150   }
151 }
152
153 void MaximizeModeController::HandleScreenRotation(const gfx::Vector3dF& lid) {
154   bool maximize_mode_engaged =
155       Shell::GetInstance()->IsMaximizeModeWindowManagerEnabled();
156
157   DisplayManager* display_manager =
158       Shell::GetInstance()->display_manager();
159   gfx::Display::Rotation current_rotation = display_manager->GetDisplayInfo(
160       gfx::Display::InternalDisplayId()).rotation();
161
162   // If maximize mode is not engaged, ensure the screen is not rotated and
163   // do not rotate to match the current device orientation.
164   if (!maximize_mode_engaged) {
165     if (current_rotation != gfx::Display::ROTATE_0) {
166       // TODO(flackr): Currently this will prevent setting a manual rotation on
167       // the screen of a device with an accelerometer, this should only set it
168       // back to ROTATE_0 if it was last set by the accelerometer.
169       // Also, SetDisplayRotation will save the setting to the local store,
170       // this should be stored in a way that we can distinguish what the
171       // rotation was set by.
172       display_manager->SetDisplayRotation(gfx::Display::InternalDisplayId(),
173                                           gfx::Display::ROTATE_0);
174     }
175     rotation_locked_ = false;
176     return;
177   }
178
179   if (rotation_locked_)
180     return;
181
182   // After determining maximize mode state, determine if the screen should
183   // be rotated.
184   gfx::Vector3dF lid_flattened(lid.x(), lid.y(), 0.0f);
185   float lid_flattened_length = lid_flattened.Length();
186   // When the lid is close to being flat, don't change rotation as it is too
187   // sensitive to slight movements.
188   if (lid_flattened_length < kMinimumAccelerationScreenRotation)
189     return;
190
191   // The reference vector is the angle of gravity when the device is rotated
192   // clockwise by 45 degrees. Computing the angle between this vector and
193   // gravity we can easily determine the expected display rotation.
194   static gfx::Vector3dF rotation_reference(-1.0f, 1.0f, 0.0f);
195
196   // Set the down vector to match the expected direction of gravity given the
197   // last configured rotation. This is used to enforce a stickiness that the
198   // user must overcome to rotate the display and prevents frequent rotations
199   // when holding the device near 45 degrees.
200   gfx::Vector3dF down(0.0f, 0.0f, 0.0f);
201   if (current_rotation == gfx::Display::ROTATE_0)
202     down.set_x(-1.0f);
203   else if (current_rotation == gfx::Display::ROTATE_90)
204     down.set_y(1.0f);
205   else if (current_rotation == gfx::Display::ROTATE_180)
206     down.set_x(1.0f);
207   else
208     down.set_y(-1.0f);
209
210   // Don't rotate if the screen has not passed the threshold.
211   if (AngleBetweenVectorsInDegrees(down, lid_flattened) <
212       kDisplayRotationStickyAngleDegrees) {
213     return;
214   }
215
216   float angle = ClockwiseAngleBetweenVectorsInDegrees(rotation_reference,
217       lid_flattened, gfx::Vector3dF(0.0f, 0.0f, -1.0f));
218
219   gfx::Display::Rotation new_rotation = gfx::Display::ROTATE_90;
220   if (angle < 90.0f)
221     new_rotation = gfx::Display::ROTATE_0;
222   else if (angle < 180.0f)
223     new_rotation = gfx::Display::ROTATE_270;
224   else if (angle < 270.0f)
225     new_rotation = gfx::Display::ROTATE_180;
226
227   // When exiting maximize mode return rotation to 0. When entering, rotate to
228   // match screen orientation.
229   if (new_rotation == gfx::Display::ROTATE_0 ||
230       maximize_mode_engaged) {
231     display_manager->SetDisplayRotation(gfx::Display::InternalDisplayId(),
232                                         new_rotation);
233   }
234 }
235
236 }  // namespace ash