// Copyright 2021 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/tray/tray_background_view.h"
#include "ash/accessibility/accessibility_controller.h"
#include "ash/root_window_controller.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shelf/shelf.h"
#include "ash/shelf/shelf_layout_manager.h"
#include "ash/shell.h"
#include "ash/system/accessibility/dictation_button_tray.h"
#include "ash/system/status_area_widget_test_helper.h"
#include "ash/system/tray/tray_bubble_view.h"
#include "ash/system/tray/tray_bubble_wrapper.h"
#include "ash/system/tray/tray_utils.h"
#include "ash/test/ash_test_base.h"
#include "ash/wm/tablet_mode/tablet_mode_controller.h"
#include "base/memory/raw_ptr.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/task_environment.h"
#include "ui/base/models/simple_menu_model.h"
#include "ui/compositor/layer_animator.h"
#include "ui/compositor/scoped_animation_duration_scale_mode.h"
#include "ui/compositor/test/layer_animation_stopped_waiter.h"
#include "ui/display/manager/display_manager.h"
#include "ui/display/manager/managed_display_info.h"
#include "ui/views/accessibility/view_accessibility.h"
namespace ash {
class TestTrayBackgroundView : public TrayBackgroundView,
public ui::SimpleMenuModel::Delegate {
public:
explicit TestTrayBackgroundView(Shelf* shelf)
: TrayBackgroundView(shelf,
TrayBackgroundViewCatalogName::kTestCatalogName,
RoundedCornerBehavior::kAllRounded) {}
TestTrayBackgroundView(const TestTrayBackgroundView&) = delete;
TestTrayBackgroundView& operator=(const TestTrayBackgroundView&) = delete;
~TestTrayBackgroundView() override = default;
// TrayBackgroundView:
void ClickedOutsideBubble(const ui::LocatedEvent& event) override {}
void UpdateTrayItemColor(bool is_active) override {}
std::u16string GetAccessibleNameForTray() override {
return u"TestTrayBackgroundView";
}
void HandleLocaleChange() override {}
void HideBubbleWithView(const TrayBubbleView* bubble_view) override {
if (bubble_view == bubble_->GetBubbleView())
CloseBubble();
}
void HideBubble(const TrayBubbleView* bubble_view) override {
if (bubble_view == bubble_->GetBubbleView()) {
CloseBubble();
}
}
std::unique_ptr<ui::SimpleMenuModel> CreateContextMenuModel() override {
return provide_menu_model_ ? std::make_unique<ui::SimpleMenuModel>(this)
: nullptr;
}
void ShowBubble() override {
show_bubble_called_ = true;
auto bubble_view = std::make_unique<TrayBubbleView>(
CreateInitParamsForTrayBubble(/*tray=*/this));
bubble_view->SetCanActivate(true);
bubble_ = std::make_unique<TrayBubbleWrapper>(this,
/*event_handling=*/false);
bubble_->ShowBubble(std::move(bubble_view));
bubble_->GetBubbleWidget()->Activate();
bubble_->bubble_view()->SetVisible(true);
SetIsActive(true);
}
void CloseBubbleInternal() override {
bubble_.reset();
SetIsActive(false);
}
// ui::SimpleMenuModel::Delegate:
void ExecuteCommand(int command_id, int event_flags) override {}
void set_provide_menu_model(bool provide_menu_model) {
provide_menu_model_ = provide_menu_model;
}
void SetShouldShowMenu(bool should_show_menu) {
SetContextMenuEnabled(should_show_menu);
}
TrayBubbleWrapper* bubble() { return bubble_.get(); }
TrayBubbleView* bubble_view() { return bubble_->bubble_view(); }
bool show_bubble_called() const { return show_bubble_called_; }
std::u16string GetAccessibleNameForBubble() override {
return u"Sample accessible name";
}
private:
std::unique_ptr<TrayBubbleWrapper> bubble_;
bool provide_menu_model_ = false;
bool show_bubble_called_ = false;
};
// A `TrayBackgroundView` whose bubble does not automatically close when the
// lock state changes.
class PersistentBubbleTestTrayBackgroundView : public TestTrayBackgroundView {
public:
explicit PersistentBubbleTestTrayBackgroundView(Shelf* shelf)
: TestTrayBackgroundView(shelf) {
set_should_close_bubble_on_lock_state_change(false);
}
PersistentBubbleTestTrayBackgroundView(
const PersistentBubbleTestTrayBackgroundView&) = delete;
PersistentBubbleTestTrayBackgroundView& operator=(
const PersistentBubbleTestTrayBackgroundView&) = delete;
~PersistentBubbleTestTrayBackgroundView() override = default;
};
class TrayBackgroundViewTest : public AshTestBase,
public ui::LayerAnimationObserver {
public:
TrayBackgroundViewTest()
: AshTestBase(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
TrayBackgroundViewTest(const TrayBackgroundViewTest&) = delete;
TrayBackgroundViewTest& operator=(const TrayBackgroundViewTest&) = delete;
~TrayBackgroundViewTest() override = default;
void SetUp() override {
AshTestBase::SetUp();
// Adds this `test_tray_background_view_` to the mock `StatusAreaWidget`. We
// need to remove the layout manager from the delegate before adding a new
// child, since there's a DCHECK in the `GridLayout` to assert no more
// children can be added.
// Can't use std::make_unique() here, because we need base class type for
// template method to link successfully without adding test code to
// status_area_widget.cc.
test_tray_background_view_ = static_cast<TestTrayBackgroundView*>(
StatusAreaWidgetTestHelper::GetStatusAreaWidget()->AddTrayButton(
std::unique_ptr<TrayBackgroundView>(
new TestTrayBackgroundView(GetPrimaryShelf()))));
// Same as above but for a `PersistentBubbleTestTrayBackgroundView`.
std::unique_ptr<PersistentBubbleTestTrayBackgroundView> tmp =
std::make_unique<PersistentBubbleTestTrayBackgroundView>(
GetPrimaryShelf());
persistent_bubble_test_tray_background_view_ =
static_cast<PersistentBubbleTestTrayBackgroundView*>(
StatusAreaWidgetTestHelper::GetStatusAreaWidget()->AddTrayButton(
std::unique_ptr<TrayBackgroundView>(std::move(tmp))));
// Set Dictation button to be visible.
AccessibilityController* controller =
Shell::Get()->accessibility_controller();
controller->dictation().SetEnabled(true);
}
// ui::LayerAnimationObserver:
void OnLayerAnimationScheduled(
ui::LayerAnimationSequence* sequence) override {
num_animations_scheduled_++;
}
void OnLayerAnimationEnded(ui::LayerAnimationSequence* sequence) override {}
void OnLayerAnimationAborted(ui::LayerAnimationSequence* sequence) override {}
TestTrayBackgroundView* test_tray_background_view() const {
return test_tray_background_view_;
}
PersistentBubbleTestTrayBackgroundView*
persistent_bubble_test_tray_background_view() const {
return persistent_bubble_test_tray_background_view_;
}
int num_animations_scheduled() const { return num_animations_scheduled_; }
protected:
// Here we use dictation tray for testing secondary screen.
DictationButtonTray* GetPrimaryDictationTray() {
return StatusAreaWidgetTestHelper::GetStatusAreaWidget()
->dictation_button_tray();
}
DictationButtonTray* GetSecondaryDictationTray() {
return StatusAreaWidgetTestHelper::GetSecondaryStatusAreaWidget()
->dictation_button_tray();
}
bool TriggerAutoHideTimeout(ShelfLayoutManager* layout_manager) {
if (!layout_manager->auto_hide_timer_.IsRunning())
return false;
layout_manager->auto_hide_timer_.FireNow();
return true;
}
private:
raw_ptr<TestTrayBackgroundView, DanglingUntriaged>
test_tray_background_view_ = nullptr;
raw_ptr<PersistentBubbleTestTrayBackgroundView, DanglingUntriaged>
persistent_bubble_test_tray_background_view_ = nullptr;
int num_animations_scheduled_ = 0;
};
// Tests that a `TrayBackgroundView` initially starts in a hidden state.
TEST_F(TrayBackgroundViewTest, InitiallyHidden) {
EXPECT_FALSE(test_tray_background_view()->GetVisible());
EXPECT_EQ(test_tray_background_view()->layer()->opacity(), 0.0f);
}
TEST_F(TrayBackgroundViewTest, ShowingAnimationAbortedByHideAnimation) {
ui::ScopedAnimationDurationScaleMode test_duration_mode(
ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
// Starts showing up animation.
test_tray_background_view()->SetVisiblePreferred(true);
EXPECT_TRUE(test_tray_background_view()->GetVisible());
EXPECT_TRUE(test_tray_background_view()->layer()->GetTargetVisibility());
EXPECT_TRUE(
test_tray_background_view()->layer()->GetAnimator()->is_animating());
// Starts hide animation. The view is visible but the layer's target
// visibility is false.
test_tray_background_view()->SetVisiblePreferred(false);
EXPECT_TRUE(test_tray_background_view()->GetVisible());
EXPECT_FALSE(test_tray_background_view()->layer()->GetTargetVisibility());
EXPECT_TRUE(
test_tray_background_view()->layer()->GetAnimator()->is_animating());
// Here we wait until the animation is finished and we give it one more second
// to finish the callbacks in `OnVisibilityAnimationFinished()`.
StatusAreaWidgetTestHelper::WaitForLayerAnimationEnd(
test_tray_background_view()->layer());
task_environment()->FastForwardBy(base::Seconds(1));
// After the hide animation is finished, test_tray_background_view() is not
// visible.
EXPECT_FALSE(test_tray_background_view()->GetVisible());
EXPECT_FALSE(
test_tray_background_view()->layer()->GetAnimator()->is_animating());
}
// Tests that a `TrayBackgroundView` doesn't get notified of events during its
// hide animation.
TEST_F(TrayBackgroundViewTest, EventsDisabledForHideAnimation) {
// Initially show the tray. Note that animations complete immediately in this
// part of the test.
test_tray_background_view()->SetVisiblePreferred(true);
// Ensure animations don't complete immediately for the rest of the test.
ui::ScopedAnimationDurationScaleMode test_duration_mode(
ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
// Start the tray's hide animation and verify that it can't process events.
test_tray_background_view()->SetVisiblePreferred(false);
ASSERT_TRUE(test_tray_background_view()->IsDrawn());
EXPECT_FALSE(test_tray_background_view()->GetCanProcessEventsWithinSubtree());
// Interrupt the hide animation with a show animation and verify that the tray
// can process events again.
test_tray_background_view()->SetVisiblePreferred(true);
EXPECT_TRUE(test_tray_background_view()->GetCanProcessEventsWithinSubtree());
}
TEST_F(TrayBackgroundViewTest, HandleSessionChange) {
ui::ScopedAnimationDurationScaleMode test_duration_mode(
ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
// Not showing animation after logging in.
GetSessionControllerClient()->SetSessionState(
session_manager::SessionState::LOGIN_PRIMARY);
// Gives it a small duration to let the session get changed. This duration is
// way smaller than the animation duration, so that the animation will not
// finish when this duration ends. The same for the other places below.
task_environment()->FastForwardBy(base::Milliseconds(20));
test_tray_background_view()->SetVisiblePreferred(false);
test_tray_background_view()->SetVisiblePreferred(true);
task_environment()->FastForwardBy(base::Milliseconds(20));
EXPECT_TRUE(
test_tray_background_view()->layer()->GetAnimator()->is_animating());
EXPECT_TRUE(test_tray_background_view()->GetVisible());
GetSessionControllerClient()->SetSessionState(
session_manager::SessionState::ACTIVE);
task_environment()->FastForwardBy(base::Milliseconds(20));
EXPECT_FALSE(
test_tray_background_view()->layer()->GetAnimator()->is_animating());
EXPECT_TRUE(test_tray_background_view()->GetVisible());
// Enable the animation after session state get changed.
task_environment()->FastForwardBy(base::Milliseconds(20));
test_tray_background_view()->SetVisiblePreferred(false);
test_tray_background_view()->SetVisiblePreferred(true);
task_environment()->FastForwardBy(base::Milliseconds(20));
EXPECT_TRUE(
test_tray_background_view()->layer()->GetAnimator()->is_animating());
EXPECT_TRUE(test_tray_background_view()->GetVisible());
// Not showing animation after unlocking screen.
GetSessionControllerClient()->SetSessionState(
session_manager::SessionState::LOCKED);
task_environment()->FastForwardBy(base::Milliseconds(20));
test_tray_background_view()->SetVisiblePreferred(false);
test_tray_background_view()->SetVisiblePreferred(true);
task_environment()->FastForwardBy(base::Milliseconds(20));
EXPECT_TRUE(
test_tray_background_view()->layer()->GetAnimator()->is_animating());
EXPECT_TRUE(test_tray_background_view()->GetVisible());
GetSessionControllerClient()->SetSessionState(
session_manager::SessionState::ACTIVE);
task_environment()->FastForwardBy(base::Milliseconds(20));
EXPECT_FALSE(
test_tray_background_view()->layer()->GetAnimator()->is_animating());
EXPECT_TRUE(test_tray_background_view()->GetVisible());
// Not showing animation when switching users.
GetSessionControllerClient()->AddUserSession("a");
test_tray_background_view()->SetVisiblePreferred(false);
test_tray_background_view()->SetVisiblePreferred(true);
EXPECT_TRUE(
test_tray_background_view()->layer()->GetAnimator()->is_animating());
EXPECT_TRUE(test_tray_background_view()->GetVisible());
// Simulates user switching by changing the order of session_ids.
Shell::Get()->session_controller()->SetUserSessionOrder({2u, 1u});
task_environment()->FastForwardBy(base::Milliseconds(20));
EXPECT_FALSE(
test_tray_background_view()->layer()->GetAnimator()->is_animating());
EXPECT_TRUE(test_tray_background_view()->GetVisible());
}
// Tests that persistent `TrayBackgroundView` bubbles stay shown across lock
// state changes.
TEST_F(TrayBackgroundViewTest, PersistentBubbleShownAcrossLockStateChanges) {
// Show the bubble.
persistent_bubble_test_tray_background_view()->SetVisiblePreferred(true);
persistent_bubble_test_tray_background_view()->ShowBubble();
ASSERT_TRUE(persistent_bubble_test_tray_background_view()
->bubble()
->bubble_view()
->IsDrawn());
// Go to the lock screen.
GetSessionControllerClient()->LockScreen();
// Verify that the bubble is still shown.
EXPECT_TRUE(persistent_bubble_test_tray_background_view()
->bubble()
->bubble_view()
->IsDrawn());
// Unlock the device.
GetSessionControllerClient()->UnlockScreen();
// Verify that the bubble is still shown.
EXPECT_TRUE(persistent_bubble_test_tray_background_view()
->bubble()
->bubble_view()
->IsDrawn());
}
// Tests that non-persistent `TrayBackgroundView` bubbles are closed when the
// lock state changes.
TEST_F(TrayBackgroundViewTest, NonPersistentBubbleClosedWhenLockStateChanges) {
// Show the bubble.
test_tray_background_view()->SetVisiblePreferred(true);
test_tray_background_view()->ShowBubble();
ASSERT_TRUE(test_tray_background_view()->bubble()->bubble_view()->IsDrawn());
// Go to the lock screen.
GetSessionControllerClient()->LockScreen();
// Verify that the bubble is closed.
EXPECT_FALSE(test_tray_background_view()->bubble());
// Open the bubble on the lock screen.
test_tray_background_view()->ShowBubble();
ASSERT_TRUE(test_tray_background_view()->bubble()->bubble_view()->IsDrawn());
// Unlock the device.
GetSessionControllerClient()->UnlockScreen();
// Verify that the bubble is closed.
EXPECT_FALSE(persistent_bubble_test_tray_background_view()->bubble());
}
TEST_F(TrayBackgroundViewTest, SecondaryDisplay) {
ui::ScopedAnimationDurationScaleMode test_duration_mode(
ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
// Add secondary screen.
UpdateDisplay("800x600,800x600");
GetPrimaryDictationTray()->layer()->GetAnimator()->AddObserver(this);
GetSecondaryDictationTray()->layer()->GetAnimator()->AddObserver(this);
// Switch the primary and secondary screen. This should not cause additional
// TrayBackgroundView animations to occur.
SwapPrimaryDisplay();
task_environment()->RunUntilIdle();
EXPECT_TRUE(GetPrimaryDictationTray()->GetVisible());
EXPECT_TRUE(GetSecondaryDictationTray()->GetVisible());
EXPECT_EQ(num_animations_scheduled(), 0);
// Enable the animation after showing up on the secondary screen.
task_environment()->RunUntilIdle();
ui::LayerAnimationStoppedWaiter animation_waiter;
GetPrimaryDictationTray()->SetVisiblePreferred(false);
EXPECT_TRUE(
GetPrimaryDictationTray()->layer()->GetAnimator()->is_animating());
animation_waiter.Wait(GetPrimaryDictationTray()->layer());
GetPrimaryDictationTray()->SetVisiblePreferred(true);
EXPECT_TRUE(
GetPrimaryDictationTray()->layer()->GetAnimator()->is_animating());
animation_waiter.Wait(GetPrimaryDictationTray()->layer());
GetSecondaryDictationTray()->SetVisiblePreferred(false);
EXPECT_TRUE(
GetSecondaryDictationTray()->layer()->GetAnimator()->is_animating());
animation_waiter.Wait(GetSecondaryDictationTray()->layer());
GetSecondaryDictationTray()->SetVisiblePreferred(true);
EXPECT_TRUE(
GetSecondaryDictationTray()->layer()->GetAnimator()->is_animating());
animation_waiter.Wait(GetSecondaryDictationTray()->layer());
EXPECT_TRUE(GetPrimaryDictationTray()->GetVisible());
EXPECT_TRUE(GetSecondaryDictationTray()->GetVisible());
// Remove the secondary screen. This should not cause additional
// TrayBackgroundView animations to occur.
int num_animations_scheduled_before = num_animations_scheduled();
UpdateDisplay("800x600");
task_environment()->RunUntilIdle();
EXPECT_EQ(num_animations_scheduled(), num_animations_scheduled_before);
EXPECT_TRUE(GetPrimaryDictationTray()->GetVisible());
}
// Tests that a context menu only appears when a tray provides a menu model and
// the tray should show a context menu.
TEST_F(TrayBackgroundViewTest, ContextMenu) {
test_tray_background_view()->SetVisiblePreferred(true);
ui::test::EventGenerator* generator = GetEventGenerator();
generator->MoveMouseTo(
test_tray_background_view()->GetBoundsInScreen().CenterPoint());
EXPECT_FALSE(test_tray_background_view()->IsShowingMenu());
// The tray should not display a context menu, and no model is provided, so
// no menu should appear.
generator->ClickRightButton();
EXPECT_FALSE(test_tray_background_view()->IsShowingMenu());
// The tray should display a context menu, but no model is provided, so no
// menu should appear.
test_tray_background_view()->SetShouldShowMenu(true);
generator->ClickRightButton();
EXPECT_FALSE(test_tray_background_view()->IsShowingMenu());
// The tray should display a context menu, and a model is provided, so a menu
// should appear.
test_tray_background_view()->set_provide_menu_model(true);
generator->ClickRightButton();
EXPECT_TRUE(test_tray_background_view()->IsShowingMenu());
// The tray should not display a context menu, so even though a model is
// provided, no menu should appear.
test_tray_background_view()->SetShouldShowMenu(false);
generator->ClickRightButton();
EXPECT_FALSE(test_tray_background_view()->IsShowingMenu());
}
// Tests the auto-hide shelf status when opening and closing a context menu.
TEST_F(TrayBackgroundViewTest, AutoHideShelfWithContextMenu) {
// Create one window, or the shelf won't auto-hide.
std::unique_ptr<views::Widget> unused =
CreateTestWidget(views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET);
// Set the shelf to auto-hide.
Shelf* shelf = test_tray_background_view()->shelf();
EXPECT_TRUE(shelf);
shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);
ShelfLayoutManager* layout_manager = shelf->shelf_layout_manager();
EXPECT_TRUE(layout_manager);
ASSERT_FALSE(TriggerAutoHideTimeout(layout_manager));
EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());
// Move mouse to display the shelf.
ui::test::EventGenerator* generator = GetEventGenerator();
gfx::Rect display_bounds =
display::Screen::GetScreen()->GetPrimaryDisplay().bounds();
generator->MoveMouseTo(display_bounds.bottom_center());
ASSERT_TRUE(TriggerAutoHideTimeout(layout_manager));
EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
// Open tray context menu.
test_tray_background_view()->SetVisiblePreferred(true);
test_tray_background_view()->set_provide_menu_model(true);
test_tray_background_view()->SetShouldShowMenu(true);
generator->MoveMouseTo(
test_tray_background_view()->GetBoundsInScreen().CenterPoint());
EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
EXPECT_FALSE(test_tray_background_view()->IsShowingMenu());
generator->ClickRightButton();
EXPECT_TRUE(test_tray_background_view()->IsShowingMenu());
// Close the context menu with the mouse over the shelf. The shelf should
// remain shown.
generator->ClickRightButton();
ASSERT_FALSE(TriggerAutoHideTimeout(layout_manager));
EXPECT_FALSE(test_tray_background_view()->IsShowingMenu());
EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
// Reopen tray context menu.
generator->ClickRightButton();
EXPECT_TRUE(test_tray_background_view()->IsShowingMenu());
// Mouse away from the shelf with the context menu still showing. The shelf
// should remain shown.
generator->MoveMouseTo(0, 0);
ASSERT_TRUE(TriggerAutoHideTimeout(layout_manager));
EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
// Close the context menu with the mouse away from the shelf. The shelf
// should hide.
generator->ClickRightButton();
ASSERT_FALSE(TriggerAutoHideTimeout(layout_manager));
EXPECT_FALSE(test_tray_background_view()->IsShowingMenu());
EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());
}
// Tests that `TrayBackgroundView::SetPressedCallback()` overrides
// TrayBackgroundView's default press behavior.
TEST_F(TrayBackgroundViewTest, PressedCallbackSet) {
test_tray_background_view()->SetVisible(true);
ui::test::EventGenerator* generator = GetEventGenerator();
generator->MoveMouseTo(
test_tray_background_view()->GetBoundsInScreen().CenterPoint());
// Set the callback. Pressing the `TestTrayBackgroundView` should execute the
// callback instead of `TrayBackgroundView::ShowBubble()`.
bool pressed = false;
test_tray_background_view()->SetCallback(base::BindRepeating(
[](bool& pressed, const ui::Event& event) { pressed = true; },
std::ref(pressed)));
generator->ClickLeftButton();
EXPECT_TRUE(pressed);
EXPECT_FALSE(test_tray_background_view()->show_bubble_called());
}
// Tests that histograms are still recorded when the TrayBackgroundView has
// custom button press behavior.
TEST_F(TrayBackgroundViewTest, HistogramRecordedPressedCallbackSet) {
auto histogram_tester = std::make_unique<base::HistogramTester>();
histogram_tester->ExpectTotalCount(
"Ash.StatusArea.TrayBackgroundView.Pressed",
/*count=*/0);
test_tray_background_view()->SetVisible(true);
ui::test::EventGenerator* generator = GetEventGenerator();
generator->MoveMouseTo(
test_tray_background_view()->GetBoundsInScreen().CenterPoint());
// Set the callback. This should not effect histogram recording.
test_tray_background_view()->SetCallback(views::Button::PressedCallback());
generator->ClickLeftButton();
histogram_tester->ExpectTotalCount(
"Ash.StatusArea.TrayBackgroundView.Pressed",
/*count=*/1);
}
// Tests that the `TrayBubbleWrapper` owned by the `TrayBackgroundView` is
// cleaned up and the active state of the `TrayBackgroundView` is updated if the
// bubble widget is destroyed independently (Real life examples would be
// clicking outside a bubble or hitting the escape key).
TEST_F(TrayBackgroundViewTest, CleanUpOnIndependentBubbleDestruction) {
test_tray_background_view()->SetVisiblePreferred(true);
test_tray_background_view()->ShowBubble();
EXPECT_TRUE(test_tray_background_view()->is_active());
EXPECT_TRUE(
test_tray_background_view()->bubble()->GetBubbleWidget()->IsVisible());
// Destroying the bubble's widget independently of the `TrayBackgroundView`
// should properly clean up `bubble()` in `TrayBackgroundView`.
test_tray_background_view()->bubble()->GetBubbleWidget()->CloseNow();
EXPECT_FALSE(test_tray_background_view()->is_active());
ASSERT_FALSE(test_tray_background_view()->bubble());
}
// Tests that the tray bubble is repositioned when the device scale factor
// changes.
TEST_F(TrayBackgroundViewTest, UpdateScaleFactor) {
test_tray_background_view()->ShowBubble();
EXPECT_TRUE(test_tray_background_view()->bubble());
// Since no zoom factor has been set on the display, it should be 1.
const display::ManagedDisplayInfo& display =
display_manager()->GetDisplayInfo(GetPrimaryDisplay().id());
EXPECT_EQ(
display_manager()->GetDisplayForId(display.id()).device_scale_factor(),
1.f);
const gfx::Rect& tablet_mode_bounds =
test_tray_background_view()->bubble_view()->GetBoundsInScreen();
// Set the device scale factor to 2.
constexpr float zoom_factor = 2.0f;
display_manager()->UpdateZoomFactor(display.id(), zoom_factor);
EXPECT_EQ(
display_manager()->GetDisplayForId(display.id()).device_scale_factor(),
zoom_factor);
const gfx::Rect& clamshell_mode_bounds =
test_tray_background_view()->bubble_view()->GetBoundsInScreen();
// The bubble should have been repositioned due to the scale factor changing,
// so the bounds should be different than when there was a scale factor of 1.
EXPECT_NE(tablet_mode_bounds, clamshell_mode_bounds);
// Close and reopen the bubble, and verify that the bounds when exiting out of
// tablet mode is the same as the initial bounds calculation when showing the
// bubble.
test_tray_background_view()->CloseBubble();
test_tray_background_view()->ShowBubble();
EXPECT_EQ(clamshell_mode_bounds,
test_tray_background_view()->bubble_view()->GetBoundsInScreen());
}
// Tests that the tray bubble is positioned correctly when the device switches
// from tablet to clamshell mode.
TEST_F(TrayBackgroundViewTest, TabletModeTransitionBase) {
TabletModeController* tablet_mode_controller =
Shell::Get()->tablet_mode_controller();
// With no windows open, switching into tablet mode should show the home
// launcher with the hotseat extended.
tablet_mode_controller->SetEnabledForTest(true);
EXPECT_EQ(HotseatState::kShownHomeLauncher,
GetPrimaryShelf()->hotseat_widget()->state());
// Show the bubble.
test_tray_background_view()->ShowBubble();
EXPECT_TRUE(test_tray_background_view()->bubble());
const gfx::Insets& tablet_mode_insets =
test_tray_background_view()->bubble_view()->GetBorderInsets();
// Switch back to clamshell mode.
tablet_mode_controller->SetEnabledForTest(false);
const gfx::Insets& clamshell_mode_insets =
test_tray_background_view()->bubble_view()->GetBorderInsets();
// The bubble should have been repositioned due to switching out of tablet
// mode, so the bottom inset should be updated with the inset hotseat
// compensation calculated from `GetBubbleInsetHotseatCompensation` removed.
EXPECT_LT(clamshell_mode_insets.bottom(), tablet_mode_insets.bottom());
// Close and reopen the bubble, and verify that the insets when exiting out of
// tablet mode is the same as the initial inset calculation when showing the
// bubble.
test_tray_background_view()->CloseBubble();
test_tray_background_view()->ShowBubble();
EXPECT_EQ(clamshell_mode_insets,
test_tray_background_view()->bubble_view()->GetBorderInsets());
}
// Tests the the tray bubble positions itself correctly even when the shelf
// alignments change when transitioning out of tablet mode. This happens when
// the shelf is aligned to either the left or the right.
TEST_F(TrayBackgroundViewTest, TabletModeTransitionForAlignments) {
Shelf* shelf = GetPrimaryShelf();
TabletModeController* tablet_mode_controller =
Shell::Get()->tablet_mode_controller();
const struct {
ShelfAlignment alignment;
} kTestCases[] = {{ShelfAlignment::kLeft}, {ShelfAlignment::kRight}};
for (auto& test : kTestCases) {
shelf->SetAlignment(test.alignment);
tablet_mode_controller->SetEnabledForTest(true);
// Show the bubble.
test_tray_background_view()->ShowBubble();
EXPECT_TRUE(test_tray_background_view()->bubble());
const gfx::Rect& tablet_mode_bounds =
test_tray_background_view()->bubble_view()->GetBoundsInScreen();
// Switch back to clamshell mode.
tablet_mode_controller->SetEnabledForTest(false);
const gfx::Rect& clamshell_mode_bounds =
test_tray_background_view()->bubble_view()->GetBoundsInScreen();
// The bubble should have been repositioned due to switching out of tablet
// mode.
EXPECT_NE(tablet_mode_bounds, clamshell_mode_bounds);
// Close and reopen the bubble, and verify that the bounds when exiting out
// of tablet mode is the same as the initial bounds calculation when showing
// the bubble.
test_tray_background_view()->CloseBubble();
test_tray_background_view()->ShowBubble();
EXPECT_EQ(clamshell_mode_bounds,
test_tray_background_view()->bubble_view()->GetBoundsInScreen());
}
}
TEST_F(TrayBackgroundViewTest, TrayBubbleViewAccessibleProperties) {
test_tray_background_view()->ShowBubble();
TrayBubbleView* bubble_view = test_tray_background_view()->bubble_view();
ASSERT_TRUE(bubble_view->CanActivate());
bubble_view->InitializeAndShowBubble();
ui::AXNodeData data;
bubble_view->GetViewAccessibility().GetAccessibleNodeData(&data);
EXPECT_FALSE(data.HasState(ax::mojom::State::kIgnored));
EXPECT_EQ(ax::mojom::Role::kWindow, data.role);
// Test that bubble view is hidden to a11y when `can_activate_` is false.
bubble_view->SetCanActivate(false);
// `can_activate_` value is set before showing the bubble.
bubble_view->InitializeAndShowBubble();
data = ui::AXNodeData();
bubble_view->GetViewAccessibility().GetAccessibleNodeData(&data);
EXPECT_TRUE(data.HasState(ax::mojom::State::kIgnored));
bubble_view->SetCanActivate(true);
bubble_view->InitializeAndShowBubble();
data = ui::AXNodeData();
bubble_view->GetViewAccessibility().GetAccessibleNodeData(&data);
EXPECT_FALSE(data.HasState(ax::mojom::State::kIgnored));
// Test that bubble view is hidden to a11y when `delegate_` is null.
bubble_view->ResetDelegate();
data = ui::AXNodeData();
bubble_view->GetViewAccessibility().GetAccessibleNodeData(&data);
EXPECT_TRUE(data.HasState(ax::mojom::State::kIgnored));
}
} // namespace ash