// Copyright 2018 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/unified_system_tray.h"
#include "ash/accessibility/accessibility_controller.h"
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_switches.h"
#include "ash/public/cpp/test/shell_test_api.h"
#include "ash/public/cpp/test/test_cast_config_controller.h"
#include "ash/public/cpp/test/test_nearby_share_delegate.h"
#include "ash/root_window_controller.h"
#include "ash/shelf/shelf.h"
#include "ash/shelf/shelf_layout_manager.h"
#include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/system/media/quick_settings_media_view_controller.h"
#include "ash/system/model/fake_power_status.h"
#include "ash/system/model/scoped_fake_power_status.h"
#include "ash/system/model/system_tray_model.h"
#include "ash/system/notification_center/notification_center_tray.h"
#include "ash/system/notification_center/views/notification_center_view.h"
#include "ash/system/power/tray_power.h"
#include "ash/system/status_area_widget.h"
#include "ash/system/status_area_widget_test_helper.h"
#include "ash/system/time/time_tray_item_view.h"
#include "ash/system/time/time_view.h"
#include "ash/system/unified/date_tray.h"
#include "ash/system/unified/ime_mode_view.h"
#include "ash/system/unified/unified_slider_bubble_controller.h"
#include "ash/system/unified/unified_system_tray_bubble.h"
#include "ash/system/video_conference/fake_video_conference_tray_controller.h"
#include "ash/system/video_conference/video_conference_tray.h"
#include "ash/test/ash_test_base.h"
#include "ash/wm/tablet_mode/tablet_mode_controller.h"
#include "base/command_line.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "chromeos/ash/components/audio/audio_device.h"
#include "chromeos/ash/components/audio/cras_audio_handler.h"
#include "chromeos/ash/components/dbus/audio/audio_node.h"
#include "chromeos/ash/components/dbus/audio/fake_cras_audio_client.h"
#include "chromeos/constants/chromeos_features.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/chromeos/styles/cros_tokens_color_mappings.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
#include "ui/events/event_constants.h"
#include "ui/message_center/message_center.h"
#include "ui/views/accessibility/view_accessibility.h"
namespace ash {
namespace {
constexpr int kQsDetailedViewHeight = 464;
constexpr char kQuickSettingsPageCountOnClose[] =
"Ash.QuickSettings.PageCountOnClose";
} // namespace
using message_center::MessageCenter;
using message_center::Notification;
class UnifiedSystemTrayTest : public AshTestBase,
public testing::WithParamInterface<bool> {
public:
UnifiedSystemTrayTest()
: AshTestBase(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
UnifiedSystemTrayTest(const UnifiedSystemTrayTest&) = delete;
UnifiedSystemTrayTest& operator=(const UnifiedSystemTrayTest&) = delete;
~UnifiedSystemTrayTest() override = default;
void SetUp() override {
std::vector<base::test::FeatureRef> enabled_features;
if (IsVcControlsUiEnabled()) {
fake_video_conference_tray_controller_ =
std::make_unique<FakeVideoConferenceTrayController>();
enabled_features.push_back(features::kFeatureManagementVideoConference);
}
feature_list_.InitWithFeatures(enabled_features, {});
AshTestBase::SetUp();
}
void TearDown() override {
AshTestBase::TearDown();
if (IsVcControlsUiEnabled()) {
fake_video_conference_tray_controller_.reset();
}
}
bool IsVcControlsUiEnabled() { return GetParam(); }
protected:
const std::string AddNotification() {
const std::string id = base::NumberToString(id_++);
MessageCenter::Get()->AddNotification(
std::make_unique<message_center::Notification>(
message_center::NOTIFICATION_TYPE_SIMPLE, id, u"test title",
u"test message", ui::ImageModel(),
std::u16string() /* display_source */, GURL(),
message_center::NotifierId(),
message_center::RichNotificationData(),
new message_center::NotificationDelegate()));
return id;
}
void RemoveNotification(const std::string id) {
MessageCenter::Get()->RemoveNotification(id, /*by_user=*/false);
}
// Show the notification center bubble. This assumes that there is at least
// one notification in the notification list.
void ShowNotificationBubble() {
Shell::Get()
->GetPrimaryRootWindowController()
->shelf()
->GetStatusAreaWidget()
->notification_center_tray()
->ShowBubble();
}
// Hide the notification center bubble. This assumes that it is already
// shown.
void HideNotificationBubble() {
Shell::Get()
->GetPrimaryRootWindowController()
->shelf()
->GetStatusAreaWidget()
->notification_center_tray()
->CloseBubble();
}
bool IsBubbleShown() {
return GetPrimaryUnifiedSystemTray()->IsBubbleShown();
}
bool IsSliderBubbleShown() {
return GetPrimaryUnifiedSystemTray()
->slider_bubble_controller_->bubble_widget_;
}
UnifiedSliderBubbleController::SliderType GetSliderBubbleType() {
return GetPrimaryUnifiedSystemTray()
->slider_bubble_controller_->slider_type_;
}
bool IsMicrophoneMuteToastShown() {
return IsSliderBubbleShown() &&
GetSliderBubbleType() ==
UnifiedSliderBubbleController::SLIDER_TYPE_MIC;
}
UnifiedSystemTrayBubble* GetUnifiedSystemTrayBubble() {
return GetPrimaryUnifiedSystemTray()->bubble_.get();
}
void UpdateAutoHideStateNow() {
GetPrimaryShelf()->shelf_layout_manager()->UpdateAutoHideStateNow();
}
gfx::Rect GetBubbleViewBounds() {
auto* bubble = GetPrimaryUnifiedSystemTray()
->slider_bubble_controller_->bubble_view_.get();
return bubble ? bubble->GetBoundsInScreen() : gfx::Rect();
}
void TransferFromCalendarViewToMainViewByFuncKeys(UnifiedSystemTray* tray,
TrayBubbleView* bubble_view,
ui::KeyboardCode key) {
ShellTestApi().PressAccelerator(ui::Accelerator(key, ui::EF_NONE));
EXPECT_FALSE(tray->IsShowingCalendarView());
// Tests that `UnifiedSystemTray` is active and has the ink drop, while
// `DateTray` becomes inactive.
EXPECT_TRUE(tray->is_active());
EXPECT_FALSE(date_tray()->is_active());
// The main bubble is shorter than the detailed view bubble.
EXPECT_GT(kQsDetailedViewHeight, bubble_view->height());
}
void CheckDetailedViewHeight(TrayBubbleView* bubble_view) {
// The bubble height should be fixed to the detailed view height.
EXPECT_EQ(kQsDetailedViewHeight, bubble_view->height());
}
TimeTrayItemView* time_view() {
return GetPrimaryUnifiedSystemTray()->time_view_;
}
ImeModeView* ime_mode_view() {
return GetPrimaryUnifiedSystemTray()->ime_mode_view_;
}
DateTray* date_tray() {
return Shell::GetPrimaryRootWindowController()
->shelf()
->GetStatusAreaWidget()
->date_tray();
}
FakeVideoConferenceTrayController* fake_video_conference_tray_controller() {
return fake_video_conference_tray_controller_.get();
}
private:
int id_ = 0;
std::unique_ptr<FakeVideoConferenceTrayController>
fake_video_conference_tray_controller_;
base::test::ScopedFeatureList feature_list_;
};
INSTANTIATE_TEST_SUITE_P(All,
UnifiedSystemTrayTest,
testing::Bool() /*IsVcControlsUiEnabled()*/);
// Regression test for crbug/1360579
TEST_P(UnifiedSystemTrayTest, GetAccessibleNameForQuickSettingsBubble) {
auto* tray = GetPrimaryUnifiedSystemTray();
tray->ShowBubble();
EXPECT_EQ(tray->GetAccessibleNameForQuickSettingsBubble(),
l10n_util::GetStringUTF16(
IDS_ASH_QUICK_SETTINGS_BUBBLE_ACCESSIBLE_DESCRIPTION));
}
TEST_P(UnifiedSystemTrayTest, ShowVolumeSliderBubble) {
// The volume popup is not visible initially.
EXPECT_FALSE(IsSliderBubbleShown());
// When set to autohide, the shelf shouldn't be shown.
StatusAreaWidget* status = StatusAreaWidgetTestHelper::GetStatusAreaWidget();
EXPECT_FALSE(status->ShouldShowShelf());
// Simulate ARC asking to show the volume view.
GetPrimaryUnifiedSystemTray()->ShowVolumeSliderBubble();
// Volume view is now visible.
EXPECT_TRUE(IsSliderBubbleShown());
EXPECT_EQ(UnifiedSliderBubbleController::SLIDER_TYPE_VOLUME,
GetSliderBubbleType());
// This does not force the shelf to automatically show. Regression tests for
// crbug.com/729188
EXPECT_FALSE(status->ShouldShowShelf());
}
TEST_P(UnifiedSystemTrayTest, SliderBubbleMovesOnShelfAutohide) {
// The slider button should be moved when the autohidden shelf is shown, so
// as to not overlap. Regression test for crbug.com/1136564
auto* shelf = GetPrimaryShelf();
shelf->SetAlignment(ShelfAlignment::kBottom);
shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);
// Create a test widget to make auto-hiding work. Auto-hidden shelf will
// remain visible if no windows are shown, making it impossible to properly
// test.
views::Widget::InitParams params(
views::Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET,
views::Widget::InitParams::TYPE_WINDOW);
params.bounds = gfx::Rect(0, 0, 200, 200);
params.context = GetContext();
views::Widget* widget = new views::Widget;
widget->Init(std::move(params));
widget->Show();
// Start off the mouse nowhere near the shelf; the shelf should be hidden.
display::Display display = display::Screen::GetScreen()->GetPrimaryDisplay();
auto center = display.bounds().CenterPoint();
auto bottom_center = display.bounds().bottom_center();
bottom_center.set_y(bottom_center.y() - 1);
ui::test::EventGenerator* generator = GetEventGenerator();
generator->MoveMouseTo(center);
UpdateAutoHideStateNow();
GetPrimaryUnifiedSystemTray()->ShowVolumeSliderBubble();
gfx::Rect before_bounds = GetBubbleViewBounds();
// Now move the mouse close to the edge, so that the shelf shows, and verify
// that the volume slider adjusts accordingly.
generator->MoveMouseTo(bottom_center);
UpdateAutoHideStateNow();
gfx::Rect after_bounds = GetBubbleViewBounds();
EXPECT_NE(after_bounds, before_bounds);
// Also verify that the shelf and slider bubble would have overlapped, but do
// not now that we've moved the slider bubble.
gfx::Rect shelf_bounds = shelf->GetShelfBoundsInScreen();
EXPECT_TRUE(before_bounds.Intersects(shelf_bounds));
EXPECT_FALSE(after_bounds.Intersects(shelf_bounds));
// Move the mouse away and verify that it adjusts back to its original
// position.
generator->MoveMouseTo(center);
UpdateAutoHideStateNow();
after_bounds = GetBubbleViewBounds();
EXPECT_EQ(after_bounds, before_bounds);
// Now fullscreen and restore our window with autohide disabled and verify
// that the bubble moves down as the shelf disappears and reappears. Disable
// autohide so that the shelf is initially showing.
shelf->SetAlignment(ShelfAlignment::kRight);
after_bounds = GetBubbleViewBounds();
EXPECT_NE(after_bounds, before_bounds);
shelf->SetAlignment(ShelfAlignment::kBottom);
after_bounds = GetBubbleViewBounds();
EXPECT_EQ(after_bounds, before_bounds);
// Adjust the alignment of the shelf, and verify that the bubble moves along
// with it.
shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kNever);
before_bounds = GetBubbleViewBounds();
widget->SetFullscreen(true);
after_bounds = GetBubbleViewBounds();
EXPECT_NE(after_bounds, before_bounds);
widget->SetFullscreen(false);
after_bounds = GetBubbleViewBounds();
EXPECT_EQ(after_bounds, before_bounds);
}
TEST_P(UnifiedSystemTrayTest, ShowBubble_MultipleDisplays_OpenedOnSameDisplay) {
// Initialize two displays with 800x700 resolution.
UpdateDisplay("400+400-800x600,1220+400-800x600");
auto* screen = display::Screen::GetScreen();
EXPECT_EQ(2, screen->GetNumDisplays());
// The tray bubble for each display should be opened on the same display.
// See crbug.com/937420.
for (int i = 0; i < screen->GetNumDisplays(); ++i) {
auto* system_tray = GetPrimaryUnifiedSystemTray();
system_tray->ShowBubble();
const gfx::Rect primary_display_bounds = GetPrimaryDisplay().bounds();
const gfx::Rect tray_bubble_bounds =
GetPrimaryUnifiedSystemTray()->GetBubbleBoundsInScreen();
EXPECT_TRUE(primary_display_bounds.Contains(tray_bubble_bounds))
<< "primary display bounds=" << primary_display_bounds.ToString()
<< ", tray bubble bounds=" << tray_bubble_bounds.ToString();
SwapPrimaryDisplay();
}
}
TEST_P(UnifiedSystemTrayTest, HorizontalImeAndTimeLabelAlignment) {
ime_mode_view()->label()->SetText(u"US");
ime_mode_view()->SetVisible(true);
gfx::Rect time_bounds = time_view()
->time_view()
->GetHorizontalTimeLabelForTesting()
->GetBoundsInScreen();
gfx::Rect ime_bounds = ime_mode_view()->label()->GetBoundsInScreen();
EXPECT_EQ(time_bounds.y(), ime_bounds.y());
EXPECT_EQ(time_bounds.height(), ime_bounds.height());
}
TEST_P(UnifiedSystemTrayTest, FocusQuickSettings) {
auto* tray = GetPrimaryUnifiedSystemTray();
tray->ShowBubble();
auto* quick_settings_view = tray->bubble()->quick_settings_view();
auto* focus_manager = quick_settings_view->GetFocusManager();
EXPECT_FALSE(quick_settings_view->Contains(focus_manager->GetFocusedView()));
// Press the tab key should focus on the first button in the qs bubble.
ui::test::EventGenerator generator(Shell::GetPrimaryRootWindow());
generator.PressKey(ui::KeyboardCode::VKEY_TAB, ui::EF_NONE);
EXPECT_TRUE(quick_settings_view->Contains(focus_manager->GetFocusedView()));
}
TEST_P(UnifiedSystemTrayTest, TimeInQuickSettingsMetric) {
base::HistogramTester histogram_tester;
constexpr base::TimeDelta kTimeInQuickSettings = base::Seconds(3);
auto* tray = GetPrimaryUnifiedSystemTray();
// Open the tray.
tray->ShowBubble();
// Spend cool-down time with tray open.
task_environment()->FastForwardBy(kTimeInQuickSettings);
// Close and record the metric.
tray->CloseBubble();
// Ensure metric recorded time passed while Quick Setting was open.
histogram_tester.ExpectTimeBucketCount("Ash.QuickSettings.UserJourneyTime",
kTimeInQuickSettings,
/*count=*/1);
// Re-open the tray.
tray->ShowBubble();
// Metric isn't recorded when adding and removing a notification.
std::string id = AddNotification();
RemoveNotification(id);
histogram_tester.ExpectTotalCount("Ash.QuickSettings.UserJourneyTime",
/*count=*/1);
// Metric is recorded after closing bubble.
tray->CloseBubble();
histogram_tester.ExpectTotalCount("Ash.QuickSettings.UserJourneyTime",
/*count=*/2);
}
// Tests that the number of quick settings pages is recorded when the QS bubble
// is closed.
TEST_P(UnifiedSystemTrayTest, QuickSettingsPageCountMetric) {
base::HistogramTester histogram_tester;
// Show the bubble with one page and verify that nothing is recorded yet.
auto* tray = GetPrimaryUnifiedSystemTray();
tray->ShowBubble();
tray->bubble()
->unified_system_tray_controller()
->model()
->pagination_model()
->SetTotalPages(1);
histogram_tester.ExpectTotalCount(kQuickSettingsPageCountOnClose, 0);
// Close the bubble and verify that the metric is recorded.
tray->CloseBubble();
histogram_tester.ExpectTotalCount(kQuickSettingsPageCountOnClose, 1);
histogram_tester.ExpectBucketCount(kQuickSettingsPageCountOnClose,
/*sample=*/1,
/*expected_count=*/1);
// Show the bubble with two pages, and verify that the metric is recorded when
// the bubble is closed.
tray->ShowBubble();
tray->bubble()
->unified_system_tray_controller()
->model()
->pagination_model()
->SetTotalPages(2);
tray->CloseBubble();
histogram_tester.ExpectTotalCount(kQuickSettingsPageCountOnClose, 2);
histogram_tester.ExpectBucketCount(kQuickSettingsPageCountOnClose,
/*sample=*/2,
/*expected_count=*/1);
histogram_tester.ExpectBucketCount(kQuickSettingsPageCountOnClose,
/*sample=*/1,
/*expected_count=*/1);
}
// Tests that pressing the TOGGLE_CALENDAR accelerator once results in the
// calendar view showing.
TEST_P(UnifiedSystemTrayTest, PressCalendarAccelerator) {
ShellTestApi().PressAccelerator(
ui::Accelerator(ui::VKEY_C, ui::EF_COMMAND_DOWN));
EXPECT_TRUE(GetPrimaryUnifiedSystemTray()->IsShowingCalendarView());
}
// Tests that pressing the TOGGLE_CALENDAR accelerator twice results in a hidden
// QuickSettings bubble.
TEST_P(UnifiedSystemTrayTest, ToggleCalendarViewAccelerator) {
ShellTestApi().PressAccelerator(
ui::Accelerator(ui::VKEY_C, ui::EF_COMMAND_DOWN));
ShellTestApi().PressAccelerator(
ui::Accelerator(ui::VKEY_C, ui::EF_COMMAND_DOWN));
EXPECT_FALSE(GetUnifiedSystemTrayBubble());
}
// Tests that showing the calendar view by the TOGGLE_CALENDAR accelerator
// results in the CalendarDateCellView being focused.
TEST_P(UnifiedSystemTrayTest, CalendarAcceleratorFocusesDateCell) {
ShellTestApi().PressAccelerator(
ui::Accelerator(ui::VKEY_C, ui::EF_COMMAND_DOWN));
auto* focus_manager =
GetUnifiedSystemTrayBubble()->GetBubbleWidget()->GetFocusManager();
EXPECT_TRUE(focus_manager->GetFocusedView());
EXPECT_STREQ(focus_manager->GetFocusedView()->GetClassName(),
"CalendarDateCellView");
}
// Tests that using functional keys to change brightness/volume when the
// `CalendarView` is open will make ink drop transfer and bubble height change.
TEST_P(UnifiedSystemTrayTest, CalendarGoesToMainViewByFunctionalKeys) {
auto* tray = GetPrimaryUnifiedSystemTray();
tray->ShowBubble();
auto* bubble_view = tray->bubble()->GetBubbleView();
ShellTestApi().PressAccelerator(
ui::Accelerator(ui::VKEY_C, ui::EF_COMMAND_DOWN));
EXPECT_TRUE(tray->IsShowingCalendarView());
CheckDetailedViewHeight(bubble_view);
// Tests the volume up/down/mute functional keys. It should hide the calendar
// view and open the `unified_system_tray_bubble_`. The ink drop should
// transfer from `DateTray` to `UnifiedSystemTray` and the `bubble_view`
// should shrink for the Qs main page.
TransferFromCalendarViewToMainViewByFuncKeys(tray, bubble_view,
ui::VKEY_VOLUME_UP);
TransferFromCalendarViewToMainViewByFuncKeys(tray, bubble_view,
ui::VKEY_VOLUME_DOWN);
TransferFromCalendarViewToMainViewByFuncKeys(tray, bubble_view,
ui::VKEY_VOLUME_MUTE);
// Tests the brightness up/down functional keys.
TransferFromCalendarViewToMainViewByFuncKeys(tray, bubble_view,
ui::VKEY_BRIGHTNESS_UP);
TransferFromCalendarViewToMainViewByFuncKeys(tray, bubble_view,
ui::VKEY_BRIGHTNESS_DOWN);
tray->CloseBubble();
}
// Tests if the microphone mute toast is displayed when the mute state is
// toggled by the software switches.
TEST_P(UnifiedSystemTrayTest, InputMuteStateToggledBySoftwareSwitch) {
// The microphone mute toast should not be visible initially.
EXPECT_FALSE(IsMicrophoneMuteToastShown());
CrasAudioHandler* cras_audio_handler = CrasAudioHandler::Get();
// Toggling the system input mute state using software switches.
cras_audio_handler->SetInputMute(
!cras_audio_handler->IsInputMuted(),
CrasAudioHandler::InputMuteChangeMethod::kOther);
// The toast should not be visible as the mute state is toggled using a
// software switch.
EXPECT_FALSE(IsMicrophoneMuteToastShown());
}
// Tests if the microphone mute toast is displayed when the mute state is
// toggled by the keyboard switch.
TEST_P(UnifiedSystemTrayTest, InputMuteStateToggledByKeyboardSwitch) {
// The microphone mute toast should not be visible initially.
EXPECT_FALSE(IsMicrophoneMuteToastShown());
CrasAudioHandler* cras_audio_handler = CrasAudioHandler::Get();
// Toggling the system input mute state using the dedicated keyboard button.
cras_audio_handler->SetInputMute(
!cras_audio_handler->IsInputMuted(),
CrasAudioHandler::InputMuteChangeMethod::kKeyboardButton);
// The toast should be visible as the mute state is toggled using the keyboard
// switch.
EXPECT_TRUE(IsMicrophoneMuteToastShown());
}
// Tests if the microphone mute toast is displayed when the mute state is
// toggled by the hw switch.
TEST_P(UnifiedSystemTrayTest, InputMuteStateToggledByHardwareSwitch) {
// The microphone mute toast should not be visible initially.
EXPECT_FALSE(IsMicrophoneMuteToastShown());
CrasAudioHandler* cras_audio_handler = CrasAudioHandler::Get();
// Toggling the input mute state using the hw switch.
ui::MicrophoneMuteSwitchMonitor::Get()->SetMicrophoneMuteSwitchValue(
!cras_audio_handler->IsInputMuted());
// The toast should be visible as the mute state is toggled using the hw
// switch.
EXPECT_TRUE(IsMicrophoneMuteToastShown());
}
// Tests if the microphone mute toast is NOT displayed when the mute state is
// toggled by the hw switch and the VC tray is visible.
TEST_P(UnifiedSystemTrayTest,
InputMuteStateToggledByHardwareSwitchVcTrayVisible) {
if (!IsVcControlsUiEnabled()) {
return;
}
// The microphone mute toast should not be visible initially.
EXPECT_FALSE(IsMicrophoneMuteToastShown());
// Show the VC tray.
auto* vc_tray = Shell::Get()
->GetPrimaryRootWindowController()
->shelf()
->GetStatusAreaWidget()
->video_conference_tray();
DCHECK(vc_tray);
// Update media state, which will make the `VideoConferenceTray` show.
VideoConferenceMediaState state;
state.has_media_app = true;
fake_video_conference_tray_controller()->UpdateWithMediaState(state);
ASSERT_TRUE(vc_tray->GetVisible());
CrasAudioHandler* cras_audio_handler = CrasAudioHandler::Get();
// Toggling the input mute state using the hw switch.
ui::MicrophoneMuteSwitchMonitor::Get()->SetMicrophoneMuteSwitchValue(
!cras_audio_handler->IsInputMuted());
// The toast should NOT be visible as the mute state is toggled using the hw
// switch and the VC tray is visible.
EXPECT_FALSE(IsMicrophoneMuteToastShown());
// Make the VC tray not-visible and toggle again, now the toast is visible.
state.has_media_app = false;
fake_video_conference_tray_controller()->UpdateWithMediaState(state);
ui::MicrophoneMuteSwitchMonitor::Get()->SetMicrophoneMuteSwitchValue(
!cras_audio_handler->IsInputMuted());
EXPECT_TRUE(IsMicrophoneMuteToastShown());
}
// Tests microphone mute toast is visible only when the device has an
// internal/external microphone attached.
TEST_P(UnifiedSystemTrayTest, InputMuteStateToggledButNoMicrophoneAvailable) {
// Creating an input device for simple usage.
AudioNode internal_mic;
internal_mic.is_input = true;
internal_mic.id = 1;
internal_mic.stable_device_id_v1 = internal_mic.id;
internal_mic.type = AudioDevice::GetTypeString(AudioDeviceType::kInternalMic);
// Creating an output device.
AudioNode internal_speaker;
internal_speaker.is_input = false;
internal_speaker.id = 2;
internal_speaker.stable_device_id_v1 = internal_speaker.id;
internal_speaker.type =
AudioDevice::GetTypeString(AudioDeviceType::kInternalSpeaker);
// The microphone mute toast should not be visible initially.
EXPECT_FALSE(IsMicrophoneMuteToastShown());
FakeCrasAudioClient* fake_cras_audio_client = FakeCrasAudioClient::Get();
CrasAudioHandler* cras_audio_handler = CrasAudioHandler::Get();
fake_cras_audio_client->SetAudioNodesAndNotifyObserversForTesting(
{internal_speaker, internal_mic});
cras_audio_handler->SetInputMute(
!cras_audio_handler->IsInputMuted(),
CrasAudioHandler::InputMuteChangeMethod::kKeyboardButton);
// The toast should be visible as the input mute has changed and there is a
// microphone for simple usage attached to the device.
EXPECT_TRUE(IsMicrophoneMuteToastShown());
fake_cras_audio_client->SetAudioNodesAndNotifyObserversForTesting(
{internal_speaker});
cras_audio_handler->SetInputMute(
!cras_audio_handler->IsInputMuted(),
CrasAudioHandler::InputMuteChangeMethod::kKeyboardButton);
// There is no microphone for simple usage attached to the device. The toast
// should not be displayed even though the input mute has changed in the
// backend.
EXPECT_FALSE(IsMicrophoneMuteToastShown());
}
// Tests that the bubble is closed after entering or exiting tablet mode. This
// is required because the `FeatureTile`'s must be recreated to switch between
// primary and compact.
TEST_P(UnifiedSystemTrayTest, BubbleClosedAfterTabletModeChange) {
auto* tray = GetPrimaryUnifiedSystemTray();
TabletModeController* tablet_mode_controller =
Shell::Get()->tablet_mode_controller();
// Show bubble.
EXPECT_FALSE(IsBubbleShown());
tray->ShowBubble();
EXPECT_TRUE(IsBubbleShown());
// Expect bubble to close after entering tablet mode.
tablet_mode_controller->SetEnabledForTest(true);
EXPECT_FALSE(IsBubbleShown());
// Show bubble again.
tray->ShowBubble();
EXPECT_TRUE(IsBubbleShown());
// Expect bubble to close after exiting tablet mode.
tablet_mode_controller->SetEnabledForTest(false);
EXPECT_FALSE(IsBubbleShown());
}
// Tests that the tray background has the correct color when entering tablet
// mode.
TEST_P(UnifiedSystemTrayTest, TrayBackgroundColorAfterSwitchToTabletMode) {
auto* tray = GetPrimaryUnifiedSystemTray();
auto* widget = tray->GetWidget();
TabletModeController* tablet_mode_controller =
Shell::Get()->tablet_mode_controller();
tablet_mode_controller->SetEnabledForTest(false);
EXPECT_EQ(tray->layer()->background_color(),
ShelfConfig::Get()->GetShelfControlButtonColor(widget));
tablet_mode_controller->SetEnabledForTest(true);
EXPECT_EQ(tray->layer()->background_color(),
widget->GetColorProvider()->GetColor(
cros_tokens::kCrosSysSystemBaseElevated));
tablet_mode_controller->SetEnabledForTest(false);
EXPECT_EQ(tray->layer()->background_color(),
ShelfConfig::Get()->GetShelfControlButtonColor(widget));
}
// Tests that the bubble automatically hides if it is visible when another
// bubble becomes visible, and otherwise does not automatically show or hide.
TEST_P(UnifiedSystemTrayTest, BubbleHideBehavior) {
// Basic verification test that the unified system tray bubble can show/hide
// itself when no other bubbles are visible.
auto* tray = GetPrimaryUnifiedSystemTray();
EXPECT_FALSE(IsBubbleShown());
tray->ShowBubble();
EXPECT_TRUE(IsBubbleShown());
tray->CloseBubble();
EXPECT_FALSE(IsBubbleShown());
// Test that the unified system tray bubble automatically hides when it is
// currently visible while another bubble becomes visible.
AddNotification();
tray->ShowBubble();
EXPECT_TRUE(IsBubbleShown());
ShowNotificationBubble();
EXPECT_FALSE(IsBubbleShown());
// Hide all currently visible bubbles.
HideNotificationBubble();
EXPECT_FALSE(IsBubbleShown());
// Test that the unified system tray bubble stays hidden when showing another
// bubble.
ShowNotificationBubble();
EXPECT_FALSE(IsBubbleShown());
}
TEST_P(UnifiedSystemTrayTest, BubbleViewSizeChangeWithEnoughSpace) {
// Set a large enough screen size.
UpdateDisplay("1600x900");
auto* tray = GetPrimaryUnifiedSystemTray();
tray->ShowBubble();
auto* bubble_view = tray->bubble()->GetBubbleView();
// The main page height should be smaller than the detailed view height.
EXPECT_GT(kQsDetailedViewHeight, bubble_view->height());
// Goes to a detailed view (here using calendar view).
ShellTestApi().PressAccelerator(
ui::Accelerator(ui::VKEY_C, ui::EF_COMMAND_DOWN));
// Asserts that calendar is actually shown.
EXPECT_TRUE(GetPrimaryUnifiedSystemTray()->IsShowingCalendarView());
CheckDetailedViewHeight(bubble_view);
tray->CloseBubble();
}
TEST_P(UnifiedSystemTrayTest, BubbleViewSizeChangeNoEnoughSpace) {
// Set a small screen size.
UpdateDisplay("300x200");
auto* tray = GetPrimaryUnifiedSystemTray();
tray->ShowBubble();
auto* bubble_view = tray->bubble()->GetBubbleView();
// The main page height should be smaller than the detailed view height.
EXPECT_GT(kQsDetailedViewHeight, bubble_view->height());
// Goes to a detailed view (here using calendar view).
ShellTestApi().PressAccelerator(
ui::Accelerator(ui::VKEY_C, ui::EF_COMMAND_DOWN));
// Asserts that calendar is actually shown.
EXPECT_TRUE(GetPrimaryUnifiedSystemTray()->IsShowingCalendarView());
// No enough space for the fixed detailed view height.
EXPECT_GT(kQsDetailedViewHeight, bubble_view->height());
tray->CloseBubble();
}
TEST_P(UnifiedSystemTrayTest, BubbleViewSizeChangeWithBigMainPage) {
// Set a large enough screen size.
UpdateDisplay("1600x900");
// The following code adds 2 more row in the tile section and 1 media view to
// the qs bubble. In this case the main page should be larger than the default
// detailed page height.
// Enables nearby sharing to show the tile.
auto* test_delegate = static_cast<TestNearbyShareDelegate*>(
Shell::Get()->nearby_share_delegate());
test_delegate->set_is_pod_button_visible(true);
// Constructs the test cast config to add the cast tile.
TestCastConfigController cast_config;
// Adds locales to show the locale tile.
std::vector<LocaleInfo> locale_list;
locale_list.emplace_back("en-US", u"English (United States)");
Shell::Get()->system_tray_model()->SetLocaleList(std::move(locale_list),
"en-US");
// Adds the media view.
auto* tray = GetPrimaryUnifiedSystemTray();
tray->ShowBubble();
auto* qs_view = tray->bubble()->quick_settings_view();
auto* tray_controller = tray->bubble()->unified_system_tray_controller();
auto media_controller =
std::make_unique<QuickSettingsMediaViewController>(tray_controller);
// Outside tests the QuickSettingsMediaViewController is set in
// UnifiedSystemTrayController::CreateQuickSettingsView() which is not
// called here, but we need to reference QuickSettingsMediaViewController in
// QuickSettingsMediaViewContainer::MaybeShowMediaView() when calling
// QuickSettingsView::SetShowMediaView(), so we need to manually set the
// controller for testing.
tray_controller->SetMediaViewControllerForTesting(
std::move(media_controller));
qs_view->AddMediaView(tray_controller->media_view_controller()->CreateView());
qs_view->SetShowMediaView(true);
auto* bubble_view = tray->bubble()->GetBubbleView();
// The main page height should be larger than the detailed view height.
EXPECT_LT(kQsDetailedViewHeight, bubble_view->height());
const int main_page_height = bubble_view->height();
// Goes to a detailed view (here using calendar view).
ShellTestApi().PressAccelerator(
ui::Accelerator(ui::VKEY_C, ui::EF_COMMAND_DOWN));
// Asserts that calendar is actually shown.
EXPECT_TRUE(GetPrimaryUnifiedSystemTray()->IsShowingCalendarView());
EXPECT_LT(kQsDetailedViewHeight, bubble_view->height());
EXPECT_EQ(main_page_height, bubble_view->height());
tray->CloseBubble();
}
// Tests that there's no bubble in the kiosk mode.
TEST_P(UnifiedSystemTrayTest, NoBubbleAndNoDetailedViewInKioskMode) {
SimulateKioskMode(user_manager::UserType::kKioskApp);
auto* tray = GetPrimaryUnifiedSystemTray();
tray->ShowBubble();
// In the kiosk mode, the bubble doesn't exist.
EXPECT_FALSE(IsBubbleShown());
// Trying to show any of the detailed view will not show the bubble.
tray->ShowAudioDetailedViewBubble();
EXPECT_FALSE(IsBubbleShown());
tray->ShowNetworkDetailedViewBubble();
EXPECT_FALSE(IsBubbleShown());
tray->ShowDisplayDetailedViewBubble();
EXPECT_FALSE(IsBubbleShown());
}
class PowerTrayViewTest : public UnifiedSystemTrayTest {
public:
FakePowerStatus* GetFakePowerStatus() {
return scoped_fake_power_status_.get()->fake_power_status();
}
PowerTrayView* power_tray_view() {
return GetPrimaryUnifiedSystemTray()->power_tray_view_;
}
// AshTestBase:
void SetUp() override {
AshTestBase::SetUp();
scoped_fake_power_status_ = std::make_unique<ScopedFakePowerStatus>();
}
// AshTestBase:
void TearDown() override {
scoped_fake_power_status_.reset();
AshTestBase::TearDown();
}
private:
std::unique_ptr<ScopedFakePowerStatus> scoped_fake_power_status_;
};
TEST_F(PowerTrayViewTest, BatteryVisibility) {
FakePowerStatus* fake_power_status = GetFakePowerStatus();
fake_power_status->SetIsBatteryPresent(false);
// OnPowerStatusChanged() is called in an asynchronous method, but for the
// purpose of this test, it is called explicitly to ensure that the visibility
// is set before the check.
power_tray_view()->OnPowerStatusChanged();
EXPECT_FALSE(power_tray_view()->GetVisible());
}
TEST_F(PowerTrayViewTest, AccessibleProperties) {
ui::AXNodeData data;
power_tray_view()->GetViewAccessibility().GetAccessibleNodeData(&data);
EXPECT_EQ(data.role, ax::mojom::Role::kImage);
}
} // namespace ash