chromium/ash/display/screen_orientation_controller.h

// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef ASH_DISPLAY_SCREEN_ORIENTATION_CONTROLLER_H_
#define ASH_DISPLAY_SCREEN_ORIENTATION_CONTROLLER_H_

#include <memory>
#include <optional>
#include <unordered_map>

#include "ash/accelerometer/accelerometer_reader.h"
#include "ash/accelerometer/accelerometer_types.h"
#include "ash/ash_export.h"
#include "ash/display/display_configuration_controller.h"
#include "ash/public/cpp/tablet_mode_observer.h"
#include "ash/wm/splitview/split_view_controller.h"
#include "ash/wm/splitview/split_view_observer.h"
#include "base/memory/raw_ptr.h"
#include "base/observer_list.h"
#include "base/scoped_observation.h"
#include "chromeos/ui/base/display_util.h"
#include "ui/aura/window_observer.h"
#include "ui/display/display.h"
#include "ui/display/display_observer.h"
#include "ui/display/manager/display_manager_observer.h"
#include "ui/wm/public/activation_change_observer.h"

namespace aura {
class Window;
}  // namespace aura

namespace display {
enum class TabletState;
}  // namespace display

namespace ash {

ASH_EXPORT chromeos::OrientationType GetCurrentScreenOrientation();
ASH_EXPORT bool IsCurrentScreenOrientationLandscape();
ASH_EXPORT bool IsCurrentScreenOrientationPrimary();

ASH_EXPORT std::ostream& operator<<(std::ostream& out,
                                    const chromeos::OrientationType& lock);

// Implements ChromeOS specific functionality for ScreenOrientationProvider.
class ASH_EXPORT ScreenOrientationController
    : public ::wm::ActivationChangeObserver,
      public aura::WindowObserver,
      public AccelerometerReader::Observer,
      public TabletModeObserver,
      public display::DisplayObserver,
      public display::DisplayManagerObserver {
 public:
  // Observer that reports changes to the state of ScreenOrientationProvider's
  // rotation lock.
  class Observer {
   public:
    // Invoked when rotation is locked or unlocked by a user.
    virtual void OnUserRotationLockChanged() {}

   protected:
    virtual ~Observer() = default;
  };

  // Controls the behavior after lock is applied to the window (when the window
  // becomes the active window). |DisableSensor| disables the sensor-based
  // rotation and locks to the specific orientation. For example, PORTRAIT may
  // rotate to PORTRAIT_PRIMARY or PORTRAIT_SECONDARY, and will allow rotation
  // between these two. |DisableSensor| disallows the sensor-based rotation by
  // locking the rotation to whichever specific orientation is applied.
  enum class LockCompletionBehavior {
    None,
    DisableSensor,
  };

  ScreenOrientationController();

  ScreenOrientationController(const ScreenOrientationController&) = delete;
  ScreenOrientationController& operator=(const ScreenOrientationController&) =
      delete;

  ~ScreenOrientationController() override;

  chromeos::OrientationType natural_orientation() const {
    return natural_orientation_;
  }

  // Add/Remove observers.
  void AddObserver(Observer* observer);
  void RemoveObserver(Observer* observer);

  // Allows/unallows a window to lock the screen orientation.
  void LockOrientationForWindow(aura::Window* requesting_window,
                                chromeos::OrientationType orientation_lock);

  void UnlockOrientationForWindow(aura::Window* window);

  // Unlock all and set the rotation back to the user specified rotation.
  void UnlockAll();

  // Returns true if the user has locked the orientation to portrait, false if
  // the user has locked the orientation to landscape or not locked the
  // orientation.
  bool IsUserLockedOrientationPortrait();

  // Returns the chromeos::OrientationType that is applied on based on whether a
  // rotation lock was requested for an app window, and whether the current
  // system state allows it to lock the rotation (e.g. being in tablet mode, on
  // the internal display, and splitview is inactive).
  chromeos::OrientationType GetCurrentAppRequestedOrientationLock() const;

  bool ignore_display_configuration_updates() const {
    return ignore_display_configuration_updates_;
  }

  // True if |rotation_lock_| has been set and accelerometer updates should not
  // rotate the display.
  bool rotation_locked() const { return rotation_locked_; }

  bool user_rotation_locked() const {
    return user_locked_orientation_ != chromeos::OrientationType::kAny;
  }

  // Trun on/off the user rotation lock. When turned on, it will lock
  // the orientation to the current orientation.
  // |user_rotation_locked()| method returns the current state of the
  // user rotation lock.
  void ToggleUserRotationLock();

  // Set locked to the given |rotation| and save it.
  void SetLockToRotation(display::Display::Rotation rotation);

  // Gets current screen orientation type.
  chromeos::OrientationType GetCurrentOrientation() const;

  // Returns true if auto-rotation is allowed. It happens when the device is in
  // a physical tablet state or kSupportsClamshellAutoRotation is set.
  bool IsAutoRotationAllowed() const;

  // wm::ActivationChangeObserver:
  void OnWindowActivated(
      ::wm::ActivationChangeObserver::ActivationReason reason,
      aura::Window* gained_active,
      aura::Window* lost_active) override;

  // aura::WindowObserver:
  void OnWindowHierarchyChanged(const HierarchyChangeParams& params) override;
  void OnWindowDestroying(aura::Window* window) override;
  void OnWindowVisibilityChanged(aura::Window* window, bool visible) override;

  // AccelerometerReader::Observer:
  void OnECLidAngleDriverStatusChanged(bool is_supported) override {}
  void OnAccelerometerUpdated(const AccelerometerUpdate& update) override;

  // TabletModeObserver:
  void OnTabletPhysicalStateChanged() override;

  // display::DisplayObserver:
  void OnDisplayTabletStateChanged(display::TabletState state) override;

  // display::DisplayManagerObserver:
  void OnWillProcessDisplayChanges() override;
  void OnDidProcessDisplayChanges(
      const DisplayConfigurationChange& configuration_change) override;

 private:
  friend class ScreenOrientationControllerTestApi;
  class WindowStateChangeNotifier;

  struct LockInfo {
    LockInfo(chromeos::OrientationType lock, aura::Window* root)
        : orientation_lock(lock), root_window(root) {}
    chromeos::OrientationType orientation_lock =
        chromeos::OrientationType::kAny;
    // Tracks the requesting window's root window and is updated whenever it
    // changes.
    raw_ptr<aura::Window> root_window = nullptr;
    LockCompletionBehavior lock_completion_behavior =
        LockCompletionBehavior::None;
  };

  // Sets the display rotation for the given |source|. The new |rotation| will
  // also become active. Display changed notifications are suppressed for this
  // change.
  void SetDisplayRotation(
      display::Display::Rotation rotation,
      display::Display::RotationSource source,
      DisplayConfigurationController::RotationAnimation mode =
          DisplayConfigurationController::ANIMATION_ASYNC);

  // Gets the target rotation for the device's internal display from the
  // `DisplayConfigurationController`.
  display::Display::Rotation GetInternalDisplayTargetRotation() const;

  void SetRotationLockedInternal(bool rotation_locked);

  // A helper method that set locked to the given |orientation| and save it.
  void SetLockToOrientation(chromeos::OrientationType orientation);

  // Sets the display rotation to |rotation|. Future accelerometer updates
  // should not be used to change the rotation. SetRotationLocked(false) removes
  // the rotation lock.
  void LockRotation(display::Display::Rotation rotation,
                    display::Display::RotationSource source);

  // Sets the display rotation based on |lock_orientation|. Future accelerometer
  // updates should not be used to change the rotation. SetRotationLocked(false)
  // removes the rotation lock.
  void LockRotationToOrientation(chromeos::OrientationType lock_orientation);

  // For orientations that do not specify primary or secondary, locks to the
  // current rotation if it matches |lock_orientation|. Otherwise locks to a
  // matching rotation.
  void LockToRotationMatchingOrientation(
      chromeos::OrientationType lock_orientation);

  // Detect screen rotation from |lid| accelerometer and automatically rotate
  // screen.
  void HandleScreenRotation(const AccelerometerReading& lid);

  // Checks DisplayManager for registered rotation lock, and rotation,
  // preferences. These are then applied.
  void LoadDisplayRotationProperties();

  // Determines the rotation lock, and orientation, for the top-most window on
  // the internal display, and applies it. If there is none, rotation lock will
  // be removed.
  // TODO(oshima|afakhry): This behavior needs to be revised when Android
  // implements multi-display support.
  void ApplyLockForTopMostWindowOnInternalDisplay();

  // If there is a rotation lock that can be applied to window, applies it and
  // returns true. Otherwise returns false.
  bool ApplyLockForWindowIfPossible(const aura::Window* window);

  // Both |chromeos::OrientationType::kLandscape| and
  // |OrientationLock::kPortrait| allow for rotation between the
  // two angles of the same screen orientation
  // (http://www.w3.org/TR/screen-orientation/). Returns true if |rotation| is
  // supported for the current |rotation_locked_orientation_|.
  bool IsRotationAllowedInLockedState(display::Display::Rotation rotation);

  // Certain orientation locks allow for rotation between the two angles of the
  // same screen orientation. Returns true if |rotation_locked_orientation_|
  // allows rotation.
  bool CanRotateInLockedState();

  void UpdateNaturalOrientationForTest();

  // The orientation of the display when at a rotation of 0.
  chromeos::OrientationType natural_orientation_;

  // True when changes being applied cause OnDisplayConfigurationChanged() to be
  // called, and for which these changes should be ignored.
  bool ignore_display_configuration_updates_;

  // When true then accelerometer updates should not rotate the display.
  bool rotation_locked_;

  // True while the displays are being updated by the display manager, so that
  // we don't set the display rotation while this operation is in progress.
  bool suspend_orientation_lock_refreshes_ = false;

  // True if there was a request to refresh the orientation lock while the
  // display manager is in the process of updating the displays. When the
  // display manager is done, we check this value to see if we need to refresh
  // the orientation lock.
  bool is_orientation_lock_refresh_pending_ = false;

  // The orientation to which the current |rotation_locked_| was applied.
  chromeos::OrientationType rotation_locked_orientation_;

  // The rotation of the display set by the user. This rotation will be
  // restored upon exiting tablet mode.
  display::Display::Rotation user_rotation_;

  // The orientation of the device locked by the user.
  chromeos::OrientationType user_locked_orientation_ =
      chromeos::OrientationType::kAny;

  // The currently applied orientation lock that was requested by an app if any.
  std::optional<chromeos::OrientationType>
      current_app_requested_orientation_lock_ = std::nullopt;

  // Rotation Lock observers.
  base::ObserverList<Observer>::Unchecked observers_;

  // Tracks all windows that have requested a lock, as well as the requested
  // orientation.
  std::unordered_map<aura::Window*, LockInfo> lock_info_map_;

  // Register for display configuration changes.
  base::ScopedObservation<display::DisplayManager,
                          display::DisplayManagerObserver>
      display_manager_observation_{this};

  display::ScopedDisplayObserver display_observer_{this};

  std::unique_ptr<WindowStateChangeNotifier> window_state_change_notifier_;
};

}  // namespace ash

#endif  // ASH_DISPLAY_SCREEN_ORIENTATION_CONTROLLER_H_