Lock Kiosk Next Home in landscape mode

Updates ScreenOrientationController to allow locking the home screen
window (normally it only checks for MRU windows, which intentionally
excludes the home screen).

Then updates KioskNextHomeController to add an orientation lock.

Bug: 951610
Change-Id: Ie932279eda9ca4a9cc4bb2325fc95b984affd025
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1612659
Commit-Queue: Michael Giuffrida <michaelpg@chromium.org>
Reviewed-by: Ahmed Fakhry <afakhry@chromium.org>
Cr-Commit-Position: refs/heads/master@{#661428}
diff --git a/ash/display/screen_orientation_controller.cc b/ash/display/screen_orientation_controller.cc
index 64ba553..764e270 100644
--- a/ash/display/screen_orientation_controller.cc
+++ b/ash/display/screen_orientation_controller.cc
@@ -6,6 +6,8 @@
 
 #include "ash/accelerometer/accelerometer_reader.h"
 #include "ash/accelerometer/accelerometer_types.h"
+#include "ash/home_screen/home_screen_controller.h"
+#include "ash/home_screen/home_screen_delegate.h"
 #include "ash/public/cpp/app_types.h"
 #include "ash/public/cpp/ash_switches.h"
 #include "ash/shell.h"
@@ -588,16 +590,32 @@
   if (!ScreenOrientationProviderSupported())
     return;
 
+  Shell* shell = Shell::Get();
   MruWindowTracker::WindowList mru_windows(
-      Shell::Get()->mru_window_tracker()->BuildMruWindowList());
+      shell->mru_window_tracker()->BuildMruWindowList());
 
+  bool has_visible_window = false;
   for (auto* window : mru_windows) {
     if (!window->TargetVisibility())
       continue;
+    has_visible_window = true;
     if (ApplyLockForWindowIfPossible(window))
       return;
   }
 
+  // No visible MRU window means that the home screen might be showing. Check
+  // if it has an orientation lock.
+  if (!has_visible_window) {
+    DCHECK(shell->home_screen_controller()->IsHomeScreenAvailable());
+    const aura::Window* home_screen_window =
+        shell->home_screen_controller()->delegate()->GetHomeScreenWindow();
+    if (home_screen_window &&
+        shell->activation_client()->GetActiveWindow() == home_screen_window &&
+        ApplyLockForWindowIfPossible(home_screen_window)) {
+      return;
+    }
+  }
+
   LockRotationToOrientation(user_locked_orientation_);
 }
 
diff --git a/ash/display/screen_orientation_controller_unittest.cc b/ash/display/screen_orientation_controller_unittest.cc
index cb47699..f5c4c21 100644
--- a/ash/display/screen_orientation_controller_unittest.cc
+++ b/ash/display/screen_orientation_controller_unittest.cc
@@ -11,7 +11,11 @@
 #include "ash/accelerometer/accelerometer_types.h"
 #include "ash/display/screen_orientation_controller.h"
 #include "ash/display/screen_orientation_controller_test_api.h"
+#include "ash/home_screen/home_screen_controller.h"
+#include "ash/kiosk_next/kiosk_next_shell_test_util.h"
+#include "ash/kiosk_next/mock_kiosk_next_shell_client.h"
 #include "ash/public/cpp/app_types.h"
+#include "ash/public/cpp/ash_features.h"
 #include "ash/public/cpp/ash_switches.h"
 #include "ash/shell.h"
 #include "ash/system/screen_layout_observer.h"
@@ -22,6 +26,8 @@
 #include "ash/wm/tablet_mode/tablet_mode_controller.h"
 #include "ash/wm/window_state.h"
 #include "base/command_line.h"
+#include "base/macros.h"
+#include "base/test/scoped_feature_list.h"
 #include "ui/aura/client/aura_constants.h"
 #include "ui/aura/window.h"
 #include "ui/compositor/layer_type.h"
@@ -723,4 +729,206 @@
   EXPECT_EQ(display::Display::ROTATE_270, GetCurrentInternalDisplayRotation());
 }
 
