chromium/ash/system/human_presence/human_presence_orientation_controller_unittest.cc

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

#include "ash/system/human_presence/human_presence_orientation_controller.h"
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_switches.h"
#include "ash/shell.h"
#include "ash/test/ash_test_base.h"
#include "base/command_line.h"
#include "base/memory/raw_ptr.h"
#include "base/test/scoped_command_line.h"
#include "base/test/scoped_feature_list.h"
#include "base/time/time.h"
#include "chromeos/dbus/power/fake_power_manager_client.h"
#include "ui/display/display.h"
#include "ui/display/manager/display_manager.h"
#include "ui/display/test/display_manager_test_api.h"

namespace ash {
namespace {

// A simple observer that lists its last observation.
class TestObserver : public HumanPresenceOrientationController::Observer {
 public:
  TestObserver() = default;
  TestObserver(const TestObserver&) = delete;
  TestObserver& operator=(const TestObserver&) = delete;
  ~TestObserver() override = default;

  // HumanPresenceOrientationController::Observer::
  void OnOrientationChanged(bool suitable_for_hps) override {
    ++observation_count_;
    last_observation_ = suitable_for_hps;
  }

  int observation_count() { return observation_count_; }

  int last_observation() { return last_observation_; }

 private:
  int observation_count_ = 0;
  bool last_observation_ = false;
};

class HumanPresenceOrientationControllerTest : public AshTestBase {
 public:
  HumanPresenceOrientationControllerTest() = default;
  HumanPresenceOrientationControllerTest(
      const HumanPresenceOrientationControllerTest&) = delete;
  HumanPresenceOrientationControllerTest& operator=(
      const HumanPresenceOrientationControllerTest&) = delete;
  ~HumanPresenceOrientationControllerTest() override = default;

  void SetUp() override {
    scoped_feature_list_.InitWithFeatures({ash::features::kSnoopingProtection},
                                          {ash::features::kQuickDim});
    base::CommandLine::ForCurrentProcess()->AppendSwitch(switches::kHasHps);

    chromeos::PowerManagerClient::InitializeFake();
    AshTestBase::SetUp();

    orientation_controller_ =
        Shell::Get()->human_presence_orientation_controller();
    tablet_mode_controller_ = Shell::Get()->tablet_mode_controller();
    display_manager_ = Shell::Get()->display_manager();
    power_manager_client_ = AshTestBase::power_manager_client();

    // Two displays: the first internal and the second external.
    UpdateDisplay("800x600,1024x768");
    display::test::DisplayManagerTestApi(display_manager_)
        .SetFirstDisplayAsInternalDisplay();
  }

 protected:
  void RotateDisplay(int display_index, int degrees) {
    const int64_t id = display_manager_->GetDisplayAt(display_index).id();
    display_manager_->SetDisplayRotation(
        id, display::Display::DegreesToRotation(degrees),
        display::Display::RotationSource::ACTIVE);
  }

  raw_ptr<HumanPresenceOrientationController, DanglingUntriaged>
      orientation_controller_ = nullptr;
  raw_ptr<TabletModeController, DanglingUntriaged> tablet_mode_controller_ =
      nullptr;
  raw_ptr<display::DisplayManager, DanglingUntriaged> display_manager_ =
      nullptr;
  raw_ptr<chromeos::FakePowerManagerClient, DanglingUntriaged>
      power_manager_client_ = nullptr;

 private:
  base::test::ScopedFeatureList scoped_feature_list_;
  base::test::ScopedCommandLine scoped_command_line_;
};

TEST_F(HumanPresenceOrientationControllerTest, TabletMode) {
  ASSERT_TRUE(orientation_controller_->IsOrientationSuitable());

  tablet_mode_controller_->SetEnabledForTest(true);
  EXPECT_FALSE(orientation_controller_->IsOrientationSuitable());
  tablet_mode_controller_->SetEnabledForTest(false);
  EXPECT_TRUE(orientation_controller_->IsOrientationSuitable());
}

TEST_F(HumanPresenceOrientationControllerTest, DisplayOrientation) {
  ASSERT_TRUE(orientation_controller_->IsOrientationSuitable());

  // Rotating the external display has no effect on our sensor.
  RotateDisplay(/*display_index=*/1, /*degrees=*/90);
  EXPECT_TRUE(orientation_controller_->IsOrientationSuitable());

  // Rotating the internal display _does_ have an effect on our sensor.
  RotateDisplay(/*display_index=*/0, /*degrees=*/270);
  EXPECT_FALSE(orientation_controller_->IsOrientationSuitable());

  RotateDisplay(/*display_index=*/1, /*degrees=*/0);
  EXPECT_FALSE(orientation_controller_->IsOrientationSuitable());

  RotateDisplay(/*display_index=*/0, /*degrees=*/0);
  EXPECT_TRUE(orientation_controller_->IsOrientationSuitable());
}

TEST_F(HumanPresenceOrientationControllerTest, LidState) {
  ASSERT_TRUE(orientation_controller_->IsOrientationSuitable());
  power_manager_client_->SetLidState(
      chromeos::PowerManagerClient::LidState::CLOSED, base::TimeTicks());
  ASSERT_FALSE(orientation_controller_->IsOrientationSuitable());
  power_manager_client_->SetLidState(
      chromeos::PowerManagerClient::LidState::OPEN, base::TimeTicks());
  ASSERT_TRUE(orientation_controller_->IsOrientationSuitable());
}

TEST_F(HumanPresenceOrientationControllerTest, Observer) {
  TestObserver observer;
  orientation_controller_->AddObserver(&observer);

  ASSERT_TRUE(orientation_controller_->IsOrientationSuitable());
  ASSERT_EQ(observer.observation_count(), 0);

  // Rotating the external display has no effect on our sensor, so no event
  // should fire.
  RotateDisplay(/*display_index=*/1, /*degrees=*/90);
  EXPECT_EQ(observer.observation_count(), 0);

  // Entering tablet mode should explicitly notify observers.
  tablet_mode_controller_->SetEnabledForTest(true);
  EXPECT_EQ(observer.observation_count(), 1);
  EXPECT_EQ(observer.last_observation(), /*suitable_for_hps=*/false);

  // While rotating the internal display makes the device unsuitable, no event
  // should fire because the suitability hasn't _changed_.
  RotateDisplay(/*display_index=*/0, /*degrees=*/90);
  EXPECT_EQ(observer.observation_count(), 1);
  EXPECT_EQ(observer.last_observation(), /*suitable_for_hps=*/false);

  tablet_mode_controller_->SetEnabledForTest(false);
  RotateDisplay(/*display_index=*/0, /*degrees=*/0);
  EXPECT_EQ(observer.observation_count(), 2);
  EXPECT_EQ(observer.last_observation(), /*suitable_for_hps=*/true);

  // Closing the device lid makes the device unsuitable.
  power_manager_client_->SetLidState(
      chromeos::PowerManagerClient::LidState::CLOSED, base::TimeTicks());
  EXPECT_EQ(observer.observation_count(), 3);
  ASSERT_FALSE(observer.last_observation());
  // Opening the device lid makes the device suitable.
  power_manager_client_->SetLidState(
      chromeos::PowerManagerClient::LidState::OPEN, base::TimeTicks());
  EXPECT_EQ(observer.observation_count(), 4);
  EXPECT_TRUE(observer.last_observation());

  orientation_controller_->RemoveObserver(&observer);
}

}  // namespace
}  // namespace ash