#include "ash/system/overview/overview_button_tray.h"
#include "ash/accessibility/accessibility_controller.h"
#include "ash/constants/ash_features.h"
#include "ash/display/window_tree_host_manager.h"
#include "ash/login_status.h"
#include "ash/public/cpp/shelf_types.h"
#include "ash/public/cpp/tablet_mode.h"
#include "ash/root_window_controller.h"
#include "ash/rotator/screen_rotation_animator.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "ash/strings/grit/ash_strings.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/ash_test_helper.h"
#include "ash/wm/overview/overview_controller.h"
#include "ash/wm/splitview/split_view_controller.h"
#include "ash/wm/tablet_mode/tablet_mode_controller.h"
#include "ash/wm/tablet_mode/tablet_mode_controller_test_api.h"
#include "ash/wm/window_state.h"
#include "ash/wm/window_util.h"
#include "base/command_line.h"
#include "base/run_loop.h"
#include "base/test/metrics/user_action_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/time/time.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/client/window_types.h"
#include "ui/aura/window.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/mojom/ui_base_types.mojom-shared.h"
#include "ui/compositor/layer_tree_owner.h"
#include "ui/display/display_switches.h"
#include "ui/display/manager/display_manager.h"
#include "ui/display/screen.h"
#include "ui/events/devices/device_data_manager_test_api.h"
#include "ui/events/devices/input_device.h"
#include "ui/events/devices/keyboard_device.h"
#include "ui/events/event.h"
#include "ui/events/gestures/gesture_types.h"
#include "ui/events/types/event_type.h"
#include "ui/gfx/scoped_animation_duration_scale_mode.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/controls/image_view.h"
#include "ui/wm/core/window_util.h"
namespace ash {
namespace {
const char kTrayOverview[] = "Tray_Overview";
OverviewButtonTray* GetTray() {
return StatusAreaWidgetTestHelper::GetStatusAreaWidget()
->overview_button_tray();
}
OverviewButtonTray* GetSecondaryTray() {
return StatusAreaWidgetTestHelper::GetSecondaryStatusAreaWidget()
->overview_button_tray();
}
}
class OverviewButtonTrayTest : public AshTestBase {
public:
OverviewButtonTrayTest() {
scoped_features_.InitAndDisableFeature(
features::kHideShelfControlsInTabletMode);
}
OverviewButtonTrayTest(const OverviewButtonTrayTest& other) = delete;
OverviewButtonTrayTest& operator=(const OverviewButtonTrayTest& other) =
delete;
~OverviewButtonTrayTest() override = default;
void SetUp() override {
base::CommandLine::ForCurrentProcess()->AppendSwitch(
::switches::kUseFirstDisplayAsInternal);
AshTestBase::SetUp();
ui::DeviceDataManagerTestApi().SetKeyboardDevices({ui::KeyboardDevice(
3, ui::InputDeviceType::INPUT_DEVICE_INTERNAL, "keyboard")});
base::RunLoop().RunUntilIdle();
TabletModeController::SetUseScreenshotForTest(true);
}
void NotifySessionStateChanged() {
GetTray()->OnSessionStateChanged(
Shell::Get()->session_controller()->GetSessionState());
}
void PerformDoubleTap() {
GestureTapOn(GetTray());
GestureTapOn(GetTray());
}
SplitViewController* split_view_controller() {
return SplitViewController::Get(Shell::GetPrimaryRootWindow());
}
protected:
views::ImageView* GetImageView(OverviewButtonTray* tray) {
return tray->icon_;
}
private:
base::test::ScopedFeatureList scoped_features_;
};
TEST_F(OverviewButtonTrayTest, BasicConstruction) {
EXPECT_TRUE(GetImageView(GetTray()));
ui::AXNodeData node_data;
GetTray()->GetViewAccessibility().GetAccessibleNodeData(&node_data);
EXPECT_EQ(node_data.GetString16Attribute(ax::mojom::StringAttribute::kName),
l10n_util::GetStringUTF16(IDS_ASH_OVERVIEW_BUTTON_ACCESSIBLE_NAME));
}
TEST_F(OverviewButtonTrayTest, VisibilityTest) {
ASSERT_FALSE(GetTray()->GetVisible());
TabletModeControllerTestApi().EnterTabletMode();
EXPECT_TRUE(display::Screen::Get()->InTabletMode());
EXPECT_TRUE(GetTray()->GetVisible());
TabletModeControllerTestApi().LeaveTabletMode();
EXPECT_FALSE(GetTray()->GetVisible());
EXPECT_FALSE(display::Screen::Get()->InTabletMode());
std::unique_ptr<aura::Window> window(
CreateTestWindowInShell({.bounds = {5, 5, 20, 20}, .window_id = 0}));
ASSERT_FALSE(GetTray()->GetVisible());
TabletMode::Waiter waiter(true);
TabletModeControllerTestApi().EnterTabletMode();
EXPECT_TRUE(display::Screen::Get()->InTabletMode());
EXPECT_FALSE(GetTray()->GetVisible());
waiter.Wait();
EXPECT_TRUE(GetTray()->GetVisible());
TabletModeControllerTestApi().LeaveTabletMode();
EXPECT_FALSE(display::Screen::Get()->InTabletMode());
EXPECT_FALSE(GetTray()->GetVisible());
}
TEST_F(OverviewButtonTrayTest, PerformAction) {
ASSERT_FALSE(Shell::Get()->overview_controller()->InOverviewSession());
std::unique_ptr<aura::Window> window(
CreateTestWindowInShell({.bounds = {5, 5, 20, 20}, .window_id = 0}));
GetTray()->SetVisiblePreferred(true);
GestureTapOn(GetTray());
EXPECT_TRUE(Shell::Get()->overview_controller()->InOverviewSession());
GestureTapOn(GetTray());
EXPECT_FALSE(Shell::Get()->overview_controller()->InOverviewSession());
TabletMode::Waiter waiter(true);
TabletModeControllerTestApi().EnterTabletMode();
waiter.Wait();
GestureTapOn(GetTray());
EXPECT_TRUE(Shell::Get()->overview_controller()->InOverviewSession());
GestureTapOn(GetTray());
EXPECT_FALSE(Shell::Get()->overview_controller()->InOverviewSession());
}
TEST_F(OverviewButtonTrayTest, PerformDoubleTapAction) {
TabletModeControllerTestApi().EnterTabletMode();
ASSERT_FALSE(Shell::Get()->overview_controller()->InOverviewSession());
std::unique_ptr<aura::Window> window1(
CreateTestWindowInShell({.bounds = {5, 5, 20, 20}, .window_id = 0}));
std::unique_ptr<aura::Window> window2(
CreateTestWindowInShell({.bounds = {5, 5, 20, 20}, .window_id = 0}));
wm::ActivateWindow(window2.get());
EXPECT_TRUE(wm::IsActiveWindow(window2.get()));
PerformDoubleTap();
EXPECT_TRUE(wm::IsActiveWindow(window1.get()));
EXPECT_FALSE(Shell::Get()->overview_controller()->InOverviewSession());
ASSERT_TRUE(wm::IsActiveWindow(window1.get()));
GestureTapOn(GetTray());
ASSERT_TRUE(Shell::Get()->overview_controller()->InOverviewSession());
PerformDoubleTap();
EXPECT_TRUE(Shell::Get()->overview_controller()->InOverviewSession());
GestureTapOn(GetTray());
ASSERT_TRUE(!Shell::Get()->overview_controller()->InOverviewSession());
ASSERT_TRUE(wm::IsActiveWindow(window1.get()));
WindowState::Get(window2.get())->Minimize();
ASSERT_EQ(window2->layer()->GetTargetOpacity(), 0.0);
PerformDoubleTap();
EXPECT_EQ(window2->layer()->GetTargetOpacity(), 1.0);
EXPECT_TRUE(wm::IsActiveWindow(window2.get()));
ASSERT_TRUE(!Shell::Get()->overview_controller()->InOverviewSession());
WindowState::Get(window1.get())->Minimize();
WindowState::Get(window2.get())->Minimize();
PerformDoubleTap();
EXPECT_FALSE(wm::IsActiveWindow(window1.get()));
EXPECT_FALSE(wm::IsActiveWindow(window2.get()));
}
TEST_F(OverviewButtonTrayTest, TrayOverviewUserAction) {
TabletModeControllerTestApi().EnterTabletMode();
ASSERT_FALSE(Shell::Get()->overview_controller()->InOverviewSession());
base::UserActionTester user_action_tester;
std::unique_ptr<aura::Window> window(
CreateTestWindowInShell({.bounds = {5, 5, 20, 20}, .window_id = 0}));
GestureTapOn(GetTray());
ASSERT_TRUE(Shell::Get()->overview_controller()->InOverviewSession());
EXPECT_EQ(1, user_action_tester.GetActionCount(kTrayOverview));
GestureTapOn(GetTray());
ASSERT_FALSE(Shell::Get()->overview_controller()->InOverviewSession());
EXPECT_EQ(2, user_action_tester.GetActionCount(kTrayOverview));
}
TEST_F(OverviewButtonTrayTest, DisplaysOnBothDisplays) {
UpdateDisplay("500x400,300x200");
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(GetTray()->GetVisible());
EXPECT_FALSE(GetSecondaryTray()->GetVisible());
TabletModeControllerTestApi().EnterTabletMode();
base::RunLoop().RunUntilIdle();
display_manager()->SetMirrorMode(display::MirrorMode::kOff, std::nullopt);
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(GetTray()->GetVisible());
EXPECT_TRUE(GetSecondaryTray()->GetVisible());
}
TEST_F(OverviewButtonTrayTest, DISABLED_SecondaryTrayCreatedVisible) {
TabletModeControllerTestApi().EnterTabletMode();
UpdateDisplay("500x400,300x200");
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(GetSecondaryTray()->GetVisible());
}
TEST_F(OverviewButtonTrayTest, VisibilityChangesForLoginStatus) {
TabletModeControllerTestApi().EnterTabletMode();
ClearLogin();
Shell::Get()->UpdateAfterLoginStatusChange(LoginStatus::NOT_LOGGED_IN);
EXPECT_FALSE(GetTray()->GetVisible());
SimulateUserLogin(kRegularUserLoginInfo);
Shell::Get()->UpdateAfterLoginStatusChange(LoginStatus::USER);
EXPECT_TRUE(GetTray()->GetVisible());
SetUserAddingScreenRunning(true);
NotifySessionStateChanged();
EXPECT_FALSE(GetTray()->GetVisible());
SetUserAddingScreenRunning(false);
NotifySessionStateChanged();
EXPECT_TRUE(GetTray()->GetVisible());
}
TEST_F(OverviewButtonTrayTest, ActiveStateOnlyDuringOverviewMode) {
ASSERT_FALSE(Shell::Get()->overview_controller()->InOverviewSession());
ASSERT_FALSE(GetTray()->is_active());
std::unique_ptr<aura::Window> window(
CreateTestWindowInShell({.bounds = {5, 5, 20, 20}, .window_id = 0}));
EXPECT_TRUE(EnterOverview());
EXPECT_TRUE(Shell::Get()->overview_controller()->InOverviewSession());
EXPECT_TRUE(GetTray()->is_active());
EXPECT_TRUE(ExitOverview());
EXPECT_FALSE(Shell::Get()->overview_controller()->InOverviewSession());
EXPECT_FALSE(GetTray()->is_active());
}
TEST_F(OverviewButtonTrayTest, HideAnimationAlwaysCompletes) {
TabletModeControllerTestApi().EnterTabletMode();
EXPECT_TRUE(GetTray()->GetVisible());
GetTray()->SetVisiblePreferred(false);
EXPECT_FALSE(GetTray()->GetVisible());
}
TEST_F(OverviewButtonTrayTest, HideAnimationAlwaysCompletesOnDelete) {
TabletModeControllerTestApi().EnterTabletMode();
std::unique_ptr<gfx::ScopedAnimationDurationScaleMode> hide_duration(
new gfx::ScopedAnimationDurationScaleMode(
gfx::ScopedAnimationDurationScaleMode::SLOW_DURATION));
GetTray()->SetVisiblePreferred(false);
aura::Window* root_window = Shell::GetRootWindowForDisplayId(
display::Screen::Get()->GetPrimaryDisplay().id());
std::unique_ptr<ui::LayerTreeOwner> old_layer_tree_owner =
::wm::RecreateLayers(root_window);
old_layer_tree_owner.reset();
EXPECT_FALSE(GetTray()->GetVisible());
}
TEST_F(OverviewButtonTrayTest, VisibilityChangesForSystemModalWindow) {
std::unique_ptr<aura::Window> window =
CreateTestWindow(gfx::Rect(), aura::client::WINDOW_TYPE_NORMAL);
window->SetProperty(aura::client::kModalKey, ui::mojom::ModalType::kSystem);
window->Show();
ParentWindowInPrimaryRootWindow(window.get());
ASSERT_TRUE(Shell::IsSystemModalWindowOpen());
TabletModeControllerTestApi().EnterTabletMode();
EXPECT_TRUE(GetTray()->GetVisible());
TabletModeControllerTestApi().LeaveTabletMode();
EXPECT_FALSE(GetTray()->GetVisible());
}
TEST_F(OverviewButtonTrayTest, TransientChildQuickSwitch) {
TabletModeControllerTestApi().EnterTabletMode();
std::unique_ptr<aura::Window> window1 = CreateTestWindow();
std::unique_ptr<aura::Window> window2 =
CreateTestWindow(gfx::Rect(), aura::client::WINDOW_TYPE_POPUP);
std::unique_ptr<aura::Window> window3 = CreateTestWindow();
wm::AddTransientChild(window1.get(), window2.get());
wm::ActivateWindow(window3.get());
wm::ActivateWindow(window2.get());
wm::ActivateWindow(window1.get());
PerformDoubleTap();
EXPECT_EQ(window3.get(), window_util::GetActiveWindow());
}
TEST_F(OverviewButtonTrayTest, SplitviewModeQuickSwitch) {
TabletModeControllerTestApi().EnterTabletMode();
std::unique_ptr<aura::Window> window3 = CreateTestWindow();
std::unique_ptr<aura::Window> window1 = CreateTestWindow();
std::unique_ptr<aura::Window> window2 = CreateTestWindow();
EnterOverview();
split_view_controller()->SnapWindow(window1.get(), SnapPosition::kPrimary);
split_view_controller()->SnapWindow(window2.get(), SnapPosition::kSecondary);
ASSERT_EQ(window1.get(), split_view_controller()->GetDefaultSnappedWindow());
PerformDoubleTap();
EXPECT_EQ(window3.get(), split_view_controller()->secondary_window());
EXPECT_EQ(window3.get(), window_util::GetActiveWindow());
wm::ActivateWindow(window1.get());
PerformDoubleTap();
EXPECT_EQ(window2.get(), split_view_controller()->secondary_window());
EXPECT_EQ(window2.get(), window_util::GetActiveWindow());
split_view_controller()->EndSplitView();
}
TEST_F(OverviewButtonTrayTest, LeaveTabletModeBecauseExternalMouse) {
TabletModeControllerTestApi().DetachAllMice();
TabletModeControllerTestApi().OpenLidToAngle(315.0f);
EXPECT_TRUE(display::Screen::Get()->InTabletMode());
ASSERT_TRUE(GetTray()->GetVisible());
TabletModeControllerTestApi().AttachExternalMouse();
EXPECT_FALSE(display::Screen::Get()->InTabletMode());
EXPECT_TRUE(GetTray()->GetVisible());
}
TEST_F(OverviewButtonTrayTest, ForDevTabletModeForcesTheButtonShown) {
TabletModeControllerTestApi().DetachAllMice();
Shell::Get()->tablet_mode_controller()->SetEnabledForDev(true);
EXPECT_TRUE(display::Screen::Get()->InTabletMode());
EXPECT_FALSE(TabletModeControllerTestApi().AreEventsBlocked());
EXPECT_TRUE(GetTray()->GetVisible());
Shell::Get()->tablet_mode_controller()->SetEnabledForDev(false);
EXPECT_FALSE(display::Screen::Get()->InTabletMode());
EXPECT_FALSE(GetTray()->GetVisible());
std::unique_ptr<aura::Window> window(
CreateTestWindowInShell({.bounds = {5, 5, 20, 20}, .window_id = 0}));
EXPECT_FALSE(GetTray()->GetVisible());
TabletMode::Waiter waiter(true);
Shell::Get()->tablet_mode_controller()->SetEnabledForDev(true);
EXPECT_FALSE(GetTray()->GetVisible());
waiter.Wait();
EXPECT_TRUE(display::Screen::Get()->InTabletMode());
EXPECT_TRUE(GetTray()->GetVisible());
Shell::Get()->tablet_mode_controller()->SetEnabledForDev(false);
EXPECT_FALSE(display::Screen::Get()->InTabletMode());
EXPECT_FALSE(GetTray()->GetVisible());
}
enum class TestAccessibilityFeature {
kNone,
kSpokenFeedback,
kAutoclick,
kSwitchAccess
};
class OverviewButtonTrayWithShelfControlsHiddenTest
: public AshTestBase,
public testing::WithParamInterface<TestAccessibilityFeature> {
public:
OverviewButtonTrayWithShelfControlsHiddenTest() {
scoped_features_.InitAndEnableFeature(
features::kHideShelfControlsInTabletMode);
}
OverviewButtonTrayWithShelfControlsHiddenTest(
const OverviewButtonTrayWithShelfControlsHiddenTest& other) = delete;
OverviewButtonTrayWithShelfControlsHiddenTest& operator=(
const OverviewButtonTrayWithShelfControlsHiddenTest& other) = delete;
~OverviewButtonTrayWithShelfControlsHiddenTest() override = default;
void SetUp() override {
base::CommandLine::ForCurrentProcess()->AppendSwitch(
::switches::kUseFirstDisplayAsInternal);
AshTestBase::SetUp();
TabletModeController::SetUseScreenshotForTest(true);
}
void SetTestA11yFeatureEnabled(bool enabled) {
switch (GetParam()) {
case TestAccessibilityFeature::kNone:
break;
case TestAccessibilityFeature::kSpokenFeedback:
Shell::Get()->accessibility_controller()->SetSpokenFeedbackEnabled(
enabled, A11Y_NOTIFICATION_NONE);
break;
case TestAccessibilityFeature::kAutoclick:
Shell::Get()->accessibility_controller()->autoclick().SetEnabled(
enabled);
break;
case TestAccessibilityFeature::kSwitchAccess:
Shell::Get()->accessibility_controller()->switch_access().SetEnabled(
enabled);
Shell::Get()
->accessibility_controller()
->DisableSwitchAccessDisableConfirmationDialogTesting();
break;
}
}
bool HasTestingAccessibilityFeature() const {
return GetParam() != TestAccessibilityFeature::kNone;
}
private:
base::test::ScopedFeatureList scoped_features_;
};
INSTANTIATE_TEST_SUITE_P(
All,
OverviewButtonTrayWithShelfControlsHiddenTest,
::testing::Values(TestAccessibilityFeature::kNone,
TestAccessibilityFeature::kSpokenFeedback,
TestAccessibilityFeature::kAutoclick,
TestAccessibilityFeature::kSwitchAccess));
TEST_P(OverviewButtonTrayWithShelfControlsHiddenTest, VisibilityTest) {
ASSERT_FALSE(GetTray()->GetVisible());
SetTestA11yFeatureEnabled(true);
TabletModeControllerTestApi().EnterTabletMode();
EXPECT_TRUE(display::Screen::Get()->InTabletMode());
EXPECT_EQ(HasTestingAccessibilityFeature(), GetTray()->GetVisible());
TabletModeControllerTestApi().LeaveTabletMode();
EXPECT_FALSE(GetTray()->GetVisible());
EXPECT_FALSE(display::Screen::Get()->InTabletMode());
std::unique_ptr<aura::Window> window(
CreateTestWindowInShell({.bounds = {5, 5, 20, 20}, .window_id = 0}));
ASSERT_FALSE(GetTray()->GetVisible());
TabletMode::Waiter waiter(true);
TabletModeControllerTestApi().EnterTabletMode();
EXPECT_TRUE(display::Screen::Get()->InTabletMode());
EXPECT_FALSE(GetTray()->GetVisible());
waiter.Wait();
EXPECT_TRUE(display::Screen::Get()->InTabletMode());
EXPECT_EQ(HasTestingAccessibilityFeature(), GetTray()->GetVisible());
SetTestA11yFeatureEnabled(false);
EXPECT_TRUE(display::Screen::Get()->InTabletMode());
EXPECT_FALSE(GetTray()->GetVisible());
TabletModeControllerTestApi().LeaveTabletMode();
EXPECT_FALSE(display::Screen::Get()->InTabletMode());
EXPECT_FALSE(GetTray()->GetVisible());
}
TEST_P(OverviewButtonTrayWithShelfControlsHiddenTest,
AccessibilityFeatureEnabledWhileInTabletMode) {
ash::TabletModeControllerTestApi().EnterTabletMode();
EXPECT_TRUE(display::Screen::Get()->InTabletMode());
EXPECT_FALSE(GetTray()->GetVisible());
SetTestA11yFeatureEnabled(true);
EXPECT_TRUE(display::Screen::Get()->InTabletMode());
EXPECT_EQ(HasTestingAccessibilityFeature(), GetTray()->GetVisible());
TabletModeControllerTestApi().LeaveTabletMode();
EXPECT_FALSE(display::Screen::Get()->InTabletMode());
EXPECT_FALSE(GetTray()->GetVisible());
}
TEST_P(OverviewButtonTrayWithShelfControlsHiddenTest,
AccessibilityFeaturesChangeWhileInOverview) {
ash::TabletModeControllerTestApi().EnterTabletMode();
EXPECT_TRUE(display::Screen::Get()->InTabletMode());
EXPECT_FALSE(GetTray()->GetVisible());
std::unique_ptr<aura::Window> window(
CreateTestWindowInShell({.bounds = {5, 5, 20, 20}, .window_id = 0}));
EnterOverview();
ASSERT_TRUE(Shell::Get()->overview_controller()->InOverviewSession());
EXPECT_FALSE(GetTray()->GetVisible());
SetTestA11yFeatureEnabled(true);
EXPECT_TRUE(display::Screen::Get()->InTabletMode());
EXPECT_EQ(HasTestingAccessibilityFeature(), GetTray()->GetVisible());
SetTestA11yFeatureEnabled(false);
EXPECT_TRUE(display::Screen::Get()->InTabletMode());
EXPECT_FALSE(GetTray()->GetVisible());
}
}