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/display/screen_orientation_controller.h"
6
7 #include "ash/accelerometer/accelerometer_reader.h"
8 #include "ash/accelerometer/accelerometer_types.h"
9 #include "ash/public/cpp/app_types.h"
10 #include "ash/public/cpp/ash_switches.h"
11 #include "ash/shell.h"
12 #include "ash/wm/mru_window_tracker.h"
13 #include "ash/wm/splitview/split_view_controller.h"
14 #include "ash/wm/tablet_mode/tablet_mode_controller.h"
15 #include "ash/wm/window_state.h"
16 #include "ash/wm/window_util.h"
17 #include "base/auto_reset.h"
18 #include "base/command_line.h"
19 #include "base/stl_util.h"
20 #include "ui/aura/client/aura_constants.h"
21 #include "ui/display/display.h"
22 #include "ui/display/manager/display_manager.h"
23 #include "ui/display/manager/managed_display_info.h"
24 #include "ui/display/screen.h"
25 #include "ui/gfx/geometry/size.h"
26 #include "ui/wm/public/activation_client.h"
27
28 namespace ash {
29
30 namespace {
31
32 // The angle which the screen has to be rotated past before the display will
33 // rotate to match it (i.e. 45.0f is no stickiness).
34 const float kDisplayRotationStickyAngleDegrees = 60.0f;
35
36 // The minimum acceleration in m/s^2 in a direction required to trigger screen
37 // rotation. This prevents rapid toggling of rotation when the device is near
38 // flat and there is very little screen aligned force on it. The value is
39 // effectively the sine of the rise angle required times the acceleration due
40 // to gravity, with the current value requiring at least a 25 degree rise.
41 const float kMinimumAccelerationScreenRotation = 4.2f;
42
43 // Return true if auto-rotation is allowed which happens when the device is in a
44 // physical tablet state.
IsAutoRotationAllowed()45 bool IsAutoRotationAllowed() {
46 return Shell::Get()->tablet_mode_controller()->is_in_tablet_physical_state();
47 }
48
GetDisplayNaturalOrientation()49 OrientationLockType GetDisplayNaturalOrientation() {
50 if (!display::Display::HasInternalDisplay())
51 return OrientationLockType::kLandscape;
52
53 display::ManagedDisplayInfo info =
54 Shell::Get()->display_manager()->GetDisplayInfo(
55 display::Display::InternalDisplayId());
56 gfx::Size size = info.GetSizeInPixelWithPanelOrientation();
57 return size.width() > size.height() ? OrientationLockType::kLandscape
58 : OrientationLockType::kPortrait;
59 }
60
RotationToOrientation(OrientationLockType natural,display::Display::Rotation rotation)61 OrientationLockType RotationToOrientation(OrientationLockType natural,
62 display::Display::Rotation rotation) {
63 if (natural == OrientationLockType::kLandscape) {
64 // To be consistent with Android, the rgotation of the primary portrait
65 // on naturally landscape device is 270.
66 switch (rotation) {
67 case display::Display::ROTATE_0:
68 return OrientationLockType::kLandscapePrimary;
69 case display::Display::ROTATE_90:
70 return OrientationLockType::kPortraitSecondary;
71 case display::Display::ROTATE_180:
72 return OrientationLockType::kLandscapeSecondary;
73 case display::Display::ROTATE_270:
74 return OrientationLockType::kPortraitPrimary;
75 }
76 } else { // Natural portrait
77 switch (rotation) {
78 case display::Display::ROTATE_0:
79 return OrientationLockType::kPortraitPrimary;
80 case display::Display::ROTATE_90:
81 return OrientationLockType::kLandscapePrimary;
82 case display::Display::ROTATE_180:
83 return OrientationLockType::kPortraitSecondary;
84 case display::Display::ROTATE_270:
85 return OrientationLockType::kLandscapeSecondary;
86 }
87 }
88 NOTREACHED();
89 return OrientationLockType::kAny;
90 }
91
92 // Returns the rotation that matches the orientation type.
93 // Returns ROTATE_0 if the given orientation is ANY, which is used
94 // to indicate that user didn't lock orientation.
OrientationToRotation(OrientationLockType natural,OrientationLockType orientation)95 display::Display::Rotation OrientationToRotation(
96 OrientationLockType natural,
97 OrientationLockType orientation) {
98 if (orientation == OrientationLockType::kAny)
99 return display::Display::ROTATE_0;
100
101 if (natural == OrientationLockType::kLandscape) {
102 // To be consistent with Android, the rotation of the primary portrait
103 // on naturally landscape device is 270.
104 switch (orientation) {
105 case OrientationLockType::kLandscapePrimary:
106 return display::Display::ROTATE_0;
107 case OrientationLockType::kPortraitPrimary:
108 return display::Display::ROTATE_270;
109 case OrientationLockType::kLandscapeSecondary:
110 return display::Display::ROTATE_180;
111 case OrientationLockType::kPortraitSecondary:
112 return display::Display::ROTATE_90;
113 default:
114 break;
115 }
116 } else { // Natural portrait
117 switch (orientation) {
118 case OrientationLockType::kPortraitPrimary:
119 return display::Display::ROTATE_0;
120 case OrientationLockType::kLandscapePrimary:
121 return display::Display::ROTATE_90;
122 case OrientationLockType::kPortraitSecondary:
123 return display::Display::ROTATE_180;
124 case OrientationLockType::kLandscapeSecondary:
125 return display::Display::ROTATE_270;
126 default:
127 break;
128 }
129 }
130 NOTREACHED() << static_cast<int>(orientation);
131 return display::Display::ROTATE_0;
132 }
133
134 // Returns the locked orientation that matches the application
135 // requested orientation, or the application orientation itself
136 // if it didn't match.
ResolveOrientationLock(OrientationLockType app_requested,OrientationLockType lock)137 OrientationLockType ResolveOrientationLock(OrientationLockType app_requested,
138 OrientationLockType lock) {
139 if (app_requested == OrientationLockType::kAny ||
140 (app_requested == OrientationLockType::kLandscape &&
141 (lock == OrientationLockType::kLandscapePrimary ||
142 lock == OrientationLockType::kLandscapeSecondary)) ||
143 (app_requested == OrientationLockType::kPortrait &&
144 (lock == OrientationLockType::kPortraitPrimary ||
145 lock == OrientationLockType::kPortraitSecondary))) {
146 return lock;
147 }
148 return app_requested;
149 }
150
151 } // namespace
152
IsPrimaryOrientation(OrientationLockType type)153 bool IsPrimaryOrientation(OrientationLockType type) {
154 return type == OrientationLockType::kLandscapePrimary ||
155 type == OrientationLockType::kPortraitPrimary;
156 }
157
IsLandscapeOrientation(OrientationLockType type)158 bool IsLandscapeOrientation(OrientationLockType type) {
159 return type == OrientationLockType::kLandscape ||
160 type == OrientationLockType::kLandscapePrimary ||
161 type == OrientationLockType::kLandscapeSecondary;
162 }
163
IsPortraitOrientation(OrientationLockType type)164 bool IsPortraitOrientation(OrientationLockType type) {
165 return type == OrientationLockType::kPortrait ||
166 type == OrientationLockType::kPortraitPrimary ||
167 type == OrientationLockType::kPortraitSecondary;
168 }
169
GetCurrentScreenOrientation()170 OrientationLockType GetCurrentScreenOrientation() {
171 // ScreenOrientationController might be nullptr during shutdown.
172 // TODO(xdai|sammiequon): See if we can reorder so that users of the function
173 // |SplitViewController::Get| get shutdown before screen orientation
174 // controller.
175 if (!Shell::Get()->screen_orientation_controller())
176 return OrientationLockType::kAny;
177 return Shell::Get()->screen_orientation_controller()->GetCurrentOrientation();
178 }
179
IsCurrentScreenOrientationLandscape()180 bool IsCurrentScreenOrientationLandscape() {
181 return IsLandscapeOrientation(GetCurrentScreenOrientation());
182 }
183
IsCurrentScreenOrientationPrimary()184 bool IsCurrentScreenOrientationPrimary() {
185 return IsPrimaryOrientation(GetCurrentScreenOrientation());
186 }
187
operator <<(std::ostream & out,const OrientationLockType & lock)188 std::ostream& operator<<(std::ostream& out, const OrientationLockType& lock) {
189 switch (lock) {
190 case OrientationLockType::kAny:
191 out << "any";
192 break;
193 case OrientationLockType::kNatural:
194 out << "natural";
195 break;
196 case OrientationLockType::kCurrent:
197 out << "current";
198 break;
199 case OrientationLockType::kPortrait:
200 out << "portrait";
201 break;
202 case OrientationLockType::kLandscape:
203 out << "landscape";
204 break;
205 case OrientationLockType::kPortraitPrimary:
206 out << "portrait-primary";
207 break;
208 case OrientationLockType::kPortraitSecondary:
209 out << "portrait-secondary";
210 break;
211 case OrientationLockType::kLandscapePrimary:
212 out << "landscape-primary";
213 break;
214 case OrientationLockType::kLandscapeSecondary:
215 out << "landscape-secondary";
216 break;
217 }
218 return out;
219 }
220
ScreenOrientationController()221 ScreenOrientationController::ScreenOrientationController()
222 : natural_orientation_(GetDisplayNaturalOrientation()),
223 ignore_display_configuration_updates_(false),
224 rotation_locked_(false),
225 rotation_locked_orientation_(OrientationLockType::kAny),
226 user_rotation_(display::Display::ROTATE_0),
227 current_rotation_(display::Display::ROTATE_0) {
228 Shell::Get()->tablet_mode_controller()->AddObserver(this);
229 SplitViewController::Get(Shell::GetPrimaryRootWindow())->AddObserver(this);
230 display::Screen::GetScreen()->AddObserver(this);
231 Shell::Get()->window_tree_host_manager()->AddObserver(this);
232 }
233
~ScreenOrientationController()234 ScreenOrientationController::~ScreenOrientationController() {
235 Shell::Get()->window_tree_host_manager()->RemoveObserver(this);
236 display::Screen::GetScreen()->RemoveObserver(this);
237 SplitViewController::Get(Shell::GetPrimaryRootWindow())->RemoveObserver(this);
238 Shell::Get()->tablet_mode_controller()->RemoveObserver(this);
239 AccelerometerReader::GetInstance()->RemoveObserver(this);
240 Shell::Get()->window_tree_host_manager()->RemoveObserver(this);
241 Shell::Get()->activation_client()->RemoveObserver(this);
242 for (auto& windows : lock_info_map_)
243 windows.first->RemoveObserver(this);
244 }
245
AddObserver(Observer * observer)246 void ScreenOrientationController::AddObserver(Observer* observer) {
247 observers_.AddObserver(observer);
248 }
249
RemoveObserver(Observer * observer)250 void ScreenOrientationController::RemoveObserver(Observer* observer) {
251 observers_.RemoveObserver(observer);
252 }
253
LockOrientationForWindow(aura::Window * requesting_window,OrientationLockType orientation_lock)254 void ScreenOrientationController::LockOrientationForWindow(
255 aura::Window* requesting_window,
256 OrientationLockType orientation_lock) {
257 if (!requesting_window->HasObserver(this))
258 requesting_window->AddObserver(this);
259 auto iter = lock_info_map_.find(requesting_window);
260 if (iter != lock_info_map_.end()) {
261 if (orientation_lock == OrientationLockType::kCurrent) {
262 // If the app previously requested an orientation,
263 // disable the sensor when that orientation is locked.
264 iter->second.lock_completion_behavior =
265 LockCompletionBehavior::DisableSensor;
266 } else {
267 iter->second.orientation_lock = orientation_lock;
268 iter->second.lock_completion_behavior = LockCompletionBehavior::None;
269 }
270 } else {
271 lock_info_map_.emplace(
272 requesting_window,
273 LockInfo(orientation_lock, requesting_window->GetRootWindow()));
274 }
275
276 ApplyLockForTopMostWindowOnInternalDisplay();
277 }
278
UnlockOrientationForWindow(aura::Window * window)279 void ScreenOrientationController::UnlockOrientationForWindow(
280 aura::Window* window) {
281 lock_info_map_.erase(window);
282 window->RemoveObserver(this);
283 ApplyLockForTopMostWindowOnInternalDisplay();
284 }
285
UnlockAll()286 void ScreenOrientationController::UnlockAll() {
287 SetRotationLockedInternal(false);
288 // TODO(oshima): Remove if when current_rotation_ is removed.
289 if (user_rotation_ != current_rotation_) {
290 SetDisplayRotation(user_rotation_,
291 display::Display::RotationSource::ACCELEROMETER,
292 DisplayConfigurationController::ANIMATION_SYNC);
293 }
294 }
295
IsUserLockedOrientationPortrait()296 bool ScreenOrientationController::IsUserLockedOrientationPortrait() {
297 switch (user_locked_orientation_) {
298 case OrientationLockType::kPortraitPrimary:
299 case OrientationLockType::kPortraitSecondary:
300 case OrientationLockType::kPortrait:
301 return true;
302 default:
303 return false;
304 }
305 }
306
307 OrientationLockType
GetCurrentAppRequestedOrientationLock() const308 ScreenOrientationController::GetCurrentAppRequestedOrientationLock() const {
309 return current_app_requested_orientation_lock_.value_or(
310 OrientationLockType::kAny);
311 }
312
ToggleUserRotationLock()313 void ScreenOrientationController::ToggleUserRotationLock() {
314 if (!display::Display::HasInternalDisplay())
315 return;
316
317 if (user_rotation_locked()) {
318 SetLockToOrientation(OrientationLockType::kAny);
319 } else {
320 display::Display::Rotation current_rotation =
321 Shell::Get()
322 ->display_manager()
323 ->GetDisplayInfo(display::Display::InternalDisplayId())
324 .GetActiveRotation();
325 SetLockToRotation(current_rotation);
326 }
327 }
328
SetLockToRotation(display::Display::Rotation rotation)329 void ScreenOrientationController::SetLockToRotation(
330 display::Display::Rotation rotation) {
331 if (!display::Display::HasInternalDisplay())
332 return;
333
334 SetLockToOrientation(RotationToOrientation(natural_orientation_, rotation));
335 }
336
GetCurrentOrientation() const337 OrientationLockType ScreenOrientationController::GetCurrentOrientation() const {
338 return RotationToOrientation(natural_orientation_, current_rotation_);
339 }
340
OnWindowActivated(::wm::ActivationChangeObserver::ActivationReason reason,aura::Window * gained_active,aura::Window * lost_active)341 void ScreenOrientationController::OnWindowActivated(
342 ::wm::ActivationChangeObserver::ActivationReason reason,
343 aura::Window* gained_active,
344 aura::Window* lost_active) {
345 ApplyLockForTopMostWindowOnInternalDisplay();
346 }
347
OnWindowHierarchyChanged(const HierarchyChangeParams & params)348 void ScreenOrientationController::OnWindowHierarchyChanged(
349 const HierarchyChangeParams& params) {
350 // Window may move to an external display or back to internal (e.g. via
351 // shortcut). In this case, we need to undo/redo any orientation lock it
352 // applies on the internal display.
353 if (!display::Display::HasInternalDisplay())
354 return;
355
356 aura::Window* window = params.receiver;
357 aura::Window* target = params.target;
358 // The target window of the hierarchy change event must be the receiving
359 // window itself, or one of its ancestors; we don't care about events
360 // happening to descendant windows, since that doesn't indicate a change of
361 // |window|'s root.
362 for (auto* curr = window; curr != target; curr = curr->parent()) {
363 if (!curr)
364 return;
365 }
366
367 auto iter = lock_info_map_.find(window);
368 if (iter != lock_info_map_.end() &&
369 iter->second.root_window != window->GetRootWindow()) {
370 iter->second.root_window = window->GetRootWindow();
371 ApplyLockForTopMostWindowOnInternalDisplay();
372 }
373 }
374
OnWindowDestroying(aura::Window * window)375 void ScreenOrientationController::OnWindowDestroying(aura::Window* window) {
376 UnlockOrientationForWindow(window);
377 }
378
379 // Currently contents::WebContents will only be able to lock rotation while
380 // fullscreen. In this state a user cannot click on the tab strip to change. If
381 // this becomes supported for non-fullscreen tabs then the following interferes
382 // with TabDragController. OnWindowVisibilityChanged is called between a mouse
383 // down and mouse up. The rotation this triggers leads to a coordinate space
384 // change in the middle of an event. Causes the tab to separate from the tab
385 // strip.
OnWindowVisibilityChanged(aura::Window * window,bool visible)386 void ScreenOrientationController::OnWindowVisibilityChanged(
387 aura::Window* window,
388 bool visible) {
389 if (base::Contains(lock_info_map_, window))
390 ApplyLockForTopMostWindowOnInternalDisplay();
391 }
392
OnAccelerometerUpdated(scoped_refptr<const AccelerometerUpdate> update)393 void ScreenOrientationController::OnAccelerometerUpdated(
394 scoped_refptr<const AccelerometerUpdate> update) {
395 if (!IsAutoRotationAllowed())
396 return;
397
398 if (rotation_locked_ && !CanRotateInLockedState())
399 return;
400 if (!update->has(ACCELEROMETER_SOURCE_SCREEN))
401 return;
402 // Ignore the reading if it appears unstable. The reading is considered
403 // unstable if it deviates too much from gravity
404 if (update->IsReadingStable(ACCELEROMETER_SOURCE_SCREEN))
405 HandleScreenRotation(update->get(ACCELEROMETER_SOURCE_SCREEN));
406 }
407
OnDisplayConfigurationChanged()408 void ScreenOrientationController::OnDisplayConfigurationChanged() {
409 if (ignore_display_configuration_updates_)
410 return;
411 if (!display::Display::HasInternalDisplay())
412 return;
413 if (!Shell::Get()->display_manager()->IsActiveDisplayId(
414 display::Display::InternalDisplayId())) {
415 return;
416 }
417 // TODO(oshima): remove current_rotation_ and always use the target rotation.
418 current_rotation_ =
419 Shell::Get()->display_configuration_controller()->GetTargetRotation(
420 display::Display::InternalDisplayId());
421 }
422
OnTabletModeStarted()423 void ScreenOrientationController::OnTabletModeStarted() {
424 // Observe window activation only while in UI tablet mode, since this the only
425 // mode in which we apply apps' requested orientation locks.
426 Shell::Get()->activation_client()->AddObserver(this);
427
428 if (!display::Display::HasInternalDisplay())
429 return;
430 ApplyLockForTopMostWindowOnInternalDisplay();
431 }
432
OnTabletModeEnded()433 void ScreenOrientationController::OnTabletModeEnded() {
434 Shell::Get()->activation_client()->RemoveObserver(this);
435 if (!display::Display::HasInternalDisplay())
436 return;
437
438 if (!IsAutoRotationAllowed()) {
439 // Rotation locks should have been cleared already in
440 // `OnTabletPhysicalStateChanged()`.
441 DCHECK(!rotation_locked());
442 DCHECK_EQ(rotation_locked_orientation_, OrientationLockType::kAny);
443 return;
444 }
445
446 // Auto-rotation is still allowed (since device is still in a physical tablet
447 // state). We no-longer apply app's requested orientation locks, so we'll
448 // call `ApplyLockForTopMostWindowOnInternalDisplay()` to apply the
449 // `user_locked_orientation_` if any.
450 ApplyLockForTopMostWindowOnInternalDisplay();
451 }
452
OnTabletPhysicalStateChanged()453 void ScreenOrientationController::OnTabletPhysicalStateChanged() {
454 auto* shell = Shell::Get();
455
456 if (IsAutoRotationAllowed()) {
457 AccelerometerReader::GetInstance()->AddObserver(this);
458
459 // Do not exit early, as the internal display can be determined after
460 // Maximize Mode has started. (chrome-os-partner:38796) Always start
461 // observing.
462 if (display::Display::HasInternalDisplay()) {
463 current_rotation_ = user_rotation_ =
464 shell->display_configuration_controller()->GetTargetRotation(
465 display::Display::InternalDisplayId());
466 }
467 if (!rotation_locked_)
468 LoadDisplayRotationProperties();
469
470 if (!display::Display::HasInternalDisplay())
471 return;
472 ApplyLockForTopMostWindowOnInternalDisplay();
473 } else {
474 AccelerometerReader::GetInstance()->RemoveObserver(this);
475
476 if (!display::Display::HasInternalDisplay())
477 return;
478
479 UnlockAll();
480 }
481
482 for (auto& observer : observers_)
483 observer.OnUserRotationLockChanged();
484 }
485
OnSplitViewStateChanged(SplitViewController::State previous_state,SplitViewController::State state)486 void ScreenOrientationController::OnSplitViewStateChanged(
487 SplitViewController::State previous_state,
488 SplitViewController::State state) {
489 if (previous_state == SplitViewController::State::kNoSnap ||
490 state == SplitViewController::State::kNoSnap) {
491 ApplyLockForTopMostWindowOnInternalDisplay();
492 }
493 }
494
OnWillProcessDisplayChanges()495 void ScreenOrientationController::OnWillProcessDisplayChanges() {
496 suspend_orientation_lock_refreshes_ = true;
497 }
498
OnDidProcessDisplayChanges()499 void ScreenOrientationController::OnDidProcessDisplayChanges() {
500 suspend_orientation_lock_refreshes_ = false;
501 if (is_orientation_lock_refresh_pending_) {
502 // Note: We must set |is_orientation_lock_refresh_pending_| to false first
503 // before calling `ApplyLockForTopMostWindowOnInternalDisplay()`, since
504 // changing the display's rotation triggers an
505 // `OnWillProcessDisplayChanges()` and `OnDidProcessDisplayChanges()` pair,
506 // and we don't want to end up here again.
507 is_orientation_lock_refresh_pending_ = false;
508 ApplyLockForTopMostWindowOnInternalDisplay();
509 }
510 }
511
SetDisplayRotation(display::Display::Rotation rotation,display::Display::RotationSource source,DisplayConfigurationController::RotationAnimation mode)512 void ScreenOrientationController::SetDisplayRotation(
513 display::Display::Rotation rotation,
514 display::Display::RotationSource source,
515 DisplayConfigurationController::RotationAnimation mode) {
516 if (!display::Display::HasInternalDisplay())
517 return;
518 current_rotation_ = rotation;
519 base::AutoReset<bool> auto_ignore_display_configuration_updates(
520 &ignore_display_configuration_updates_, true);
521
522 Shell::Get()->display_configuration_controller()->SetDisplayRotation(
523 display::Display::InternalDisplayId(), rotation, source, mode);
524 }
525
SetRotationLockedInternal(bool rotation_locked)526 void ScreenOrientationController::SetRotationLockedInternal(
527 bool rotation_locked) {
528 if (rotation_locked_ == rotation_locked)
529 return;
530 rotation_locked_ = rotation_locked;
531 if (!rotation_locked_)
532 rotation_locked_orientation_ = OrientationLockType::kAny;
533 }
534
SetLockToOrientation(OrientationLockType orientation)535 void ScreenOrientationController::SetLockToOrientation(
536 OrientationLockType orientation) {
537 user_locked_orientation_ = orientation;
538 base::AutoReset<bool> auto_ignore_display_configuration_updates(
539 &ignore_display_configuration_updates_, true);
540 Shell::Get()->display_manager()->RegisterDisplayRotationProperties(
541 user_rotation_locked(),
542 OrientationToRotation(natural_orientation_, user_locked_orientation_));
543
544 ApplyLockForTopMostWindowOnInternalDisplay();
545 for (auto& observer : observers_)
546 observer.OnUserRotationLockChanged();
547 }
548
LockRotation(display::Display::Rotation rotation,display::Display::RotationSource source)549 void ScreenOrientationController::LockRotation(
550 display::Display::Rotation rotation,
551 display::Display::RotationSource source) {
552 SetRotationLockedInternal(true);
553 SetDisplayRotation(rotation, source);
554 }
555
LockRotationToOrientation(OrientationLockType lock_orientation)556 void ScreenOrientationController::LockRotationToOrientation(
557 OrientationLockType lock_orientation) {
558 rotation_locked_orientation_ = lock_orientation;
559 switch (lock_orientation) {
560 case OrientationLockType::kAny:
561 SetRotationLockedInternal(false);
562 break;
563 case OrientationLockType::kLandscape:
564 case OrientationLockType::kPortrait:
565 LockToRotationMatchingOrientation(lock_orientation);
566 break;
567
568 case OrientationLockType::kLandscapePrimary:
569 case OrientationLockType::kLandscapeSecondary:
570 case OrientationLockType::kPortraitPrimary:
571 case OrientationLockType::kPortraitSecondary:
572 LockRotation(
573 OrientationToRotation(natural_orientation_, lock_orientation),
574 display::Display::RotationSource::ACTIVE);
575
576 break;
577 case OrientationLockType::kNatural:
578 LockRotation(display::Display::ROTATE_0,
579 display::Display::RotationSource::ACTIVE);
580 break;
581 default:
582 NOTREACHED();
583 break;
584 }
585 }
586
LockToRotationMatchingOrientation(OrientationLockType lock_orientation)587 void ScreenOrientationController::LockToRotationMatchingOrientation(
588 OrientationLockType lock_orientation) {
589 if (!display::Display::HasInternalDisplay())
590 return;
591
592 display::Display::Rotation rotation =
593 Shell::Get()
594 ->display_manager()
595 ->GetDisplayInfo(display::Display::InternalDisplayId())
596 .GetActiveRotation();
597 if (natural_orientation_ == lock_orientation) {
598 if (rotation == display::Display::ROTATE_0 ||
599 rotation == display::Display::ROTATE_180) {
600 SetRotationLockedInternal(true);
601 } else {
602 LockRotation(display::Display::ROTATE_0,
603 display::Display::RotationSource::ACTIVE);
604 }
605 } else {
606 if (rotation == display::Display::ROTATE_90 ||
607 rotation == display::Display::ROTATE_270) {
608 SetRotationLockedInternal(true);
609 } else {
610 // Rotate to the default rotation of the requested orientation.
611 display::Display::Rotation default_rotation =
612 natural_orientation_ == OrientationLockType::kLandscape
613 ? display::Display::ROTATE_270 // portrait in landscape device.
614 : display::Display::ROTATE_90; // landscape in portrait device.
615 LockRotation(default_rotation, display::Display::RotationSource::ACTIVE);
616 }
617 }
618 }
619
HandleScreenRotation(const AccelerometerReading & lid)620 void ScreenOrientationController::HandleScreenRotation(
621 const AccelerometerReading& lid) {
622 gfx::Vector3dF lid_flattened(lid.x, lid.y, 0.0f);
623 float lid_flattened_length = lid_flattened.Length();
624 // When the lid is close to being flat, don't change rotation as it is too
625 // sensitive to slight movements.
626 if (lid_flattened_length < kMinimumAccelerationScreenRotation)
627 return;
628
629 // The reference vector is the angle of gravity when the device is rotated
630 // clockwise by 45 degrees. Computing the angle between this vector and
631 // gravity we can easily determine the expected display rotation.
632 static constexpr gfx::Vector3dF rotation_reference(-1.0f, 1.0f, 0.0f);
633
634 // Set the down vector to match the expected direction of gravity given the
635 // last configured rotation. This is used to enforce a stickiness that the
636 // user must overcome to rotate the display and prevents frequent rotations
637 // when holding the device near 45 degrees.
638 gfx::Vector3dF down(0.0f, 0.0f, 0.0f);
639 if (current_rotation_ == display::Display::ROTATE_0)
640 down.set_y(1.0f);
641 else if (current_rotation_ == display::Display::ROTATE_90)
642 down.set_x(1.0f);
643 else if (current_rotation_ == display::Display::ROTATE_180)
644 down.set_y(-1.0f);
645 else
646 down.set_x(-1.0f);
647
648 // Don't rotate if the screen has not passed the threshold.
649 if (gfx::AngleBetweenVectorsInDegrees(down, lid_flattened) <
650 kDisplayRotationStickyAngleDegrees) {
651 return;
652 }
653
654 float angle = gfx::ClockwiseAngleBetweenVectorsInDegrees(
655 rotation_reference, lid_flattened, gfx::Vector3dF(0.0f, 0.0f, 1.0f));
656
657 display::Display::Rotation new_rotation = display::Display::ROTATE_270;
658 if (angle < 90.0f)
659 new_rotation = display::Display::ROTATE_0;
660 else if (angle < 180.0f)
661 new_rotation = display::Display::ROTATE_90;
662 else if (angle < 270.0f)
663 new_rotation = display::Display::ROTATE_180;
664
665 if (new_rotation != current_rotation_ &&
666 IsRotationAllowedInLockedState(new_rotation)) {
667 SetDisplayRotation(new_rotation,
668 display::Display::RotationSource::ACCELEROMETER);
669 }
670 }
671
LoadDisplayRotationProperties()672 void ScreenOrientationController::LoadDisplayRotationProperties() {
673 display::DisplayManager* display_manager = Shell::Get()->display_manager();
674 if (!display_manager->registered_internal_display_rotation_lock())
675 return;
676 user_locked_orientation_ = RotationToOrientation(
677 natural_orientation_,
678 display_manager->registered_internal_display_rotation());
679 }
680
ApplyLockForTopMostWindowOnInternalDisplay()681 void ScreenOrientationController::ApplyLockForTopMostWindowOnInternalDisplay() {
682 if (suspend_orientation_lock_refreshes_) {
683 is_orientation_lock_refresh_pending_ = true;
684 return;
685 }
686
687 current_app_requested_orientation_lock_ = base::nullopt;
688 if (!display::Display::HasInternalDisplay())
689 return;
690
691 aura::Window* const internal_display_root =
692 Shell::GetRootWindowForDisplayId(display::Display::InternalDisplayId());
693 if (!internal_display_root) {
694 // We might have an internal display, but no root window for it, such as in
695 // the case of Unified Display. Also, some tests may not set an internal
696 // display.
697 // Since rotation lock is applied only on internal displays (see
698 // ScreenOrientationController::SetDisplayRotation()), there's no need to
699 // continue.
700 return;
701 }
702
703 bool in_tablet_mode = Shell::Get()->tablet_mode_controller()->InTabletMode();
704 if (!in_tablet_mode) {
705 if (IsAutoRotationAllowed()) {
706 // We ignore windows and app requested orientation locks while the UI is
707 // in clamshell mode when the device is physically in a tablet state.
708 // Instead we apply the orientation lock requested by the user.
709 LockRotationToOrientation(user_locked_orientation_);
710 }
711
712 return;
713 }
714
715 if (SplitViewController::Get(internal_display_root)
716 ->InTabletSplitViewMode()) {
717 // While split view is enabled, ignore rotation lock set by windows.
718 LockRotationToOrientation(user_locked_orientation_);
719 return;
720 }
721
722 MruWindowTracker::WindowList mru_windows(
723 Shell::Get()->mru_window_tracker()->BuildWindowListIgnoreModal(
724 kActiveDesk));
725
726 for (auto* window : mru_windows) {
727 if (window->GetRootWindow() != internal_display_root)
728 continue;
729
730 if (!window->TargetVisibility())
731 continue;
732
733 if (ApplyLockForWindowIfPossible(window))
734 return;
735 }
736
737 LockRotationToOrientation(user_locked_orientation_);
738 }
739
ApplyLockForWindowIfPossible(const aura::Window * window)740 bool ScreenOrientationController::ApplyLockForWindowIfPossible(
741 const aura::Window* window) {
742 for (auto& pair : lock_info_map_) {
743 const aura::Window* lock_window = pair.first;
744 LockInfo& lock_info = pair.second;
745 if (lock_window->TargetVisibility() && window->Contains(lock_window)) {
746 if (lock_info.orientation_lock == OrientationLockType::kCurrent) {
747 // If the app requested "current" without previously
748 // specifying an orientation, use the current rotation.
749 lock_info.orientation_lock =
750 RotationToOrientation(natural_orientation_, current_rotation_);
751 LockRotationToOrientation(lock_info.orientation_lock);
752 } else {
753 const auto orientation_lock = ResolveOrientationLock(
754 lock_info.orientation_lock, user_locked_orientation_);
755 LockRotationToOrientation(orientation_lock);
756 if (lock_info.lock_completion_behavior ==
757 LockCompletionBehavior::DisableSensor) {
758 lock_info.lock_completion_behavior = LockCompletionBehavior::None;
759 lock_info.orientation_lock = orientation_lock;
760 }
761 }
762 current_app_requested_orientation_lock_ =
763 base::make_optional<OrientationLockType>(lock_info.orientation_lock);
764 return true;
765 }
766 }
767
768 // The default orientation for all chrome browser/apps windows is
769 // ANY, so use the user_locked_orientation_;
770 if (static_cast<AppType>(window->GetProperty(aura::client::kAppType)) !=
771 AppType::NON_APP) {
772 LockRotationToOrientation(user_locked_orientation_);
773 return true;
774 }
775 return false;
776 }
777
IsRotationAllowedInLockedState(display::Display::Rotation rotation)778 bool ScreenOrientationController::IsRotationAllowedInLockedState(
779 display::Display::Rotation rotation) {
780 if (!rotation_locked_)
781 return true;
782
783 if (!CanRotateInLockedState())
784 return false;
785
786 if (natural_orientation_ == rotation_locked_orientation_) {
787 return rotation == display::Display::ROTATE_0 ||
788 rotation == display::Display::ROTATE_180;
789 } else {
790 return rotation == display::Display::ROTATE_90 ||
791 rotation == display::Display::ROTATE_270;
792 }
793 return false;
794 }
795
CanRotateInLockedState()796 bool ScreenOrientationController::CanRotateInLockedState() {
797 return rotation_locked_orientation_ == OrientationLockType::kLandscape ||
798 rotation_locked_orientation_ == OrientationLockType::kPortrait;
799 }
800
UpdateNaturalOrientationForTest()801 void ScreenOrientationController::UpdateNaturalOrientationForTest() {
802 natural_orientation_ = GetDisplayNaturalOrientation();
803 }
804
805 } // namespace ash
806