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