blob: 7b6fe5472211dd71db6a00677cc4b61ca9a080d8 [file] [log] [blame]
// 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_controller.h"
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/constants/quick_settings_catalogs.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "ash/system/audio/unified_volume_slider_controller.h"
#include "ash/system/brightness/unified_brightness_slider_controller.h"
#include "ash/system/brightness_control_delegate.h"
#include "ash/system/message_center/ash_message_center_lock_screen_controller.h"
#include "ash/system/unified/notification_hidden_view.h"
#include "ash/system/unified/unified_system_tray.h"
#include "ash/system/unified/unified_system_tray_bubble.h"
#include "ash/system/unified/unified_system_tray_model.h"
#include "ash/system/unified/unified_system_tray_view.h"
#include "ash/test/ash_test_base.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "chromeos/ash/components/audio/cras_audio_handler.h"
#include "chromeos/ash/components/dbus/shill/shill_clients.h"
#include "chromeos/ash/services/network_config/public/cpp/cros_network_config_test_helper.h"
#include "components/prefs/testing_pref_service.h"
#include "ui/compositor/scoped_animation_duration_scale_mode.h"
#include "ui/gfx/animation/slide_animation.h"
#include "ui/views/test/views_test_utils.h"
#include "ui/views/view_observer.h"
namespace ash {
namespace {
void SetSessionState(const session_manager::SessionState& state) {
SessionInfo info;
info.state = state;
Shell::Get()->session_controller()->SetSessionInfo(info);
}
} // anonymous namespace
class UnifiedSystemTrayControllerTest : public AshTestBase,
public views::ViewObserver {
public:
UnifiedSystemTrayControllerTest() = default;
UnifiedSystemTrayControllerTest(const UnifiedSystemTrayControllerTest&) =
delete;
UnifiedSystemTrayControllerTest& operator=(
const UnifiedSystemTrayControllerTest&) = delete;
~UnifiedSystemTrayControllerTest() override = default;
// AshTestBase:
void SetUp() override {
network_config_helper_ =
std::make_unique<network_config::CrosNetworkConfigTestHelper>();
AshTestBase::SetUp();
// Networking stubs may have asynchronous initialization.
base::RunLoop().RunUntilIdle();
model_ = base::MakeRefCounted<UnifiedSystemTrayModel>(nullptr);
controller_ = std::make_unique<UnifiedSystemTrayController>(model());
}
void TearDown() override {
DCHECK(view_) << "Must call InitializeView() during the tests";
view_->RemoveObserver(this);
view_.reset();
controller_.reset();
model_.reset();
AshTestBase::TearDown();
}
void TurnUpVolume() {
CrasAudioHandler::Get()->SetOutputVolumePercent(10);
controller_->volume_slider_controller_->SliderValueChanged(
nullptr, 0.9, 0.1, views::SliderChangeReason::kByUser);
}
void TurnDownVolume() {
CrasAudioHandler::Get()->SetOutputVolumePercent(90);
controller_->volume_slider_controller_->SliderValueChanged(
nullptr, 0.1, 0.9, views::SliderChangeReason::kByUser);
}
void PressOnVolumeButton() {
controller_->volume_slider_controller_->SliderButtonPressed();
}
void ChangeBrightness(float target_value) {
controller_->brightness_slider_controller_->SliderValueChanged(
nullptr, target_value, 0.0, views::SliderChangeReason::kByUser);
}
// views::ViewObserver:
void OnViewPreferredSizeChanged(views::View* observed_view) override {
view_->SetBoundsRect(gfx::Rect(view_->GetPreferredSize()));
views::test::RunScheduledLayout(view_.get());
++preferred_size_changed_count_;
}
protected:
void WaitForAnimation(UnifiedSystemTrayController* controller) {
while (controller->animation_->is_animating())
base::RunLoop().RunUntilIdle();
}
int preferred_size_changed_count() const {
return preferred_size_changed_count_;
}
void InitializeView() {
view_ = controller_->CreateUnifiedQuickSettingsView();
view_->AddObserver(this);
OnViewPreferredSizeChanged(view());
preferred_size_changed_count_ = 0;
}
UnifiedSystemTrayModel* model() { return model_.get(); }
UnifiedSystemTrayController* controller() { return controller_.get(); }
UnifiedSystemTrayView* view() { return view_.get(); }
bool PrimarySystemTrayIsExpandedOnOpen() {
return GetPrimaryUnifiedSystemTray()->model()->IsExpandedOnOpen();
}
private:
std::unique_ptr<network_config::CrosNetworkConfigTestHelper>
network_config_helper_;
scoped_refptr<UnifiedSystemTrayModel> model_;
std::unique_ptr<UnifiedSystemTrayController> controller_;
std::unique_ptr<UnifiedSystemTrayView> view_;
int preferred_size_changed_count_ = 0;
};
class QsRevampUnifiedSystemTrayControllerTest : public AshTestBase {
public:
QsRevampUnifiedSystemTrayControllerTest()
: scoped_feature_list_(features::kQsRevamp) {}
QsRevampUnifiedSystemTrayControllerTest(
const QsRevampUnifiedSystemTrayControllerTest&) = delete;
QsRevampUnifiedSystemTrayControllerTest& operator=(
const QsRevampUnifiedSystemTrayControllerTest&) = delete;
~QsRevampUnifiedSystemTrayControllerTest() override = default;
// AshTestBase:
void SetUp() override {
network_config_helper_ =
std::make_unique<network_config::CrosNetworkConfigTestHelper>();
AshTestBase::SetUp();
// Networking stubs may have asynchronous initialization.
base::RunLoop().RunUntilIdle();
model_ = base::MakeRefCounted<UnifiedSystemTrayModel>(nullptr);
controller_ = std::make_unique<UnifiedSystemTrayController>(model_.get());
}
void TearDown() override {
DCHECK(quick_settings_view_);
widget_.reset();
quick_settings_view_ = nullptr;
controller_.reset();
model_.reset();
AshTestBase::TearDown();
}
void InitializeQuickSettingsView() {
widget_ = CreateFramelessTestWidget();
widget_->SetFullscreen(true);
quick_settings_view_ =
widget_->SetContentsView(controller_->CreateQuickSettingsView(600));
}
std::unique_ptr<network_config::CrosNetworkConfigTestHelper>
network_config_helper_;
scoped_refptr<UnifiedSystemTrayModel> model_;
std::unique_ptr<UnifiedSystemTrayController> controller_;
std::unique_ptr<views::Widget> widget_;
// Owned by `widget_`.
raw_ptr<QuickSettingsView, ExperimentalAsh> quick_settings_view_;
base::test::ScopedFeatureList scoped_feature_list_;
};
// Tests that setting the `UnifiedSystemTrayModel::StateOnOpen` pref to
// collapsed is a no-op with the QSRevamp enabled.
TEST_F(QsRevampUnifiedSystemTrayControllerTest, ExpandedPrefIsNoOp) {
// Set the pref to collapsed, there should be no effect.
model_->set_expanded_on_open(UnifiedSystemTrayModel::StateOnOpen::COLLAPSED);
InitializeQuickSettingsView();
EXPECT_TRUE(model_->IsExpandedOnOpen());
EXPECT_TRUE(controller_->IsExpanded());
}
TEST_F(UnifiedSystemTrayControllerTest, ToggleExpanded) {
InitializeView();
EXPECT_TRUE(model()->IsExpandedOnOpen());
const int expanded_height = view()->GetPreferredSize().height();
controller()->ToggleExpanded();
WaitForAnimation(controller());
const int collapsed_height = view()->GetPreferredSize().height();
EXPECT_LT(collapsed_height, expanded_height);
EXPECT_FALSE(model()->IsExpandedOnOpen());
EXPECT_EQ(expanded_height, view()->GetExpandedSystemTrayHeight());
}
TEST_F(UnifiedSystemTrayControllerTest, UMATracking) {
// No metrics logged before rendering on any views.
auto histogram_tester = std::make_unique<base::HistogramTester>();
histogram_tester->ExpectTotalCount("Ash.UnifiedSystemView.FeaturePod.Visible",
/*count=*/0);
InitializeView();
EXPECT_TRUE(model()->IsExpandedOnOpen());
// Should show network pod.
histogram_tester->ExpectBucketCount(
"Ash.UnifiedSystemView.FeaturePod.Visible",
QsFeatureCatalogName::kNetwork,
/*expected_count=*/1);
// Should not show cast pod since no casting service is setup.
histogram_tester->ExpectBucketCount(
"Ash.UnifiedSystemView.FeaturePod.Visible", QsFeatureCatalogName::kCast,
/*expected_count=*/0);
GetPrimaryUnifiedSystemTray()->CloseBubble();
GetPrimaryUnifiedSystemTray()->ShowBubble();
// Should show network pod again.
histogram_tester->ExpectBucketCount(
"Ash.UnifiedSystemView.FeaturePod.Visible",
QsFeatureCatalogName::kNetwork,
/*expected_count=*/2);
}
TEST_F(UnifiedSystemTrayControllerTest, EnsureExpanded_UserChooserShown) {
InitializeView();
EXPECT_FALSE(view()->detailed_view_for_testing()->GetVisible());
// Show the user chooser view.
controller()->ShowUserChooserView();
EXPECT_TRUE(view()->detailed_view_for_testing()->GetVisible());
// Calling EnsureExpanded() should hide the detailed view (e.g. this can
// happen when changing the brightness or volume).
controller()->EnsureExpanded();
EXPECT_FALSE(view()->detailed_view_for_testing()->GetVisible());
}
TEST_F(UnifiedSystemTrayControllerTest, PreferredSizeChanged) {
InitializeView();
// Checks PreferredSizeChanged is not called too frequently.
EXPECT_EQ(0, preferred_size_changed_count());
view()->SetExpandedAmount(0.0);
EXPECT_EQ(1, preferred_size_changed_count());
view()->SetExpandedAmount(0.25);
EXPECT_EQ(2, preferred_size_changed_count());
view()->SetExpandedAmount(0.75);
EXPECT_EQ(3, preferred_size_changed_count());
view()->SetExpandedAmount(1.0);
EXPECT_EQ(4, preferred_size_changed_count());
}
TEST_F(UnifiedSystemTrayControllerTest, NotificationHiddenView_ModeShow) {
AshMessageCenterLockScreenController::OverrideModeForTest(
AshMessageCenterLockScreenController::Mode::SHOW);
SetSessionState(session_manager::SessionState::LOCKED);
InitializeView();
EXPECT_TRUE(AshMessageCenterLockScreenController::IsAllowed());
EXPECT_TRUE(AshMessageCenterLockScreenController::IsEnabled());
EXPECT_FALSE(view()->notification_hidden_view_for_testing()->GetVisible());
}
TEST_F(UnifiedSystemTrayControllerTest, NotificationHiddenView_ModeHide) {
AshMessageCenterLockScreenController::OverrideModeForTest(
AshMessageCenterLockScreenController::Mode::HIDE);
SetSessionState(session_manager::SessionState::LOCKED);
InitializeView();
EXPECT_TRUE(AshMessageCenterLockScreenController::IsAllowed());
EXPECT_FALSE(AshMessageCenterLockScreenController::IsEnabled());
EXPECT_TRUE(view()->notification_hidden_view_for_testing()->GetVisible());
EXPECT_NE(nullptr, view()
->notification_hidden_view_for_testing()
->change_button_for_testing());
}
TEST_F(UnifiedSystemTrayControllerTest,
NotificationHiddenView_ModeHideSensitive) {
AshMessageCenterLockScreenController::OverrideModeForTest(
AshMessageCenterLockScreenController::Mode::HIDE_SENSITIVE);
SetSessionState(session_manager::SessionState::LOCKED);
InitializeView();
EXPECT_TRUE(AshMessageCenterLockScreenController::IsAllowed());
EXPECT_TRUE(AshMessageCenterLockScreenController::IsEnabled());
EXPECT_FALSE(view()->notification_hidden_view_for_testing()->GetVisible());
}
TEST_F(UnifiedSystemTrayControllerTest, NotificationHiddenView_ModeProhibited) {
AshMessageCenterLockScreenController::OverrideModeForTest(
AshMessageCenterLockScreenController::Mode::PROHIBITED);
SetSessionState(session_manager::SessionState::LOCKED);
InitializeView();
EXPECT_FALSE(AshMessageCenterLockScreenController::IsAllowed());
EXPECT_FALSE(AshMessageCenterLockScreenController::IsEnabled());
EXPECT_TRUE(view()->notification_hidden_view_for_testing()->GetVisible());
EXPECT_EQ(nullptr, view()
->notification_hidden_view_for_testing()
->change_button_for_testing());
}
TEST_F(UnifiedSystemTrayControllerTest, SystemTrayCollapsePref) {
InitializeView();
GetPrimaryUnifiedSystemTray()->ShowBubble();
UnifiedSystemTrayController* controller =
GetPrimaryUnifiedSystemTray()->bubble()->unified_system_tray_controller();
PrefService* prefs =
Shell::Get()->session_controller()->GetLastActiveUserPrefService();
// System tray is initially expanded when no pref is set.
EXPECT_FALSE(prefs->HasPrefPath(prefs::kSystemTrayExpanded));
EXPECT_TRUE(PrimarySystemTrayIsExpandedOnOpen());
// Toggle collapsed state.
controller->ToggleExpanded();
WaitForAnimation(controller);
EXPECT_FALSE(PrimarySystemTrayIsExpandedOnOpen());
// Close bubble and assert pref has been set.
GetPrimaryUnifiedSystemTray()->CloseBubble();
EXPECT_TRUE(prefs->HasPrefPath(prefs::kSystemTrayExpanded));
// Reopen bubble to load `kSystemTrayExpanded` pref.
GetPrimaryUnifiedSystemTray()->ShowBubble();
EXPECT_FALSE(PrimarySystemTrayIsExpandedOnOpen());
}
TEST_F(UnifiedSystemTrayControllerTest, SliderUMA) {
InitializeView();
GetPrimaryUnifiedSystemTray()->ShowBubble();
// No metrics logged before making any action.
auto histogram_tester = std::make_unique<base::HistogramTester>();
histogram_tester->ExpectTotalCount("Ash.UnifiedSystemView.Slider.Up",
/*count=*/0);
histogram_tester->ExpectTotalCount("Ash.UnifiedSystemView.Slider.Down",
/*count=*/0);
histogram_tester->ExpectTotalCount(
"Ash.UnifiedSystemView.Slider.EnableFeature",
/*count=*/0);
histogram_tester->ExpectTotalCount(
"Ash.UnifiedSystemView.Slider.DisableFeature",
/*count=*/0);
TurnUpVolume();
histogram_tester->ExpectTotalCount("Ash.UnifiedSystemView.Slider.Down",
/*count=*/0);
histogram_tester->ExpectTotalCount("Ash.UnifiedSystemView.Slider.Up",
/*count=*/1);
histogram_tester->ExpectBucketCount("Ash.UnifiedSystemView.Slider.Up",
QsSliderCatalogName::kVolume,
/*expected_count=*/1);
TurnDownVolume();
histogram_tester->ExpectTotalCount("Ash.UnifiedSystemView.Slider.Down",
/*count=*/1);
histogram_tester->ExpectTotalCount("Ash.UnifiedSystemView.Slider.Up",
/*count=*/1);
histogram_tester->ExpectBucketCount("Ash.UnifiedSystemView.Slider.Down",
QsSliderCatalogName::kVolume,
/*expected_count=*/1);
// Set the current state to unmute.
CrasAudioHandler::Get()->SetOutputMute(false);
// Mute.
PressOnVolumeButton();
histogram_tester->ExpectTotalCount(
"Ash.UnifiedSystemView.Slider.EnableFeature",
/*count=*/1);
histogram_tester->ExpectTotalCount(
"Ash.UnifiedSystemView.Slider.DisableFeature",
/*count=*/0);
histogram_tester->ExpectBucketCount(
"Ash.UnifiedSystemView.Slider.EnableFeature",
QsSliderCatalogName::kVolume,
/*expected_count=*/1);
// Press again to unmute.
PressOnVolumeButton();
histogram_tester->ExpectTotalCount(
"Ash.UnifiedSystemView.Slider.EnableFeature",
/*count=*/1);
histogram_tester->ExpectTotalCount(
"Ash.UnifiedSystemView.Slider.DisableFeature",
/*count=*/1);
histogram_tester->ExpectBucketCount(
"Ash.UnifiedSystemView.Slider.DisableFeature",
QsSliderCatalogName::kVolume,
/*expected_count=*/1);
// Turn down the brightness. The init value should be 1.0 (100%).
ChangeBrightness(/*target_value=*/0.5);
histogram_tester->ExpectTotalCount("Ash.UnifiedSystemView.Slider.Down",
/*count=*/2);
histogram_tester->ExpectTotalCount("Ash.UnifiedSystemView.Slider.Up",
/*count=*/1);
histogram_tester->ExpectBucketCount("Ash.UnifiedSystemView.Slider.Down",
QsSliderCatalogName::kBrightness,
/*expected_count=*/1);
// Turn up the brightness.
ChangeBrightness(/*target_value=*/0.9);
histogram_tester->ExpectTotalCount("Ash.UnifiedSystemView.Slider.Down",
/*count=*/2);
histogram_tester->ExpectTotalCount("Ash.UnifiedSystemView.Slider.Up",
/*count=*/2);
histogram_tester->ExpectBucketCount("Ash.UnifiedSystemView.Slider.Up",
QsSliderCatalogName::kBrightness,
/*expected_count=*/1);
}
} // namespace ash