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/content/display/screen_orientation_controller_chromeos.h"
7 #include "ash/ash_switches.h"
8 #include "ash/display/display_info.h"
9 #include "ash/display/display_manager.h"
10 #include "ash/shell.h"
11 #include "ash/wm/maximize_mode/maximize_mode_controller.h"
12 #include "base/auto_reset.h"
13 #include "base/command_line.h"
14 #include "chromeos/accelerometer/accelerometer_reader.h"
15 #include "content/public/browser/screen_orientation_provider.h"
16 #include "content/public/browser/web_contents.h"
17 #include "ui/aura/window.h"
18 #include "ui/gfx/display.h"
19 #include "ui/gfx/geometry/size.h"
23 // The angle which the screen has to be rotated past before the display will
24 // rotate to match it (i.e. 45.0f is no stickiness).
25 const float kDisplayRotationStickyAngleDegrees
= 60.0f
;
27 // The minimum acceleration in m/s^2 in a direction required to trigger screen
28 // rotation. This prevents rapid toggling of rotation when the device is near
29 // flat and there is very little screen aligned force on it. The value is
30 // effectively the sine of the rise angle required times the acceleration due
31 // to gravity, with the current value requiring at least a 25 degree rise.
32 const float kMinimumAccelerationScreenRotation
= 4.2f
;
34 blink::WebScreenOrientationLockType
GetDisplayNaturalOrientation() {
35 ash::DisplayManager
* display_manager
=
36 ash::Shell::GetInstance()->display_manager();
37 if (!display_manager
->HasInternalDisplay())
38 return blink::WebScreenOrientationLockLandscape
;
40 ash::DisplayInfo info
=
41 display_manager
->GetDisplayInfo(gfx::Display::InternalDisplayId());
42 gfx::Size size
= info
.size_in_pixel();
43 switch (info
.rotation()) {
44 case gfx::Display::ROTATE_0
:
45 case gfx::Display::ROTATE_180
:
46 return size
.height() >= size
.width()
47 ? blink::WebScreenOrientationLockPortrait
48 : blink::WebScreenOrientationLockLandscape
;
49 case gfx::Display::ROTATE_90
:
50 case gfx::Display::ROTATE_270
:
51 return size
.height() < size
.width()
52 ? blink::WebScreenOrientationLockPortrait
53 : blink::WebScreenOrientationLockLandscape
;
56 return blink::WebScreenOrientationLockLandscape
;
63 ScreenOrientationController::ScreenOrientationController()
64 : locking_window_(NULL
),
65 natural_orientation_(GetDisplayNaturalOrientation()),
66 ignore_display_configuration_updates_(false),
67 rotation_locked_(false),
68 user_rotation_(gfx::Display::ROTATE_0
),
69 current_rotation_(gfx::Display::ROTATE_0
) {
70 content::ScreenOrientationProvider::SetDelegate(this);
71 Shell::GetInstance()->AddShellObserver(this);
74 ScreenOrientationController::~ScreenOrientationController() {
75 content::ScreenOrientationProvider::SetDelegate(NULL
);
76 Shell::GetInstance()->RemoveShellObserver(this);
77 Shell::GetInstance()->accelerometer_reader()->RemoveObserver(this);
78 Shell::GetInstance()->display_controller()->RemoveObserver(this);
81 void ScreenOrientationController::AddObserver(Observer
* observer
) {
82 observers_
.AddObserver(observer
);
85 void ScreenOrientationController::RemoveObserver(Observer
* observer
) {
86 observers_
.RemoveObserver(observer
);
89 void ScreenOrientationController::SetRotationLocked(bool rotation_locked
) {
90 if (rotation_locked_
== rotation_locked
)
92 rotation_locked_
= rotation_locked
;
93 FOR_EACH_OBSERVER(Observer
, observers_
,
94 OnRotationLockChanged(rotation_locked_
));
95 DisplayManager
* display_manager
= Shell::GetInstance()->display_manager();
96 if (!display_manager
->HasInternalDisplay())
98 base::AutoReset
<bool> auto_ignore_display_configuration_updates(
99 &ignore_display_configuration_updates_
, true);
100 display_manager
->RegisterDisplayRotationProperties(rotation_locked_
,
104 void ScreenOrientationController::SetDisplayRotation(
105 gfx::Display::Rotation rotation
) {
106 DisplayManager
* display_manager
= Shell::GetInstance()->display_manager();
107 if (!display_manager
->HasInternalDisplay())
109 current_rotation_
= rotation
;
110 base::AutoReset
<bool> auto_ignore_display_configuration_updates(
111 &ignore_display_configuration_updates_
, true);
112 display_manager
->SetDisplayRotation(gfx::Display::InternalDisplayId(),
116 void ScreenOrientationController::OnAccelerometerUpdated(
117 const ui::AccelerometerUpdate
& update
) {
118 if (rotation_locked_
)
120 if (!update
.has(ui::ACCELEROMETER_SOURCE_SCREEN
))
122 // Ignore the reading if it appears unstable. The reading is considered
123 // unstable if it deviates too much from gravity
124 if (chromeos::AccelerometerReader::IsReadingStable(
125 update
, ui::ACCELEROMETER_SOURCE_SCREEN
)) {
126 HandleScreenRotation(update
.get(ui::ACCELEROMETER_SOURCE_SCREEN
));
130 bool ScreenOrientationController::FullScreenRequired(
131 content::WebContents
* web_contents
) {
135 void ScreenOrientationController::Lock(
136 content::WebContents
* web_contents
,
137 blink::WebScreenOrientationLockType lock_orientation
) {
138 aura::Window
* requesting_window
= web_contents
->GetNativeView();
139 // TODO(jonross): Track one rotation lock per window. When the active window
140 // changes apply any corresponding rotation lock.
141 if (!locking_window_
)
142 locking_window_
= requesting_window
;
143 else if (requesting_window
!= locking_window_
)
146 switch (lock_orientation
) {
147 case blink::WebScreenOrientationLockAny
:
148 SetRotationLocked(false);
149 locking_window_
= NULL
;
151 case blink::WebScreenOrientationLockDefault
:
154 case blink::WebScreenOrientationLockPortraitPrimary
:
155 LockRotationToPrimaryOrientation(blink::WebScreenOrientationLockPortrait
);
157 case blink::WebScreenOrientationLockLandscape
:
158 case blink::WebScreenOrientationLockPortrait
:
159 LockToRotationMatchingOrientation(lock_orientation
);
161 case blink::WebScreenOrientationLockPortraitSecondary
:
162 LockRotationToSecondaryOrientation(
163 blink::WebScreenOrientationLockPortrait
);
165 case blink::WebScreenOrientationLockLandscapeSecondary
:
166 LockRotationToSecondaryOrientation(
167 blink::WebScreenOrientationLockLandscape
);
169 case blink::WebScreenOrientationLockLandscapePrimary
:
170 LockRotationToPrimaryOrientation(
171 blink::WebScreenOrientationLockLandscape
);
173 case blink::WebScreenOrientationLockNatural
:
174 LockRotation(gfx::Display::ROTATE_0
);
182 bool ScreenOrientationController::ScreenOrientationProviderSupported() {
183 return Shell::GetInstance()
184 ->maximize_mode_controller()
185 ->IsMaximizeModeWindowManagerEnabled() &&
186 base::CommandLine::ForCurrentProcess()->HasSwitch(
187 switches::kAshEnableTouchViewTesting
);
190 void ScreenOrientationController::Unlock(content::WebContents
* web_contents
) {
191 aura::Window
* requesting_window
= web_contents
->GetNativeView();
192 if (requesting_window
!= locking_window_
)
194 locking_window_
= NULL
;
195 SetRotationLocked(false);
198 void ScreenOrientationController::OnDisplayConfigurationChanged() {
199 if (ignore_display_configuration_updates_
)
201 gfx::Display::Rotation user_rotation
=
204 ->GetDisplayInfo(gfx::Display::InternalDisplayId())
206 if (user_rotation
!= current_rotation_
) {
207 // A user may change other display configuration settings. When the user
208 // does change the rotation setting, then lock rotation to prevent the
209 // accelerometer from erasing their change.
210 SetRotationLocked(true);
211 user_rotation_
= current_rotation_
= user_rotation
;
215 void ScreenOrientationController::OnMaximizeModeStarted() {
216 DisplayManager
* display_manager
= Shell::GetInstance()->display_manager();
217 if (!display_manager
->HasInternalDisplay())
219 current_rotation_
= user_rotation_
=
220 display_manager
->GetDisplayInfo(gfx::Display::InternalDisplayId())
222 LoadDisplayRotationProperties();
223 Shell::GetInstance()->accelerometer_reader()->AddObserver(this);
224 Shell::GetInstance()->display_controller()->AddObserver(this);
227 void ScreenOrientationController::OnMaximizeModeEnded() {
228 if (!Shell::GetInstance()->display_manager()->HasInternalDisplay())
230 Shell::GetInstance()->accelerometer_reader()->RemoveObserver(this);
231 Shell::GetInstance()->display_controller()->RemoveObserver(this);
232 if (current_rotation_
!= user_rotation_
)
233 SetDisplayRotation(user_rotation_
);
236 void ScreenOrientationController::LockRotation(
237 gfx::Display::Rotation rotation
) {
238 SetRotationLocked(true);
239 SetDisplayRotation(rotation
);
242 void ScreenOrientationController::LockRotationToPrimaryOrientation(
243 blink::WebScreenOrientationLockType lock_orientation
) {
244 LockRotation(natural_orientation_
== lock_orientation
245 ? gfx::Display::ROTATE_0
246 : gfx::Display::ROTATE_90
);
249 void ScreenOrientationController::LockRotationToSecondaryOrientation(
250 blink::WebScreenOrientationLockType lock_orientation
) {
251 LockRotation(natural_orientation_
== lock_orientation
252 ? gfx::Display::ROTATE_180
253 : gfx::Display::ROTATE_270
);
256 void ScreenOrientationController::LockToRotationMatchingOrientation(
257 blink::WebScreenOrientationLockType lock_orientation
) {
258 // TODO(jonross): Update MaximizeModeController to allow rotation between
259 // two angles of an orientation (e.g. from ROTATE_0 to ROTATE_180, and from
260 // ROTATE_90 to ROTATE_270)
261 DisplayManager
* display_manager
= Shell::GetInstance()->display_manager();
262 if (!display_manager
->HasInternalDisplay())
265 gfx::Display::Rotation rotation
=
266 display_manager
->GetDisplayInfo(gfx::Display::InternalDisplayId())
268 if (natural_orientation_
== lock_orientation
) {
269 if (rotation
== gfx::Display::ROTATE_0
||
270 rotation
== gfx::Display::ROTATE_180
) {
271 SetRotationLocked(true);
273 LockRotation(gfx::Display::ROTATE_0
);
276 if (rotation
== gfx::Display::ROTATE_90
||
277 rotation
== gfx::Display::ROTATE_270
) {
278 SetRotationLocked(true);
280 LockRotation(gfx::Display::ROTATE_90
);
285 void ScreenOrientationController::HandleScreenRotation(
286 const gfx::Vector3dF
& lid
) {
287 gfx::Vector3dF
lid_flattened(lid
.x(), lid
.y(), 0.0f
);
288 float lid_flattened_length
= lid_flattened
.Length();
289 // When the lid is close to being flat, don't change rotation as it is too
290 // sensitive to slight movements.
291 if (lid_flattened_length
< kMinimumAccelerationScreenRotation
)
294 // The reference vector is the angle of gravity when the device is rotated
295 // clockwise by 45 degrees. Computing the angle between this vector and
296 // gravity we can easily determine the expected display rotation.
297 static const gfx::Vector3dF
rotation_reference(-1.0f
, -1.0f
, 0.0f
);
299 // Set the down vector to match the expected direction of gravity given the
300 // last configured rotation. This is used to enforce a stickiness that the
301 // user must overcome to rotate the display and prevents frequent rotations
302 // when holding the device near 45 degrees.
303 gfx::Vector3dF
down(0.0f
, 0.0f
, 0.0f
);
304 if (current_rotation_
== gfx::Display::ROTATE_0
)
306 else if (current_rotation_
== gfx::Display::ROTATE_90
)
308 else if (current_rotation_
== gfx::Display::ROTATE_180
)
313 // Don't rotate if the screen has not passed the threshold.
314 if (gfx::AngleBetweenVectorsInDegrees(down
, lid_flattened
) <
315 kDisplayRotationStickyAngleDegrees
) {
319 float angle
= gfx::ClockwiseAngleBetweenVectorsInDegrees(
320 rotation_reference
, lid_flattened
, gfx::Vector3dF(0.0f
, 0.0f
, -1.0f
));
322 gfx::Display::Rotation new_rotation
= gfx::Display::ROTATE_90
;
324 new_rotation
= gfx::Display::ROTATE_0
;
325 else if (angle
< 180.0f
)
326 new_rotation
= gfx::Display::ROTATE_270
;
327 else if (angle
< 270.0f
)
328 new_rotation
= gfx::Display::ROTATE_180
;
330 if (new_rotation
!= current_rotation_
)
331 SetDisplayRotation(new_rotation
);
334 void ScreenOrientationController::LoadDisplayRotationProperties() {
335 DisplayManager
* display_manager
= Shell::GetInstance()->display_manager();
336 if (!display_manager
->registered_internal_display_rotation_lock())
338 SetDisplayRotation(display_manager
->registered_internal_display_rotation());
339 SetRotationLocked(true);