blob: 51e37f7ad7923902661756b29239a445f8d2a140 [file] [log] [blame]
// 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/unified/power_button.h"
#include "ash/constants/quick_settings_catalogs.h"
#include "ash/public/cpp/ash_view_ids.h"
#include "ash/shell.h"
#include "ash/shutdown_controller_impl.h"
#include "ash/style/icon_button.h"
#include "ash/system/tray/tray_detailed_view.h"
#include "ash/system/unified/quick_settings_footer.h"
#include "ash/system/unified/quick_settings_view.h"
#include "ash/system/unified/unified_system_tray.h"
#include "ash/system/unified/unified_system_tray_bubble.h"
#include "ash/system/unified/user_chooser_view.h"
#include "ash/test/ash_test_base.h"
#include "base/i18n/rtl.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/test/metrics/histogram_tester.h"
#include "components/user_manager/user_type.h"
#include "ui/chromeos/styles/cros_tokens_color_mappings.h"
#include "ui/compositor/layer.h"
#include "ui/events/base_event_utils.h"
#include "ui/gfx/skia_util.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/menu/menu_item_view.h"
#include "ui/views/test/views_test_utils.h"
#include "ui/views/view.h"
#include "ui/views/view_utils.h"
namespace ash {
// Tests for `PowerButton`, which is inited with no user session to test the
// non-login state.
class PowerButtonTest : public NoSessionAshTestBase {
public:
PowerButtonTest() = default;
PowerButtonTest(const PowerButtonTest&) = delete;
PowerButtonTest& operator=(const PowerButtonTest&) = delete;
~PowerButtonTest() override = default;
void SetUp() override {
NoSessionAshTestBase::SetUp();
// Test with the real system tray bubble so that the power button has a real
// UnifiedSystemTrayController to test clicking on the email item.
ShowSystemTrayBubble();
}
protected:
void ShowSystemTrayBubble() {
auto* system_tray = GetPrimaryUnifiedSystemTray();
system_tray->ShowBubble();
button_ = system_tray->bubble()
->quick_settings_view()
->footer_for_testing()
->power_button_for_testing();
}
views::MenuItemView* GetMenuView() {
return button_->GetMenuViewForTesting();
}
bool IsMenuShowing() { return button_->IsMenuShowing(); }
views::MenuItemView* GetEmailButton() {
if (!IsMenuShowing()) {
return nullptr;
}
return GetMenuView()->GetMenuItemByID(VIEW_ID_QS_POWER_EMAIL_MENU_BUTTON);
}
views::View* GetRestartButton() {
if (!IsMenuShowing()) {
return nullptr;
}
return GetMenuView()->GetMenuItemByID(VIEW_ID_QS_POWER_RESTART_MENU_BUTTON);
}
views::View* GetPowerOffButton() {
if (!IsMenuShowing()) {
return nullptr;
}
return GetMenuView()->GetMenuItemByID(VIEW_ID_QS_POWER_OFF_MENU_BUTTON);
}
views::View* GetSignOutButton() {
if (!IsMenuShowing()) {
return nullptr;
}
return GetMenuView()->GetMenuItemByID(VIEW_ID_QS_POWER_SIGNOUT_MENU_BUTTON);
}
views::View* GetLockButton() {
if (!IsMenuShowing()) {
return nullptr;
}
return GetMenuView()->GetMenuItemByID(VIEW_ID_QS_POWER_LOCK_MENU_BUTTON);
}
PowerButton* GetPowerButton() { return button_; }
views::ImageView* GetPowerChevronIcon() {
auto* icon = button_->button_content_->GetViewByID(
VIEW_ID_QS_POWER_BUTTON_CHEVRON_ICON);
CHECK(icon);
return static_cast<views::ImageView*>(icon);
}
ui::Layer* GetBackgroundLayer() { return button_->background_view_->layer(); }
// Simulates mouse press event on the power button.
void SimulatePowerButtonPress() { LeftClickOn(button_->button_content_); }
ui::ImageModel GetExpectedChevronImageModel(bool use_up_chevron) {
auto icon_color_id = use_up_chevron
? cros_tokens::kCrosSysSystemOnPrimaryContainer
: cros_tokens::kCrosSysOnSurface;
return ui::ImageModel::FromVectorIcon(
use_up_chevron ? kChevronUpSmallIcon : kChevronDownSmallIcon,
icon_color_id);
}
bool ChevronIconsMatch(bool use_up_chevron) {
const auto* power_chevron_icon = GetPowerChevronIcon();
return gfx::BitmapsAreEqual(
*power_chevron_icon->GetImage().bitmap(),
*GetExpectedChevronImageModel(use_up_chevron)
.Rasterize(power_chevron_icon->GetColorProvider())
.bitmap());
}
bool IsUpChevron() { return ChevronIconsMatch(/*use_up_chevron=*/true); }
bool IsDownChevron() { return ChevronIconsMatch(/*use_up_chevron=*/false); }
// Owned by view hierarchy.
raw_ptr<PowerButton, DanglingUntriaged> button_ = nullptr;
base::HistogramTester histogram_tester_;
};
// `PowerButton` should be with the correct view id and have the UMA tracking
// with the correct catalog name.
TEST_F(PowerButtonTest, PowerButtonHasCorrectViewIdAndUma) {
SimulateUserLogin(kRegularUserLoginInfo);
// No metrics logged before clicking on any buttons.
histogram_tester_.ExpectTotalCount("Ash.QuickSettings.Button.Activated",
/*count=*/0);
// The power button is visible and with the corresponding id.
EXPECT_TRUE(GetPowerButton()->GetVisible());
EXPECT_EQ(VIEW_ID_QS_POWER_BUTTON, GetPowerButton()->GetID());
// Clicks on the power button.
SimulatePowerButtonPress();
EXPECT_TRUE(IsMenuShowing());
histogram_tester_.ExpectTotalCount("Ash.QuickSettings.Button.Activated",
/*count=*/1);
histogram_tester_.ExpectBucketCount("Ash.QuickSettings.Button.Activated",
QsButtonCatalogName::kPowerButton,
/*expected_count=*/1);
}
TEST_F(PowerButtonTest, LockMenuButtonRecordsUma) {
SimulateUserLogin(kRegularUserLoginInfo);
// TOOD(crbug.com/383442863): Move this to SimulateUserLogin.
GetSessionControllerClient()->SetCanLockScreen(true);
SimulatePowerButtonPress();
LeftClickOn(GetLockButton());
// Expect a count of 2 because the power button was activated above.
histogram_tester_.ExpectTotalCount("Ash.QuickSettings.Button.Activated",
/*count=*/2);
histogram_tester_.ExpectBucketCount("Ash.QuickSettings.Button.Activated",
QsButtonCatalogName::kPowerLockMenuButton,
/*expected_count=*/1);
}
TEST_F(PowerButtonTest, SignOutMenuButtonRecordsUma) {
SimulateUserLogin(kRegularUserLoginInfo);
SimulatePowerButtonPress();
LeftClickOn(GetSignOutButton());
// Expect a count of 2 because the power button was activated above.
histogram_tester_.ExpectTotalCount("Ash.QuickSettings.Button.Activated",
/*count=*/2);
histogram_tester_.ExpectBucketCount(
"Ash.QuickSettings.Button.Activated",
QsButtonCatalogName::kPowerSignoutMenuButton,
/*expected_count=*/1);
}
TEST_F(PowerButtonTest, RestartMenuButtonRecordsUma) {
SimulateUserLogin(kRegularUserLoginInfo);
SimulatePowerButtonPress();
LeftClickOn(GetRestartButton());
// Expect a count of 2 because the power button was activated above.
histogram_tester_.ExpectTotalCount("Ash.QuickSettings.Button.Activated",
/*count=*/2);
histogram_tester_.ExpectBucketCount(
"Ash.QuickSettings.Button.Activated",
QsButtonCatalogName::kPowerRestartMenuButton,
/*expected_count=*/1);
}
TEST_F(PowerButtonTest, PowerOffMenuButtonRecordsUma) {
SimulateUserLogin(kRegularUserLoginInfo);
SimulatePowerButtonPress();
LeftClickOn(GetPowerOffButton());
// Expect a count of 2 because the power button was activated above.
histogram_tester_.ExpectTotalCount("Ash.QuickSettings.Button.Activated",
/*count=*/2);
histogram_tester_.ExpectBucketCount("Ash.QuickSettings.Button.Activated",
QsButtonCatalogName::kPowerOffMenuButton,
/*expected_count=*/1);
}
TEST_F(PowerButtonTest, EmailMenuButtonRecordsUma) {
SimulateUserLogin(kRegularUserLoginInfo);
SimulatePowerButtonPress();
LeftClickOn(GetEmailButton());
// Expect a count of 2 because the power button was activated above.
histogram_tester_.ExpectTotalCount("Ash.QuickSettings.Button.Activated",
/*count=*/2);
histogram_tester_.ExpectBucketCount(
"Ash.QuickSettings.Button.Activated",
QsButtonCatalogName::kPowerEmailMenuButton,
/*expected_count=*/1);
}
// No lock and sign out buttons in the menu before login.
TEST_F(PowerButtonTest, ButtonStatesNotLoggedIn) {
EXPECT_TRUE(GetPowerButton()->GetVisible());
// No menu buttons are visible before showing the menu.
EXPECT_FALSE(IsMenuShowing());
EXPECT_EQ(nullptr, GetEmailButton());
EXPECT_EQ(nullptr, GetRestartButton());
EXPECT_EQ(nullptr, GetSignOutButton());
EXPECT_EQ(nullptr, GetLockButton());
EXPECT_EQ(nullptr, GetPowerOffButton());
// Clicks on the power button.
SimulatePowerButtonPress();
EXPECT_TRUE(IsMenuShowing());
// Only show power off and restart buttons.
EXPECT_EQ(nullptr, GetEmailButton());
EXPECT_EQ(nullptr, GetSignOutButton());
EXPECT_EQ(nullptr, GetLockButton());
EXPECT_TRUE(GetPowerOffButton()->GetVisible());
EXPECT_TRUE(GetRestartButton()->GetVisible());
}
// All buttons are shown after login.
TEST_F(PowerButtonTest, ButtonStatesLoggedIn) {
SimulateUserLogin(kRegularUserLoginInfo);
// TOOD(crbug.com/383442863): Move this to SimulateUserLogin.
GetSessionControllerClient()->SetCanLockScreen(true);
EXPECT_TRUE(GetPowerButton()->GetVisible());
// No menu buttons are visible before showing the menu.
EXPECT_FALSE(IsMenuShowing());
EXPECT_EQ(nullptr, GetEmailButton());
EXPECT_EQ(nullptr, GetRestartButton());
EXPECT_EQ(nullptr, GetSignOutButton());
EXPECT_EQ(nullptr, GetLockButton());
EXPECT_EQ(nullptr, GetPowerOffButton());
// Clicks on the power button.
SimulatePowerButtonPress();
EXPECT_TRUE(IsMenuShowing());
// Show all buttons in the menu.
EXPECT_TRUE(GetEmailButton()->GetVisible());
EXPECT_TRUE(GetLockButton()->GetVisible());
EXPECT_TRUE(GetSignOutButton()->GetVisible());
EXPECT_TRUE(GetPowerOffButton()->GetVisible());
EXPECT_TRUE(GetRestartButton()->GetVisible());
}
// The lock button are hidden at the lock screen.
TEST_F(PowerButtonTest, ButtonStatesLockScreen) {
SimulateUserLogin(kRegularUserLoginInfo);
BlockUserSession(BLOCKED_BY_LOCK_SCREEN);
// Changes in lock state close the system tray bubble, so re-show it.
ShowSystemTrayBubble();
EXPECT_TRUE(GetPowerButton()->GetVisible());
// No menu buttons are visible before showing the menu.
EXPECT_FALSE(IsMenuShowing());
EXPECT_EQ(nullptr, GetEmailButton());
EXPECT_EQ(nullptr, GetRestartButton());
EXPECT_EQ(nullptr, GetSignOutButton());
EXPECT_EQ(nullptr, GetLockButton());
EXPECT_EQ(nullptr, GetPowerOffButton());
// Clicks on the power button.
SimulatePowerButtonPress();
EXPECT_TRUE(IsMenuShowing());
EXPECT_EQ(nullptr, GetLockButton());
EXPECT_TRUE(GetEmailButton()->GetVisible());
EXPECT_TRUE(GetSignOutButton()->GetVisible());
EXPECT_TRUE(GetPowerOffButton()->GetVisible());
EXPECT_TRUE(GetRestartButton()->GetVisible());
// The multi-profile user chooser is disabled at the lock screen.
EXPECT_FALSE(GetEmailButton()->GetEnabled());
}
// The lock button is hidden when adding a second multiprofile user.
TEST_F(PowerButtonTest, ButtonStatesAddingUser) {
SimulateUserLogin(kRegularUserLoginInfo);
SetUserAddingScreenRunning(true);
EXPECT_TRUE(GetPowerButton()->GetVisible());
// No menu buttons are visible before showing the menu.
EXPECT_FALSE(IsMenuShowing());
EXPECT_EQ(nullptr, GetEmailButton());
EXPECT_EQ(nullptr, GetRestartButton());
EXPECT_EQ(nullptr, GetSignOutButton());
EXPECT_EQ(nullptr, GetLockButton());
EXPECT_EQ(nullptr, GetPowerOffButton());
// Clicks on the power button.
SimulatePowerButtonPress();
EXPECT_TRUE(IsMenuShowing());
EXPECT_EQ(nullptr, GetLockButton());
EXPECT_TRUE(GetEmailButton()->GetVisible());
EXPECT_TRUE(GetSignOutButton()->GetVisible());
EXPECT_TRUE(GetPowerOffButton()->GetVisible());
EXPECT_TRUE(GetRestartButton()->GetVisible());
// The multi-profile user chooser is disabled at the lock screen.
EXPECT_FALSE(GetEmailButton()->GetEnabled());
}
TEST_F(PowerButtonTest, ButtonStatesGuestMode) {
SimulateGuestLogin();
SimulatePowerButtonPress();
EXPECT_TRUE(IsMenuShowing());
EXPECT_EQ(nullptr, GetEmailButton());
EXPECT_EQ(nullptr, GetLockButton());
EXPECT_TRUE(GetSignOutButton()->GetVisible());
EXPECT_TRUE(GetPowerOffButton()->GetVisible());
EXPECT_TRUE(GetRestartButton()->GetVisible());
}
TEST_F(PowerButtonTest, EmailIsShownForRegularAccount) {
SimulateUserLogin({"user@gmail.com", user_manager::UserType::kRegular});
SimulatePowerButtonPress();
EXPECT_TRUE(GetEmailButton()->GetVisible());
EXPECT_TRUE(GetEmailButton()->GetEnabled());
EXPECT_EQ(u"user@gmail.com", GetEmailButton()->title());
}
TEST_F(PowerButtonTest, EmailIsShownForChildAccount) {
SimulateUserLogin({"child@gmail.com", user_manager::UserType::kChild});
SimulatePowerButtonPress();
EXPECT_TRUE(GetEmailButton()->GetVisible());
// The multi-profile user chooser is disabled for child accounts.
EXPECT_FALSE(GetEmailButton()->GetEnabled());
EXPECT_EQ(u"child@gmail.com", GetEmailButton()->title());
}
TEST_F(PowerButtonTest, EmailIsNotShownForPublicAccount) {
SimulateUserLogin({"test@test.com", user_manager::UserType::kPublicAccount});
SimulatePowerButtonPress();
EXPECT_EQ(nullptr, GetEmailButton());
}
// NOTE: Kiosk user types are not tested because quick settings cannot be
// accessed in kiosk mode.
TEST_F(PowerButtonTest, ClickingEmailShowsUserChooserView) {
SimulateUserLogin({"user@gmail.com", user_manager::UserType::kRegular});
SimulatePowerButtonPress();
LeftClickOn(GetEmailButton());
QuickSettingsView* quick_settings_view =
GetPrimaryUnifiedSystemTray()->bubble()->quick_settings_view();
EXPECT_TRUE(quick_settings_view->IsDetailedViewShown());
EXPECT_TRUE(quick_settings_view->GetDetailedViewForTest<UserChooserView>());
}
TEST_F(PowerButtonTest, UserItemButtonTooltipText) {
SimulateUserLogin({"user@gmail.com", user_manager::UserType::kRegular});
SimulatePowerButtonPress();
LeftClickOn(GetEmailButton());
QuickSettingsView* quick_settings_view =
GetPrimaryUnifiedSystemTray()->bubble()->quick_settings_view();
EXPECT_TRUE(quick_settings_view->IsDetailedViewShown());
auto* user_chooser_view =
quick_settings_view->GetDetailedViewForTest<UserChooserView>();
ui::AXNodeData data;
gfx::Size zero_size;
for (const auto& button : user_chooser_view->user_item_buttons_) {
EXPECT_EQ(button->GetRenderedTooltipText(gfx::Point()),
user_chooser_view->GetUserItemAccessibleStringForTesting(
button->user_index_for_testing()));
data = ui::AXNodeData();
button->GetViewAccessibility().GetAccessibleNodeData(&data);
EXPECT_EQ(data.GetString16Attribute(ax::mojom::StringAttribute::kName),
button->GetRenderedTooltipText(gfx::Point()));
EXPECT_NE(
data.GetString16Attribute(ax::mojom::StringAttribute::kDescription),
button->GetRenderedTooltipText(gfx::Point()));
button->SetBoundsRect(gfx::Rect(zero_size));
EXPECT_EQ(button->GetRenderedTooltipText(gfx::Point()), u"");
data = ui::AXNodeData();
button->GetViewAccessibility().GetAccessibleNodeData(&data);
EXPECT_NE(data.GetString16Attribute(ax::mojom::StringAttribute::kName),
button->GetRenderedTooltipText(gfx::Point()));
EXPECT_EQ(
data.GetString16Attribute(ax::mojom::StringAttribute::kDescription),
button->GetRenderedTooltipText(gfx::Point()));
}
}
// Power button's rounded radii should change correctly when switching between
// active/inactive.
TEST_F(PowerButtonTest, ButtonRoundedRadii) {
SimulateUserLogin(kRegularUserLoginInfo);
// Sets a LTR locale.
base::i18n::SetICUDefaultLocale("en_US");
EXPECT_TRUE(GetPowerButton()->GetVisible());
EXPECT_EQ(gfx::RoundedCornersF(16, 16, 16, 16),
GetBackgroundLayer()->rounded_corner_radii());
// Clicks on the power button.
SimulatePowerButtonPress();
EXPECT_EQ(gfx::RoundedCornersF(4, 16, 16, 16),
GetBackgroundLayer()->rounded_corner_radii());
// Click the power button again to close the menu.
SimulatePowerButtonPress();
// Sets a RTL locale.
base::i18n::SetICUDefaultLocale("ar");
EXPECT_EQ(gfx::RoundedCornersF(16, 16, 16, 16),
GetBackgroundLayer()->rounded_corner_radii());
// Clicks on the power button.
SimulatePowerButtonPress();
EXPECT_EQ(gfx::RoundedCornersF(16, 4, 16, 16),
GetBackgroundLayer()->rounded_corner_radii());
}
TEST_F(PowerButtonTest, DeviceRebootOnShutdownPolicyHidesPowerOffButton) {
SimulateUserLogin(kRegularUserLoginInfo);
// Simulate DeviceRebootOnShutdownPolicy is enabled.
Shell::Get()->shutdown_controller()->SetRebootOnShutdown(true);
SimulatePowerButtonPress();
EXPECT_FALSE(GetPowerOffButton());
EXPECT_TRUE(GetRestartButton());
}
TEST_F(PowerButtonTest, ChevronFlipsWhenMenuIsShowing) {
SimulateUserLogin(kRegularUserLoginInfo);
EXPECT_TRUE(GetPowerButton()->GetVisible());
EXPECT_FALSE(IsMenuShowing());
EXPECT_TRUE(IsDownChevron());
SimulatePowerButtonPress();
EXPECT_TRUE(IsMenuShowing());
EXPECT_TRUE(IsUpChevron());
// Click the power button again to close the menu.
SimulatePowerButtonPress();
EXPECT_FALSE(IsMenuShowing());
EXPECT_TRUE(IsDownChevron());
}
} // namespace ash