blob: fc94adb8fe2a8618fb66deafb18d0a3a6fb4b44f [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/audio/unified_volume_view.h"
#include <memory>
#include <utility>
#include "ash/accessibility/accessibility_controller_impl.h"
#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/icon_button.h"
#include "ash/system/audio/unified_volume_slider_controller.h"
#include "ash/system/tray/tray_constants.h"
#include "ash/wm/screen_pinning_controller.h"
#include "ash/wm/window_state.h"
#include "chromeos/ash/components/audio/cras_audio_handler.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/chromeos/styles/cros_tokens_color_mappings.h"
#include "ui/gfx/vector_icon_types.h"
#include "ui/views/layout/box_layout.h"
namespace ash {
namespace {
// References to the icons that correspond to different volume levels.
const gfx::VectorIcon* const kVolumeLevelIcons[] = {
&kUnifiedMenuVolumeLowIcon, // Low volume.
&kUnifiedMenuVolumeMediumIcon, // Medium volume.
&kUnifiedMenuVolumeHighIcon, // High volume.
&kUnifiedMenuVolumeHighIcon, // Full volume.
};
// The maximum index of `kVolumeLevelIcons`.
constexpr int kVolumeLevels = std::size(kVolumeLevelIcons) - 1;
// The maximum index of `kQsVolumeLevelIcons`.
constexpr int kQsVolumeLevels =
std::size(UnifiedVolumeView::kQsVolumeLevelIcons) - 1;
// Get vector icon reference that corresponds to the given volume level. `level`
// is between 0.0 to 1.0 inclusive.
const gfx::VectorIcon& GetVolumeIconForLevel(float level) {
if (!features::IsQsRevampEnabled()) {
int index = static_cast<int>(std::ceil(level * kVolumeLevels));
DCHECK(index >= 0 && index <= kVolumeLevels);
return *kVolumeLevelIcons[index];
}
int index = static_cast<int>(std::ceil(level * kQsVolumeLevels));
DCHECK(index >= 0 && index <= kQsVolumeLevels);
return *UnifiedVolumeView::kQsVolumeLevelIcons[index];
}
} // namespace
UnifiedVolumeView::UnifiedVolumeView(
UnifiedVolumeSliderController* controller,
UnifiedVolumeSliderController::Delegate* delegate,
bool is_active_output_node)
: UnifiedSliderView(base::BindRepeating(
&UnifiedVolumeSliderController::SliderButtonPressed,
base::Unretained(controller)),
controller,
kSystemMenuVolumeHighIcon,
IDS_ASH_STATUS_TRAY_VOLUME_SLIDER_LABEL),
more_button_(AddChildView(std::make_unique<IconButton>(
base::BindRepeating(&UnifiedVolumeSliderController::Delegate::
OnAudioSettingsButtonClicked,
delegate->weak_ptr_factory_.GetWeakPtr()),
features::IsQsRevampEnabled() ? IconButton::Type::kMediumFloating
: IconButton::Type::kMedium,
&kQuickSettingsRightArrowIcon,
IDS_ASH_STATUS_TRAY_AUDIO))),
is_active_output_node_(is_active_output_node),
slider_style_(QuickSettingsSlider::Style::kDefault),
a11y_controller_(Shell::Get()->accessibility_controller()),
device_id_(CrasAudioHandler::Get()->GetPrimaryActiveOutputNode()) {
CrasAudioHandler::Get()->AddAudioObserver(this);
// In the case that there is a trusted pinned window (fullscreen lock mode)
// and the volume slider popup is shown, do not allow the more_button_ to
// open quick settings.
auto* window = Shell::Get()->screen_pinning_controller()->pinned_window();
if (window && WindowState::Get(window)->IsTrustedPinned()) {
more_button_->SetEnabled(false);
}
if (features::IsQsRevampEnabled()) {
// TODO(b/257151067): Update the a11y name id.
// Adds the live caption button before `more_button_`.
a11y_controller_->AddObserver(this);
const bool enabled = a11y_controller_->live_caption().enabled();
live_caption_button_ = AddChildViewAt(
std::make_unique<IconButton>(
base::BindRepeating(&UnifiedVolumeView::OnLiveCaptionButtonPressed,
base::Unretained(this)),
IconButton::Type::kMedium,
enabled ? &kUnifiedMenuLiveCaptionIcon
: &kUnifiedMenuLiveCaptionOffIcon,
l10n_util::GetStringFUTF16(
IDS_ASH_STATUS_TRAY_LIVE_CAPTION_TOGGLE_TOOLTIP,
l10n_util::GetStringUTF16(
enabled
? IDS_ASH_STATUS_TRAY_LIVE_CAPTION_ENABLED_STATE_TOOLTIP
: IDS_ASH_STATUS_TRAY_LIVE_CAPTION_DISABLED_STATE_TOOLTIP)),
/*is_togglable=*/true,
/*has_border=*/true),
GetIndexOf(more_button_).value());
// Sets the icon, icon color, background color for `live_caption_button_`
// when it's toggled.
live_caption_button_->SetToggledVectorIcon(kUnifiedMenuLiveCaptionIcon);
live_caption_button_->SetIconToggledColorId(
cros_tokens::kCrosSysSystemOnPrimaryContainer);
live_caption_button_->SetBackgroundToggledColorId(
cros_tokens::kCrosSysSystemPrimaryContainer);
// Sets the icon, icon color, background color for `live_caption_button_`
// when it's not toggled.
live_caption_button_->SetVectorIcon(kUnifiedMenuLiveCaptionOffIcon);
live_caption_button_->SetIconColorId(cros_tokens::kCrosSysOnSurface);
live_caption_button_->SetBackgroundColorId(
cros_tokens::kCrosSysSystemOnBase);
live_caption_button_->SetToggled(enabled);
}
Update(/*by_user=*/false);
}
UnifiedVolumeView::UnifiedVolumeView(UnifiedVolumeSliderController* controller,
uint64_t device_id,
bool is_active_output_node)
: UnifiedSliderView(base::BindRepeating(
&UnifiedVolumeSliderController::SliderButtonPressed,
base::Unretained(controller)),
controller,
kSystemMenuVolumeHighIcon,
IDS_ASH_STATUS_TRAY_VOLUME_SLIDER_LABEL,
false,
QuickSettingsSlider::Style::kRadioActive),
more_button_(nullptr),
is_active_output_node_(is_active_output_node),
slider_style_(QuickSettingsSlider::Style::kRadioActive),
a11y_controller_(Shell::Get()->accessibility_controller()),
device_id_(device_id) {
CrasAudioHandler::Get()->AddAudioObserver(this);
auto* layout = SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kHorizontal, kRadioSliderViewPadding,
kRadioSliderViewSpacing));
slider()->SetBorder(views::CreateEmptyBorder(kRadioSliderPadding));
slider()->SetPreferredSize(kRadioSliderPreferredSize);
slider_icon()->SetBorder(views::CreateEmptyBorder(kRadioSliderIconPadding));
layout->SetFlexForView(slider()->parent(), /*flex=*/1);
layout->set_cross_axis_alignment(
views::BoxLayout::CrossAxisAlignment::kCenter);
SetPreferredSize(kRadioSliderPreferredSize);
Update(/*by_user=*/false);
}
UnifiedVolumeView::~UnifiedVolumeView() {
CrasAudioHandler::Get()->RemoveAudioObserver(this);
if (features::IsQsRevampEnabled()) {
a11y_controller_->RemoveObserver(this);
}
}
void UnifiedVolumeView::Update(bool by_user) {
auto* audio_handler = CrasAudioHandler::Get();
float level = audio_handler->GetOutputVolumePercent() / 100.f;
if (!features::IsQsRevampEnabled()) {
bool is_muted = CrasAudioHandler::Get()->IsOutputMuted();
// To indicate that the volume is muted, set the volume slider to the
// minimal visual style.
slider()->SetRenderingStyle(
is_muted ? views::Slider::RenderingStyle::kMinimalStyle
: views::Slider::RenderingStyle::kDefaultStyle);
slider()->SetEnabled(!CrasAudioHandler::Get()->IsOutputMutedByPolicy());
// The button should be gray when muted and colored otherwise.
button()->SetToggled(!is_muted);
button()->SetVectorIcon(is_muted ? kUnifiedMenuVolumeMuteIcon
: GetVolumeIconForLevel(level));
std::u16string state_tooltip_text = l10n_util::GetStringUTF16(
is_muted ? IDS_ASH_STATUS_TRAY_VOLUME_STATE_MUTED
: IDS_ASH_STATUS_TRAY_VOLUME_STATE_ON);
button()->SetTooltipText(l10n_util::GetStringFUTF16(
IDS_ASH_STATUS_TRAY_VOLUME, state_tooltip_text));
} else {
level = audio_handler->GetOutputVolumePercentForDevice(device_id_) / 100.f;
// When muted by keyboard, the level stored in `audio_handler` should be
// preserved but the slider should appear as muted. `level` is set to 0
// manually to update the slider and icon.
if (audio_handler->IsOutputMutedForDevice(device_id_)) {
level = 0;
}
auto active_device_id = audio_handler->GetPrimaryActiveOutputNode();
switch (slider_style_) {
case QuickSettingsSlider::Style::kDefault: {
// TODO(b/257151067): Adds tooltip.
slider_icon()->SetImage(ui::ImageModel::FromVectorIcon(
GetVolumeIconForLevel(level),
cros_tokens::kCrosSysSystemOnPrimaryContainer, kQsSliderIconSize));
break;
}
case QuickSettingsSlider::Style::kRadioActive:
case QuickSettingsSlider::Style::kRadioInactive: {
static_cast<QuickSettingsSlider*>(slider())->SetSliderStyle(
active_device_id == device_id_
? QuickSettingsSlider::Style::kRadioActive
: QuickSettingsSlider::Style::kRadioInactive);
slider_icon()->SetImage(ui::ImageModel::FromVectorIcon(
GetVolumeIconForLevel(level),
active_device_id == device_id_
? cros_tokens::kCrosSysSystemOnPrimaryContainer
: cros_tokens::kCrosSysSecondary,
kQsSliderIconSize));
break;
}
default:
NOTREACHED();
}
}
// Slider's value is in finer granularity than audio volume level(0.01),
// there will be a small discrepancy between slider's value and volume level
// on audio side. To avoid the jittering in slider UI, use the slider's
// current value.
if (level != 1.0 && std::abs(level - slider()->GetValue()) <
kAudioSliderIgnoreUpdateThreshold) {
level = slider()->GetValue();
}
// Note: even if the value does not change, we still need to call this
// function to enable accessibility events (crbug.com/1013251).
SetSliderValue(level, by_user);
}
void UnifiedVolumeView::OnLiveCaptionButtonPressed() {
a11y_controller_->live_caption().SetEnabled(
!a11y_controller_->live_caption().enabled());
}
void UnifiedVolumeView::OnOutputNodeVolumeChanged(uint64_t node_id,
int volume) {
Update(/*by_user=*/true);
}
void UnifiedVolumeView::OnOutputMuteChanged(bool mute_on) {
Update(/*by_user=*/true);
}
void UnifiedVolumeView::OnAudioNodesChanged() {
Update(/*by_user=*/true);
}
void UnifiedVolumeView::OnActiveOutputNodeChanged() {
// If this view is for the active output node, we need to update the
// `device_id_` before repaint.
if (is_active_output_node_) {
device_id_ = CrasAudioHandler::Get()->GetPrimaryActiveOutputNode();
}
Update(/*by_user=*/true);
}
void UnifiedVolumeView::OnActiveInputNodeChanged() {
Update(/*by_user=*/true);
}
void UnifiedVolumeView::OnAccessibilityStatusChanged() {
if (!features::IsQsRevampEnabled()) {
return;
}
const bool enabled = a11y_controller_->live_caption().enabled();
// Sets `live_caption_button_` toggle state to update its icon, icon color,
// and background color.
live_caption_button_->SetToggled(enabled);
// Updates the tooltip of `live_caption_button_`.
std::u16string toggle_tooltip = l10n_util::GetStringUTF16(
enabled ? IDS_ASH_STATUS_TRAY_LIVE_CAPTION_ENABLED_STATE_TOOLTIP
: IDS_ASH_STATUS_TRAY_LIVE_CAPTION_DISABLED_STATE_TOOLTIP);
live_caption_button_->SetTooltipText(l10n_util::GetStringFUTF16(
IDS_ASH_STATUS_TRAY_LIVE_CAPTION_TOGGLE_TOOLTIP, toggle_tooltip));
}
void UnifiedVolumeView::ChildVisibilityChanged(views::View* child) {
Layout();
}
void UnifiedVolumeView::VisibilityChanged(View* starting_from,
bool is_visible) {
Update(/*by_user=*/true);
}
BEGIN_METADATA(UnifiedVolumeView, views::View)
END_METADATA
} // namespace ash