+// Test fixture for Kiosk Next Home's rotation lock.
+class ScreenOrientationControllerKioskNextTest
+    : public ScreenOrientationControllerTest {
+ public:
+  ScreenOrientationControllerKioskNextTest() = default;
+  ~ScreenOrientationControllerKioskNextTest() override = default;
+
+  // AshTestBase:
+  void SetUp() override {
+    base::CommandLine::ForCurrentProcess()->AppendSwitch(
+        ::switches::kUseFirstDisplayAsInternal);
+    scoped_feature_list_.InitAndEnableFeature(features::kKioskNextShell);
+
+    set_start_session(false);
+    AshTestBase::SetUp();
+
+    client_ = BindMockKioskNextShellClient();
+  }
+
+  void TearDown() override {
+    home_screen_window_.reset();
+    AshTestBase::TearDown();
+  }
+
+ protected:
+  aura::Window* CreateHomeScreenWindow() {
+    home_screen_window_.reset(CreateAppWindowInShellWithId(0));
+    ash::Shell::GetContainer(ash::Shell::GetPrimaryRootWindow(),
+                             ash::kShellWindowId_HomeScreenContainer)
+        ->AddChild(home_screen_window_.get());
+    return home_screen_window_.get();
+  }
+
+ private:
+  std::unique_ptr<MockKioskNextShellClient> client_;
+  base::test::ScopedFeatureList scoped_feature_list_;
+
+  std::unique_ptr<aura::Window> home_screen_window_;
+
+  DISALLOW_COPY_AND_ASSIGN(ScreenOrientationControllerKioskNextTest);
+};
+
+// Tests that the home screen can be inverted but not rotated to portrait.
+TEST_F(ScreenOrientationControllerKioskNextTest,
+       LandscapeOrientationAllowsRotation) {
+  LogInKioskNextUser(GetSessionControllerClient());
+  aura::Window* home_screen_window = CreateHomeScreenWindow();
+
+  Shell::Get()->activation_client()->ActivateWindow(home_screen_window);
+
+  // The home screen window is rotation-locked to landscape.
+  EXPECT_EQ(display::Display::ROTATE_0, GetCurrentInternalDisplayRotation());
+  EXPECT_TRUE(RotationLocked());
+
+  // Inverse of orientation is allowed.
+  TriggerLidUpdate(gfx::Vector3dF(0.0f, kMeanGravity, 0.0f));
+  EXPECT_EQ(display::Display::ROTATE_180, GetCurrentInternalDisplayRotation());
+
+  // Display rotations in between are not allowed.
+  TriggerLidUpdate(gfx::Vector3dF(kMeanGravity, 0.0f, 0.0f));
+  EXPECT_EQ(display::Display::ROTATE_180, GetCurrentInternalDisplayRotation());
+  TriggerLidUpdate(gfx::Vector3dF(-kMeanGravity, 0.0f, 0.0f));
+  EXPECT_EQ(display::Display::ROTATE_180, GetCurrentInternalDisplayRotation());
+}
+
+// Tests that app windows outside of the home screen follow normal orientation
+// rules.
+TEST_F(ScreenOrientationControllerKioskNextTest,
+       ActiveWindowChangesUpdateOrientation) {
+  LogInKioskNextUser(GetSessionControllerClient());
+  aura::Window* home_screen_window = CreateHomeScreenWindow();
+  Shell::Get()->activation_client()->ActivateWindow(home_screen_window);
+
+  // The home screen window is rotation-locked to landscape.
+  EXPECT_TRUE(RotationLocked());
+  EXPECT_EQ(display::Display::ROTATE_0, GetCurrentInternalDisplayRotation());
+
+  std::unique_ptr<aura::Window> child_window = CreateControlWindow();
+  std::unique_ptr<aura::Window> focus_window(CreateAppWindowInShellWithId(0));
+
+  // The rotation is unlocked when an app window is opened.
+  AddWindowAndActivateParent(child_window.get(), focus_window.get());
+  EXPECT_FALSE(RotationLocked());
+  EXPECT_EQ(display::Display::ROTATE_0, GetCurrentInternalDisplayRotation());
+  TriggerLidUpdate(gfx::Vector3dF(-kMeanGravity, 0.0f, 0.0f));
+  EXPECT_EQ(display::Display::ROTATE_90, GetCurrentInternalDisplayRotation());
+
+  // The rotation becomes locked again when returning to Home.
+  Shell::Get()->home_screen_controller()->GoHome(
+      display::Display::InternalDisplayId());
+  EXPECT_TRUE(RotationLocked());
+  EXPECT_EQ(display::Display::ROTATE_0, GetCurrentInternalDisplayRotation());
+}
+
+TEST_F(ScreenOrientationControllerKioskNextTest,
+       ActiveWindowChangesUpdateLock) {
+  LogInKioskNextUser(GetSessionControllerClient());
+  aura::Window* home_screen_window = CreateHomeScreenWindow();
+  Shell::Get()->activation_client()->ActivateWindow(home_screen_window);
+
+  // The home screen window is rotation-locked to landscape.
+  EXPECT_TRUE(RotationLocked());
+  EXPECT_EQ(display::Display::ROTATE_0, GetCurrentInternalDisplayRotation());
+
+  std::unique_ptr<aura::Window> child_window = CreateControlWindow();
+  std::unique_ptr<aura::Window> focus_window(CreateAppWindowInShellWithId(0));
+
+  // An app window can specify its own lock.
+  Lock(child_window.get(), OrientationLockType::kPortrait);
+  AddWindowAndActivateParent(child_window.get(), focus_window.get());
+  EXPECT_EQ(display::Display::ROTATE_270, GetCurrentInternalDisplayRotation());
+  EXPECT_TRUE(RotationLocked());
+
+  // The rotation returns to landscape when returning to Home.
+  Shell::Get()->home_screen_controller()->GoHome(
+      display::Display::InternalDisplayId());
+  EXPECT_TRUE(RotationLocked());
+  EXPECT_EQ(display::Display::ROTATE_0, GetCurrentInternalDisplayRotation());
+}
+
+// Tests that the active window removing a rotation lock doesn't affect the home
+// screen window's lock.
+TEST_F(ScreenOrientationControllerKioskNextTest,
+       ActiveWindowRemovesUpdateLock) {
+  LogInKioskNextUser(GetSessionControllerClient());
+  aura::Window* home_screen_window = CreateHomeScreenWindow();
+  Shell::Get()->activation_client()->ActivateWindow(home_screen_window);
+
+  // The home screen window is rotation-locked to landscape.
+  EXPECT_TRUE(RotationLocked());
+  EXPECT_EQ(display::Display::ROTATE_0, GetCurrentInternalDisplayRotation());
+
+  std::unique_ptr<aura::Window> child_window = CreateControlWindow();
+  std::unique_ptr<aura::Window> focus_window(CreateAppWindowInShellWithId(0));
+
+  // An app window can specify its own lock.
+  Lock(child_window.get(), OrientationLockType::kLandscape);
+  AddWindowAndActivateParent(child_window.get(), focus_window.get());
+  EXPECT_TRUE(RotationLocked());
+  EXPECT_EQ(display::Display::ROTATE_0, GetCurrentInternalDisplayRotation());
+  TriggerLidUpdate(gfx::Vector3dF(kMeanGravity, 0.0f, 0.0f));
+  EXPECT_EQ(display::Display::ROTATE_0, GetCurrentInternalDisplayRotation());
+
+  // Returning home preserves the home screen lock, even after the previously
+  // active window drops its lock.
+  Shell::Get()->home_screen_controller()->GoHome(
+      display::Display::InternalDisplayId());
+  EXPECT_TRUE(RotationLocked());
+  EXPECT_EQ(display::Display::ROTATE_0, GetCurrentInternalDisplayRotation());
+
+  Unlock(child_window.get());
+  EXPECT_TRUE(RotationLocked());
+  EXPECT_EQ(display::Display::ROTATE_0, GetCurrentInternalDisplayRotation());
+}
+
+// Tests that a user rotation lock doesn't override the home screen window's
+// orientation lock.
+TEST_F(ScreenOrientationControllerKioskNextTest, UserRotationLock) {
+  LogInKioskNextUser(GetSessionControllerClient());
+  aura::Window* home_screen_window = CreateHomeScreenWindow();
+  ::wm::ActivationClient* activation_client = Shell::Get()->activation_client();
+  activation_client->ActivateWindow(home_screen_window);
+
+  std::unique_ptr<aura::Window> child_window = CreateControlWindow();
+  std::unique_ptr<aura::Window> focus_window(CreateAppWindowInShellWithId(0));
+
+  // The rotation is unlocked when an app window is opened.
+  AddWindowAndActivateParent(child_window.get(), focus_window.get());
+  EXPECT_EQ(display::Display::ROTATE_0, GetCurrentInternalDisplayRotation());
+  EXPECT_FALSE(RotationLocked());
+  TriggerLidUpdate(gfx::Vector3dF(-kMeanGravity, 0.0f, 0.0f));
+  EXPECT_EQ(display::Display::ROTATE_90, GetCurrentInternalDisplayRotation());
+
+  // The user can lock the rotation.
+  SetUserRotationLocked(true);
+  EXPECT_TRUE(UserRotationLocked());
+  EXPECT_TRUE(RotationLocked());
+
+  // Rotating the screen doesn't update the orientation while locked.
+  TriggerLidUpdate(gfx::Vector3dF(0.0f, kMeanGravity, 0.0f));
+  EXPECT_EQ(display::Display::ROTATE_90, GetCurrentInternalDisplayRotation());
+
+  // The rotation returns to landscape when returning to Home.
+  Shell::Get()->home_screen_controller()->GoHome(
+      display::Display::InternalDisplayId());
+  EXPECT_TRUE(RotationLocked());
+  EXPECT_EQ(display::Display::ROTATE_0, GetCurrentInternalDisplayRotation());
+
+  // The user rotation lock applies again when re-activating an app window.
+  activation_client->ActivateWindow(focus_window.get());
+  EXPECT_TRUE(RotationLocked());
+  EXPECT_EQ(display::Display::ROTATE_90, GetCurrentInternalDisplayRotation());
+
+  // Removing the user rotation lock doesn't affect Home.
+  Shell::Get()->home_screen_controller()->GoHome(
+      display::Display::InternalDisplayId());
+  SetUserRotationLocked(false);
+  EXPECT_FALSE(UserRotationLocked());
+  EXPECT_TRUE(RotationLocked());
+  EXPECT_EQ(display::Display::ROTATE_0, GetCurrentInternalDisplayRotation());
+}
+
 }  // namespace ash
