910e62b5创建于 1月15日历史提交
// Copyright 2013 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/screen_layout_observer.h"

#include <string>

#include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/test/ash_test_base.h"
#include "ash/wm/tablet_mode/tablet_mode_controller.h"
#include "base/command_line.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/chromeos/devicetype_utils.h"
#include "ui/display/display_layout_builder.h"
#include "ui/display/display_switches.h"
#include "ui/display/manager/display_layout_store.h"
#include "ui/display/manager/display_manager.h"
#include "ui/display/manager/util/display_manager_test_util.h"
#include "ui/display/manager/util/display_manager_util.h"
#include "ui/display/test/display_manager_test_api.h"
#include "ui/display/util/display_util.h"
#include "ui/message_center/message_center.h"
#include "ui/message_center/notification_list.h"
#include "ui/message_center/public/cpp/notification.h"
#include "ui/views/controls/label.h"

namespace ash {

class ScreenLayoutObserverTest : public AshTestBase {
 public:
  ScreenLayoutObserverTest();

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

  ~ScreenLayoutObserverTest() override;

  // AshTestBase:
  void SetUp() override;

 protected:
  ScreenLayoutObserver* GetScreenLayoutObserver();
  void CheckUpdate();

  void CloseNotification();
  std::u16string GetDisplayNotificationText() const;
  std::u16string GetDisplayNotificationAdditionalText() const;

  std::u16string GetFirstDisplayName();

  std::u16string GetSecondDisplayName();

  std::u16string GetMirroringDisplayNames();

  std::u16string GetUnifiedDisplayName();

  bool IsNotificationShown() const;

  display::ManagedDisplayInfo CreateDisplayInfo(int64_t id,
                                                const gfx::Rect& bounds);

