// Copyright 2017 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/palette/palette_tray.h"
#include <memory>
#include <string>
#include "ash/constants/ash_pref_names.h"
#include "ash/constants/ash_switches.h"
#include "ash/projector/model/projector_session_impl.h"
#include "ash/projector/projector_controller_impl.h"
#include "ash/public/cpp/stylus_utils.h"
#include "ash/root_window_controller.h"
#include "ash/session/session_controller_impl.h"
#include "ash/session/test_session_controller_client.h"
#include "ash/shell.h"
#include "ash/system/palette/palette_tray_test_api.h"
#include "ash/system/palette/palette_utils.h"
#include "ash/system/palette/palette_welcome_bubble.h"
#include "ash/system/status_area_widget.h"
#include "ash/system/status_area_widget_test_helper.h"
#include "ash/test/ash_test_base.h"
#include "ash/test_shell_delegate.h"
#include "base/command_line.h"
#include "base/files/safe_base_name.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/simple_test_tick_clock.h"
#include "components/prefs/pref_service.h"
#include "components/session_manager/session_manager_types.h"
#include "ui/compositor/scoped_animation_duration_scale_mode.h"
#include "ui/display/manager/display_manager.h"
#include "ui/display/test/display_manager_test_api.h"
#include "ui/events/base_event_utils.h"
#include "ui/events/devices/device_data_manager.h"
#include "ui/events/devices/device_data_manager_test_api.h"
#include "ui/events/devices/stylus_state.h"
#include "ui/events/event.h"
#include "ui/events/test/event_generator.h"
namespace ash {
class PaletteTrayTest : public AshTestBase {
public:
PaletteTrayTest() = default;
PaletteTrayTest(const PaletteTrayTest&) = delete;
PaletteTrayTest& operator=(const PaletteTrayTest&) = delete;
~PaletteTrayTest() override = default;
// Fake a stylus ejection.
void EjectStylus() {
test_api_->OnStylusStateChanged(ui::StylusState::REMOVED);
}
// Fake a stylus insertion.
void InsertStylus() {
test_api_->OnStylusStateChanged(ui::StylusState::INSERTED);
}
// AshTestBase:
void SetUp() override {
base::CommandLine::ForCurrentProcess()->AppendSwitch(
switches::kAshEnablePaletteOnAllDisplays);
stylus_utils::SetHasStylusInputForTesting();
AshTestBase::SetUp();
palette_tray_ =
StatusAreaWidgetTestHelper::GetStatusAreaWidget()->palette_tray();
test_api_ = std::make_unique<PaletteTrayTestApi>(palette_tray_);
display::test::DisplayManagerTestApi(display_manager())
.SetFirstDisplayAsInternalDisplay();
}
// Sends a stylus event, which makes the `PaletteTray` show up.
void ShowPaletteTray() {
ui::test::EventGenerator* generator = GetEventGenerator();
generator->EnterPenPointerMode();
generator->PressTouch();
generator->ReleaseTouch();
generator->ExitPenPointerMode();
ASSERT_TRUE(palette_tray_->GetVisible());
}
PrefService* prefs() {
return Shell::Get()->session_controller()->GetPrimaryUserPrefService();
}
protected:
PrefService* active_user_pref_service() {
return Shell::Get()->session_controller()->GetActivePrefService();
}
raw_ptr<PaletteTray, DanglingUntriaged> palette_tray_ = nullptr; // not owned
std::unique_ptr<PaletteTrayTestApi> test_api_;
};
// Verify the palette tray button exists and but is not visible initially.
TEST_F(PaletteTrayTest, PaletteTrayIsInvisible) {
ASSERT_TRUE(palette_tray_);
EXPECT_FALSE(palette_tray_->GetVisible());
}
// Verify if the has seen stylus pref is not set initially, the palette tray
// should become visible after seeing a stylus event.
TEST_F(PaletteTrayTest, PaletteTrayVisibleAfterStylusSeen) {
ASSERT_FALSE(palette_tray_->GetVisible());
ASSERT_FALSE(local_state()->GetBoolean(prefs::kHasSeenStylus));
// Send a stylus event.
ui::test::EventGenerator* generator = GetEventGenerator();
generator->EnterPenPointerMode();
generator->PressTouch();
generator->ReleaseTouch();
generator->ExitPenPointerMode();
EXPECT_TRUE(palette_tray_->GetVisible());
}
// Verify if the has seen stylus pref is initially set, the palette tray is
// visible.
TEST_F(PaletteTrayTest, StylusSeenPrefInitiallySet) {
ASSERT_FALSE(palette_tray_->GetVisible());
active_user_pref_service()->SetBoolean(prefs::kEnableStylusTools, true);
local_state()->SetBoolean(prefs::kHasSeenStylus, true);
EXPECT_TRUE(palette_tray_->GetVisible());
}
// Verify if the kEnableStylusTools pref was never set the stylus
// should become visible after a stylus event. Even if kHasSeenStylus
// has been previously set.
TEST_F(PaletteTrayTest, PaletteTrayVisibleIfEnableStylusToolsNotSet) {
local_state()->SetBoolean(prefs::kHasSeenStylus, true);
ASSERT_FALSE(palette_tray_->GetVisible());
// Send a stylus event.
ui::test::EventGenerator* generator = GetEventGenerator();
generator->EnterPenPointerMode();
generator->PressTouch();
generator->ReleaseTouch();
generator->ExitPenPointerMode();
EXPECT_TRUE(palette_tray_->GetVisible());
active_user_pref_service()->SetBoolean(prefs::kEnableStylusTools, false);
EXPECT_FALSE(palette_tray_->GetVisible());
// Send a stylus event.
generator->EnterPenPointerMode();
generator->PressTouch();
generator->ReleaseTouch();
generator->ExitPenPointerMode();
EXPECT_FALSE(palette_tray_->GetVisible());
}
// A basic test to ensure the OnPressedCallback is triggered on tap.
TEST_F(PaletteTrayTest, PressingTrayButton) {
ShowPaletteTray();
GestureTapOn(palette_tray_);
EXPECT_TRUE(palette_tray_->is_active());
}
// Verify taps on the palette tray button results in expected behaviour.
TEST_F(PaletteTrayTest, PaletteTrayWorkflow) {
ShowPaletteTray();
// Verify the palette tray button is not active, and the palette tray bubble
// is not shown initially.
EXPECT_FALSE(palette_tray_->is_active());
EXPECT_FALSE(test_api_->tray_bubble_wrapper());
// Verify that by tapping the palette tray button, the button will become
// active and the palette tray bubble will be open.
GestureTapOn(palette_tray_);
EXPECT_TRUE(palette_tray_->is_active());
EXPECT_TRUE(test_api_->tray_bubble_wrapper());
// Verify that activating a mode tool will close the palette tray bubble, but
// leave the palette tray button active.
test_api_->palette_tool_manager()->ActivateTool(PaletteToolId::LASER_POINTER);
EXPECT_TRUE(test_api_->palette_tool_manager()->IsToolActive(
PaletteToolId::LASER_POINTER));
EXPECT_TRUE(palette_tray_->is_active());
EXPECT_FALSE(test_api_->tray_bubble_wrapper());
// Verify that tapping the palette tray while a tool is active will deactivate
// the tool, and the palette tray button will not be active.
GestureTapOn(palette_tray_);
EXPECT_FALSE(palette_tray_->is_active());
EXPECT_FALSE(test_api_->palette_tool_manager()->IsToolActive(
PaletteToolId::LASER_POINTER));
// Verify that activating a action tool will close the palette tray bubble and
// the palette tray button is will not be active.
GestureTapOn(palette_tray_);
ASSERT_TRUE(test_api_->tray_bubble_wrapper());
const auto capture_tool_id = PaletteToolId::ENTER_CAPTURE_MODE;
test_api_->palette_tool_manager()->ActivateTool(capture_tool_id);
EXPECT_FALSE(
test_api_->palette_tool_manager()->IsToolActive(capture_tool_id));
// Wait for the tray bubble widget to close.
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(test_api_->tray_bubble_wrapper());
EXPECT_FALSE(palette_tray_->is_active());
}
// Verify that the palette tray button and bubble are as expected when modes
// that can be deactivated without pressing the palette tray button (such as
// capture region) are deactivated.
TEST_F(PaletteTrayTest, ModeToolDeactivatedAutomatically) {
// Open the palette tray with a tap.
ShowPaletteTray();
GestureTapOn(palette_tray_);
ASSERT_TRUE(palette_tray_->is_active());
ASSERT_TRUE(test_api_->tray_bubble_wrapper());
// Activate and deactivate the laser pointer tool.
test_api_->palette_tool_manager()->ActivateTool(PaletteToolId::LASER_POINTER);
ASSERT_TRUE(test_api_->palette_tool_manager()->IsToolActive(
PaletteToolId::LASER_POINTER));
test_api_->palette_tool_manager()->DeactivateTool(
PaletteToolId::LASER_POINTER);
// Verify the bubble is hidden and the button is inactive after deactivating
// the capture region tool.
EXPECT_FALSE(test_api_->tray_bubble_wrapper());
EXPECT_FALSE(palette_tray_->is_active());
}
TEST_F(PaletteTrayTest, EnableStylusPref) {
local_state()->SetBoolean(prefs::kHasSeenStylus, true);
// kEnableStylusTools is false by default
ASSERT_FALSE(
active_user_pref_service()->GetBoolean(prefs::kEnableStylusTools));
EXPECT_FALSE(palette_tray_->GetVisible());
// Setting the pref again shows the palette tray.
active_user_pref_service()->SetBoolean(prefs::kEnableStylusTools, true);
EXPECT_TRUE(palette_tray_->GetVisible());
// Resetting the pref hides the palette tray.
active_user_pref_service()->SetBoolean(prefs::kEnableStylusTools, false);
EXPECT_FALSE(palette_tray_->GetVisible());
}
// Verify that the kEnableStylusTools pref is switched to true automatically
// when a stylus is detected for the first time.
TEST_F(PaletteTrayTest, EnableStylusPrefSwitchedOnStylusEvent) {
ASSERT_FALSE(
active_user_pref_service()->GetBoolean(prefs::kEnableStylusTools));
ui::test::EventGenerator* generator = GetEventGenerator();
generator->EnterPenPointerMode();
generator->PressTouch();
generator->ReleaseTouch();
EXPECT_TRUE(
active_user_pref_service()->GetBoolean(prefs::kEnableStylusTools));
}
TEST_F(PaletteTrayTest, WelcomeBubbleVisibility) {
ASSERT_FALSE(active_user_pref_service()->GetBoolean(
prefs::kShownPaletteWelcomeBubble));
EXPECT_FALSE(test_api_->welcome_bubble()->GetBubbleViewForTesting());
// Verify that the welcome bubble does not shown up after tapping the screen
// with a finger.
ui::test::EventGenerator* generator = GetEventGenerator();
generator->PressTouch();
generator->ReleaseTouch();
EXPECT_FALSE(test_api_->welcome_bubble()->GetBubbleViewForTesting());
// Verify that the welcome bubble shows up after tapping the screen with a
// stylus for the first time.
generator->EnterPenPointerMode();
generator->PressTouch();
generator->ReleaseTouch();
EXPECT_TRUE(test_api_->welcome_bubble()->GetBubbleViewForTesting());
}
// Base class for tests that need to simulate an internal stylus.
class PaletteTrayTestWithInternalStylus : public PaletteTrayTest {
public:
PaletteTrayTestWithInternalStylus() {
base::CommandLine::ForCurrentProcess()->AppendSwitch(
switches::kHasInternalStylus);
}
PaletteTrayTestWithInternalStylus(const PaletteTrayTestWithInternalStylus&) =
delete;
PaletteTrayTestWithInternalStylus& operator=(
const PaletteTrayTestWithInternalStylus&) = delete;
~PaletteTrayTestWithInternalStylus() override = default;
// PaletteTrayTest:
void SetUp() override {
PaletteTrayTest::SetUp();
test_api_->SetDisplayHasStylus();
}
};
// Verify the palette tray button exists and is visible if the device has an
// internal stylus.
TEST_F(PaletteTrayTestWithInternalStylus, Visible) {
active_user_pref_service()->SetBoolean(prefs::kEnableStylusTools, true);
ASSERT_TRUE(palette_tray_);
EXPECT_TRUE(palette_tray_->GetVisible());
}
// Verify that when entering or exiting the lock screen, the behavior of the
// palette tray button is as expected.
TEST_F(PaletteTrayTestWithInternalStylus, PaletteTrayOnLockScreenBehavior) {
active_user_pref_service()->SetBoolean(prefs::kEnableStylusTools, true);
ASSERT_TRUE(palette_tray_->GetVisible());
PaletteToolManager* manager = test_api_->palette_tool_manager();
manager->ActivateTool(PaletteToolId::LASER_POINTER);
EXPECT_TRUE(manager->IsToolActive(PaletteToolId::LASER_POINTER));
// Verify that when entering the lock screen, the palette tray button is
// hidden, and the tool that was active is no longer active.
GetSessionControllerClient()->LockScreen();
EXPECT_FALSE(manager->IsToolActive(PaletteToolId::LASER_POINTER));
EXPECT_FALSE(palette_tray_->GetVisible());
// Verify that when logging back in the tray is visible, but the tool that was
// active before locking the screen is still inactive.
GetSessionControllerClient()->UnlockScreen();
EXPECT_TRUE(palette_tray_->GetVisible());
EXPECT_FALSE(manager->IsToolActive(PaletteToolId::LASER_POINTER));
}
// Verify a tool deactivates when the palette bubble is opened while the tool
// is active.
TEST_F(PaletteTrayTestWithInternalStylus, ToolDeactivatesWhenOpeningBubble) {
active_user_pref_service()->SetBoolean(prefs::kEnableStylusTools, true);
ASSERT_TRUE(palette_tray_->GetVisible());
palette_tray_->ShowBubble();
EXPECT_TRUE(test_api_->tray_bubble_wrapper());
PaletteToolManager* manager = test_api_->palette_tool_manager();
manager->ActivateTool(PaletteToolId::LASER_POINTER);
EXPECT_TRUE(manager->IsToolActive(PaletteToolId::LASER_POINTER));
EXPECT_FALSE(test_api_->tray_bubble_wrapper());
palette_tray_->ShowBubble();
EXPECT_TRUE(test_api_->tray_bubble_wrapper());
EXPECT_FALSE(manager->IsToolActive(PaletteToolId::LASER_POINTER));
}
// Verify the palette welcome bubble is shown the first time the stylus is
// removed.
TEST_F(PaletteTrayTestWithInternalStylus, WelcomeBubbleShownOnEject) {
active_user_pref_service()->SetBoolean(prefs::kEnableStylusTools, true);
active_user_pref_service()->SetBoolean(prefs::kLaunchPaletteOnEjectEvent,
false);
ASSERT_FALSE(active_user_pref_service()->GetBoolean(
prefs::kShownPaletteWelcomeBubble));
EXPECT_FALSE(test_api_->welcome_bubble()->GetBubbleViewForTesting());
EjectStylus();
EXPECT_TRUE(test_api_->welcome_bubble()->GetBubbleViewForTesting());
}
// Verify if the pref which tracks if the welcome bubble has been shown before
// is true, the welcome bubble is not shown when the stylus is removed.
// TODO(crbug.com/1423035): Disabled due to flakiness.
TEST_F(PaletteTrayTestWithInternalStylus,
DISABLED_WelcomeBubbleNotShownIfShownBefore) {
active_user_pref_service()->SetBoolean(prefs::kLaunchPaletteOnEjectEvent,
false);
active_user_pref_service()->SetBoolean(prefs::kShownPaletteWelcomeBubble,
true);
EXPECT_FALSE(test_api_->welcome_bubble()->GetBubbleViewForTesting());
EjectStylus();
EXPECT_FALSE(test_api_->welcome_bubble()->GetBubbleViewForTesting());
}
// Verify that the bubble does not get shown if the auto open palette setting is
// true.
TEST_F(PaletteTrayTestWithInternalStylus,
WelcomeBubbleNotShownIfAutoOpenPaletteTrue) {
ASSERT_TRUE(active_user_pref_service()->GetBoolean(
prefs::kLaunchPaletteOnEjectEvent));
active_user_pref_service()->SetBoolean(prefs::kShownPaletteWelcomeBubble,
false);
EXPECT_FALSE(test_api_->welcome_bubble()->GetBubbleViewForTesting());
EjectStylus();
EXPECT_FALSE(test_api_->welcome_bubble()->GetBubbleViewForTesting());
}
// Verify that the bubble does not get shown if a stylus event has been seen by
// the tray prior to the first stylus ejection.
TEST_F(PaletteTrayTestWithInternalStylus,
WelcomeBubbleNotShownIfStylusTouchTray) {
active_user_pref_service()->SetBoolean(prefs::kEnableStylusTools, true);
ASSERT_FALSE(active_user_pref_service()->GetBoolean(
prefs::kShownPaletteWelcomeBubble));
EXPECT_FALSE(test_api_->welcome_bubble()->GetBubbleViewForTesting());
ui::test::EventGenerator* generator = GetEventGenerator();
generator->EnterPenPointerMode();
generator->set_current_screen_location(
palette_tray_->GetBoundsInScreen().CenterPoint());
generator->PressTouch();
generator->ReleaseTouch();
EXPECT_TRUE(active_user_pref_service()->GetBoolean(
prefs::kShownPaletteWelcomeBubble));
EjectStylus();
EXPECT_FALSE(test_api_->welcome_bubble()->GetBubbleViewForTesting());
}
// Verify that palette bubble is shown/hidden on stylus eject/insert iff the
// auto open palette setting is true.
TEST_F(PaletteTrayTestWithInternalStylus, PaletteBubbleShownOnEject) {
active_user_pref_service()->SetBoolean(prefs::kEnableStylusTools, true);
// kLaunchPaletteOnEjectEvent is true by default.
ASSERT_TRUE(active_user_pref_service()->GetBoolean(
prefs::kLaunchPaletteOnEjectEvent));
// Removing the stylus shows the bubble.
EjectStylus();
EXPECT_TRUE(palette_tray_->GetBubbleView());
// Inserting the stylus hides the bubble.
InsertStylus();
EXPECT_FALSE(palette_tray_->GetBubbleView());
// Removing the stylus while kLaunchPaletteOnEjectEvent==false does nothing.
active_user_pref_service()->SetBoolean(prefs::kLaunchPaletteOnEjectEvent,
false);
EjectStylus();
EXPECT_FALSE(palette_tray_->GetBubbleView());
InsertStylus();
// Removing the stylus while kEnableStylusTools==false does nothing.
active_user_pref_service()->SetBoolean(prefs::kLaunchPaletteOnEjectEvent,
true);
active_user_pref_service()->SetBoolean(prefs::kEnableStylusTools, false);
EjectStylus();
EXPECT_FALSE(palette_tray_->GetBubbleView());
InsertStylus();
// Set both prefs to true, removing should work again.
active_user_pref_service()->SetBoolean(prefs::kEnableStylusTools, true);
EjectStylus();
EXPECT_TRUE(palette_tray_->GetBubbleView());
// Inserting the stylus should disable a currently selected tool.
test_api_->palette_tool_manager()->ActivateTool(PaletteToolId::LASER_POINTER);
EXPECT_TRUE(test_api_->palette_tool_manager()->IsToolActive(
PaletteToolId::LASER_POINTER));
InsertStylus();
EXPECT_FALSE(test_api_->palette_tool_manager()->IsToolActive(
PaletteToolId::LASER_POINTER));
}
// Base class for tests that need to simulate an internal stylus, and need to
// start without an active session.
class PaletteTrayNoSessionTestWithInternalStylus : public PaletteTrayTest {
public:
PaletteTrayNoSessionTestWithInternalStylus() {
base::CommandLine::ForCurrentProcess()->AppendSwitch(
switches::kHasInternalStylus);
stylus_utils::SetHasStylusInputForTesting();
}
PaletteTrayNoSessionTestWithInternalStylus(
const PaletteTrayNoSessionTestWithInternalStylus&) = delete;
PaletteTrayNoSessionTestWithInternalStylus& operator=(
const PaletteTrayNoSessionTestWithInternalStylus&) = delete;
~PaletteTrayNoSessionTestWithInternalStylus() override = default;
protected:
PrefService* active_user_pref_service() {
return Shell::Get()->session_controller()->GetActivePrefService();
}
};
// Verify that the palette tray is created on an external display, but it is not
// shown, and the palette tray bubble does not appear when the stylus is
// ejected.
TEST_F(PaletteTrayNoSessionTestWithInternalStylus,
ExternalMonitorBubbleNotShownOnEject) {
// Fakes a stylus event with state |state| on all palette trays.
auto fake_stylus_event_on_all_trays = [](ui::StylusState state) {
Shell::RootWindowControllerList controllers =
Shell::GetAllRootWindowControllers();
for (size_t i = 0; i < controllers.size(); ++i) {
PaletteTray* tray = controllers[i]->GetStatusAreaWidget()->palette_tray();
PaletteTrayTestApi api(tray);
api.OnStylusStateChanged(state);
}
};
// Add a external display, then sign in.
UpdateDisplay("300x200,300x200");
display::test::DisplayManagerTestApi(display_manager())
.SetFirstDisplayAsInternalDisplay();
Shell::RootWindowControllerList controllers =
Shell::GetAllRootWindowControllers();
ASSERT_EQ(2u, controllers.size());
SimulateUserLogin("[email protected]");
base::CommandLine::ForCurrentProcess()->RemoveSwitch(
switches::kAshEnablePaletteOnAllDisplays);
active_user_pref_service()->SetBoolean(prefs::kEnableStylusTools, true);
PaletteTray* main_tray =
controllers[0]->GetStatusAreaWidget()->palette_tray();
PaletteTray* external_tray =
controllers[1]->GetStatusAreaWidget()->palette_tray();
test_api_->SetDisplayHasStylus();
// The palette tray on the external monitor is not visible.
EXPECT_TRUE(main_tray->GetVisible());
EXPECT_FALSE(external_tray->GetVisible());
// Removing the stylus shows the bubble only on the main palette tray.
fake_stylus_event_on_all_trays(ui::StylusState::REMOVED);
EXPECT_TRUE(main_tray->GetBubbleView());
EXPECT_FALSE(external_tray->GetBubbleView());
// Inserting the stylus hides the bubble on both palette trays.
fake_stylus_event_on_all_trays(ui::StylusState::INSERTED);
EXPECT_FALSE(main_tray->GetBubbleView());
EXPECT_FALSE(external_tray->GetBubbleView());
}
class PaletteTrayTestWithOOBE : public PaletteTrayTest {
public:
PaletteTrayTestWithOOBE() = default;
~PaletteTrayTestWithOOBE() override = default;
// PalatteTrayTest:
void SetUp() override {
set_start_session(false);
PaletteTrayTest::SetUp();
}
};
// Verify there are no crashes if the stylus is used during OOBE.
TEST_F(PaletteTrayTestWithOOBE, StylusEventsSafeDuringOOBE) {
GetSessionControllerClient()->SetSessionState(
session_manager::SessionState::OOBE);
ui::test::EventGenerator* generator = GetEventGenerator();
generator->EnterPenPointerMode();
generator->PressTouch();
generator->ReleaseTouch();
}
// Base class for tests that need to simulate multiple pen
// capable displays.
class PaletteTrayTestMultiDisplay : public PaletteTrayTest {
public:
PaletteTrayTestMultiDisplay() = default;
~PaletteTrayTestMultiDisplay() override = default;
PaletteTrayTestMultiDisplay(const PaletteTrayTestMultiDisplay&) = delete;
PaletteTrayTestMultiDisplay& operator=(const PaletteTrayTestMultiDisplay&) =
delete;
// Fake a stylus ejection.
void EjectStylus() {
test_api_->OnStylusStateChanged(ui::StylusState::REMOVED);
test_api_external_->OnStylusStateChanged(ui::StylusState::REMOVED);
}
// Fake a stylus insertion.
void InsertStylus() {
test_api_->OnStylusStateChanged(ui::StylusState::INSERTED);
test_api_external_->OnStylusStateChanged(ui::StylusState::INSERTED);
}
// PaletteTrayTest:
void SetUp() override {
PaletteTrayTest::SetUp();
base::CommandLine::ForCurrentProcess()->RemoveSwitch(
switches::kAshEnablePaletteOnAllDisplays);
// Add a external display, then sign in.
UpdateDisplay("300x200,300x200");
display::test::DisplayManagerTestApi(display_manager())
.SetFirstDisplayAsInternalDisplay();
Shell::RootWindowControllerList controllers =
Shell::GetAllRootWindowControllers();
ASSERT_EQ(2u, controllers.size());
SimulateUserLogin("[email protected]");
palette_tray_ = controllers[0]->GetStatusAreaWidget()->palette_tray();
palette_tray_external_ =
controllers[1]->GetStatusAreaWidget()->palette_tray();
ASSERT_TRUE(palette_tray_);
ASSERT_TRUE(palette_tray_external_);
test_api_external_ =
std::make_unique<PaletteTrayTestApi>(palette_tray_external_);
}
protected:
raw_ptr<PaletteTray, DanglingUntriaged> palette_tray_external_ = nullptr;
std::unique_ptr<PaletteTrayTestApi> test_api_external_;
};
// Verify the palette welcome bubble is shown only on the internal display
// the first time the stylus is removed.
TEST_F(PaletteTrayTestMultiDisplay, WelcomeBubbleShownOnEject) {
test_api_->SetDisplayHasStylus();
test_api_external_->SetDisplayHasStylus();
active_user_pref_service()->SetBoolean(prefs::kEnableStylusTools, true);
active_user_pref_service()->SetBoolean(prefs::kLaunchPaletteOnEjectEvent,
false);
base::CommandLine::ForCurrentProcess()->AppendSwitch(
switches::kHasInternalStylus);
ASSERT_FALSE(active_user_pref_service()->GetBoolean(
prefs::kShownPaletteWelcomeBubble));
EXPECT_FALSE(test_api_->welcome_bubble()->GetBubbleViewForTesting());
EXPECT_FALSE(test_api_external_->welcome_bubble()->GetBubbleViewForTesting());
EjectStylus();
EXPECT_TRUE(test_api_->welcome_bubble()->GetBubbleViewForTesting());
EXPECT_FALSE(test_api_external_->welcome_bubble()->GetBubbleViewForTesting());
}
// Verify that palette bubble does not open on the external display
// on stylus eject/insert.
TEST_F(PaletteTrayTestMultiDisplay, PaletteBubbleShownOnEject) {
test_api_->SetDisplayHasStylus();
test_api_external_->SetDisplayHasStylus();
active_user_pref_service()->SetBoolean(prefs::kEnableStylusTools, true);
base::CommandLine::ForCurrentProcess()->AppendSwitch(
switches::kHasInternalStylus);
// kLaunchPaletteOnEjectEvent is true by default.
ASSERT_TRUE(active_user_pref_service()->GetBoolean(
prefs::kLaunchPaletteOnEjectEvent));
// Removing the stylus shows the bubble on the internal display.
EjectStylus();
EXPECT_TRUE(palette_tray_->GetBubbleView());
EXPECT_FALSE(palette_tray_external_->GetBubbleView());
// Inserting the stylus hides the bubble.
InsertStylus();
EXPECT_FALSE(palette_tray_->GetBubbleView());
EXPECT_FALSE(palette_tray_external_->GetBubbleView());
// Inserting the stylus should disable a currently selected tool
// even if it is on an external display.
test_api_external_->palette_tool_manager()->ActivateTool(
PaletteToolId::LASER_POINTER);
EXPECT_TRUE(test_api_external_->palette_tool_manager()->IsToolActive(
PaletteToolId::LASER_POINTER));
InsertStylus();
EXPECT_FALSE(test_api_external_->palette_tool_manager()->IsToolActive(
PaletteToolId::LASER_POINTER));
}
void addStylusToDisplay(int64_t display_id) {
ui::DeviceDataManager* device_data_manager =
ui::DeviceDataManager::GetInstance();
int stylus_device_id = 10;
base::RunLoop().RunUntilIdle();
if (device_data_manager->GetTouchscreenDevices().size() == 0) {
ui::TouchscreenDevice stylus_device = ui::TouchscreenDevice(
stylus_device_id, ui::InputDeviceType::INPUT_DEVICE_USB,
std::string("Stylus"), gfx::Size(1, 1), 1, true);
std::vector<ui::TouchscreenDevice> devices;
devices.push_back(stylus_device);
static_cast<ui::DeviceHotplugEventObserver*>(device_data_manager)
->OnTouchscreenDevicesUpdated(devices);
}
std::vector<ui::TouchDeviceTransform> device_transforms(1);
device_transforms[0].display_id = display_id;
device_transforms[0].device_id = stylus_device_id;
device_data_manager->ConfigureTouchDevices(device_transforms);
ASSERT_EQ(1U, device_data_manager->GetTouchscreenDevices().size());
ASSERT_EQ(display_id,
device_data_manager->GetTouchscreenDevices()[0].target_display_id);
ASSERT_TRUE(device_data_manager->AreTouchscreenTargetDisplaysValid());
}
// Verify that palette state is refreshed when the display
// layout changes.
TEST_F(PaletteTrayTestMultiDisplay, MirrorModeEnable) {
active_user_pref_service()->SetBoolean(prefs::kEnableStylusTools, true);
// We should already by in extended mode with two displays
ASSERT_EQ(2U, Shell::Get()->display_manager()->GetNumDisplays());
const int64_t external_display_id =
display::test::DisplayManagerTestApi(Shell::Get()->display_manager())
.GetSecondaryDisplay()
.id();
addStylusToDisplay(external_display_id);
// The palette tray on the internal monitor is not visible.
EXPECT_FALSE(palette_tray_->GetVisible());
EXPECT_TRUE(palette_tray_external_->GetVisible());
// Enable mirror mode
Shell::Get()->display_manager()->SetMultiDisplayMode(
display::DisplayManager::MIRRORING);
Shell::Get()->display_manager()->UpdateDisplays();
base::RunLoop().RunUntilIdle();
ASSERT_EQ(1U, Shell::Get()->display_manager()->GetNumDisplays());
addStylusToDisplay(external_display_id);
// The external tray will have been deleted, so only check if
// we're visible on the internal display now.
EXPECT_TRUE(palette_tray_->GetVisible());
}
class PaletteTrayTestWithProjector : public PaletteTrayTest {
public:
PaletteTrayTestWithProjector() = default;
PaletteTrayTestWithProjector(const PaletteTrayTestWithProjector&) = delete;
PaletteTrayTestWithProjector& operator=(const PaletteTrayTestWithProjector&) =
delete;
~PaletteTrayTestWithProjector() override = default;
// AshTestBase:
void SetUp() override {
PaletteTrayTest::SetUp();
projector_session_ = ProjectorControllerImpl::Get()->projector_session();
}
protected:
raw_ptr<ProjectorSessionImpl, DanglingUntriaged> projector_session_;
};
// Verify that the palette tray is hidden during a Projector session.
TEST_F(PaletteTrayTestWithProjector,
PaletteTrayNotVisibleDuringProjectorSession) {
active_user_pref_service()->SetBoolean(prefs::kEnableStylusTools, true);
local_state()->SetBoolean(prefs::kHasSeenStylus, true);
test_api_->palette_tool_manager()->ActivateTool(PaletteToolId::LASER_POINTER);
EXPECT_TRUE(palette_tray_->GetVisible());
EXPECT_EQ(
test_api_->palette_tool_manager()->GetActiveTool(PaletteGroup::MODE),
PaletteToolId::LASER_POINTER);
// Verify palette tray is hidden and the active tool is deactivated during
// Projector session.
projector_session_->Start(
base::SafeBaseName::Create("projector_data").value());
EXPECT_FALSE(palette_tray_->GetVisible());
EXPECT_EQ(
test_api_->palette_tool_manager()->GetActiveTool(PaletteGroup::MODE),
PaletteToolId::NONE);
// Verify palette tray is visible when Projector session ends.
projector_session_->Stop();
EXPECT_TRUE(palette_tray_->GetVisible());
}
} // namespace ash