diff --git a/ash/kiosk_next/kiosk_next_home_controller.cc b/ash/kiosk_next/kiosk_next_home_controller.cc
index accbc87..90b20e1e 100644
--- a/ash/kiosk_next/kiosk_next_home_controller.cc
+++ b/ash/kiosk_next/kiosk_next_home_controller.cc
@@ -6,12 +6,13 @@
 
 #include <memory>
 
+#include "ash/display/screen_orientation_controller.h"
 #include "ash/public/cpp/shell_window_ids.h"
 #include "ash/screen_util.h"
+#include "ash/shell.h"
 #include "ash/wm/container_finder.h"
 #include "ash/wm/wm_event.h"
 #include "base/logging.h"
-#include "ui/aura/client/window_types.h"
 #include "ui/aura/window.h"
 #include "ui/compositor/layer.h"
 #include "ui/compositor/layer_animator.h"
@@ -24,10 +25,21 @@
 
 KioskNextHomeController::KioskNextHomeController() {
   display::Screen::GetScreen()->AddObserver(this);
+
+  home_screen_container_ = Shell::GetPrimaryRootWindow()->GetChildById(
+      kShellWindowId_HomeScreenContainer);
+  home_screen_container_->AddObserver(this);
+  if (!home_screen_container_->children().empty()) {
+    DCHECK_EQ(1u, home_screen_container_->children().size());
+    home_screen_window_ = home_screen_container_->children()[0];
+  }
 }
 
 KioskNextHomeController::~KioskNextHomeController() {
   display::Screen::GetScreen()->RemoveObserver(this);
+
+  if (home_screen_container_)
+    home_screen_container_->RemoveObserver(this);
 }
 
 void KioskNextHomeController::ShowHomeScreenView() {
@@ -37,24 +49,7 @@
 }
 
 aura::Window* KioskNextHomeController::GetHomeScreenWindow() {
-  aura::Window::Windows containers = wm::GetContainersFromAllRootWindows(
-      kShellWindowId_HomeScreenContainer, nullptr);
-  for (aura::Window* container : containers) {
-    if (container->children().empty())
-      continue;
-
-    // Expect only one window.
-    DCHECK_EQ(1u, container->children().size());
-    aura::Window* window = container->children()[0];
-
-    // When the app list is being destroyed (before the Home app is launched),
-    // the app list will appear here; filter for the Home app by checking for a
-    // normal type window.
-    if (window->type() == aura::client::WindowType::WINDOW_TYPE_NORMAL)
-      return window;
-  }
-
-  return nullptr;
+  return home_screen_window_;
 }
 
 void KioskNextHomeController::UpdateYPositionAndOpacityForHomeLauncher(
@@ -109,4 +104,23 @@
   wm::GetWindowState(window)->OnWMEvent(&event);
 }
 
