0

Reland "Added the display subpage for QsRevamp"

This is a reland of commit 5ccff3bed9

The root cause of the revert issue:
In this CL(
https://chromium-review.googlesource.com/c/chromium/src/+/4195759), we
identified the issue with the brightness slider that it cannot be
changed via keyboard, and this issue caused the tast test failure.

The fix:
We fixed the issue by reverting the logic for brightness slider(combined
the change in the above linked CL with the original CL for the display
subpage).

Original change's description:
> Added the display subpage for QsRevamp
>
> 1. Added the display detailed view for QsRevamp. This view contains a
>    night light feature tile, a dark mode feature tile, and a display
>    brightness slider. The title of this view is `Display` and there is a
>    settings button leading to the display system settings page at the
>    end of the title row.
> 2. Added corresponding unit test for display detailed view and
>    parameterize unit tests for night light and dark mode feature pod
>    controllers.
> 3. Applied the callback for the night light button in the main page.
> 4. Added the vector icon resource for the night light when disabled.
>    Added the string for the display detailed view title.
> 5. Fixed an existing issue with the brightness slider by adding a
>    variable in unified_system_tray_model to store the actual brightness
>    value to render the slider.
>
> Display detailed view overview(locked screen & logged-in screen):
> https://screenshot.googleplex.com/4BDGFDMAv3By5UW
>
> Night Light feature tile different sub labels:
> https://screenshot.googleplex.com/5Uw9bbGimcvd9bG
>
> Dark Mode feature tile different sub labels:
> https://screenshot.googleplex.com/4KTbcabeF2n2x53
>
> Focus behavior:
> https://screenshot.googleplex.com/5dJtJUsPyrHbdQz
>
> Bug: b/252870817
> Change-Id: I7b89b2d21096c94b4d96280c01111a3129dd3b51
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4174144
> Reviewed-by: Jiaming Cheng <jiamingc@chromium.org>
> Reviewed-by: Mitsuru Oshima <oshima@chromium.org>
> Commit-Queue: Sylvie Liu <sylvieliu@chromium.org>
> Cr-Commit-Position: refs/heads/main@{#1097004}

Bug: b/252870817
Change-Id: I4c4d86eed53df82e3e6ce57a41c6f668394e9b01
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4198495
Reviewed-by: Alex Newcomer <newcomer@chromium.org>
Reviewed-by: Mitsuru Oshima <oshima@chromium.org>
Commit-Queue: Sylvie Liu <sylvieliu@chromium.org>
Reviewed-by: Jiaming Cheng <jiamingc@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1097621}
This commit is contained in:
Sylvie Liu
2023-01-26 21:58:54 +00:00
committed by Chromium LUCI CQ
parent 4f2ddafd32
commit 1fbe4538d6
29 changed files with 1157 additions and 244 deletions

@ -1190,6 +1190,10 @@ component("ash") {
"system/bluetooth/bluetooth_notification_controller.h",
"system/brightness/brightness_controller_chromeos.cc",
"system/brightness/brightness_controller_chromeos.h",
"system/brightness/display_detailed_view.cc",
"system/brightness/display_detailed_view.h",
"system/brightness/quick_settings_display_detailed_view_controller.cc",
"system/brightness/quick_settings_display_detailed_view_controller.h",
"system/brightness/unified_brightness_slider_controller.cc",
"system/brightness/unified_brightness_slider_controller.h",
"system/brightness/unified_brightness_view.cc",
@ -3027,6 +3031,7 @@ test("ash_unittests") {
"system/bluetooth/fake_bluetooth_detailed_view.h",
"system/bluetooth/fake_bluetooth_device_list_controller.cc",
"system/bluetooth/fake_bluetooth_device_list_controller.h",
"system/brightness/display_detailed_view_unittest.cc",
"system/brightness/unified_brightness_view_unittest.cc",
"system/camera/autozoom_feature_pod_controller_unittest.cc",
"system/camera/autozoom_toast_controller_unittest.cc",

@ -2703,6 +2703,9 @@ Connect your device to power.
<message name="IDS_ASH_STATUS_TRAY_AUDIO_INPUT" desc="The label used in audio detailed page for audio input section of ash tray pop up.">
Input
</message>
<message name="IDS_ASH_STATUS_TRAY_DISPLAY" desc="The label used for the button in the status tray to show the display detailed page.">
Display
</message>
<message name="IDS_ASH_STATUS_TRAY_DISPLAY_REMOVED_EXCEEDED_MAXIMUM" desc="The label used when user connects more external displays than the maximum that the device can support.">
This device couldn't support all of your displays, so one has been disconnected
</message>

@ -0,0 +1 @@
4fe893b2832867a95f7c85a014a56f7712e6929b

@ -61,6 +61,13 @@ enum ViewID {
// Shown in system tray detailed views:
VIEW_ID_QS_DETAILED_VIEW_BACK_BUTTON,
// QS revamped display detailed view:
VIEW_ID_QS_DISPLAY_MIN,
VIEW_ID_QS_DISPLAY_BRIGHTNESS_SLIDER = VIEW_ID_QS_DISPLAY_MIN,
VIEW_ID_QS_DISPLAY_SCROLL_CONTENT,
VIEW_ID_QS_DISPLAY_TILE_CONTAINER,
VIEW_ID_QS_DISPLAY_MAX = VIEW_ID_QS_DISPLAY_TILE_CONTAINER,
// Status area trays:
VIEW_ID_SA_MIN,
VIEW_ID_SA_DATE_TRAY = VIEW_ID_SA_MIN,

@ -471,6 +471,7 @@ aggregate_vector_icons("ash_vector_icons") {
"unified_menu_brightness_medium.icon",
"unified_menu_cast.icon",
"unified_menu_dark_mode.icon",
"unified_menu_dark_mode_off.icon",
"unified_menu_do_not_disturb.icon",
"unified_menu_expand.icon",
"unified_menu_info.icon",

@ -0,0 +1,30 @@
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
CANVAS_DIMENSIONS, 20,
MOVE_TO, 3.64f, 5.14f,
LINE_TO, 2, 3.5f,
LINE_TO, 3.24f, 2.26f,
LINE_TO, 18.09f, 17.11f,
LINE_TO, 16.85f, 18.35f,
LINE_TO, 14.86f, 16.36f,
CUBIC_TO, 13.51f, 17.39f, 11.83f, 18, 10, 18,
CUBIC_TO, 5.58f, 18, 2, 14.42f, 2, 10,
CUBIC_TO, 2, 8.17f, 2.61f, 6.49f, 3.64f, 5.14f,
CLOSE,
MOVE_TO, 13.51f, 15.01f,
CUBIC_TO, 12.52f, 15.71f, 11.31f, 16.12f, 10, 16.12f,
V_LINE_TO, 11.5f,
LINE_TO, 13.51f, 15.01f,
CLOSE,
MOVE_TO, 16.12f, 10,
CUBIC_TO, 16.12f, 10.79f, 15.97f, 11.55f, 15.69f, 12.24f,
LINE_TO, 17.11f, 13.66f,
CUBIC_TO, 17.68f, 12.57f, 18, 11.32f, 18, 10,
CUBIC_TO, 18, 5.58f, 14.42f, 2, 10, 2,
CUBIC_TO, 8.68f, 2, 7.43f, 2.32f, 6.34f, 2.89f,
LINE_TO, 10, 6.55f,
V_LINE_TO, 3.88f,
CUBIC_TO, 13.38f, 3.88f, 16.12f, 6.62f, 16.12f, 10,
CLOSE

@ -0,0 +1,133 @@
// Copyright 2023 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/brightness/display_detailed_view.h"
#include <memory>
#include "ash/public/cpp/ash_view_ids.h"
#include "ash/public/cpp/system_tray_client.h"
#include "ash/root_window_controller.h"
#include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/system/brightness/unified_brightness_slider_controller.h"
#include "ash/system/dark_mode/dark_mode_feature_pod_controller.h"
#include "ash/system/model/system_tray_model.h"
#include "ash/system/night_light/night_light_feature_pod_controller.h"
#include "ash/system/status_area_widget.h"
#include "ash/system/tray/detailed_view_delegate.h"
#include "ash/system/tray/tray_popup_utils.h"
#include "ash/system/unified/feature_tile.h"
#include "ash/system/unified/unified_system_tray.h"
#include "ash/system/unified/unified_system_tray_controller.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/views/border.h"
#include "ui/views/controls/scroll_view.h"
#include "ui/views/layout/flex_layout.h"
#include "ui/views/view.h"
#include "ui/views/view_class_properties.h"
namespace ash {
namespace {
constexpr auto kScrollViewMargin = gfx::Insets::TLBR(0, 12, 16, 12);
constexpr auto kTileMargin = gfx::Insets::TLBR(1, 4, 8, 4);
constexpr auto kSliderPadding = gfx::Insets::TLBR(4, 0, 0, 0);
constexpr auto kSliderBorder = gfx::Insets::VH(0, 4);
} // namespace
DisplayDetailedView::DisplayDetailedView(
DetailedViewDelegate* delegate,
UnifiedSystemTrayController* tray_controller)
: TrayDetailedView(delegate),
unified_system_tray_controller_(tray_controller) {
CreateScrollableList();
CreateTitleRow(IDS_ASH_STATUS_TRAY_DISPLAY);
CreateTitleSettingsButton();
// Sets the margin for `ScrollView` to leave some space for the focus ring.
scroller()->SetProperty(views::kMarginsKey, kScrollViewMargin);
auto night_light_controller =
std::make_unique<NightLightFeaturePodController>(
unified_system_tray_controller_);
auto dark_mode_controller = std::make_unique<DarkModeFeaturePodController>(
unified_system_tray_controller_);
auto tile_container = std::make_unique<views::View>();
// Sets the ID for testing.
tile_container->SetID(VIEW_ID_QS_DISPLAY_TILE_CONTAINER);
tile_container->AddChildView(night_light_controller->CreateTile());
tile_container->AddChildView(dark_mode_controller->CreateTile());
// Transfers the ownership so the controllers won't die while the page is
// open.
feature_tile_controllers_.push_back(std::move(night_light_controller));
feature_tile_controllers_.push_back(std::move(dark_mode_controller));
auto* tile_layout =
tile_container->SetLayoutManager(std::make_unique<views::FlexLayout>());
tile_layout->SetDefault(views::kMarginsKey, kTileMargin);
scroll_content()->AddChildView(std::move(tile_container));
brightness_slider_controller_ =
std::make_unique<UnifiedBrightnessSliderController>(
Shell::GetPrimaryRootWindowController()
->GetStatusAreaWidget()
->unified_system_tray()
->model(),
views::Button::PressedCallback());
auto unified_brightness_view =
brightness_slider_controller_->CreateBrightnessSlider();
// Sets the ID for testing.
unified_brightness_view->SetID(VIEW_ID_QS_DISPLAY_BRIGHTNESS_SLIDER);
unified_brightness_view->slider()->SetBorder(
views::CreateEmptyBorder(kSliderBorder));
auto* slider_layout = unified_brightness_view->SetLayoutManager(
std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kHorizontal, kSliderPadding,
/*between_child_spacing=*/0));
slider_layout->SetFlexForView(unified_brightness_view->slider()->parent(),
/*flex=*/1);
scroll_content()->AddChildView(std::move(unified_brightness_view));
// Sets the ID for testing.
scroll_content()->SetID(VIEW_ID_QS_DISPLAY_SCROLL_CONTENT);
}
DisplayDetailedView::~DisplayDetailedView() = default;
views::View* DisplayDetailedView::GetScrollContentForTest() {
// Provides access to the protected scroll_content() in the base class.
return scroll_content();
}
void DisplayDetailedView::CreateTitleSettingsButton() {
DCHECK(!settings_button_);
tri_view()->SetContainerVisible(TriView::Container::END, /*visible=*/true);
settings_button_ = CreateSettingsButton(
base::BindRepeating(&DisplayDetailedView::OnSettingsClicked,
weak_factory_.GetWeakPtr()),
IDS_ASH_STATUS_TRAY_NIGHT_LIGHT_SETTINGS_TOOLTIP);
settings_button_->SetState(TrayPopupUtils::CanOpenWebUISettings()
? views::Button::STATE_NORMAL
: views::Button::STATE_DISABLED);
tri_view()->AddView(TriView::Container::END, settings_button_);
}
void DisplayDetailedView::OnSettingsClicked() {
if (TrayPopupUtils::CanOpenWebUISettings()) {
CloseBubble();
Shell::Get()->system_tray_model()->client()->ShowDisplaySettings();
}
}
BEGIN_METADATA(DisplayDetailedView, views::View)
END_METADATA
} // namespace ash

@ -0,0 +1,65 @@
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef ASH_SYSTEM_BRIGHTNESS_DISPLAY_DETAILED_VIEW_H_
#define ASH_SYSTEM_BRIGHTNESS_DISPLAY_DETAILED_VIEW_H_
#include <memory>
#include "ash/ash_export.h"
#include "ash/system/tray/tray_detailed_view.h"
#include "ui/base/metadata/metadata_header_macros.h"
namespace views {
class View;
} // namespace views
namespace ash {
class UnifiedBrightnessSliderController;
class UnifiedSystemTrayController;
class FeaturePodControllerBase;
// The detailed view to show when the drill-in button next to the brightness
// slider is clicked. This view contains a night light feature tile, a dark mode
// feature tile, and a brightness slider.
class ASH_EXPORT DisplayDetailedView : public TrayDetailedView {
public:
METADATA_HEADER(DisplayDetailedView);
DisplayDetailedView(DetailedViewDelegate* delegate,
UnifiedSystemTrayController* tray_controller);
DisplayDetailedView(const DisplayDetailedView&) = delete;
DisplayDetailedView& operator=(const DisplayDetailedView&) = delete;
~DisplayDetailedView() override;
views::View* GetScrollContentForTest();
private:
// Creates the `settings_button_` on the right end of the title row.
void CreateTitleSettingsButton();
// Callback of the `settings_button_` to open the display system settings
// page.
void OnSettingsClicked();
std::unique_ptr<UnifiedBrightnessSliderController>
brightness_slider_controller_;
UnifiedSystemTrayController* const unified_system_tray_controller_;
// The vector of `FeaturePodControllerBase`. This is needed to store the
// controllers of both tiles so that the controllers exist while the page is
// open.
std::vector<std::unique_ptr<FeaturePodControllerBase>>
feature_tile_controllers_;
// Owned by the views hierarchy.
views::Button* settings_button_ = nullptr;
base::WeakPtrFactory<DisplayDetailedView> weak_factory_{this};
};
} // namespace ash
#endif // ASH_SYSTEM_BRIGHTNESS_DISPLAY_DETAILED_VIEW_H_

@ -0,0 +1,76 @@
// Copyright 2023 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/brightness/display_detailed_view.h"
#include <memory>
#include <vector>
#include "ash/constants/ash_features.h"
#include "ash/public/cpp/ash_view_ids.h"
#include "ash/system/tray/detailed_view_delegate.h"
#include "ash/system/tray/fake_detailed_view_delegate.h"
#include "ash/test/ash_test_base.h"
#include "base/test/scoped_feature_list.h"
#include "ui/views/widget/widget.h"
namespace ash {
namespace {
class DisplayDetailedViewTest : public AshTestBase {
public:
void SetUp() override {
feature_list_.InitWithFeatures({features::kQsRevamp}, {});
AshTestBase::SetUp();
// Create a widget so tests can click on views.
widget_ = CreateFramelessTestWidget();
widget_->SetFullscreen(true);
delegate_ = std::make_unique<FakeDetailedViewDelegate>();
// Passes in a fake delegate and a nullptr as `tray_controller` since we
// don't need to test the actual functionality of controllers.
detailed_view_ =
widget_->SetContentsView(std::make_unique<DisplayDetailedView>(
delegate_.get(), /*tray_controller=*/nullptr));
}
void TearDown() override {
widget_.reset();
detailed_view_ = nullptr;
delegate_.reset();
AshTestBase::TearDown();
}
base::test::ScopedFeatureList feature_list_;
std::unique_ptr<views::Widget> widget_;
std::unique_ptr<DetailedViewDelegate> delegate_;
DisplayDetailedView* detailed_view_ = nullptr;
};
TEST_F(DisplayDetailedViewTest, ScrollContentChildren) {
// The scroll content has two children, one feature tile container and one
// `UnifiedBrightnessView`.
views::View* scroll_content =
detailed_view_->GetViewByID(VIEW_ID_QS_DISPLAY_SCROLL_CONTENT);
ASSERT_TRUE(scroll_content);
ASSERT_EQ(scroll_content->children().size(), 2u);
// The first child of scroll content is the `tile_container`, which has two
// children (night light and dark mode feature tiles).
views::View* tile_container =
scroll_content->GetViewByID(VIEW_ID_QS_DISPLAY_TILE_CONTAINER);
ASSERT_TRUE(tile_container);
ASSERT_EQ(tile_container->children().size(), 2u);
EXPECT_STREQ(tile_container->children()[0]->GetClassName(), "FeatureTile");
EXPECT_STREQ(tile_container->children()[1]->GetClassName(), "FeatureTile");
// The second children of scroll content is the `UnifiedBrightnessView`.
views::View* unified_brightness_view =
scroll_content->GetViewByID(VIEW_ID_QS_DISPLAY_BRIGHTNESS_SLIDER);
EXPECT_STREQ(unified_brightness_view->GetClassName(),
"UnifiedBrightnessView");
}
} // namespace
} // namespace ash

@ -0,0 +1,35 @@
// Copyright 2023 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/brightness/quick_settings_display_detailed_view_controller.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/system/brightness/display_detailed_view.h"
#include "ash/system/tray/detailed_view_delegate.h"
#include "ui/base/l10n/l10n_util.h"
namespace ash {
QuickSettingsDisplayDetailedViewController::
QuickSettingsDisplayDetailedViewController(
UnifiedSystemTrayController* tray_controller)
: detailed_view_delegate_(
std::make_unique<DetailedViewDelegate>(tray_controller)),
tray_controller_(tray_controller) {}
QuickSettingsDisplayDetailedViewController::
~QuickSettingsDisplayDetailedViewController() = default;
std::unique_ptr<views::View>
QuickSettingsDisplayDetailedViewController::CreateView() {
return std::make_unique<DisplayDetailedView>(detailed_view_delegate_.get(),
tray_controller_);
}
std::u16string QuickSettingsDisplayDetailedViewController::GetAccessibleName()
const {
return l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_DISPLAY);
}
} // namespace ash

@ -0,0 +1,44 @@
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef ASH_SYSTEM_BRIGHTNESS_QUICK_SETTINGS_DISPLAY_DETAILED_VIEW_CONTROLLER_H_
#define ASH_SYSTEM_BRIGHTNESS_QUICK_SETTINGS_DISPLAY_DETAILED_VIEW_CONTROLLER_H_
#include <memory>
#include "ash/ash_export.h"
#include "ash/system/unified/detailed_view_controller.h"
namespace ash {
class DisplayDetailedView;
class DetailedViewDelegate;
class UnifiedSystemTrayController;
// Controller of `DisplayDetailedView` in `UnifiedSystemTray`.
class ASH_EXPORT QuickSettingsDisplayDetailedViewController
: public DetailedViewController {
public:
explicit QuickSettingsDisplayDetailedViewController(
UnifiedSystemTrayController* tray_controller);
QuickSettingsDisplayDetailedViewController(
const QuickSettingsDisplayDetailedViewController&) = delete;
QuickSettingsDisplayDetailedViewController& operator=(
const QuickSettingsDisplayDetailedViewController&) = delete;
~QuickSettingsDisplayDetailedViewController() override;
// DetailedViewController:
std::unique_ptr<views::View> CreateView() override;
std::u16string GetAccessibleName() const override;
private:
const std::unique_ptr<DetailedViewDelegate> detailed_view_delegate_;
UnifiedSystemTrayController* const tray_controller_;
};
} // namespace ash
#endif // ASH_SYSTEM_BRIGHTNESS_QUICK_SETTINGS_DISPLAY_DETAILED_VIEW_CONTROLLER_H_

@ -4,6 +4,8 @@
#include "ash/system/brightness/unified_brightness_slider_controller.h"
#include <memory>
#include "ash/constants/quick_settings_catalogs.h"
#include "ash/shell.h"
#include "ash/system/brightness/unified_brightness_view.h"
@ -13,16 +15,32 @@
namespace ash {
namespace {
// We don't let the screen brightness go lower than this when it's being
// adjusted via the slider. Otherwise, if the user doesn't know about the
// brightness keys, they may turn the backlight off and not know how to turn
// it back on.
static constexpr double kMinBrightnessPercent = 5.0;
} // namespace
UnifiedBrightnessSliderController::UnifiedBrightnessSliderController(
scoped_refptr<UnifiedSystemTrayModel> model)
: model_(model) {}
scoped_refptr<UnifiedSystemTrayModel> model,
views::Button::PressedCallback callback)
: model_(model), callback_(callback) {}
UnifiedBrightnessSliderController::~UnifiedBrightnessSliderController() =
default;
std::unique_ptr<UnifiedBrightnessView>
UnifiedBrightnessSliderController::CreateBrightnessSlider() {
return std::make_unique<UnifiedBrightnessView>(this, model_);
}
views::View* UnifiedBrightnessSliderController::CreateView() {
DCHECK(!slider_);
slider_ = new UnifiedBrightnessView(this, model_);
slider_ = new UnifiedBrightnessView(this, model_, callback_);
return slider_;
}
@ -48,9 +66,6 @@ void UnifiedBrightnessSliderController::SliderValueChanged(
// we don't update the actual brightness.
if (percent < kMinBrightnessPercent &&
previous_percent_ < kMinBrightnessPercent) {
// We still need to call `OnDisplayBrightnessChanged()` to update the icon
// of the slider, we just don't update the brightness value.
brightness_control_delegate->SetBrightnessPercent(previous_percent_, true);
return;
}

@ -5,6 +5,8 @@
#ifndef ASH_SYSTEM_BRIGHTNESS_UNIFIED_BRIGHTNESS_SLIDER_CONTROLLER_H_
#define ASH_SYSTEM_BRIGHTNESS_UNIFIED_BRIGHTNESS_SLIDER_CONTROLLER_H_
#include <memory>
#include "ash/constants/quick_settings_catalogs.h"
#include "ash/system/unified/unified_slider_view.h"
#include "base/memory/scoped_refptr.h"
@ -12,12 +14,14 @@
namespace ash {
class UnifiedSystemTrayModel;
class UnifiedBrightnessView;
// Controller of a slider that can change display brightness.
class UnifiedBrightnessSliderController : public UnifiedSliderListener {
class ASH_EXPORT UnifiedBrightnessSliderController
: public UnifiedSliderListener {
public:
explicit UnifiedBrightnessSliderController(
scoped_refptr<UnifiedSystemTrayModel> model);
UnifiedBrightnessSliderController(scoped_refptr<UnifiedSystemTrayModel> model,
views::Button::PressedCallback callback);
UnifiedBrightnessSliderController(const UnifiedBrightnessSliderController&) =
delete;
@ -26,6 +30,10 @@ class UnifiedBrightnessSliderController : public UnifiedSliderListener {
~UnifiedBrightnessSliderController() override;
// For QsRevamp: Creates a slider view for the brightness slider in
// `DisplayDetailedView`.
std::unique_ptr<UnifiedBrightnessView> CreateBrightnessSlider();
// UnifiedSliderListener:
views::View* CreateView() override;
QsSliderCatalogName GetCatalogName() override;
@ -34,14 +42,9 @@ class UnifiedBrightnessSliderController : public UnifiedSliderListener {
float old_value,
views::SliderChangeReason reason) override;
// We don't let the screen brightness go lower than this when it's being
// adjusted via the slider. Otherwise, if the user doesn't know about the
// brightness keys, they may turn the backlight off and not know how to turn
// it back on.
static constexpr double kMinBrightnessPercent = 5.0;
private:
scoped_refptr<UnifiedSystemTrayModel> model_;
views::Button::PressedCallback const callback_;
UnifiedSliderView* slider_ = nullptr;
// We have to store previous manually set value because |old_value| might be

@ -4,16 +4,22 @@
#include "ash/system/brightness/unified_brightness_view.h"
#include <memory>
#include "ash/constants/ash_features.h"
#include "ash/resources/vector_icons/vector_icons.h"
#include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/style/ash_color_id.h"
#include "ash/system/brightness/unified_brightness_slider_controller.h"
#include "ash/system/night_light/night_light_controller_impl.h"
#include "ash/system/tray/tray_constants.h"
#include "base/functional/bind.h"
#include "base/memory/scoped_refptr.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/models/image_model.h"
#include "ui/chromeos/styles/cros_tokens_color_mappings.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/gfx/vector_icon_types.h"
namespace ash {
@ -36,25 +42,42 @@ const gfx::VectorIcon& GetBrightnessIconForLevel(float level) {
UnifiedBrightnessView::UnifiedBrightnessView(
UnifiedBrightnessSliderController* controller,
scoped_refptr<UnifiedSystemTrayModel> model)
scoped_refptr<UnifiedSystemTrayModel> model,
absl::optional<views::Button::PressedCallback> detailed_button_callback)
: UnifiedSliderView(views::Button::PressedCallback(),
controller,
kUnifiedMenuBrightnessIcon,
IDS_ASH_STATUS_TRAY_BRIGHTNESS),
model_(model),
controller_(controller) {
night_light_controller_(Shell::Get()->night_light_controller()) {
model_->AddObserver(this);
if (features::IsQsRevampEnabled()) {
AddChildView(std::make_unique<IconButton>(
views::Button::PressedCallback(), IconButton::Type::kMedium,
&kUnifiedMenuNightLightOffIcon,
IDS_ASH_STATUS_TRAY_NIGHT_LIGHT_BUTTON_LABEL,
// For QsRevamp: This case applies to the brightness slider in the
// `DisplayDetailedView`. If `detailed_button_callback` is not passed in,
// both the `night_light_button_` and the drill-in button will not be added.
if (!detailed_button_callback.has_value()) {
OnDisplayBrightnessChanged(/*by_user=*/false);
return;
}
const bool enabled = night_light_controller_->GetEnabled();
night_light_button_ = AddChildView(std::make_unique<IconButton>(
base::BindRepeating(&UnifiedBrightnessView::OnNightLightButtonPressed,
base::Unretained(this)),
IconButton::Type::kMedium,
enabled ? &kUnifiedMenuNightLightIcon : &kUnifiedMenuNightLightOffIcon,
l10n_util::GetStringFUTF16(
IDS_ASH_STATUS_TRAY_NIGHT_LIGHT_TOGGLE_TOOLTIP,
l10n_util::GetStringUTF16(
enabled
? IDS_ASH_STATUS_TRAY_NIGHT_LIGHT_ENABLED_STATE_TOOLTIP
: IDS_ASH_STATUS_TRAY_NIGHT_LIGHT_DISABLED_STATE_TOOLTIP)),
/*is_togglable=*/true,
/*has_border=*/true));
AddChildView(std::make_unique<IconButton>(
views::Button::PressedCallback(),
features::IsQsRevampEnabled() ? IconButton::Type::kMediumFloating
: IconButton::Type::kMedium,
&kQuickSettingsRightArrowIcon,
std::move(detailed_button_callback.value()),
IconButton::Type::kMediumFloating, &kQuickSettingsRightArrowIcon,
IDS_ASH_STATUS_TRAY_NIGHT_LIGHT_SETTINGS_TOOLTIP));
} else {
button()->SetEnabled(false);
@ -65,9 +88,7 @@ UnifiedBrightnessView::UnifiedBrightnessView(
ui::ImageModel::FromVectorIcon(kUnifiedMenuBrightnessIcon,
kColorAshButtonIconColor));
}
model_->AddObserver(this);
OnDisplayBrightnessChanged(false /* by_user */);
OnDisplayBrightnessChanged(/*by_user=*/false);
}
UnifiedBrightnessView::~UnifiedBrightnessView() {
@ -76,25 +97,45 @@ UnifiedBrightnessView::~UnifiedBrightnessView() {
void UnifiedBrightnessView::OnDisplayBrightnessChanged(bool by_user) {
float level = model_->display_brightness();
float slider_level = slider()->GetValue();
// If level is less than `kMinBrightnessPercent`, use the slider value as
// `level` so that when the slider is at 0 point, the icon for the slider is
// `kUnifiedMenuBrightnessLowIcon`. Otherwise `level` will remain to be
// `kMinBrightnessPercent` and the icon cannot be updated.
if (level * 100 <= controller_->kMinBrightnessPercent) {
level = slider_level;
}
if (features::IsQsRevampEnabled()) {
slider_icon()->SetImage(ui::ImageModel::FromVectorIcon(
GetBrightnessIconForLevel(level),
cros_tokens::kCrosSysSystemOnPrimaryContainer, kQsSliderIconSize));
}
SetSliderValue(level, by_user);
}
void UnifiedBrightnessView::OnNightLightButtonPressed() {
night_light_controller_->Toggle();
UpdateNightLightButton();
}
void UnifiedBrightnessView::UpdateNightLightButton() {
const bool enabled = night_light_controller_->GetEnabled();
// Updates the icon of `night_light_button_`.
night_light_button_->SetVectorIcon(enabled ? kUnifiedMenuNightLightIcon
: kUnifiedMenuNightLightOffIcon);
// Updates the tooltip of `night_light_button_`.
std::u16string toggle_tooltip = l10n_util::GetStringUTF16(
enabled ? IDS_ASH_STATUS_TRAY_NIGHT_LIGHT_ENABLED_STATE_TOOLTIP
: IDS_ASH_STATUS_TRAY_NIGHT_LIGHT_DISABLED_STATE_TOOLTIP);
night_light_button_->SetTooltipText(l10n_util::GetStringFUTF16(
IDS_ASH_STATUS_TRAY_NIGHT_LIGHT_TOGGLE_TOOLTIP, toggle_tooltip));
}
void UnifiedBrightnessView::VisibilityChanged(View* starting_from,
bool is_visible) {
OnDisplayBrightnessChanged(/*by_user=*/false);
// Only updates the `night_light_button_` if in the main page.
if (night_light_button_) {
UpdateNightLightButton();
}
}
BEGIN_METADATA(UnifiedBrightnessView, views::View)
END_METADATA

@ -7,15 +7,14 @@
#include "ash/ash_export.h"
#include "ash/resources/vector_icons/vector_icons.h"
#include "ash/system/brightness/unified_brightness_slider_controller.h"
#include "ash/system/night_light/night_light_controller_impl.h"
#include "ash/system/unified/unified_slider_view.h"
#include "ash/system/unified/unified_system_tray_model.h"
#include "base/memory/scoped_refptr.h"
#include "ui/base/metadata/metadata_header_macros.h"
namespace ash {
class UnifiedBrightnessSliderController;
// View of a slider that can change display brightness. It observes current
// brightness level from UnifiedSystemTrayModel.
class ASH_EXPORT UnifiedBrightnessView
@ -25,11 +24,11 @@ class ASH_EXPORT UnifiedBrightnessView
METADATA_HEADER(UnifiedBrightnessView);
UnifiedBrightnessView(UnifiedBrightnessSliderController* controller,
scoped_refptr<UnifiedSystemTrayModel> model);
scoped_refptr<UnifiedSystemTrayModel> model,
absl::optional<views::Button::PressedCallback>
detailed_button_callback = absl::nullopt);
UnifiedBrightnessView(const UnifiedBrightnessView&) = delete;
UnifiedBrightnessView& operator=(const UnifiedBrightnessView&) = delete;
~UnifiedBrightnessView() override;
// UnifiedSystemTrayModel::Observer:
@ -45,8 +44,19 @@ class ASH_EXPORT UnifiedBrightnessView
};
private:
// Callback called when `night_light_button_` is pressed.
void OnNightLightButtonPressed();
// Updates the icon and tooltip of `night_light_button_`.
void UpdateNightLightButton();
// UnifiedSliderView::
void VisibilityChanged(View* starting_from, bool is_visible) override;
scoped_refptr<UnifiedSystemTrayModel> model_;
UnifiedBrightnessSliderController* const controller_;
NightLightControllerImpl* const night_light_controller_;
// Owned by the views hierarchy.
IconButton* night_light_button_ = nullptr;
};
} // namespace ash

@ -36,12 +36,15 @@ class UnifiedBrightnessViewTest : public AshTestBase {
controller()->brightness_slider_controller_.get();
unified_brightness_view_ = static_cast<UnifiedBrightnessView*>(
controller()->unified_brightness_view_);
brightness_slider_ =
brightness_slider_controller_->CreateBrightnessSlider();
}
void TearDown() override {
// Tests the environment tears down with the bubble closed.
// In `UnifiedVolumeViewTest`, the environment is torn down with the bubble
// open, so we can test both cases.
brightness_slider_ = nullptr;
GetPrimaryUnifiedSystemTray()->CloseBubble();
AshTestBase::TearDown();
}
@ -59,6 +62,10 @@ class UnifiedBrightnessViewTest : public AshTestBase {
return unified_brightness_view_;
}
UnifiedBrightnessView* brightness_slider() {
return brightness_slider_.get();
}
views::Slider* slider() { return unified_brightness_view_->slider(); }
views::ImageView* slider_icon() {
@ -72,7 +79,13 @@ class UnifiedBrightnessViewTest : public AshTestBase {
}
private:
// The `UnifiedBrightnessView` containing a `QuickSettingsSlider`, a
// `NightLight` button, and a drill-in button.
UnifiedBrightnessView* unified_brightness_view_ = nullptr;
// The `UnifiedBrightnessView` containing only a `QuickSettingsSlider`.
std::unique_ptr<UnifiedBrightnessView> brightness_slider_ = nullptr;
UnifiedBrightnessSliderController* brightness_slider_controller_ = nullptr;
base::test::ScopedFeatureList feature_list_;
};
@ -80,6 +93,7 @@ class UnifiedBrightnessViewTest : public AshTestBase {
// Tests that `UnifiedBrightnessView` is made up of a `QuickSettingsSlider`, a
// `NightLight` button, and a drill-in button that leads to the display subpage.
TEST_F(UnifiedBrightnessViewTest, SliderButtonComponents) {
EXPECT_EQ(unified_brightness_view()->children().size(), 3u);
EXPECT_STREQ(
unified_brightness_view()->children()[0]->children()[0]->GetClassName(),
"QuickSettingsSlider");
@ -88,10 +102,13 @@ TEST_F(UnifiedBrightnessViewTest, SliderButtonComponents) {
auto* night_light_button =
static_cast<IconButton*>(unified_brightness_view()->children()[1]);
EXPECT_STREQ(night_light_button->GetClassName(), "IconButton");
EXPECT_EQ(
night_light_button->GetAccessibleName(),
l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_NIGHT_LIGHT_BUTTON_LABEL));
EXPECT_EQ(night_light_button->GetTooltipText(), u"Night Light");
EXPECT_EQ(night_light_button->GetAccessibleName(),
l10n_util::GetStringFUTF16(
IDS_ASH_STATUS_TRAY_NIGHT_LIGHT_TOGGLE_TOOLTIP,
l10n_util::GetStringUTF16(
IDS_ASH_STATUS_TRAY_NIGHT_LIGHT_DISABLED_STATE_TOOLTIP)));
EXPECT_EQ(night_light_button->GetTooltipText(),
u"Toggle Night Light. Night Light is off.");
auto* display_subpage_drill_in_button =
static_cast<IconButton*>(unified_brightness_view()->children()[2]);
@ -106,6 +123,15 @@ TEST_F(UnifiedBrightnessViewTest, SliderButtonComponents) {
// drill-in button.
}
// Tests that `UnifiedBrightnessView` in the display subpage is made up of a
// `QuickSettingsSlider`.
TEST_F(UnifiedBrightnessViewTest, SliderComponent) {
EXPECT_EQ(brightness_slider()->children().size(), 1u);
EXPECT_STREQ(
brightness_slider()->children()[0]->children()[0]->GetClassName(),
"QuickSettingsSlider");
}
// Tests the slider icon matches the slider level.
TEST_F(UnifiedBrightnessViewTest, SliderIcon) {
const float levels[] = {0.0, 0.04, 0.2, 0.25, 0.49, 0.5, 0.7, 0.75, 0.9, 1};
@ -126,8 +152,10 @@ TEST_F(UnifiedBrightnessViewTest, SliderIcon) {
slider_icon()->GetImageModel().GetVectorIcon().vector_icon();
if (level <= 0.0) {
// The minimum level for brightness is 0.05, since `SliderValueChanged()`
// will adjust the brightness level and set the icon accordingly.
EXPECT_STREQ(icon->name,
UnifiedBrightnessView::kBrightnessLevelIcons[0]->name);
UnifiedBrightnessView::kBrightnessLevelIcons[1]->name);
} else if (level <= 0.5) {
EXPECT_STREQ(icon->name,
UnifiedBrightnessView::kBrightnessLevelIcons[1]->name);

@ -4,6 +4,7 @@
#include "ash/system/dark_mode/dark_mode_feature_pod_controller.h"
#include "ash/constants/ash_features.h"
#include "ash/constants/quick_settings_catalogs.h"
#include "ash/public/cpp/system_tray_client.h"
#include "ash/resources/vector_icons/vector_icons.h"
@ -14,16 +15,28 @@
#include "ash/system/model/system_tray_model.h"
#include "ash/system/tray/tray_popup_utils.h"
#include "ash/system/unified/feature_pod_button.h"
#include "ash/system/unified/feature_tile.h"
#include "ash/system/unified/quick_settings_metrics_util.h"
#include "base/metrics/histogram_functions.h"
#include "ui/base/l10n/l10n_util.h"
namespace ash {
namespace {
bool IsVisible() {
// TODO(minch): Add the logic for login screen.
// Disable dark mode feature pod in OOBE since only light mode should be
// allowed there.
return Shell::Get()->session_controller()->IsActiveUserSessionStarted() &&
Shell::Get()->session_controller()->GetSessionState() !=
session_manager::SessionState::OOBE;
}
} // namespace
DarkModeFeaturePodController::DarkModeFeaturePodController(
UnifiedSystemTrayController* tray_controller)
: tray_controller_(tray_controller) {
DCHECK(tray_controller_);
UnifiedSystemTrayController* tray_controller) {
DarkLightModeControllerImpl::Get()->AddObserver(this);
}
@ -38,13 +51,7 @@ FeaturePodButton* DarkModeFeaturePodController::CreateButton() {
button_->SetLabel(l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_DARK_THEME));
button_->SetLabelTooltip(l10n_util::GetStringUTF16(
IDS_ASH_STATUS_TRAY_DARK_THEME_SETTINGS_TOOLTIP));
// TODO(minch): Add the logic for login screen.
// Disable dark mode feature pod in OOBE since only light mode should be
// allowed there.
const bool visible =
Shell::Get()->session_controller()->IsActiveUserSessionStarted() &&
Shell::Get()->session_controller()->GetSessionState() !=
session_manager::SessionState::OOBE;
const bool visible = IsVisible();
if (visible)
TrackVisibilityUMA();
button_->SetVisible(visible);
@ -53,6 +60,26 @@ FeaturePodButton* DarkModeFeaturePodController::CreateButton() {
return button_;
}
std::unique_ptr<FeatureTile> DarkModeFeaturePodController::CreateTile(
bool compact) {
DCHECK(features::IsQsRevampEnabled());
DCHECK(!tile_);
auto tile = std::make_unique<FeatureTile>(
base::BindRepeating(&DarkModeFeaturePodController::OnIconPressed,
weak_ptr_factory_.GetWeakPtr()));
tile_ = tile.get();
tile_->SetVectorIcon(kUnifiedMenuDarkModeIcon);
tile_->SetLabel(l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_DARK_THEME));
const bool visible = IsVisible();
if (visible) {
TrackVisibilityUMA();
}
tile_->SetVisible(visible);
UpdateTile(DarkLightModeControllerImpl::Get()->IsDarkModeEnabled());
return tile;
}
QsFeatureCatalogName DarkModeFeaturePodController::GetCatalogName() {
return QsFeatureCatalogName::kDarkMode;
}
@ -73,11 +100,18 @@ void DarkModeFeaturePodController::OnIconPressed() {
}
void DarkModeFeaturePodController::OnLabelPressed() {
if (features::IsQsRevampEnabled()) {
return;
}
TrackDiveInUMA();
Shell::Get()->system_tray_model()->client()->ShowDarkModeSettings();
}
void DarkModeFeaturePodController::OnColorModeChanged(bool dark_mode_enabled) {
if (features::IsQsRevampEnabled()) {
UpdateTile(dark_mode_enabled);
return;
}
UpdateButton(dark_mode_enabled);
}
@ -104,4 +138,27 @@ void DarkModeFeaturePodController::UpdateButton(bool dark_mode_enabled) {
IDS_ASH_STATUS_TRAY_DARK_THEME_TOGGLE_TOOLTIP, tooltip_state));
}
void DarkModeFeaturePodController::UpdateTile(bool dark_mode_enabled) {
tile_->SetToggled(dark_mode_enabled);
if (Shell::Get()->dark_light_mode_controller()->GetAutoScheduleEnabled()) {
tile_->SetSubLabel(l10n_util::GetStringUTF16(
dark_mode_enabled
? IDS_ASH_STATUS_TRAY_DARK_THEME_ON_STATE_AUTO_SCHEDULED
: IDS_ASH_STATUS_TRAY_DARK_THEME_OFF_STATE_AUTO_SCHEDULED));
} else {
tile_->SetSubLabel(l10n_util::GetStringUTF16(
dark_mode_enabled ? IDS_ASH_STATUS_TRAY_DARK_THEME_ON_STATE
: IDS_ASH_STATUS_TRAY_DARK_THEME_OFF_STATE));
}
std::u16string tooltip_state = l10n_util::GetStringUTF16(
dark_mode_enabled
? IDS_ASH_STATUS_TRAY_DARK_THEME_ENABLED_STATE_TOOLTIP
: IDS_ASH_STATUS_TRAY_DARK_THEME_DISABLED_STATE_TOOLTIP);
tile_->SetTooltipText(l10n_util::GetStringFUTF16(
IDS_ASH_STATUS_TRAY_DARK_THEME_TOGGLE_TOOLTIP, tooltip_state));
tile_->SetVectorIcon(dark_mode_enabled ? kUnifiedMenuDarkModeIcon
: kUnifiedMenuDarkModeOffIcon);
}
} // namespace ash

@ -28,6 +28,7 @@ class ASH_EXPORT DarkModeFeaturePodController : public FeaturePodControllerBase,
// FeaturePodControllerBase:
FeaturePodButton* CreateButton() override;
std::unique_ptr<FeatureTile> CreateTile(bool compact = false) override;
QsFeatureCatalogName GetCatalogName() override;
void OnIconPressed() override;
void OnLabelPressed() override;
@ -38,9 +39,14 @@ class ASH_EXPORT DarkModeFeaturePodController : public FeaturePodControllerBase,
private:
void UpdateButton(bool dark_mode_enabled);
UnifiedSystemTrayController* const tray_controller_;
// For QsRevamp:
void UpdateTile(bool dark_mode_enabled);
// Owned by the views hierarchy.
FeaturePodButton* button_ = nullptr;
FeatureTile* tile_ = nullptr;
base::WeakPtrFactory<DarkModeFeaturePodController> weak_ptr_factory_{this};
};
} // namespace ash

@ -9,6 +9,7 @@
#include "ash/shell.h"
#include "ash/style/dark_light_mode_controller_impl.h"
#include "ash/system/unified/feature_pod_button.h"
#include "ash/system/unified/feature_tile.h"
#include "ash/system/unified/unified_system_tray.h"
#include "ash/system/unified/unified_system_tray_bubble.h"
#include "ash/test/ash_test_base.h"
@ -18,38 +19,101 @@
namespace ash {
using DarkModeFeaturePodControllerTest = AshTestBase;
// Tests are parameterized by feature QsRevamp.
class DarkModeFeaturePodControllerTest
: public AshTestBase,
public testing::WithParamInterface<bool> {
public:
bool IsQsRevampEnabled() const { return GetParam(); }
// AshTestBase:
void SetUp() override {
if (IsQsRevampEnabled()) {
feature_list_.InitWithFeatures(
{chromeos::features::kDarkLightMode, features::kQsRevamp}, {});
} else {
feature_list_.InitWithFeatures({chromeos::features::kDarkLightMode},
{features::kQsRevamp});
}
AshTestBase::SetUp();
system_tray_ = GetPrimaryUnifiedSystemTray();
system_tray_->ShowBubble();
feature_pod_controller_ = std::make_unique<DarkModeFeaturePodController>(
system_tray_->bubble()->unified_system_tray_controller());
}
void TearDown() override {
tile_.reset();
button_.reset();
feature_pod_controller_.reset();
system_tray_->CloseBubble();
AshTestBase::TearDown();
}
void CreateButton() {
if (IsQsRevampEnabled()) {
tile_ = feature_pod_controller_->CreateTile();
} else {
button_ = base::WrapUnique(feature_pod_controller_->CreateButton());
}
}
bool IsButtonVisible() {
return IsQsRevampEnabled() ? tile_->GetVisible() : button_->GetVisible();
}
bool IsButtonToggled() {
return IsQsRevampEnabled() ? tile_->IsToggled() : button_->IsToggled();
}
void PressIcon() { feature_pod_controller_->OnIconPressed(); }
void PressLabel() { feature_pod_controller_->OnLabelPressed(); }
private:
base::test::ScopedFeatureList feature_list_;
std::unique_ptr<DarkModeFeaturePodController> feature_pod_controller_;
std::unique_ptr<FeaturePodButton> button_;
std::unique_ptr<FeatureTile> tile_;
UnifiedSystemTray* system_tray_ = nullptr;
};
INSTANTIATE_TEST_SUITE_P(QsRevamp,
DarkModeFeaturePodControllerTest,
testing::Bool());
// Tests that toggling dark mode from the system tray disables auto scheduling
// and switches the color mode properly.
TEST_F(DarkModeFeaturePodControllerTest, ToggleDarkMode) {
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeature(chromeos::features::kDarkLightMode);
TEST_P(DarkModeFeaturePodControllerTest, ToggleDarkMode) {
CreateButton();
EXPECT_TRUE(IsButtonVisible());
auto* dark_light_mode_controller = DarkLightModeControllerImpl::Get();
dark_light_mode_controller->OnActiveUserPrefServiceChanged(
Shell::Get()->session_controller()->GetActivePrefService());
UnifiedSystemTray* system_tray = GetPrimaryUnifiedSystemTray();
system_tray->ShowBubble();
std::unique_ptr<DarkModeFeaturePodController>
dark_mode_feature_pod_controller =
std::make_unique<DarkModeFeaturePodController>(
system_tray->bubble()->unified_system_tray_controller());
std::unique_ptr<FeaturePodButton> button(
dark_mode_feature_pod_controller->CreateButton());
// No metrics logged before clicking on any views.
auto histogram_tester = std::make_unique<base::HistogramTester>();
histogram_tester->ExpectTotalCount(
"Ash.UnifiedSystemView.FeaturePod.ToggledOn",
/*count=*/0);
histogram_tester->ExpectTotalCount(
"Ash.UnifiedSystemView.FeaturePod.ToggledOff",
/*count=*/0);
histogram_tester->ExpectTotalCount("Ash.UnifiedSystemView.FeaturePod.DiveIn",
/*count=*/0);
if (IsQsRevampEnabled()) {
histogram_tester->ExpectTotalCount("Ash.QuickSettings.FeaturePod.ToggledOn",
/*expected_count=*/0);
histogram_tester->ExpectTotalCount(
"Ash.QuickSettings.FeaturePod.ToggledOff",
/*expected_count=*/0);
histogram_tester->ExpectTotalCount("Ash.QuickSettings.FeaturePod.DiveIn",
/*expected_count=*/0);
} else {
histogram_tester->ExpectTotalCount(
"Ash.UnifiedSystemView.FeaturePod.ToggledOn",
/*expected_count=*/0);
histogram_tester->ExpectTotalCount(
"Ash.UnifiedSystemView.FeaturePod.ToggledOff",
/*expected_count=*/0);
histogram_tester->ExpectTotalCount(
"Ash.UnifiedSystemView.FeaturePod.DiveIn",
/*expected_count=*/0);
}
// Enable dark mode auto scheduling.
auto* controller = Shell::Get()->dark_light_mode_controller();
@ -58,7 +122,7 @@ TEST_F(DarkModeFeaturePodControllerTest, ToggleDarkMode) {
// Check that the statuses of toggle and dark mode are consistent.
bool dark_mode_enabled = dark_light_mode_controller->IsDarkModeEnabled();
EXPECT_EQ(dark_mode_enabled, button->IsToggled());
EXPECT_EQ(dark_mode_enabled, IsButtonToggled());
// Set the init state to enabled.
if (!dark_mode_enabled)
@ -66,56 +130,101 @@ TEST_F(DarkModeFeaturePodControllerTest, ToggleDarkMode) {
// Pressing the dark mode button should disable the scheduling and switch the
// dark mode status.
dark_mode_feature_pod_controller->OnIconPressed();
PressIcon();
EXPECT_FALSE(controller->GetAutoScheduleEnabled());
EXPECT_EQ(false, dark_light_mode_controller->IsDarkModeEnabled());
EXPECT_EQ(false, button->IsToggled());
histogram_tester->ExpectTotalCount(
"Ash.UnifiedSystemView.FeaturePod.ToggledOn",
/*count=*/0);
histogram_tester->ExpectTotalCount(
"Ash.UnifiedSystemView.FeaturePod.ToggledOff",
/*count=*/1);
histogram_tester->ExpectTotalCount("Ash.UnifiedSystemView.FeaturePod.DiveIn",
/*count=*/0);
histogram_tester->ExpectBucketCount(
"Ash.UnifiedSystemView.FeaturePod.ToggledOff",
QsFeatureCatalogName::kDarkMode,
/*expected_count=*/1);
EXPECT_EQ(false, IsButtonToggled());
// Pressing the dark mode button again should only switch the dark mode status
// while maintaining the disabled status of scheduling.
dark_mode_feature_pod_controller->OnIconPressed();
if (IsQsRevampEnabled()) {
histogram_tester->ExpectTotalCount("Ash.QuickSettings.FeaturePod.ToggledOn",
/*expected_count=*/0);
histogram_tester->ExpectTotalCount(
"Ash.QuickSettings.FeaturePod.ToggledOff",
/*expected_count=*/1);
histogram_tester->ExpectTotalCount("Ash.QuickSettings.FeaturePod.DiveIn",
/*expected_count=*/0);
histogram_tester->ExpectBucketCount(
"Ash.QuickSettings.FeaturePod.ToggledOff",
QsFeatureCatalogName::kDarkMode,
/*expected_count=*/1);
} else {
histogram_tester->ExpectTotalCount(
"Ash.UnifiedSystemView.FeaturePod.ToggledOn",
/*expected_count=*/0);
histogram_tester->ExpectTotalCount(
"Ash.UnifiedSystemView.FeaturePod.ToggledOff",
/*expected_count=*/1);
histogram_tester->ExpectTotalCount(
"Ash.UnifiedSystemView.FeaturePod.DiveIn",
/*expected_count=*/0);
histogram_tester->ExpectBucketCount(
"Ash.UnifiedSystemView.FeaturePod.ToggledOff",
QsFeatureCatalogName::kDarkMode,
/*expected_count=*/1);
}
// Pressing the dark mode button again should only switch the dark mode
// status while maintaining the disabled status of scheduling.
PressIcon();
EXPECT_FALSE(controller->GetAutoScheduleEnabled());
EXPECT_EQ(true, dark_light_mode_controller->IsDarkModeEnabled());
EXPECT_EQ(true, button->IsToggled());
histogram_tester->ExpectTotalCount(
"Ash.UnifiedSystemView.FeaturePod.ToggledOn",
/*count=*/1);
histogram_tester->ExpectTotalCount(
"Ash.UnifiedSystemView.FeaturePod.ToggledOff",
/*count=*/1);
histogram_tester->ExpectTotalCount("Ash.UnifiedSystemView.FeaturePod.DiveIn",
/*count=*/0);
histogram_tester->ExpectBucketCount(
"Ash.UnifiedSystemView.FeaturePod.ToggledOn",
QsFeatureCatalogName::kDarkMode,
/*expected_count=*/1);
EXPECT_EQ(true, IsButtonToggled());
dark_mode_feature_pod_controller->OnLabelPressed();
histogram_tester->ExpectTotalCount(
"Ash.UnifiedSystemView.FeaturePod.ToggledOn",
/*count=*/1);
histogram_tester->ExpectTotalCount(
"Ash.UnifiedSystemView.FeaturePod.ToggledOff",
/*count=*/1);
histogram_tester->ExpectTotalCount("Ash.UnifiedSystemView.FeaturePod.DiveIn",
/*count=*/1);
histogram_tester->ExpectBucketCount("Ash.UnifiedSystemView.FeaturePod.DiveIn",
QsFeatureCatalogName::kDarkMode,
/*expected_count=*/1);
if (IsQsRevampEnabled()) {
histogram_tester->ExpectTotalCount("Ash.QuickSettings.FeaturePod.ToggledOn",
/*expected_count=*/1);
histogram_tester->ExpectTotalCount(
"Ash.QuickSettings.FeaturePod.ToggledOff",
/*expected_count=*/1);
histogram_tester->ExpectTotalCount("Ash.QuickSettings.FeaturePod.DiveIn",
/*expected_count=*/0);
histogram_tester->ExpectBucketCount(
"Ash.QuickSettings.FeaturePod.ToggledOn",
QsFeatureCatalogName::kDarkMode,
/*expected_count=*/1);
} else {
histogram_tester->ExpectTotalCount(
"Ash.UnifiedSystemView.FeaturePod.ToggledOn",
/*expected_count=*/1);
histogram_tester->ExpectTotalCount(
"Ash.UnifiedSystemView.FeaturePod.ToggledOff",
/*expected_count=*/1);
histogram_tester->ExpectTotalCount(
"Ash.UnifiedSystemView.FeaturePod.DiveIn",
/*expected_count=*/0);
histogram_tester->ExpectBucketCount(
"Ash.UnifiedSystemView.FeaturePod.ToggledOn",
QsFeatureCatalogName::kDarkMode,
/*expected_count=*/1);
}
system_tray->CloseBubble();
PressLabel();
if (IsQsRevampEnabled()) {
histogram_tester->ExpectTotalCount("Ash.QuickSettings.FeaturePod.ToggledOn",
/*expected_count=*/1);
histogram_tester->ExpectTotalCount(
"Ash.QuickSettings.FeaturePod.ToggledOff",
/*expected_count=*/1);
histogram_tester->ExpectTotalCount("Ash.QuickSettings.FeaturePod.DiveIn",
/*expected_count=*/0);
histogram_tester->ExpectBucketCount("Ash.QuickSettings.FeaturePod.DiveIn",
QsFeatureCatalogName::kDarkMode,
/*expected_count=*/0);
} else {
histogram_tester->ExpectTotalCount(
"Ash.UnifiedSystemView.FeaturePod.ToggledOn",
/*expected_count=*/1);
histogram_tester->ExpectTotalCount(
"Ash.UnifiedSystemView.FeaturePod.ToggledOff",
/*expected_count=*/1);
histogram_tester->ExpectTotalCount(
"Ash.UnifiedSystemView.FeaturePod.DiveIn",
/*expected_count=*/1);
histogram_tester->ExpectBucketCount(
"Ash.UnifiedSystemView.FeaturePod.DiveIn",
QsFeatureCatalogName::kDarkMode,
/*expected_count=*/1);
}
}
} // namespace ash

@ -5,6 +5,7 @@
#include "ash/system/night_light/night_light_feature_pod_controller.h"
#include <string>
#include "ash/constants/ash_features.h"
#include "ash/constants/quick_settings_catalogs.h"
#include "ash/public/cpp/system_tray_client.h"
#include "ash/resources/vector_icons/vector_icons.h"
@ -17,6 +18,7 @@
#include "ash/system/night_light/night_light_controller_impl.h"
#include "ash/system/tray/tray_popup_utils.h"
#include "ash/system/unified/feature_pod_button.h"
#include "ash/system/unified/feature_tile.h"
#include "ash/system/unified/quick_settings_metrics_util.h"
#include "ash/system/unified/unified_system_tray_controller.h"
#include "base/i18n/time_formatting.h"
@ -39,7 +41,6 @@ void LogUserNightLightEvent(const bool enabled) {
NightLightFeaturePodController::NightLightFeaturePodController(
UnifiedSystemTrayController* tray_controller)
: tray_controller_(tray_controller) {
DCHECK(tray_controller_);
Shell::Get()->system_tray_model()->clock()->AddObserver(this);
}
@ -65,6 +66,28 @@ FeaturePodButton* NightLightFeaturePodController::CreateButton() {
return button_;
}
std::unique_ptr<FeatureTile> NightLightFeaturePodController::CreateTile(
bool compact) {
DCHECK(features::IsQsRevampEnabled());
DCHECK(!tile_);
auto tile = std::make_unique<FeatureTile>(
base::BindRepeating(&NightLightFeaturePodController::OnIconPressed,
weak_factory_.GetWeakPtr()),
/*is_togglable=*/true);
tile_ = tile.get();
const bool visible =
Shell::Get()->session_controller()->ShouldEnableSettings();
tile_->SetVisible(visible);
if (visible) {
TrackVisibilityUMA();
}
tile_->SetLabel(
l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_NIGHT_LIGHT_BUTTON_LABEL));
UpdateTile();
return tile;
}
QsFeatureCatalogName NightLightFeaturePodController::GetCatalogName() {
return QsFeatureCatalogName::kNightLight;
}
@ -76,7 +99,7 @@ void NightLightFeaturePodController::OnIconPressed() {
Shell::Get()->night_light_controller()->Toggle();
LogUserNightLightEvent(Shell::Get()->night_light_controller()->GetEnabled());
UpdateButton();
Update();
if (Shell::Get()->night_light_controller()->GetEnabled()) {
base::RecordAction(
@ -88,6 +111,9 @@ void NightLightFeaturePodController::OnIconPressed() {
}
void NightLightFeaturePodController::OnLabelPressed() {
if (features::IsQsRevampEnabled()) {
return;
}
if (TrayPopupUtils::CanOpenWebUISettings()) {
TrackDiveInUMA();
base::RecordAction(
@ -98,20 +124,20 @@ void NightLightFeaturePodController::OnLabelPressed() {
}
void NightLightFeaturePodController::OnDateFormatChanged() {
UpdateButton();
Update();
}
void NightLightFeaturePodController::OnSystemClockTimeUpdated() {
UpdateButton();
Update();
}
void NightLightFeaturePodController::OnSystemClockCanSetTimeChanged(
bool can_set_time) {
UpdateButton();
Update();
}
void NightLightFeaturePodController::Refresh() {
UpdateButton();
Update();
}
const std::u16string NightLightFeaturePodController::GetPodSubLabel() {
@ -148,6 +174,14 @@ const std::u16string NightLightFeaturePodController::GetPodSubLabel() {
}
}
void NightLightFeaturePodController::Update() {
if (features::IsQsRevampEnabled()) {
UpdateTile();
return;
}
UpdateButton();
}
void NightLightFeaturePodController::UpdateButton() {
auto* controller = Shell::Get()->night_light_controller();
const bool is_enabled = controller->GetEnabled();
@ -161,4 +195,19 @@ void NightLightFeaturePodController::UpdateButton() {
IDS_ASH_STATUS_TRAY_NIGHT_LIGHT_TOGGLE_TOOLTIP, tooltip_state));
}
void NightLightFeaturePodController::UpdateTile() {
auto* controller = Shell::Get()->night_light_controller();
const bool is_enabled = controller->GetEnabled();
tile_->SetToggled(is_enabled);
tile_->SetSubLabel(GetPodSubLabel());
std::u16string tooltip_state = l10n_util::GetStringUTF16(
is_enabled ? IDS_ASH_STATUS_TRAY_NIGHT_LIGHT_ENABLED_STATE_TOOLTIP
: IDS_ASH_STATUS_TRAY_NIGHT_LIGHT_DISABLED_STATE_TOOLTIP);
tile_->SetTooltipText(l10n_util::GetStringFUTF16(
IDS_ASH_STATUS_TRAY_NIGHT_LIGHT_TOGGLE_TOOLTIP, tooltip_state));
tile_->SetVectorIcon(is_enabled ? kUnifiedMenuNightLightIcon
: kUnifiedMenuNightLightOffIcon);
}
} // namespace ash

@ -10,9 +10,11 @@
#include "ash/constants/quick_settings_catalogs.h"
#include "ash/system/model/clock_observer.h"
#include "ash/system/unified/feature_pod_controller_base.h"
#include "base/memory/weak_ptr.h"
namespace ash {
class FeatureTile;
class UnifiedSystemTrayController;
// Controller of a feature pod button that toggles night light mode.
@ -32,6 +34,7 @@ class ASH_EXPORT NightLightFeaturePodController
// FeaturePodControllerBase:
FeaturePodButton* CreateButton() override;
std::unique_ptr<FeatureTile> CreateTile(bool compact = false) override;
QsFeatureCatalogName GetCatalogName() override;
void OnIconPressed() override;
void OnLabelPressed() override;
@ -47,12 +50,23 @@ class ASH_EXPORT NightLightFeaturePodController
// current status and schedule type of night light.
const std::u16string GetPodSubLabel();
// For QsRevamp: Updates `button_` or `tile_` based on whether QsRevamp flag
// is on.
void Update();
// Updates the toggle state, sub label, and icon tooltip of the `button_`.
void UpdateButton();
UnifiedSystemTrayController* const tray_controller_;
// For QsRevamp: Updates the toggle state, sub label, and icon tooltip of the
// `tile_`.
void UpdateTile();
UnifiedSystemTrayController* const tray_controller_;
// Owned by the views hierarchy.
FeaturePodButton* button_ = nullptr;
FeatureTile* tile_ = nullptr;
base::WeakPtrFactory<NightLightFeaturePodController> weak_factory_{this};
};
} // namespace ash

@ -3,6 +3,7 @@
// found in the LICENSE file.
#include "ash/system/night_light/night_light_feature_pod_controller.h"
#include "ash/constants/ash_features.h"
#include "ash/constants/quick_settings_catalogs.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
@ -11,6 +12,7 @@
#include "ash/system/model/system_tray_model.h"
#include "ash/system/night_light/night_light_controller_impl.h"
#include "ash/system/unified/feature_pod_button.h"
#include "ash/system/unified/feature_tile.h"
#include "ash/system/unified/unified_system_tray.h"
#include "ash/system/unified/unified_system_tray_bubble.h"
#include "ash/test/ash_test_base.h"
@ -21,75 +23,120 @@
namespace ash {
class NightLightFeaturePodControllerTest : public AshTestBase {
class NightLightFeaturePodControllerTest
: public AshTestBase,
public testing::WithParamInterface<bool> {
public:
NightLightFeaturePodControllerTest() {
if (IsQsRevampEnabled()) {
feature_list_.InitWithFeatures({features::kQsRevamp}, {});
} else {
feature_list_.InitWithFeatures({}, {features::kQsRevamp});
}
}
bool IsQsRevampEnabled() const { return GetParam(); }
void SetUp() override {
AshTestBase::SetUp();
UnifiedSystemTray* system_tray = GetPrimaryUnifiedSystemTray();
system_tray->ShowBubble();
feature_pod_controller_ = std::make_unique<NightLightFeaturePodController>(
system_tray->bubble()->unified_system_tray_controller());
feature_pod_button_.reset(feature_pod_controller_->CreateButton());
system_tray_ = GetPrimaryUnifiedSystemTray();
system_tray_->ShowBubble();
}
void TearDown() override {
feature_pod_controller_.reset();
feature_pod_button_.reset();
button_.reset();
tile_.reset();
controller_.reset();
system_tray_->CloseBubble();
AshTestBase::TearDown();
}
void CreateButton() {
controller_ = std::make_unique<NightLightFeaturePodController>(
system_tray_->bubble()->unified_system_tray_controller());
if (IsQsRevampEnabled()) {
tile_ = controller_->CreateTile();
} else {
button_ = base::WrapUnique(controller_->CreateButton());
}
}
bool IsButtonVisible() {
return IsQsRevampEnabled() ? tile_->GetVisible() : button_->GetVisible();
}
bool IsButtonToggled() {
return IsQsRevampEnabled() ? tile_->IsToggled() : button_->IsToggled();
}
protected:
NightLightFeaturePodController* feature_pod_controller() {
return feature_pod_controller_.get();
void PressIcon() { controller_->OnIconPressed(); }
void PressLabel() { controller_->OnLabelPressed(); }
const std::u16string& GetButtonLabelText() {
if (IsQsRevampEnabled()) {
return tile_->sub_label()->GetText();
}
return button_->label_button_->GetSubLabelText();
}
FeaturePodButton* feature_pod_button() { return feature_pod_button_.get(); }
const ash::FeaturePodLabelButton* feature_pod_label_button() {
return feature_pod_button_->label_button_;
}
void PressIcon() { feature_pod_controller_->OnIconPressed(); }
void PressLabel() { feature_pod_controller_->OnLabelPressed(); }
private:
std::unique_ptr<FeaturePodButton> feature_pod_button_;
std::unique_ptr<NightLightFeaturePodController> feature_pod_controller_;
base::test::ScopedFeatureList feature_list_;
UnifiedSystemTray* system_tray_;
std::unique_ptr<NightLightFeaturePodController> controller_;
std::unique_ptr<FeaturePodButton> button_;
std::unique_ptr<FeatureTile> tile_;
};
INSTANTIATE_TEST_SUITE_P(QsRevamp,
NightLightFeaturePodControllerTest,
testing::Bool());
TEST_P(NightLightFeaturePodControllerTest, ButtonVisibility) {
// The button is visible in an active session.
CreateButton();
EXPECT_TRUE(IsButtonVisible());
// The button is not visible at the lock screen.
GetSessionControllerClient()->LockScreen();
CreateButton();
EXPECT_FALSE(IsButtonVisible());
}
// Tests that toggling night light from the system tray switches the color
// mode and its button label properly.
TEST_F(NightLightFeaturePodControllerTest, Toggle) {
TEST_P(NightLightFeaturePodControllerTest, Toggle) {
CreateButton();
NightLightControllerImpl* controller = Shell::Get()->night_light_controller();
// Check that the feature pod button and its label reflects the default
// Night light off without any auto scheduling.
EXPECT_FALSE(controller->GetEnabled());
EXPECT_FALSE(feature_pod_button()->IsToggled());
EXPECT_FALSE(IsButtonToggled());
EXPECT_EQ(NightLightController::ScheduleType::kNone,
controller->GetScheduleType());
EXPECT_EQ(
l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_NIGHT_LIGHT_OFF_STATE),
feature_pod_label_button()->GetSubLabelText());
GetButtonLabelText());
// Toggling the button should enable night light and update the button label
// correctly and maintaining no scheduling.
feature_pod_controller()->OnIconPressed();
PressIcon();
EXPECT_TRUE(controller->GetEnabled());
EXPECT_TRUE(feature_pod_button()->IsToggled());
EXPECT_TRUE(IsButtonToggled());
EXPECT_EQ(NightLightController::ScheduleType::kNone,
controller->GetScheduleType());
EXPECT_EQ(l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_NIGHT_LIGHT_ON_STATE),
feature_pod_label_button()->GetSubLabelText());
GetButtonLabelText());
}
// Tests that toggling sunset-to-sunrise-scheduled night light from the system
// tray while switches the color mode temporarily and maintains the auto
// scheduling.
TEST_F(NightLightFeaturePodControllerTest, SunsetToSunrise) {
TEST_P(NightLightFeaturePodControllerTest, SunsetToSunrise) {
CreateButton();
// Enable sunset-to-sunrise scheduling.
NightLightControllerImpl* controller = Shell::Get()->night_light_controller();
controller->SetScheduleType(
@ -105,28 +152,28 @@ TEST_F(NightLightFeaturePodControllerTest, SunsetToSunrise) {
// Pressing the night light button should switch the status but keep
// sunset-to-sunrise scheduling.
bool enabled = controller->GetEnabled();
feature_pod_controller()->OnIconPressed();
PressIcon();
EXPECT_EQ(NightLightController::ScheduleType::kSunsetToSunrise,
controller->GetScheduleType());
EXPECT_EQ(!enabled, controller->GetEnabled());
EXPECT_EQ(!enabled, feature_pod_button()->IsToggled());
EXPECT_EQ(!enabled ? sublabel_on : sublabel_off,
feature_pod_label_button()->GetSubLabelText());
EXPECT_EQ(!enabled, IsButtonToggled());
EXPECT_EQ(!enabled ? sublabel_on : sublabel_off, GetButtonLabelText());
// Pressing the night light button should switch the status but keep
// sunset-to-sunrise scheduling.
feature_pod_controller()->OnIconPressed();
PressIcon();
EXPECT_EQ(NightLightController::ScheduleType::kSunsetToSunrise,
controller->GetScheduleType());
EXPECT_EQ(enabled, controller->GetEnabled());
EXPECT_EQ(enabled, feature_pod_button()->IsToggled());
EXPECT_EQ(enabled ? sublabel_on : sublabel_off,
feature_pod_label_button()->GetSubLabelText());
EXPECT_EQ(enabled, IsButtonToggled());
EXPECT_EQ(enabled ? sublabel_on : sublabel_off, GetButtonLabelText());
}
// Tests that custom-scheduled night light displays the right custom start or
// end time for custom schedule type on the button label of the system tray.
TEST_F(NightLightFeaturePodControllerTest, Custom) {
TEST_P(NightLightFeaturePodControllerTest, Custom) {
CreateButton();
// Enable custom scheduling.
NightLightControllerImpl* controller = Shell::Get()->night_light_controller();
controller->SetScheduleType(NightLightController::ScheduleType::kCustom);
@ -151,98 +198,167 @@ TEST_F(NightLightFeaturePodControllerTest, Custom) {
// Pressing the night light button should switch the status and update the
// label but keep the custom scheduling.
bool enabled = controller->GetEnabled();
feature_pod_controller()->OnIconPressed();
PressIcon();
EXPECT_EQ(NightLightController::ScheduleType::kCustom,
controller->GetScheduleType());
EXPECT_EQ(!enabled, controller->GetEnabled());
EXPECT_EQ(!enabled, feature_pod_button()->IsToggled());
EXPECT_EQ(!enabled ? sublabel_on : sublabel_off,
feature_pod_label_button()->GetSubLabelText());
EXPECT_EQ(!enabled, IsButtonToggled());
EXPECT_EQ(!enabled ? sublabel_on : sublabel_off, GetButtonLabelText());
// Pressing the night light button should switch the status and update the
// label but keep the custom scheduling.
feature_pod_controller()->OnIconPressed();
PressIcon();
EXPECT_EQ(NightLightController::ScheduleType::kCustom,
controller->GetScheduleType());
EXPECT_EQ(enabled, controller->GetEnabled());
EXPECT_EQ(enabled, feature_pod_button()->IsToggled());
EXPECT_EQ(enabled ? sublabel_on : sublabel_off,
feature_pod_label_button()->GetSubLabelText());
EXPECT_EQ(enabled, IsButtonToggled());
EXPECT_EQ(enabled ? sublabel_on : sublabel_off, GetButtonLabelText());
}
TEST_F(NightLightFeaturePodControllerTest, IconUMATracking) {
TEST_P(NightLightFeaturePodControllerTest, IconUMATracking) {
CreateButton();
// Disable sunset-to-sunrise scheduling.
NightLightControllerImpl* controller = Shell::Get()->night_light_controller();
controller->SetScheduleType(NightLightController::ScheduleType::kNone);
// No metrics logged before clicking on any views.
auto histogram_tester = std::make_unique<base::HistogramTester>();
histogram_tester->ExpectTotalCount(
"Ash.UnifiedSystemView.FeaturePod.ToggledOn",
/*count=*/0);
histogram_tester->ExpectTotalCount(
"Ash.UnifiedSystemView.FeaturePod.ToggledOff",
/*count=*/0);
histogram_tester->ExpectTotalCount("Ash.UnifiedSystemView.FeaturePod.DiveIn",
/*count=*/0);
if (IsQsRevampEnabled()) {
histogram_tester->ExpectTotalCount("Ash.QuickSettings.FeaturePod.ToggledOn",
/*expected_count=*/0);
histogram_tester->ExpectTotalCount(
"Ash.QuickSettings.FeaturePod.ToggledOff",
/*expected_count=*/0);
histogram_tester->ExpectTotalCount("Ash.QuickSettings.FeaturePod.DiveIn",
/*expected_count=*/0);
} else {
histogram_tester->ExpectTotalCount(
"Ash.UnifiedSystemView.FeaturePod.ToggledOn",
/*expected_count=*/0);
histogram_tester->ExpectTotalCount(
"Ash.UnifiedSystemView.FeaturePod.ToggledOff",
/*expected_count=*/0);
histogram_tester->ExpectTotalCount(
"Ash.UnifiedSystemView.FeaturePod.DiveIn",
/*expected_count=*/0);
}
// Toggle on the nightlight feature when pressing on the icon.
PressIcon();
histogram_tester->ExpectTotalCount(
"Ash.UnifiedSystemView.FeaturePod.ToggledOn",
/*count=*/1);
histogram_tester->ExpectTotalCount(
"Ash.UnifiedSystemView.FeaturePod.ToggledOff",
/*count=*/0);
histogram_tester->ExpectTotalCount("Ash.UnifiedSystemView.FeaturePod.DiveIn",
/*count=*/0);
histogram_tester->ExpectBucketCount(
"Ash.UnifiedSystemView.FeaturePod.ToggledOn",
QsFeatureCatalogName::kNightLight,
/*expected_count=*/1);
if (IsQsRevampEnabled()) {
histogram_tester->ExpectTotalCount("Ash.QuickSettings.FeaturePod.ToggledOn",
/*expected_count=*/1);
histogram_tester->ExpectTotalCount(
"Ash.QuickSettings.FeaturePod.ToggledOff",
/*expected_count=*/0);
histogram_tester->ExpectTotalCount("Ash.QuickSettings.FeaturePod.DiveIn",
/*expected_count=*/0);
histogram_tester->ExpectBucketCount(
"Ash.QuickSettings.FeaturePod.ToggledOn",
QsFeatureCatalogName::kNightLight,
/*expected_count=*/1);
} else {
histogram_tester->ExpectTotalCount(
"Ash.UnifiedSystemView.FeaturePod.ToggledOn",
/*expected_count=*/1);
histogram_tester->ExpectTotalCount(
"Ash.UnifiedSystemView.FeaturePod.ToggledOff",
/*expected_count=*/0);
histogram_tester->ExpectTotalCount(
"Ash.UnifiedSystemView.FeaturePod.DiveIn",
/*expected_count=*/0);
histogram_tester->ExpectBucketCount(
"Ash.UnifiedSystemView.FeaturePod.ToggledOn",
QsFeatureCatalogName::kNightLight,
/*expected_count=*/1);
}
// Toggle off the nightlight feature when pressing on the icon again.
PressIcon();
histogram_tester->ExpectTotalCount(
"Ash.UnifiedSystemView.FeaturePod.ToggledOn",
/*count=*/1);
histogram_tester->ExpectTotalCount(
"Ash.UnifiedSystemView.FeaturePod.ToggledOff",
/*count=*/1);
histogram_tester->ExpectTotalCount("Ash.UnifiedSystemView.FeaturePod.DiveIn",
/*count=*/0);
histogram_tester->ExpectBucketCount(
"Ash.UnifiedSystemView.FeaturePod.ToggledOff",
QsFeatureCatalogName::kNightLight,
/*expected_count=*/1);
if (IsQsRevampEnabled()) {
histogram_tester->ExpectTotalCount("Ash.QuickSettings.FeaturePod.ToggledOn",
/*expected_count=*/1);
histogram_tester->ExpectTotalCount(
"Ash.QuickSettings.FeaturePod.ToggledOff",
/*expected_count=*/1);
histogram_tester->ExpectTotalCount("Ash.QuickSettings.FeaturePod.DiveIn",
/*expected_count=*/0);
histogram_tester->ExpectBucketCount(
"Ash.QuickSettings.FeaturePod.ToggledOff",
QsFeatureCatalogName::kNightLight,
/*expected_count=*/1);
} else {
histogram_tester->ExpectTotalCount(
"Ash.UnifiedSystemView.FeaturePod.ToggledOn",
/*expected_count=*/1);
histogram_tester->ExpectTotalCount(
"Ash.UnifiedSystemView.FeaturePod.ToggledOff",
/*expected_count=*/1);
histogram_tester->ExpectTotalCount(
"Ash.UnifiedSystemView.FeaturePod.DiveIn",
/*expected_count=*/0);
histogram_tester->ExpectBucketCount(
"Ash.UnifiedSystemView.FeaturePod.ToggledOff",
QsFeatureCatalogName::kNightLight,
/*expected_count=*/1);
}
}
TEST_F(NightLightFeaturePodControllerTest, LabelUMATracking) {
TEST_P(NightLightFeaturePodControllerTest, LabelUMATracking) {
CreateButton();
// No metrics logged before clicking on any views.
auto histogram_tester = std::make_unique<base::HistogramTester>();
histogram_tester->ExpectTotalCount(
"Ash.UnifiedSystemView.FeaturePod.ToggledOn",
/*count=*/0);
histogram_tester->ExpectTotalCount(
"Ash.UnifiedSystemView.FeaturePod.ToggledOff",
/*count=*/0);
histogram_tester->ExpectTotalCount("Ash.UnifiedSystemView.FeaturePod.DiveIn",
/*count=*/0);
if (IsQsRevampEnabled()) {
histogram_tester->ExpectTotalCount("Ash.QuickSettings.FeaturePod.ToggledOn",
/*expected_count=*/0);
histogram_tester->ExpectTotalCount(
"Ash.QuickSettings.FeaturePod.ToggledOff",
/*expected_count=*/0);
histogram_tester->ExpectTotalCount("Ash.QuickSettings.FeaturePod.DiveIn",
/*expected_count=*/0);
} else {
histogram_tester->ExpectTotalCount(
"Ash.UnifiedSystemView.FeaturePod.ToggledOn",
/*expected_count=*/0);
histogram_tester->ExpectTotalCount(
"Ash.UnifiedSystemView.FeaturePod.ToggledOff",
/*expected_count=*/0);
histogram_tester->ExpectTotalCount(
"Ash.UnifiedSystemView.FeaturePod.DiveIn",
/*expected_count=*/0);
}
// Show nightlight detailed view (settings window) when pressing on the
// label.
PressLabel();
histogram_tester->ExpectTotalCount(
"Ash.UnifiedSystemView.FeaturePod.ToggledOn",
/*count=*/0);
histogram_tester->ExpectTotalCount(
"Ash.UnifiedSystemView.FeaturePod.ToggledOff",
/*count=*/0);
histogram_tester->ExpectTotalCount("Ash.UnifiedSystemView.FeaturePod.DiveIn",
/*count=*/1);
histogram_tester->ExpectBucketCount("Ash.UnifiedSystemView.FeaturePod.DiveIn",
QsFeatureCatalogName::kNightLight,
/*expected_count=*/1);
if (IsQsRevampEnabled()) {
histogram_tester->ExpectTotalCount("Ash.QuickSettings.FeaturePod.ToggledOn",
/*expected_count=*/0);
histogram_tester->ExpectTotalCount(
"Ash.QuickSettings.FeaturePod.ToggledOff",
/*expected_count=*/0);
histogram_tester->ExpectTotalCount("Ash.QuickSettings.FeaturePod.DiveIn",
/*expected_count=*/0);
histogram_tester->ExpectBucketCount("Ash.QuickSettings.FeaturePod.DiveIn",
QsFeatureCatalogName::kNightLight,
/*expected_count=*/0);
} else {
histogram_tester->ExpectTotalCount(
"Ash.UnifiedSystemView.FeaturePod.ToggledOn",
/*expected_count=*/0);
histogram_tester->ExpectTotalCount(
"Ash.UnifiedSystemView.FeaturePod.ToggledOff",
/*expected_count=*/0);
histogram_tester->ExpectTotalCount(
"Ash.UnifiedSystemView.FeaturePod.DiveIn",
/*expected_count=*/1);
histogram_tester->ExpectBucketCount(
"Ash.UnifiedSystemView.FeaturePod.DiveIn",
QsFeatureCatalogName::kNightLight,
/*expected_count=*/1);
}
}
} // namespace ash
} // namespace ash

@ -22,7 +22,7 @@
#include "ash/system/tray/tray_utils.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_view.h"
#include "base/functional/bind.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/views/border.h"
@ -189,6 +189,14 @@ void UnifiedSliderBubbleController::ShowBubble(SliderType slider_type) {
return;
}
// When tray bubble is already shown, the brightness slider will get shown in
// display detailed view. Bail out if the display details are already showing
// to avoid resetting the bubble state.
if (slider_type == SLIDER_TYPE_DISPLAY_BRIGHTNESS && tray_->bubble() &&
tray_->bubble()->ShowingDisplayDetailedView()) {
return;
}
if (IsAnyMainBubbleShown()) {
tray_->EnsureBubbleExpanded();
@ -279,7 +287,9 @@ void UnifiedSliderBubbleController::CreateSliderController() {
return;
case SLIDER_TYPE_DISPLAY_BRIGHTNESS:
slider_controller_ = std::make_unique<UnifiedBrightnessSliderController>(
tray_->model().get());
tray_->model().get(),
base::BindRepeating(&UnifiedSystemTray::ShowDisplayDetailedViewBubble,
base::Unretained(tray_)));
return;
case SLIDER_TYPE_KEYBOARD_BACKLIGHT_TOGGLE:
slider_controller_ = std::make_unique<KeyboardBacklightToggleController>(

@ -379,11 +379,15 @@ void UnifiedSystemTray::ShowVolumeSliderBubble() {
}
void UnifiedSystemTray::ShowAudioDetailedViewBubble() {
// The settings menu bubble gains focus when |show_by_click| is true.
ShowBubble();
bubble_->ShowAudioDetailedView();
}
void UnifiedSystemTray::ShowDisplayDetailedViewBubble() {
ShowBubble();
bubble_->ShowDisplayDetailedView();
}
void UnifiedSystemTray::ShowNetworkDetailedViewBubble() {
ShowBubble();
bubble_->ShowNetworkDetailedView(true /* force */);

@ -136,6 +136,9 @@ class ASH_EXPORT UnifiedSystemTray
// Shows main bubble with audio settings detailed view.
void ShowAudioDetailedViewBubble();
// Shows main bubble with display settings detailed view.
void ShowDisplayDetailedViewBubble();
// Shows main bubble with network settings detailed view.
void ShowNetworkDetailedViewBubble();
@ -247,6 +250,10 @@ class ASH_EXPORT UnifiedSystemTray
return channel_indicator_view_;
}
UnifiedSliderBubbleController* slider_bubble_controller() {
return slider_bubble_controller_.get();
}
private:
static const base::TimeDelta kNotificationCountUpdateDelay;

@ -192,6 +192,16 @@ void UnifiedSystemTrayBubble::ShowAudioDetailedView() {
controller_->ShowAudioDetailedView();
}
void UnifiedSystemTrayBubble::ShowDisplayDetailedView() {
if (!bubble_widget_) {
return;
}
DCHECK(unified_view_ || quick_settings_view_);
DCHECK(controller_);
controller_->ShowDisplayDetailedView();
}
void UnifiedSystemTrayBubble::ShowCalendarView(
calendar_metrics::CalendarViewShowSource show_source,
calendar_metrics::CalendarEventSource event_source) {
@ -388,6 +398,10 @@ bool UnifiedSystemTrayBubble::ShowingAudioDetailedView() const {
return bubble_widget_ && controller_->showing_audio_detailed_view();
}
bool UnifiedSystemTrayBubble::ShowingDisplayDetailedView() const {
return bubble_widget_ && controller_->showing_display_detailed_view();
}
bool UnifiedSystemTrayBubble::ShowingCalendarView() const {
return bubble_widget_ && controller_->showing_calendar_view();
}

@ -84,6 +84,9 @@ class ASH_EXPORT UnifiedSystemTrayBubble
// Show audio settings detailed view.
void ShowAudioDetailedView();
// Show display settings detailed view.
void ShowDisplayDetailedView();
// Show calendar view.
void ShowCalendarView(calendar_metrics::CalendarViewShowSource show_source,
calendar_metrics::CalendarEventSource event_source);
@ -110,8 +113,10 @@ class ASH_EXPORT UnifiedSystemTrayBubble
// Fire a notification that an accessibility event has occured on this object.
void NotifyAccessibilityEvent(ax::mojom::Event event, bool send_native_event);
// Whether the bubble is currently showing audio details or calendar view.
// Whether the bubble is currently showing audio details or display details or
// calendar view.
bool ShowingAudioDetailedView() const;
bool ShowingDisplayDetailedView() const;
bool ShowingCalendarView() const;
// TrayBubbleBase:

@ -22,6 +22,7 @@
#include "ash/system/audio/unified_volume_slider_controller.h"
#include "ash/system/bluetooth/bluetooth_detailed_view_controller.h"
#include "ash/system/bluetooth/bluetooth_feature_pod_controller.h"
#include "ash/system/brightness/quick_settings_display_detailed_view_controller.h"
#include "ash/system/brightness/unified_brightness_slider_controller.h"
#include "ash/system/camera/autozoom_feature_pod_controller.h"
#include "ash/system/cast/cast_feature_pod_controller.h"
@ -179,7 +180,10 @@ UnifiedSystemTrayController::CreateUnifiedQuickSettingsView() {
unified_view->AddSliderView(volume_slider_controller_->CreateView());
brightness_slider_controller_ =
std::make_unique<UnifiedBrightnessSliderController>(model_);
std::make_unique<UnifiedBrightnessSliderController>(
model_, views::Button::PressedCallback(base::BindRepeating(
&UnifiedSystemTrayController::ShowDisplayDetailedView,
base::Unretained(this))));
unified_view->AddSliderView(brightness_slider_controller_->CreateView());
return unified_view;
@ -205,7 +209,10 @@ UnifiedSystemTrayController::CreateQuickSettingsView(int max_height) {
qs_view->AddSliderView(unified_volume_view_);
brightness_slider_controller_ =
std::make_unique<UnifiedBrightnessSliderController>(model_);
std::make_unique<UnifiedBrightnessSliderController>(
model_, views::Button::PressedCallback(base::BindRepeating(
&UnifiedSystemTrayController::ShowDisplayDetailedView,
base::Unretained(this))));
unified_brightness_view_ = brightness_slider_controller_->CreateView();
qs_view->AddSliderView(unified_brightness_view_);
@ -484,6 +491,12 @@ void UnifiedSystemTrayController::ShowAudioDetailedView() {
showing_audio_detailed_view_ = true;
}
void UnifiedSystemTrayController::ShowDisplayDetailedView() {
ShowDetailedView(
std::make_unique<QuickSettingsDisplayDetailedViewController>(this));
showing_display_detailed_view_ = true;
}
void UnifiedSystemTrayController::ShowNotifierSettingsView() {
if (features::IsOsSettingsAppBadgingToggleEnabled()) {
return;
@ -506,6 +519,7 @@ void UnifiedSystemTrayController::ShowCalendarView(
showing_calendar_view_ = true;
showing_audio_detailed_view_ = false;
showing_display_detailed_view_ = false;
for (auto& observer : observers_) {
observer.OnOpeningCalendarView();
@ -526,6 +540,7 @@ void UnifiedSystemTrayController::TransitionToMainView(bool restore_focus) {
}
showing_audio_detailed_view_ = false;
showing_display_detailed_view_ = false;
// Transfer `detailed_view_controller_` to a scoped object, which will be
// destroyed once it's out of this method's scope (after resetting
@ -573,6 +588,7 @@ void UnifiedSystemTrayController::EnsureCollapsed() {
void UnifiedSystemTrayController::EnsureExpanded() {
if (detailed_view_controller_) {
showing_audio_detailed_view_ = false;
showing_display_detailed_view_ = false;
if (features::IsQsRevampEnabled()) {
quick_settings_view_->ResetDetailedView();
} else {
@ -761,6 +777,7 @@ void UnifiedSystemTrayController::ShowDetailedView(
}
showing_audio_detailed_view_ = false;
showing_display_detailed_view_ = false;
if (features::IsQsRevampEnabled()) {
bubble_->UpdateBubbleHeight(/*is_showing_detiled_view=*/true);
quick_settings_view_->SetDetailedView(controller->CreateView());

@ -127,6 +127,8 @@ class ASH_EXPORT UnifiedSystemTrayController
void ShowLocaleDetailedView();
// Show the detailed view of audio. Called from the view.
void ShowAudioDetailedView();
// Show the detailed view of display. Called from the view.
void ShowDisplayDetailedView();
// Show the detailed view of notifier settings. Called from the view.
void ShowNotifierSettingsView();
// Show the detailed view of media controls. Called from the view.
@ -201,6 +203,10 @@ class ASH_EXPORT UnifiedSystemTrayController
return showing_audio_detailed_view_;
}
bool showing_display_detailed_view() const {
return showing_display_detailed_view_;
}
bool showing_calendar_view() const { return showing_calendar_view_; }
private:
@ -320,6 +326,8 @@ class ASH_EXPORT UnifiedSystemTrayController
bool showing_audio_detailed_view_ = false;
bool showing_display_detailed_view_ = false;
bool showing_calendar_view_ = false;
base::ObserverList<Observer> observers_;