 private:
  const message_center::Notification* GetDisplayNotification() const;
};
ScreenLayoutObserverTest::ScreenLayoutObserverTest() = default;

ScreenLayoutObserverTest::~ScreenLayoutObserverTest() = default;

void ScreenLayoutObserverTest::SetUp() {
  AshTestBase::SetUp();
  GetScreenLayoutObserver()->set_show_notifications_for_testing(true);
}

ScreenLayoutObserver* ScreenLayoutObserverTest::GetScreenLayoutObserver() {
  return Shell::Get()->screen_layout_observer();
}

void ScreenLayoutObserverTest::CloseNotification() {
  message_center::MessageCenter::Get()->RemoveNotification(
      ScreenLayoutObserver::kNotificationId, false);
  base::RunLoop().RunUntilIdle();
}

std::u16string ScreenLayoutObserverTest::GetDisplayNotificationText() const {
  const message_center::Notification* notification = GetDisplayNotification();
  return notification ? notification->title() : std::u16string();
}

std::u16string ScreenLayoutObserverTest::GetDisplayNotificationAdditionalText()
    const {
  const message_center::Notification* notification = GetDisplayNotification();
  return notification ? notification->message() : std::u16string();
}

std::u16string ScreenLayoutObserverTest::GetFirstDisplayName() {
  return base::UTF8ToUTF16(display_manager()->GetDisplayNameForId(
      display_manager()->first_display_id()));
}

std::u16string ScreenLayoutObserverTest::GetSecondDisplayName() {
  return base::UTF8ToUTF16(display_manager()->GetDisplayNameForId(
      display::test::DisplayManagerTestApi(display_manager())
          .GetSecondaryDisplay()
          .id()));
}

std::u16string ScreenLayoutObserverTest::GetMirroringDisplayNames() {
  DCHECK(display_manager()->IsInMirrorMode());
  std::u16string display_names;
  for (auto& id : display_manager()->GetMirroringDestinationDisplayIdList()) {
    if (!display_names.empty())
      display_names.append(u",");
    display_names.append(
        base::UTF8ToUTF16(display_manager()->GetDisplayNameForId(id)));
  }
  return display_names;
}

std::u16string ScreenLayoutObserverTest::GetUnifiedDisplayName() {
  return base::UTF8ToUTF16(
      display_manager()->GetDisplayNameForId(display::kUnifiedDisplayId));
}

bool ScreenLayoutObserverTest::IsNotificationShown() const {
  return !(GetDisplayNotificationText().empty() &&
           GetDisplayNotificationAdditionalText().empty());
}

display::ManagedDisplayInfo ScreenLayoutObserverTest::CreateDisplayInfo(
    int64_t id,
    const gfx::Rect& bounds) {
  display::ManagedDisplayInfo info = display::CreateDisplayInfo(id, bounds);
  // Each display should have at least one native mode.
  display::ManagedDisplayMode mode(bounds.size(), /*refresh_rate=*/60.f,
                                   /*is_interlaced=*/true,
                                   /*native=*/true);
  info.SetManagedDisplayModes({mode});
  return info;
}

const message_center::Notification*
ScreenLayoutObserverTest::GetDisplayNotification() const {
  const message_center::NotificationList::Notifications notifications =
      message_center::MessageCenter::Get()->GetVisibleNotifications();
  for (const message_center::Notification* notification : notifications) {
    if (notification->id() == ScreenLayoutObserver::kNotificationId)
      return notification;
  }

  return nullptr;
}

TEST_F(ScreenLayoutObserverTest, DisplayNotificationsDisabled) {
  UpdateDisplay("500x400");
  display::SetInternalDisplayIds({display_manager()->first_display_id()});
  EXPECT_TRUE(GetDisplayNotificationText().empty());

  // Adding a display.
  UpdateDisplay("500x400,300x200");
  EXPECT_FALSE(IsNotificationShown());

  const int64_t first_display_id =
      display::Screen::Get()->GetPrimaryDisplay().id();
  const int64_t second_display_id =
      display::SynthesizeDisplayIdFromSeed(first_display_id);
  display::ManagedDisplayInfo first_display_info =
      CreateDisplayInfo(first_display_id, gfx::Rect(1, 1, 500, 400));
  display::ManagedDisplayInfo second_display_info =
      CreateDisplayInfo(second_display_id, gfx::Rect(2, 2, 500, 400));
  std::vector<display::ManagedDisplayInfo> display_info_list;
  display_info_list.push_back(first_display_info);
  display_info_list.push_back(second_display_info);
  display_manager()->OnNativeDisplaysChanged(display_info_list);

  // Simulate that device can support at most two displays and user
  // connects it with three displays. Notification should still be created to
  // warn user of it. See issue 827406 (https://crbug.com/827406).
  display::test::DisplayManagerTestApi(Shell::Get()->display_manager())
      .set_maximum_display(2u);
  UpdateDisplay("500x400,300x200,200x100");
  EXPECT_TRUE(GetDisplayNotificationText().empty());
  EXPECT_EQ(l10n_util::GetStringUTF16(
                IDS_ASH_STATUS_TRAY_DISPLAY_REMOVED_EXCEEDED_MAXIMUM),
            GetDisplayNotificationAdditionalText());
  EXPECT_TRUE(GetDisplayNotificationText().empty());
  UpdateDisplay("500x400,300x200");
  CloseNotification();

  // Start tablet mode and wait until display mode is updated.
  Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
  base::RunLoop().RunUntilIdle();

  // Exit mirror mode manually. Now display mode should be extending mode.
  display_manager()->SetMirrorMode(display::MirrorMode::kOff, std::nullopt);
  EXPECT_FALSE(IsNotificationShown());

  // Simulate that device can support at most two displays and user connects
  // it with three displays. Because device is in tablet mode, display mode
  // becomes mirror mode from extending mode. Under this circumstance, user is
  // still notified of connecting more displays than maximum. See issue 827406
  // (https://crbug.com/827406). Notification should still be shown.
  UpdateDisplay("500x400,300x200,200x100");
  EXPECT_EQ(l10n_util::GetStringUTF16(
                IDS_ASH_STATUS_TRAY_DISPLAY_REMOVED_EXCEEDED_MAXIMUM),
            GetDisplayNotificationAdditionalText());
  // The tablet should no longer be in mirror mode.
  EXPECT_FALSE(display_manager()->IsInMirrorMode());
  EXPECT_TRUE(GetDisplayNotificationText().empty());
  CloseNotification();
}

// Tests that the mirror mode notification is shown correctly when an
// unassociated display is connected, which forces the system into mirror mode.
// This critical edge case is described in a comment in
// `screen_layout_observer.cc`, inside the
// `ScreenLayoutObserver::GetUnassociatedDisplayMessage` function.
//
// The flow is as follows:
// 1. Start with two displays in tablet mode, which defaults to mirror mode.
// 2. Manually switch to extended mode.
// 3. Set the maximum number of supported displays to 2.
// 4. Connect a third display. On a real device, this would cause the system to
//    fall back to mirror mode because it cannot support three extended
//    displays.
// 5. The test simulates this by forcing mirror mode via
//    `set_forced_mirror_mode_for_tablet(true)` before updating the displays.
// 6. This is a critical edge case because `OnDidApplyDisplayChanges` currently
//    only proceeds to check for mirror mode notifications if a new
//    unassociated display is detected (`should_notify_has_unassociated_display`
//    is true). This test verifies that under these specific circumstances, both
//    the "entering mirror mode" and the "unassociated display" notifications
//    are correctly generated and displayed.
TEST_F(ScreenLayoutObserverTest, EnterMirrorModeWithUnassociatedDisplay) {
  UpdateDisplay("500x400,300x200");
  display::SetInternalDisplayIds({display_manager()->first_display_id()});
  EXPECT_FALSE(IsNotificationShown());

  // Start tablet mode and wait until display mode is updated.
  Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
  base::RunLoop().RunUntilIdle();

  // Exit mirror mode manually. Now display mode should be extending mode.
  display_manager()->SetMirrorMode(display::MirrorMode::kOff, std::nullopt);
  EXPECT_FALSE(IsNotificationShown());

  // Simulate that device can support at most two displays and user connects
  // it with three displays.
  display::test::DisplayManagerTestApi(Shell::Get()->display_manager())
      .set_maximum_display(2u);

  // On a real device, connecting a third display here would trigger mirror
  // mode. The test environment doesn't simulate this automatic switch, so we
  // force mirror mode to test the notification logic.
  display_manager()->layout_store()->set_forced_mirror_mode_for_tablet(true);
  UpdateDisplay("500x400,300x200,200x100");

  EXPECT_TRUE(IsNotificationShown());
  EXPECT_EQ(l10n_util::GetStringFUTF16(IDS_ASH_STATUS_TRAY_DISPLAY_MIRRORING,
                                       GetMirroringDisplayNames()),
            GetDisplayNotificationText());
  EXPECT_EQ(l10n_util::GetStringUTF16(
                IDS_ASH_STATUS_TRAY_DISPLAY_REMOVED_EXCEEDED_MAXIMUM),
            GetDisplayNotificationAdditionalText());
  EXPECT_TRUE(display_manager()->IsInMirrorMode());
  CloseNotification();
  display_manager()->layout_store()->set_forced_mirror_mode_for_tablet(false);
}

// Verify that no notification is shown when overscan of a screen is changed.
TEST_F(ScreenLayoutObserverTest, OverscanDisplay) {
  UpdateDisplay("500x400, 400x300");
  // Close the notification that is shown from initially adding a monitor.
  CloseNotification();
  display::SetInternalDisplayIds({display_manager()->first_display_id()});

  // /o creates the default overscan.
  UpdateDisplay("500x400, 400x300/o");
  EXPECT_FALSE(IsNotificationShown());

  // Reset the overscan.
  Shell::Get()->display_manager()->SetOverscanInsets(
      display::test::DisplayManagerTestApi(display_manager())
          .GetSecondaryDisplay()
          .id(),
      gfx::Insets());
  EXPECT_FALSE(IsNotificationShown());
}
}  // namespace ash