+void KioskNextHomeController::OnWindowAdded(aura::Window* new_window) {
+  DCHECK(!home_screen_window_);
+  DCHECK_EQ(new_window->type(), aura::client::WindowType::WINDOW_TYPE_NORMAL);
+  home_screen_window_ = new_window;
+
+  Shell::Get()->screen_orientation_controller()->LockOrientationForWindow(
+      home_screen_window_, OrientationLockType::kLandscape);
+}
+
+void KioskNextHomeController::OnWillRemoveWindow(aura::Window* window) {
+  DCHECK_EQ(home_screen_window_, window);
+  home_screen_window_ = nullptr;
+}
+
+void KioskNextHomeController::OnWindowDestroying(aura::Window* window) {
+  if (window == home_screen_container_)
+    home_screen_container_ = nullptr;
+}
+
 }  // namespace ash
diff --git a/ash/kiosk_next/kiosk_next_home_controller.h b/ash/kiosk_next/kiosk_next_home_controller.h
index 5625209..aed0000 100644
--- a/ash/kiosk_next/kiosk_next_home_controller.h
+++ b/ash/kiosk_next/kiosk_next_home_controller.h
@@ -8,6 +8,7 @@
 #include "ash/ash_export.h"
 #include "ash/home_screen/home_screen_delegate.h"
 #include "base/macros.h"
+#include "ui/aura/window_observer.h"
 #include "ui/display/display_observer.h"
 
 namespace ash {
@@ -16,7 +17,8 @@
 // TODO(michaelpg): Manage gestures on the Home window, such as dragging down
 // from the top for Overview mode.
 class ASH_EXPORT KioskNextHomeController : public HomeScreenDelegate,
-                                           public display::DisplayObserver {
+                                           public display::DisplayObserver,
+                                           public aura::WindowObserver {
  public:
   KioskNextHomeController();
   ~KioskNextHomeController() override;
@@ -37,7 +39,15 @@
   void OnDisplayMetricsChanged(const display::Display& display,
                                uint32_t changed_metrics) override;
 
+  // WindowObserver:
+  void OnWindowAdded(aura::Window* new_window) override;
+  void OnWillRemoveWindow(aura::Window* window) override;
+  void OnWindowDestroying(aura::Window* window) override;
+
  private:
+  aura::Window* home_screen_container_ = nullptr;
+  aura::Window* home_screen_window_ = nullptr;
+
   DISALLOW_COPY_AND_ASSIGN(KioskNextHomeController);
 };