Reland: Add browser_test for extension app API with missing schema
[chromium-blink-merge.git] / ash / wm / maximize_mode / maximize_mode_controller.cc
blob61ce2f2205611a724a838ebb705b713a088f2055
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_event_blocker.h"
14 #include "base/command_line.h"
15 #include "ui/base/accelerators/accelerator.h"
16 #include "ui/events/event.h"
17 #include "ui/events/event_handler.h"
18 #include "ui/events/keycodes/keyboard_codes.h"
19 #include "ui/gfx/vector3d_f.h"
21 namespace ash {
23 namespace {
25 // The hinge angle at which to enter maximize mode.
26 const float kEnterMaximizeModeAngle = 200.0f;
28 // The angle at which to exit maximize mode, this is specifically less than the
29 // angle to enter maximize mode to prevent rapid toggling when near the angle.
30 const float kExitMaximizeModeAngle = 160.0f;
32 // When the lid is fully open 360 degrees, the accelerometer readings can
33 // occasionally appear as though the lid is almost closed. If the lid appears
34 // near closed but the device is on we assume it is an erroneous reading from
35 // it being open 360 degrees.
36 const float kFullyOpenAngleErrorTolerance = 20.0f;
38 // When the device approaches vertical orientation (i.e. portrait orientation)
39 // the accelerometers for the base and lid approach the same values (i.e.
40 // gravity pointing in the direction of the hinge). When this happens we cannot
41 // compute the hinge angle reliably and must turn ignore accelerometer readings.
42 // This is the minimum acceleration perpendicular to the hinge under which to
43 // detect hinge angle.
44 const float kHingeAngleDetectionThreshold = 0.25f;
46 // The maximum deviation from the acceleration expected due to gravity under
47 // which to detect hinge angle and screen rotation.
48 const float kDeviationFromGravityThreshold = 0.1f;
50 // The maximum deviation between the magnitude of the two accelerometers under
51 // which to detect hinge angle and screen rotation. These accelerometers are
52 // attached to the same physical device and so should be under the same
53 // acceleration.
54 const float kNoisyMagnitudeDeviation = 0.1f;
56 // The angle which the screen has to be rotated past before the display will
57 // rotate to match it (i.e. 45.0f is no stickiness).
58 const float kDisplayRotationStickyAngleDegrees = 60.0f;
60 // The minimum acceleration in a direction required to trigger screen rotation.
61 // This prevents rapid toggling of rotation when the device is near flat and
62 // there is very little screen aligned force on it.
63 const float kMinimumAccelerationScreenRotation = 0.3f;
65 const float kRadiansToDegrees = 180.0f / 3.14159265f;
67 // Returns the angle between |base| and |other| in degrees.
68 float AngleBetweenVectorsInDegrees(const gfx::Vector3dF& base,
69 const gfx::Vector3dF& other) {
70 return acos(gfx::DotProduct(base, other) /
71 base.Length() / other.Length()) * kRadiansToDegrees;
74 // Returns the clockwise angle between |base| and |other| where |normal| is the
75 // normal of the virtual surface to measure clockwise according to.
76 float ClockwiseAngleBetweenVectorsInDegrees(const gfx::Vector3dF& base,
77 const gfx::Vector3dF& other,
78 const gfx::Vector3dF& normal) {
79 float angle = AngleBetweenVectorsInDegrees(base, other);
80 gfx::Vector3dF cross(base);
81 cross.Cross(other);
82 // If the dot product of this cross product is normal, it means that the
83 // shortest angle between |base| and |other| was counterclockwise with respect
84 // to the surface represented by |normal| and this angle must be reversed.
85 if (gfx::DotProduct(cross, normal) > 0.0f)
86 angle = 360.0f - angle;
87 return angle;
90 #if defined(OS_CHROMEOS)
92 // An event handler which listens for a volume down + power keypress and
93 // triggers a screenshot when this is seen.
94 class ScreenshotActionHandler : public ui::EventHandler {
95 public:
96 ScreenshotActionHandler();
97 virtual ~ScreenshotActionHandler();
99 // ui::EventHandler:
100 virtual void OnKeyEvent(ui::KeyEvent* event) OVERRIDE;
102 private:
103 bool volume_down_pressed_;
105 DISALLOW_COPY_AND_ASSIGN(ScreenshotActionHandler);
108 ScreenshotActionHandler::ScreenshotActionHandler()
109 : volume_down_pressed_(false) {
110 Shell::GetInstance()->PrependPreTargetHandler(this);
113 ScreenshotActionHandler::~ScreenshotActionHandler() {
114 Shell::GetInstance()->RemovePreTargetHandler(this);
117 void ScreenshotActionHandler::OnKeyEvent(ui::KeyEvent* event) {
118 if (event->key_code() == ui::VKEY_VOLUME_DOWN) {
119 volume_down_pressed_ = event->type() == ui::ET_KEY_PRESSED ||
120 event->type() == ui::ET_TRANSLATED_KEY_PRESS;
121 } else if (volume_down_pressed_ &&
122 event->key_code() == ui::VKEY_POWER &&
123 event->type() == ui::ET_KEY_PRESSED) {
124 Shell::GetInstance()->accelerator_controller()->PerformAction(
125 ash::TAKE_SCREENSHOT, ui::Accelerator());
129 #endif // OS_CHROMEOS
131 } // namespace
133 MaximizeModeController::MaximizeModeController()
134 : rotation_locked_(false),
135 have_seen_accelerometer_data_(false) {
136 Shell::GetInstance()->accelerometer_controller()->AddObserver(this);
139 MaximizeModeController::~MaximizeModeController() {
140 Shell::GetInstance()->accelerometer_controller()->RemoveObserver(this);
143 bool MaximizeModeController::CanEnterMaximizeMode() {
144 // If we have ever seen accelerometer data, then HandleHingeRotation may
145 // trigger maximize mode at some point in the future.
146 // The --enable-touch-view-testing switch can also mean that we may enter
147 // maximize mode.
148 return have_seen_accelerometer_data_ ||
149 CommandLine::ForCurrentProcess()->HasSwitch(
150 switches::kAshEnableTouchViewTesting);
153 void MaximizeModeController::OnAccelerometerUpdated(
154 const gfx::Vector3dF& base,
155 const gfx::Vector3dF& lid) {
156 have_seen_accelerometer_data_ = true;
158 // Ignore the reading if it appears unstable. The reading is considered
159 // unstable if it deviates too much from gravity and/or the magnitude of the
160 // reading from the lid differs too much from the reading from the base.
161 float base_magnitude = base.Length();
162 float lid_magnitude = lid.Length();
163 if (std::abs(base_magnitude - lid_magnitude) > kNoisyMagnitudeDeviation ||
164 std::abs(base_magnitude - 1.0f) > kDeviationFromGravityThreshold ||
165 std::abs(lid_magnitude - 1.0f) > kDeviationFromGravityThreshold) {
166 return;
169 // Responding to the hinge rotation can change the maximize mode state which
170 // affects screen rotation, so we handle hinge rotation first.
171 HandleHingeRotation(base, lid);
172 HandleScreenRotation(lid);
175 void MaximizeModeController::HandleHingeRotation(const gfx::Vector3dF& base,
176 const gfx::Vector3dF& lid) {
177 static const gfx::Vector3dF hinge_vector(0.0f, 1.0f, 0.0f);
178 bool maximize_mode_engaged =
179 Shell::GetInstance()->IsMaximizeModeWindowManagerEnabled();
180 // Ignore the component of acceleration parallel to the hinge for the purposes
181 // of hinge angle calculation.
182 gfx::Vector3dF base_flattened(base);
183 gfx::Vector3dF lid_flattened(lid);
184 base_flattened.set_y(0.0f);
185 lid_flattened.set_y(0.0f);
187 // As the hinge approaches a vertical angle, the base and lid accelerometers
188 // approach the same values making any angle calculations highly inaccurate.
189 // Bail out early when it is too close.
190 if (base_flattened.Length() < kHingeAngleDetectionThreshold ||
191 lid_flattened.Length() < kHingeAngleDetectionThreshold) {
192 return;
195 // Compute the angle between the base and the lid.
196 float angle = ClockwiseAngleBetweenVectorsInDegrees(base_flattened,
197 lid_flattened, hinge_vector);
199 // Toggle maximize mode on or off when corresponding thresholds are passed.
200 // TODO(flackr): Make MaximizeModeController own the MaximizeModeWindowManager
201 // such that observations of state changes occur after the change and shell
202 // has fewer states to track.
203 if (maximize_mode_engaged &&
204 angle > kFullyOpenAngleErrorTolerance &&
205 angle < kExitMaximizeModeAngle) {
206 Shell::GetInstance()->EnableMaximizeModeWindowManager(false);
207 event_blocker_.reset();
208 event_handler_.reset();
209 } else if (!maximize_mode_engaged &&
210 angle > kEnterMaximizeModeAngle) {
211 Shell::GetInstance()->EnableMaximizeModeWindowManager(true);
212 event_blocker_.reset(new MaximizeModeEventBlocker);
213 #if defined(OS_CHROMEOS)
214 event_handler_.reset(new ScreenshotActionHandler);
215 #endif
219 void MaximizeModeController::HandleScreenRotation(const gfx::Vector3dF& lid) {
220 bool maximize_mode_engaged =
221 Shell::GetInstance()->IsMaximizeModeWindowManagerEnabled();
223 DisplayManager* display_manager =
224 Shell::GetInstance()->display_manager();
225 gfx::Display::Rotation current_rotation = display_manager->GetDisplayInfo(
226 gfx::Display::InternalDisplayId()).rotation();
228 // If maximize mode is not engaged, ensure the screen is not rotated and
229 // do not rotate to match the current device orientation.
230 if (!maximize_mode_engaged) {
231 if (current_rotation != gfx::Display::ROTATE_0) {
232 // TODO(flackr): Currently this will prevent setting a manual rotation on
233 // the screen of a device with an accelerometer, this should only set it
234 // back to ROTATE_0 if it was last set by the accelerometer.
235 // Also, SetDisplayRotation will save the setting to the local store,
236 // this should be stored in a way that we can distinguish what the
237 // rotation was set by.
238 display_manager->SetDisplayRotation(gfx::Display::InternalDisplayId(),
239 gfx::Display::ROTATE_0);
241 rotation_locked_ = false;
242 return;
245 if (rotation_locked_)
246 return;
248 // After determining maximize mode state, determine if the screen should
249 // be rotated.
250 gfx::Vector3dF lid_flattened(lid.x(), lid.y(), 0.0f);
251 float lid_flattened_length = lid_flattened.Length();
252 // When the lid is close to being flat, don't change rotation as it is too
253 // sensitive to slight movements.
254 if (lid_flattened_length < kMinimumAccelerationScreenRotation)
255 return;
257 // The reference vector is the angle of gravity when the device is rotated
258 // clockwise by 45 degrees. Computing the angle between this vector and
259 // gravity we can easily determine the expected display rotation.
260 static gfx::Vector3dF rotation_reference(-1.0f, 1.0f, 0.0f);
262 // Set the down vector to match the expected direction of gravity given the
263 // last configured rotation. This is used to enforce a stickiness that the
264 // user must overcome to rotate the display and prevents frequent rotations
265 // when holding the device near 45 degrees.
266 gfx::Vector3dF down(0.0f, 0.0f, 0.0f);
267 if (current_rotation == gfx::Display::ROTATE_0)
268 down.set_x(-1.0f);
269 else if (current_rotation == gfx::Display::ROTATE_90)
270 down.set_y(1.0f);
271 else if (current_rotation == gfx::Display::ROTATE_180)
272 down.set_x(1.0f);
273 else
274 down.set_y(-1.0f);
276 // Don't rotate if the screen has not passed the threshold.
277 if (AngleBetweenVectorsInDegrees(down, lid_flattened) <
278 kDisplayRotationStickyAngleDegrees) {
279 return;
282 float angle = ClockwiseAngleBetweenVectorsInDegrees(rotation_reference,
283 lid_flattened, gfx::Vector3dF(0.0f, 0.0f, -1.0f));
285 gfx::Display::Rotation new_rotation = gfx::Display::ROTATE_90;
286 if (angle < 90.0f)
287 new_rotation = gfx::Display::ROTATE_0;
288 else if (angle < 180.0f)
289 new_rotation = gfx::Display::ROTATE_270;
290 else if (angle < 270.0f)
291 new_rotation = gfx::Display::ROTATE_180;
293 // When exiting maximize mode return rotation to 0. When entering, rotate to
294 // match screen orientation.
295 if (new_rotation == gfx::Display::ROTATE_0 ||
296 maximize_mode_engaged) {
297 display_manager->SetDisplayRotation(gfx::Display::InternalDisplayId(),
298 new_rotation);
302 } // namespace ash