diff options
23 files changed, 1018 insertions, 329 deletions
diff --git a/ash/ash.gyp b/ash/ash.gyp index d53debc..d38804c 100644 --- a/ash/ash.gyp +++ b/ash/ash.gyp @@ -184,8 +184,8 @@ 'system/brightness/tray_brightness.cc', 'system/brightness/tray_brightness.h', 'system/chromeos/audio/audio_observer.h', - 'system/chromeos/audio/tray_volume.cc', - 'system/chromeos/audio/tray_volume.h', + 'system/chromeos/audio/tray_audio.cc', + 'system/chromeos/audio/tray_audio.h', 'system/chromeos/enterprise/enterprise_domain_observer.h', 'system/chromeos/enterprise/tray_enterprise.h', 'system/chromeos/enterprise/tray_enterprise.cc', diff --git a/ash/ash_strings.grd b/ash/ash_strings.grd index 14de4a9..0dbb598 100644 --- a/ash/ash_strings.grd +++ b/ash/ash_strings.grd @@ -284,6 +284,15 @@ Press Ctrl+Alt+Z to disable. <message name="IDS_ASH_STATUS_TRAY_VOLUME" desc="The accessible text for the volume slider."> Volume </message> + <message name="IDS_ASH_STATUS_TRAY_AUDIO" desc="The label used in audio detailed page bottom header of ash tray pop up."> + Audio Settings + </message> + <message name="IDS_ASH_STATUS_TRAY_AUDIO_OUTPUT" desc="The label used in audio detailed page for audio output section of ash tray pop up."> + OUTPUT + </message> + <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_MIRRORING" desc="The label used in the tray to show that the current status is mirroring."> Mirroring to <ph name="DISPLAY_NAME">$1</ph> diff --git a/ash/ash_switches.cc b/ash/ash_switches.cc index bbbc3db..b6908a7 100644 --- a/ash/ash_switches.cc +++ b/ash/ash_switches.cc @@ -78,6 +78,9 @@ const char kAshEnableAdvancedGestures[] = "ash-enable-advanced-gestures"; // main monitor as internal. const char kAshEnableBrightnessControl[] = "ash-enable-brightness-control"; +// Enable the new audio handler. +const char kAshEnableNewAudioHandler[] = "ash-enable-new-audio-handler"; + // Enable immersive fullscreen mode, regardless of default setting. const char kAshEnableImmersiveFullscreen[] = "ash-enable-immersive-fullscreen"; diff --git a/ash/ash_switches.h b/ash/ash_switches.h index b0ceae2..a09f06d 100644 --- a/ash/ash_switches.h +++ b/ash/ash_switches.h @@ -36,6 +36,7 @@ ASH_EXPORT extern const char kAshDisableUIScaling[]; ASH_EXPORT extern const char kAshDisableDisplayRotation[]; ASH_EXPORT extern const char kAshEnableAdvancedGestures[]; ASH_EXPORT extern const char kAshEnableBrightnessControl[]; +ASH_EXPORT extern const char kAshEnableNewAudioHandler[]; #if defined(OS_LINUX) ASH_EXPORT extern const char kAshEnableMemoryMonitor[]; #endif diff --git a/ash/resources/ash_resources.grd b/ash/resources/ash_resources.grd index 47aca7f..f1d145c 100644 --- a/ash/resources/ash_resources.grd +++ b/ash/resources/ash_resources.grd @@ -163,6 +163,11 @@ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_VOLUME_LEVELS" file="cros/status/status_volume_dark.png" /> <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_VOLUME_MUTE" file="cros/status/status_volume_mute.png" /> + <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_AUDIO_HEADPHONE" file="cros/status/status_audio_device_headphones.png" /> + <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_AUDIO_USB" file="cros/status/status_audio_device_usb.png" /> + <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_AUDIO_BLUETOOTH" file="cros/status/status_audio_device_bluetooth.png" /> + <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_AUDIO_HDMI" file="cros/status/status_audio_device_hdmi.png" /> + <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_WIFI_DISABLED" file="cros/network/status_wifi_disabled.png" /> <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_WIFI_DISABLED_HOVER" file="cros/network/status_wifi_disabled_hover.png" /> <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_WIFI_ENABLED" file="cros/network/status_wifi_enabled.png" /> diff --git a/ash/system/chromeos/audio/audio_observer.h b/ash/system/chromeos/audio/audio_observer.h index 9d529c0..0c3f5d7 100644 --- a/ash/system/chromeos/audio/audio_observer.h +++ b/ash/system/chromeos/audio/audio_observer.h @@ -9,6 +9,8 @@ namespace ash { +// TODO(jennyz): crbug.com/233310. Remove this file when new audio handler +// stabilized. class ASH_EXPORT AudioObserver { public: virtual ~AudioObserver() {} diff --git a/ash/system/chromeos/audio/tray_audio.cc b/ash/system/chromeos/audio/tray_audio.cc new file mode 100644 index 0000000..ea0cb94a --- /dev/null +++ b/ash/system/chromeos/audio/tray_audio.cc @@ -0,0 +1,612 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ash/system/chromeos/audio/tray_audio.h" + +#include <cmath> + +#include "ash/ash_constants.h" +#include "ash/ash_switches.h" +#include "ash/shell.h" +#include "ash/system/tray/actionable_view.h" +#include "ash/system/tray/fixed_sized_scroll_view.h" +#include "ash/system/tray/hover_highlight_view.h" +#include "ash/system/tray/system_tray.h" +#include "ash/system/tray/system_tray_delegate.h" +#include "ash/system/tray/system_tray_notifier.h" +#include "ash/system/tray/tray_constants.h" +#include "ash/volume_control_delegate.h" +#include "base/command_line.h" +#include "base/utf_string_conversions.h" +#include "chromeos/audio/cras_audio_handler.h" +#include "grit/ash_resources.h" +#include "grit/ash_strings.h" +#include "third_party/skia/include/core/SkCanvas.h" +#include "third_party/skia/include/core/SkPaint.h" +#include "third_party/skia/include/core/SkRect.h" +#include "third_party/skia/include/effects/SkGradientShader.h" +#include "ui/base/l10n/l10n_util.h" +#include "ui/base/resource/resource_bundle.h" +#include "ui/gfx/canvas.h" +#include "ui/gfx/image/image.h" +#include "ui/gfx/image/image_skia_operations.h" +#include "ui/views/controls/button/image_button.h" +#include "ui/views/controls/image_view.h" +#include "ui/views/controls/label.h" +#include "ui/views/controls/slider.h" +#include "ui/views/layout/box_layout.h" +#include "ui/views/view.h" + +namespace ash { +namespace internal { + +namespace { +const int kVolumeImageWidth = 25; +const int kVolumeImageHeight = 25; +const int kBarSeparatorWidth = 25; +const int kBarSeparatorHeight = 30; +const int kSliderRightPaddingToVolumeViewEdge = 17; +const int kExtraPaddingBetweenBarAndMore = 10; + +const int kNoAudioDeviceIcon = -1; + +// IDR_AURA_UBER_TRAY_VOLUME_LEVELS contains 5 images, +// The one for mute is at the 0 index and the other +// four are used for ascending volume levels. +const int kVolumeLevels = 4; + +bool UseNewAudioHandler() { + return CommandLine::ForCurrentProcess()-> + HasSwitch(ash::switches::kAshEnableNewAudioHandler); +} + +bool IsAudioMuted() { + if(UseNewAudioHandler()) { + return chromeos::CrasAudioHandler::Get()->IsOutputMuted(); + } else { + return Shell::GetInstance()->system_tray_delegate()-> + GetVolumeControlDelegate()->IsAudioMuted(); + } +} + +float GetVolumeLevel() { + if (UseNewAudioHandler()) { + return chromeos::CrasAudioHandler::Get()->GetOutputVolumePercent() / 100.0f; + } else { + return Shell::GetInstance()->system_tray_delegate()-> + GetVolumeControlDelegate()->GetVolumeLevel(); + } +} + +int GetAudioDeviceIconId(chromeos::AudioDeviceType type) { + if (type == chromeos::AUDIO_TYPE_HEADPHONE) + return IDR_AURA_UBER_TRAY_AUDIO_HEADPHONE; + else if (type == chromeos::AUDIO_TYPE_USB) + return IDR_AURA_UBER_TRAY_AUDIO_USB; + else if (type == chromeos::AUDIO_TYPE_BLUETOOTH) + return IDR_AURA_UBER_TRAY_AUDIO_BLUETOOTH; + else if (type == chromeos::AUDIO_TYPE_HDMI) + return IDR_AURA_UBER_TRAY_AUDIO_HDMI; + else + return kNoAudioDeviceIcon; +} + +} // namespace + +namespace tray { + +class VolumeButton : public views::ToggleImageButton { + public: + explicit VolumeButton(views::ButtonListener* listener) + : views::ToggleImageButton(listener), + image_index_(-1) { + SetImageAlignment(ALIGN_CENTER, ALIGN_MIDDLE); + image_ = ui::ResourceBundle::GetSharedInstance().GetImageNamed( + IDR_AURA_UBER_TRAY_VOLUME_LEVELS); + SetPreferredSize(gfx::Size(kTrayPopupItemHeight, kTrayPopupItemHeight)); + Update(); + } + + virtual ~VolumeButton() {} + + void Update() { + float level = GetVolumeLevel(); + int image_index = IsAudioMuted() ? + 0 : (level == 1.0 ? + kVolumeLevels : + std::max(1, int(std::ceil(level * (kVolumeLevels - 1))))); + if (image_index != image_index_) { + gfx::Rect region(0, image_index * kVolumeImageHeight, + kVolumeImageWidth, kVolumeImageHeight); + gfx::ImageSkia image_skia = gfx::ImageSkiaOperations::ExtractSubset( + *(image_.ToImageSkia()), region); + SetImage(views::CustomButton::STATE_NORMAL, &image_skia); + image_index_ = image_index; + } + SchedulePaint(); + } + + private: + // Overridden from views::View. + virtual gfx::Size GetPreferredSize() OVERRIDE { + gfx::Size size = views::ToggleImageButton::GetPreferredSize(); + size.set_height(kTrayPopupItemHeight); + return size; + } + + gfx::Image image_; + int image_index_; + + DISALLOW_COPY_AND_ASSIGN(VolumeButton); +}; + +class VolumeSlider : public views::Slider { + public: + explicit VolumeSlider(views::SliderListener* listener) + : views::Slider(listener, views::Slider::HORIZONTAL) { + set_focus_border_color(kFocusBorderColor); + SetValue(GetVolumeLevel()); + SetAccessibleName( + ui::ResourceBundle::GetSharedInstance().GetLocalizedString( + IDS_ASH_STATUS_TRAY_VOLUME)); + Update(); + } + virtual ~VolumeSlider() {} + + void Update() { + UpdateState(!IsAudioMuted()); + } + + DISALLOW_COPY_AND_ASSIGN(VolumeSlider); +}; + +// Vertical bar separator that can be placed on the VolumeView. +class BarSeparator : public views::View { + public: + BarSeparator() {} + virtual ~BarSeparator() {} + + private: + // Overriden from views::View. + virtual gfx::Size GetPreferredSize() OVERRIDE { + return gfx::Size(kBarSeparatorWidth, kBarSeparatorHeight); + } + + virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE { + canvas->FillRect(gfx::Rect(width() / 2, 0, 1, height()), + kButtonStrokeColor); + } + + DISALLOW_COPY_AND_ASSIGN(BarSeparator); +}; + +class VolumeView : public ActionableView, + public views::ButtonListener, + public views::SliderListener { + public: + VolumeView(SystemTrayItem* owner, bool is_default_view) + : owner_(owner), + icon_(NULL), + slider_(NULL), + bar_(NULL), + device_type_(NULL), + more_(NULL), + is_default_view_(is_default_view) { + SetLayoutManager(new views::BoxLayout(views::BoxLayout::kHorizontal, + kTrayPopupPaddingHorizontal, 0, kTrayPopupPaddingBetweenItems)); + + icon_ = new VolumeButton(this); + AddChildView(icon_); + + slider_ = new VolumeSlider(this); + AddChildView(slider_); + + device_type_ = new views::ImageView; + AddChildView(device_type_); + + bar_ = new BarSeparator; + AddChildView(bar_); + + more_ = new views::ImageView; + more_->EnableCanvasFlippingForRTLUI(true); + more_->SetImage(ui::ResourceBundle::GetSharedInstance().GetImageNamed( + IDR_AURA_UBER_TRAY_MORE).ToImageSkia()); + AddChildView(more_); + + Update(); + } + + virtual ~VolumeView() {} + + void Update() { + icon_->Update(); + slider_->Update(); + UpdateDeviceTypeAndMore(); + Layout(); + } + + void SetVolumeLevel(float percent) { + // The change in volume will be reflected via accessibility system events, + // so we prevent the UI event from being sent here. + slider_->set_enable_accessibility_events(false); + slider_->SetValue(percent); + // It is possible that the volume was (un)muted, but the actual volume level + // did not change. In that case, setting the value of the slider won't + // trigger an update. So explicitly trigger an update. + Update(); + slider_->set_enable_accessibility_events(true); + } + + private: + // Updates bar_, device_type_ icon, and more_ buttons. + void UpdateDeviceTypeAndMore() { + if (!UseNewAudioHandler() || !is_default_view_) { + more_->SetVisible(false); + bar_->SetVisible(false); + device_type_->SetVisible(false); + return; + } + + chromeos::CrasAudioHandler* audio_handler = + chromeos::CrasAudioHandler::Get(); + bool show_more = audio_handler->has_alternative_output() || + audio_handler->has_alternative_input(); + more_->SetVisible(show_more); + + // Show output device icon if necessary. + chromeos::AudioDevice device; + audio_handler->GetActiveOutputDevice(&device); + int device_icon = GetAudioDeviceIconId(device.type); + if (device_icon != kNoAudioDeviceIcon) { + device_type_->SetVisible(true); + device_type_->SetImage( + ui::ResourceBundle::GetSharedInstance().GetImageNamed( + device_icon).ToImageSkia()); + bar_->SetVisible(false); + } else { + device_type_->SetVisible(false); + bar_->SetVisible(show_more); + } + } + + // Overridden from views::View. + virtual void Layout() OVERRIDE { + views::View::Layout(); + + if (!more_->visible()) { + int w = width() - slider_->bounds().x() - + kSliderRightPaddingToVolumeViewEdge; + slider_->SetSize(gfx::Size(w, slider_->height())); + return; + } + + // Make sure the chevron always has the full size. + gfx::Size size = more_->GetPreferredSize(); + gfx::Rect bounds(size); + bounds.set_x(width() - size.width() - kTrayPopupPaddingBetweenItems); + bounds.set_y((height() - size.height()) / 2); + more_->SetBoundsRect(bounds); + + // Layout bar_ or device_type_ at the left of the more_ button. + views::View* view_left_to_more; + if (bar_->visible()) + view_left_to_more = bar_; + else + view_left_to_more = device_type_; + gfx::Size bar_size = view_left_to_more->GetPreferredSize(); + gfx::Rect bar_bounds(bar_size); + bar_bounds.set_x(more_->bounds().x() - bar_size.width() - + kExtraPaddingBetweenBarAndMore); + bar_bounds.set_y((height() - bar_size.height()) / 2); + view_left_to_more->SetBoundsRect(bar_bounds); + + + // Layout slider, calculate slider width. + gfx::Rect slider_bounds = slider_->bounds(); + slider_bounds.set_width( + view_left_to_more->bounds().x() - kTrayPopupPaddingBetweenItems + - slider_bounds.x()); + slider_->SetBoundsRect(slider_bounds); + } + + // Overridden from views::ButtonListener. + virtual void ButtonPressed(views::Button* sender, + const ui::Event& event) OVERRIDE { + CHECK(sender == icon_); + if (UseNewAudioHandler()) { + chromeos::CrasAudioHandler::Get()->SetOutputMute(!IsAudioMuted()); + } else { + ash::Shell::GetInstance()->system_tray_delegate()-> + GetVolumeControlDelegate()->SetAudioMuted(!IsAudioMuted()); + } + } + + // Overridden from views:SliderListener. + virtual void SliderValueChanged(views::Slider* sender, + float value, + float old_value, + views::SliderChangeReason reason) OVERRIDE { + if (reason == views::VALUE_CHANGED_BY_USER) { + if (UseNewAudioHandler()) { + chromeos::CrasAudioHandler::Get()-> + SetOutputVolumePercent(value * 100.0f); + } + else { + ash::Shell::GetInstance()->system_tray_delegate()-> + GetVolumeControlDelegate()->SetVolumeLevel(value); + } + } + icon_->Update(); + } + + // Overriden from ActinableView. + virtual bool PerformAction(const ui::Event& event) OVERRIDE { + if (!more_->visible()) + return false; + owner_->TransitionDetailedView(); + return true; + } + + SystemTrayItem* owner_; + VolumeButton* icon_; + VolumeSlider* slider_; + BarSeparator* bar_; + views::ImageView* device_type_; + views::ImageView* more_; + bool is_default_view_; + + DISALLOW_COPY_AND_ASSIGN(VolumeView); +}; + +class AudioDetailedView : public TrayDetailsView, + public ViewClickListener { + public: + AudioDetailedView(SystemTrayItem* owner, user::LoginStatus login) + : TrayDetailsView(owner), + login_(login) { + CreateItems(); + Update(); + } + + virtual ~AudioDetailedView() { + } + + void Update() { + UpdateAudioDevices(); + Layout(); + } + + private: + void CreateItems() { + CreateScrollableList(); + CreateHeaderEntry(); + } + + void CreateHeaderEntry() { + CreateSpecialRow(IDS_ASH_STATUS_TRAY_AUDIO, this); + } + + void UpdateAudioDevices() { + output_devices_.clear(); + input_devices_.clear(); + chromeos::AudioDeviceList devices; + chromeos::CrasAudioHandler::Get()->GetAudioDevices(&devices); + for (size_t i = 0; i < devices.size(); ++i) { + if (devices[i].is_input) + input_devices_.push_back(devices[i]); + else + output_devices_.push_back(devices[i]); + } + UpdateScrollableList(); + } + + void UpdateScrollableList() { + scroll_content()->RemoveAllChildViews(true); + device_map_.clear(); + + // Add audio output devices. + AddScrollListItem( + l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_AUDIO_OUTPUT), + gfx::Font::BOLD, + false); /* no checkmark */ + for (size_t i = 0; i < output_devices_.size(); ++i) { + HoverHighlightView* container = AddScrollListItem( + output_devices_[i].display_name, + gfx::Font::NORMAL, + output_devices_[i].active); /* checkmark if active */ + device_map_[container] = output_devices_[i]; + } + + AddScrollSeparator(); + + // Add audio input devices. + AddScrollListItem( + l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_AUDIO_INPUT), + gfx::Font::BOLD, + false); /* no checkmark */ + for (size_t i = 0; i < input_devices_.size(); ++i) { + HoverHighlightView* container = AddScrollListItem( + input_devices_[i].display_name, + gfx::Font::NORMAL, + input_devices_[i].active); /* checkmark if active */ + device_map_[container] = input_devices_[i]; + } + + scroll_content()->SizeToPreferredSize(); + scroller()->Layout(); + } + + HoverHighlightView* AddScrollListItem(const string16& text, + gfx::Font::FontStyle style, + bool checked) { + HoverHighlightView* container = new HoverHighlightView(this); + container->AddCheckableLabel(text, style, checked); + scroll_content()->AddChildView(container); + return container; + } + + // Overridden from ViewClickListener. + virtual void OnViewClicked(views::View* sender) OVERRIDE { + if (sender == footer()->content()) { + owner()->system_tray()->ShowDefaultView(BUBBLE_USE_EXISTING); + } else { + AudioDeviceMap::iterator iter = device_map_.find(sender); + if (iter == device_map_.end()) + return; + chromeos::AudioDevice& device = iter->second; + if (device.is_input) + chromeos::CrasAudioHandler::Get()->SetActiveInputNode(device.id); + else + chromeos::CrasAudioHandler::Get()->SetActiveOutputNode(device.id); + } + } + + typedef std::map<views::View*, chromeos::AudioDevice> AudioDeviceMap; + + user::LoginStatus login_; + chromeos::AudioDeviceList output_devices_; + chromeos::AudioDeviceList input_devices_; + AudioDeviceMap device_map_; + + DISALLOW_COPY_AND_ASSIGN(AudioDetailedView); +}; + +} // namespace tray + +TrayAudio::TrayAudio(SystemTray* system_tray) + : TrayImageItem(system_tray, IDR_AURA_UBER_TRAY_VOLUME_MUTE), + volume_view_(NULL), + audio_detail_(NULL), + pop_up_volume_view_(false) { + if (UseNewAudioHandler()) + chromeos::CrasAudioHandler::Get()->AddAudioObserver(this); + else + Shell::GetInstance()->system_tray_notifier()->AddAudioObserver(this); +} + +TrayAudio::~TrayAudio() { + if (UseNewAudioHandler()) { + if (chromeos::CrasAudioHandler::IsInitialized()) + chromeos::CrasAudioHandler::Get()->RemoveAudioObserver(this); + } else { + Shell::GetInstance()->system_tray_notifier()->RemoveAudioObserver(this); + } +} + +bool TrayAudio::GetInitialVisibility() { + return IsAudioMuted(); +} + +views::View* TrayAudio::CreateDefaultView(user::LoginStatus status) { + volume_view_ = new tray::VolumeView(this, true); + return volume_view_; +} + +views::View* TrayAudio::CreateDetailedView(user::LoginStatus status) { + if (!UseNewAudioHandler() || pop_up_volume_view_) { + volume_view_ = new tray::VolumeView(this, false); + return volume_view_; + } else { + audio_detail_ = new tray::AudioDetailedView(this, status); + return audio_detail_; + } +} + +void TrayAudio::DestroyDefaultView() { + volume_view_ = NULL; +} + +void TrayAudio::DestroyDetailedView() { + if (audio_detail_) { + audio_detail_ = NULL; + } else if (volume_view_) { + volume_view_ = NULL; + pop_up_volume_view_ = false; + } +} + +bool TrayAudio::ShouldHideArrow() const { + return true; +} + +bool TrayAudio::ShouldShowLauncher() const { + return false; +} + +void TrayAudio::OnVolumeChanged(float percent) { + DCHECK(!UseNewAudioHandler()); + if (tray_view()) + tray_view()->SetVisible(GetInitialVisibility()); + + if (volume_view_) { + if (IsAudioMuted()) + percent = 0.0; + volume_view_->SetVolumeLevel(percent); + SetDetailedViewCloseDelay(kTrayPopupAutoCloseDelayInSeconds); + return; + } + PopupDetailedView(kTrayPopupAutoCloseDelayInSeconds, false); +} + +void TrayAudio::OnMuteToggled() { + DCHECK(!UseNewAudioHandler()); + if (tray_view()) + tray_view()->SetVisible(GetInitialVisibility()); + + if (volume_view_) + volume_view_->Update(); + else + PopupDetailedView(kTrayPopupAutoCloseDelayInSeconds, false); +} + + +void TrayAudio::OnOutputVolumeChanged() { + DCHECK(UseNewAudioHandler()); + float percent = GetVolumeLevel(); + if (tray_view()) + tray_view()->SetVisible(GetInitialVisibility()); + + if (volume_view_) { + volume_view_->SetVolumeLevel(percent); + SetDetailedViewCloseDelay(kTrayPopupAutoCloseDelayInSeconds); + return; + } + pop_up_volume_view_ = true; + PopupDetailedView(kTrayPopupAutoCloseDelayInSeconds, false); +} + +void TrayAudio::OnOutputMuteChanged() { + DCHECK(UseNewAudioHandler()); + if (tray_view()) + tray_view()->SetVisible(GetInitialVisibility()); + + if (volume_view_) + volume_view_->Update(); + else { + pop_up_volume_view_ = true; + PopupDetailedView(kTrayPopupAutoCloseDelayInSeconds, false); + } +} + +void TrayAudio::OnAudioNodesChanged() { + Update(); +} + +void TrayAudio::OnActiveOutputNodeChanged() { + Update(); +} + +void TrayAudio::OnActiveInputNodeChanged() { + Update(); +} + +void TrayAudio::Update() { + if (audio_detail_) + audio_detail_->Update(); + if (volume_view_) + volume_view_->Update(); +} + +} // namespace internal +} // namespace ash diff --git a/ash/system/chromeos/audio/tray_volume.h b/ash/system/chromeos/audio/tray_audio.h index 601aa39..af8f7a9 100644 --- a/ash/system/chromeos/audio/tray_volume.h +++ b/ash/system/chromeos/audio/tray_audio.h @@ -2,24 +2,27 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef ASH_SYSTEM_CHROMEOS_AUDIO_TRAY_VOLUME_H_ -#define ASH_SYSTEM_CHROMEOS_AUDIO_TRAY_VOLUME_H_ +#ifndef ASH_SYSTEM_CHROMEOS_AUDIO_TRAY_AUDIO_H_ +#define ASH_SYSTEM_CHROMEOS_AUDIO_TRAY_AUDIO_H_ #include "ash/system/chromeos/audio/audio_observer.h" #include "ash/system/tray/tray_image_item.h" +#include "chromeos/audio/cras_audio_handler.h" namespace ash { namespace internal { namespace tray { class VolumeView; +class AudioDetailedView; } -class TrayVolume : public TrayImageItem, - public AudioObserver { +class TrayAudio : public TrayImageItem, + public chromeos::CrasAudioHandler::AudioObserver, + public AudioObserver { public: - explicit TrayVolume(SystemTray* system_tray); - virtual ~TrayVolume(); + explicit TrayAudio(SystemTray* system_tray); + virtual ~TrayAudio(); private: // Overridden from TrayImageItem. @@ -37,17 +40,26 @@ class TrayVolume : public TrayImageItem, virtual void OnVolumeChanged(float percent) OVERRIDE; virtual void OnMuteToggled() OVERRIDE; + // Overridden from chromeos::CrasAudioHandler::AudioObserver. + virtual void OnOutputVolumeChanged() OVERRIDE; + virtual void OnOutputMuteChanged() OVERRIDE; + virtual void OnAudioNodesChanged() OVERRIDE; + virtual void OnActiveOutputNodeChanged() OVERRIDE; + virtual void OnActiveInputNodeChanged() OVERRIDE; + + void Update(); + tray::VolumeView* volume_view_; + tray::AudioDetailedView* audio_detail_; - // Was |volume_view_| created for CreateDefaultView() rather than - // CreateDetailedView()? Used to avoid resetting |volume_view_| - // inappropriately in DestroyDefaultView() or DestroyDetailedView(). - bool is_default_view_; + // True if VolumeView should be created for accelerator pop up; + // Otherwise, it should be created for detailed view in ash tray bubble. + bool pop_up_volume_view_; - DISALLOW_COPY_AND_ASSIGN(TrayVolume); + DISALLOW_COPY_AND_ASSIGN(TrayAudio); }; } // namespace internal } // namespace ash -#endif // ASH_SYSTEM_CHROMEOS_AUDIO_TRAY_VOLUME_H_ +#endif // ASH_SYSTEM_CHROMEOS_AUDIO_TRAY_AUDIO_H_ diff --git a/ash/system/chromeos/audio/tray_volume.cc b/ash/system/chromeos/audio/tray_volume.cc deleted file mode 100644 index 7551a4b..0000000 --- a/ash/system/chromeos/audio/tray_volume.cc +++ /dev/null @@ -1,288 +0,0 @@ -// Copyright (c) 2012 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "ash/system/chromeos/audio/tray_volume.h" - -#include <cmath> - -#include "ash/ash_constants.h" -#include "ash/shell.h" -#include "ash/system/tray/system_tray_delegate.h" -#include "ash/system/tray/system_tray_notifier.h" -#include "ash/system/tray/tray_bar_button_with_title.h" -#include "ash/system/tray/tray_constants.h" -#include "ash/volume_control_delegate.h" -#include "base/utf_string_conversions.h" -#include "grit/ash_resources.h" -#include "grit/ash_strings.h" -#include "third_party/skia/include/core/SkCanvas.h" -#include "third_party/skia/include/core/SkPaint.h" -#include "third_party/skia/include/core/SkRect.h" -#include "third_party/skia/include/effects/SkGradientShader.h" -#include "ui/base/resource/resource_bundle.h" -#include "ui/gfx/canvas.h" -#include "ui/gfx/image/image.h" -#include "ui/gfx/image/image_skia_operations.h" -#include "ui/views/controls/button/image_button.h" -#include "ui/views/controls/image_view.h" -#include "ui/views/controls/label.h" -#include "ui/views/controls/slider.h" -#include "ui/views/layout/box_layout.h" -#include "ui/views/view.h" - -namespace ash { -namespace internal { - -namespace { -const int kVolumeImageWidth = 25; -const int kVolumeImageHeight = 25; - -// IDR_AURA_UBER_TRAY_VOLUME_LEVELS contains 5 images, -// The one for mute is at the 0 index and the other -// four are used for ascending volume levels. -const int kVolumeLevels = 4; - -bool IsAudioMuted() { - return Shell::GetInstance()->system_tray_delegate()-> - GetVolumeControlDelegate()->IsAudioMuted(); -} - -float GetVolumeLevel() { - return Shell::GetInstance()->system_tray_delegate()-> - GetVolumeControlDelegate()->GetVolumeLevel(); -} - -} // namespace - -namespace tray { - -class VolumeButton : public views::ToggleImageButton { - public: - explicit VolumeButton(views::ButtonListener* listener) - : views::ToggleImageButton(listener), - image_index_(-1) { - SetImageAlignment(ALIGN_CENTER, ALIGN_MIDDLE); - image_ = ui::ResourceBundle::GetSharedInstance().GetImageNamed( - IDR_AURA_UBER_TRAY_VOLUME_LEVELS); - SetPreferredSize(gfx::Size(kTrayPopupItemHeight, kTrayPopupItemHeight)); - Update(); - } - - virtual ~VolumeButton() {} - - void Update() { - float level = GetVolumeLevel(); - int image_index = IsAudioMuted() ? - 0 : (level == 1.0 ? - kVolumeLevels : - std::max(1, int(std::ceil(level * (kVolumeLevels - 1))))); - if (image_index != image_index_) { - gfx::Rect region(0, image_index * kVolumeImageHeight, - kVolumeImageWidth, kVolumeImageHeight); - gfx::ImageSkia image_skia = gfx::ImageSkiaOperations::ExtractSubset( - *(image_.ToImageSkia()), region); - SetImage(views::CustomButton::STATE_NORMAL, &image_skia); - image_index_ = image_index; - } - SchedulePaint(); - } - - private: - // Overridden from views::View. - virtual gfx::Size GetPreferredSize() OVERRIDE { - gfx::Size size = views::ToggleImageButton::GetPreferredSize(); - size.set_height(kTrayPopupItemHeight); - return size; - } - - gfx::Image image_; - int image_index_; - - DISALLOW_COPY_AND_ASSIGN(VolumeButton); -}; - -class MuteButton : public TrayBarButtonWithTitle { - public: - explicit MuteButton(views::ButtonListener* listener) - : TrayBarButtonWithTitle(listener, - -1, // no title under mute button - kTrayBarButtonWidth) { - Update(); - } - virtual ~MuteButton() {} - - void Update() { - UpdateButton(IsAudioMuted()); - SchedulePaint(); - } - - private: - DISALLOW_COPY_AND_ASSIGN(MuteButton); -}; - -class VolumeSlider : public views::Slider { - public: - explicit VolumeSlider(views::SliderListener* listener) - : views::Slider(listener, views::Slider::HORIZONTAL) { - set_focus_border_color(kFocusBorderColor); - SetValue(GetVolumeLevel()); - SetAccessibleName( - ui::ResourceBundle::GetSharedInstance().GetLocalizedString( - IDS_ASH_STATUS_TRAY_VOLUME)); - Update(); - } - virtual ~VolumeSlider() {} - - void Update() { - UpdateState(!IsAudioMuted()); - } - - DISALLOW_COPY_AND_ASSIGN(VolumeSlider); -}; - -class VolumeView : public views::View, - public views::ButtonListener, - public views::SliderListener { - public: - VolumeView() { - SetLayoutManager(new views::BoxLayout(views::BoxLayout::kHorizontal, - kTrayPopupPaddingHorizontal, 0, kTrayPopupPaddingBetweenItems)); - - icon_ = new VolumeButton(this); - AddChildView(icon_); - - mute_ = new MuteButton(this); - AddChildView(mute_); - - slider_ = new VolumeSlider(this); - AddChildView(slider_); - } - - virtual ~VolumeView() {} - - void Update() { - icon_->Update(); - mute_->Update(); - slider_->Update(); - } - - void SetVolumeLevel(float percent) { - // The change in volume will be reflected via accessibility system events, - // so we prevent the UI event from being sent here. - slider_->set_enable_accessibility_events(false); - slider_->SetValue(percent); - // It is possible that the volume was (un)muted, but the actual volume level - // did not change. In that case, setting the value of the slider won't - // trigger an update. So explicitly trigger an update. - Update(); - slider_->set_enable_accessibility_events(true); - } - - private: - // Overridden from views::View. - virtual void OnBoundsChanged(const gfx::Rect& old_bounds) OVERRIDE { - int w = width() - slider_->x(); - slider_->SetSize(gfx::Size(w, slider_->height())); - } - - // Overridden from views::ButtonListener. - virtual void ButtonPressed(views::Button* sender, - const ui::Event& event) OVERRIDE { - CHECK(sender == icon_ || sender == mute_); - ash::Shell::GetInstance()->system_tray_delegate()-> - GetVolumeControlDelegate()->SetAudioMuted(!IsAudioMuted()); - } - - // Overridden from views:SliderListener. - virtual void SliderValueChanged(views::Slider* sender, - float value, - float old_value, - views::SliderChangeReason reason) OVERRIDE { - if (reason == views::VALUE_CHANGED_BY_USER) { - ash::Shell::GetInstance()->system_tray_delegate()-> - GetVolumeControlDelegate()->SetVolumeLevel(value); - } - icon_->Update(); - } - - VolumeButton* icon_; - MuteButton* mute_; - VolumeSlider* slider_; - - DISALLOW_COPY_AND_ASSIGN(VolumeView); -}; - -} // namespace tray - -TrayVolume::TrayVolume(SystemTray* system_tray) - : TrayImageItem(system_tray, IDR_AURA_UBER_TRAY_VOLUME_MUTE), - volume_view_(NULL), - is_default_view_(false) { - Shell::GetInstance()->system_tray_notifier()->AddAudioObserver(this); -} - -TrayVolume::~TrayVolume() { - Shell::GetInstance()->system_tray_notifier()->RemoveAudioObserver(this); -} - -bool TrayVolume::GetInitialVisibility() { - return IsAudioMuted(); -} - -views::View* TrayVolume::CreateDefaultView(user::LoginStatus status) { - volume_view_ = new tray::VolumeView; - is_default_view_ = true; - return volume_view_; -} - -views::View* TrayVolume::CreateDetailedView(user::LoginStatus status) { - volume_view_ = new tray::VolumeView; - is_default_view_ = false; - return volume_view_; -} - -void TrayVolume::DestroyDefaultView() { - if (is_default_view_) - volume_view_ = NULL; -} - -void TrayVolume::DestroyDetailedView() { - if (!is_default_view_) - volume_view_ = NULL; -} - -bool TrayVolume::ShouldHideArrow() const { - return true; -} - -bool TrayVolume::ShouldShowLauncher() const { - return false; -} - -void TrayVolume::OnVolumeChanged(float percent) { - if (tray_view()) - tray_view()->SetVisible(GetInitialVisibility()); - - if (volume_view_) { - if (IsAudioMuted()) - percent = 0.0; - volume_view_->SetVolumeLevel(percent); - SetDetailedViewCloseDelay(kTrayPopupAutoCloseDelayInSeconds); - return; - } - PopupDetailedView(kTrayPopupAutoCloseDelayInSeconds, false); -} - -void TrayVolume::OnMuteToggled() { - if (tray_view()) - tray_view()->SetVisible(GetInitialVisibility()); - - if (volume_view_) - volume_view_->Update(); - else - PopupDetailedView(kTrayPopupAutoCloseDelayInSeconds, false); -} - -} // namespace internal -} // namespace ash diff --git a/ash/system/tray/system_tray.cc b/ash/system/tray/system_tray.cc index 2418f80..198d847 100644 --- a/ash/system/tray/system_tray.cc +++ b/ash/system/tray/system_tray.cc @@ -50,7 +50,7 @@ #include "ui/views/view.h" #if defined(OS_CHROMEOS) -#include "ash/system/chromeos/audio/tray_volume.h" +#include "ash/system/chromeos/audio/tray_audio.h" #include "ash/system/chromeos/enterprise/tray_enterprise.h" #include "ash/system/chromeos/network/tray_network.h" #include "ash/system/chromeos/network/tray_sms.h" @@ -162,7 +162,7 @@ void SystemTray::CreateItems(SystemTrayDelegate* delegate) { #if defined(OS_CHROMEOS) AddTrayItem(new internal::TrayDisplay(this)); AddTrayItem(new internal::TrayScreenCapture(this)); - AddTrayItem(new internal::TrayVolume(this)); + AddTrayItem(new internal::TrayAudio(this)); #endif #if !defined(OS_WIN) AddTrayItem(new internal::TrayBrightness(this)); diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd index e86ae00..eea2baf 100644 --- a/chrome/app/generated_resources.grd +++ b/chrome/app/generated_resources.grd @@ -6780,6 +6780,12 @@ Keep your key file in a safe place. You will need it to create new versions of y <message name="IDS_FLAGS_ASH_DISABLE_NEW_NETWORK_STATUS_AREA_DESCRIPTION" desc="Title for the flag to enable using the new Network State Handler."> Disables the new network handlers which handle Shill communication without using NetworkLibrary for the status area. </message> + <message name="IDS_FLAGS_ASH_ENABLE_NEW_AUDIO_HANDLER_NAME" desc="Title for the flag to enable using the new audio handler."> + Enables new audio handler + </message> + <message name="IDS_FLAGS_ASH_ENABLE_NEW_AUDIO_HANDLER_DESCRIPTION" desc="Description for the flag to enable using the new audio handler."> + Enable the new audio handler which uses the new cras audio dbus apis. + </message> <message name="IDS_FLAGS_ENABLE_LOCALLY_MANAGED_USERS_NAME" desc="Title for the flag to enable locally managed users."> Enable locally managed users </message> diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc index 1e7902b..5a7f492 100644 --- a/chrome/browser/about_flags.cc +++ b/chrome/browser/about_flags.cc @@ -1162,6 +1162,13 @@ const Experiment kExperiments[] = { SINGLE_VALUE_TYPE(ash::switches::kAshDisableNewNetworkStatusArea), }, { + "ash-enable-new-audio-handler", + IDS_FLAGS_ASH_ENABLE_NEW_AUDIO_HANDLER_NAME, + IDS_FLAGS_ASH_ENABLE_NEW_AUDIO_HANDLER_DESCRIPTION, + kOsCrOS, + SINGLE_VALUE_TYPE(ash::switches::kAshEnableNewAudioHandler) + }, + { "enable-carrier-switching", IDS_FLAGS_ENABLE_CARRIER_SWITCHING, IDS_FLAGS_ENABLE_CARRIER_SWITCHING_DESCRIPTION, diff --git a/chrome/browser/chromeos/chrome_browser_main_chromeos.cc b/chrome/browser/chromeos/chrome_browser_main_chromeos.cc index 6617ce3..6cc01e0 100644 --- a/chrome/browser/chromeos/chrome_browser_main_chromeos.cc +++ b/chrome/browser/chromeos/chrome_browser_main_chromeos.cc @@ -7,6 +7,7 @@ #include <string> #include <vector> +#include "ash/ash_switches.h" #include "ash/shell.h" #include "base/bind.h" #include "base/callback.h" @@ -90,6 +91,7 @@ #include "chrome/common/logging_chrome.h" #include "chrome/common/pref_names.h" #include "chromeos/audio/audio_pref_handler.h" +#include "chromeos/audio/cras_audio_handler.h" #include "chromeos/chromeos_switches.h" #include "chromeos/cryptohome/async_method_caller.h" #include "chromeos/dbus/dbus_thread_manager.h" @@ -464,8 +466,14 @@ void ChromeBrowserMainPartsChromeos::PostMainMessageLoopStart() { // Threads are initialized between MainMessageLoopStart and MainMessageLoopRun. // about_flags settings are applied in ChromeBrowserMainParts::PreCreateThreads. void ChromeBrowserMainPartsChromeos::PreMainMessageLoopRun() { - AudioHandler::Initialize( - AudioPrefHandler::Create(g_browser_process->local_state())); + if (CommandLine::ForCurrentProcess()-> + HasSwitch(ash::switches::kAshEnableNewAudioHandler)) { + CrasAudioHandler::Initialize( + AudioPrefHandler::Create(g_browser_process->local_state())); + } else { + AudioHandler::Initialize( + AudioPrefHandler::Create(g_browser_process->local_state())); + } base::FilePath downloads_directory; CHECK(PathService::Get(chrome::DIR_DEFAULT_DOWNLOADS, &downloads_directory)); @@ -776,7 +784,12 @@ void ChromeBrowserMainPartsChromeos::PostMainMessageLoopRun() { // even if Initialize() wasn't called. SystemKeyEventListener::Shutdown(); imageburner::BurnManager::Shutdown(); - AudioHandler::Shutdown(); + if (CommandLine::ForCurrentProcess()-> + HasSwitch(ash::switches::kAshEnableNewAudioHandler)) { + CrasAudioHandler::Shutdown(); + } else { + AudioHandler::Shutdown(); + } WebSocketProxyController::Shutdown(); diff --git a/chrome/browser/chromeos/login/chrome_restart_request.cc b/chrome/browser/chromeos/login/chrome_restart_request.cc index 7db0062..9279f5e 100644 --- a/chrome/browser/chromeos/login/chrome_restart_request.cc +++ b/chrome/browser/chromeos/login/chrome_restart_request.cc @@ -124,6 +124,7 @@ std::string DeriveCommandLine(const GURL& start_url, ash::switches::kAshTouchHud, ash::switches::kAuraLegacyPowerButton, ash::switches::kAshDisableNewNetworkStatusArea, + ash::switches::kAshEnableNewAudioHandler, // Please keep these in alphabetical order. Non-UI Compositor switches // here should also be added to // content/browser/renderer_host/render_process_host_impl.cc. diff --git a/chrome/browser/chromeos/system/ash_system_tray_delegate.cc b/chrome/browser/chromeos/system/ash_system_tray_delegate.cc index 1f64fcc..b8d187b 100644 --- a/chrome/browser/chromeos/system/ash_system_tray_delegate.cc +++ b/chrome/browser/chromeos/system/ash_system_tray_delegate.cc @@ -272,7 +272,10 @@ class SystemTrayDelegate : public ash::SystemTrayDelegate, } virtual void Initialize() OVERRIDE { - AudioHandler::GetInstance()->AddVolumeObserver(this); + if (!CommandLine::ForCurrentProcess()-> + HasSwitch(ash::switches::kAshEnableNewAudioHandler)) { + AudioHandler::GetInstance()->AddVolumeObserver(this); + } DBusThreadManager::Get()->GetPowerManagerClient()->AddObserver(this); DBusThreadManager::Get()->GetPowerManagerClient()->RequestStatusUpdate( PowerManagerClient::UPDATE_INITIAL); @@ -333,9 +336,12 @@ class SystemTrayDelegate : public ash::SystemTrayDelegate, } virtual ~SystemTrayDelegate() { - AudioHandler* audiohandler = AudioHandler::GetInstance(); - if (audiohandler) - audiohandler->RemoveVolumeObserver(this); + if (!CommandLine::ForCurrentProcess()-> + HasSwitch(ash::switches::kAshEnableNewAudioHandler) && + AudioHandler::GetInstance()) { + AudioHandler::GetInstance()->RemoveVolumeObserver(this); + } + DBusThreadManager::Get()->GetSessionManagerClient()->RemoveObserver(this); DBusThreadManager::Get()->GetPowerManagerClient()->RemoveObserver(this); DBusThreadManager::Get()->GetSystemClockClient()->RemoveObserver(this); diff --git a/chrome/browser/ui/ash/volume_controller_chromeos.cc b/chrome/browser/ui/ash/volume_controller_chromeos.cc index d9ba52d..5450b55 100644 --- a/chrome/browser/ui/ash/volume_controller_chromeos.cc +++ b/chrome/browser/ui/ash/volume_controller_chromeos.cc @@ -4,6 +4,8 @@ #include "chrome/browser/ui/ash/volume_controller_chromeos.h" +#include "ash/ash_switches.h" +#include "base/command_line.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/chromeos/audio/audio_handler.h" #include "chrome/browser/extensions/api/system_private/system_private_api.h" @@ -16,10 +18,25 @@ const double kStepPercentage = 4.0; } // namespace +VolumeController::VolumeController() { + if (UseNewAudioHandler()) + chromeos::CrasAudioHandler::Get()->AddAudioObserver(this); +} + +VolumeController::~VolumeController() { + if (UseNewAudioHandler() && chromeos::CrasAudioHandler::IsInitialized()) + chromeos::CrasAudioHandler::Get()->RemoveAudioObserver(this); +} + bool VolumeController::HandleVolumeMute(const ui::Accelerator& accelerator) { if (accelerator.key_code() == ui::VKEY_VOLUME_MUTE) content::RecordAction(content::UserMetricsAction("Accel_VolumeMute_F8")); + if (UseNewAudioHandler()) { + chromeos::CrasAudioHandler::Get()->SetOutputMute(true); + return true; + } + chromeos::AudioHandler* audio_handler = chromeos::AudioHandler::GetInstance(); // Always muting (and not toggling) as per final decision on @@ -35,6 +52,16 @@ bool VolumeController::HandleVolumeDown(const ui::Accelerator& accelerator) { if (accelerator.key_code() == ui::VKEY_VOLUME_DOWN) content::RecordAction(content::UserMetricsAction("Accel_VolumeDown_F9")); + if (UseNewAudioHandler()) { + chromeos::CrasAudioHandler* audio_handler = + chromeos::CrasAudioHandler::Get(); + if (audio_handler->IsOutputMuted()) + audio_handler->SetOutputVolumePercent(0); + else + audio_handler->AdjustOutputVolumeByPercent(-kStepPercentage); + return true; + } + chromeos::AudioHandler* audio_handler = chromeos::AudioHandler::GetInstance(); if (audio_handler->IsMuted()) audio_handler->SetVolumePercent(0.0); @@ -50,6 +77,16 @@ bool VolumeController::HandleVolumeUp(const ui::Accelerator& accelerator) { if (accelerator.key_code() == ui::VKEY_VOLUME_UP) content::RecordAction(content::UserMetricsAction("Accel_VolumeUp_F10")); + if (UseNewAudioHandler()) { + chromeos::CrasAudioHandler* audio_handler = + chromeos::CrasAudioHandler::Get(); + if (audio_handler->IsOutputMuted()) + audio_handler->SetOutputMute(false); + else + audio_handler->AdjustOutputVolumeByPercent(kStepPercentage); + return true; + } + chromeos::AudioHandler* audio_handler = chromeos::AudioHandler::GetInstance(); if (audio_handler->IsMuted()) { audio_handler->SetMuted(false); @@ -63,15 +100,18 @@ bool VolumeController::HandleVolumeUp(const ui::Accelerator& accelerator) { } bool VolumeController::IsAudioMuted() const { + DCHECK(!UseNewAudioHandler()); return chromeos::AudioHandler::GetInstance()->IsMuted(); } void VolumeController::SetAudioMuted(bool muted) { + DCHECK(!UseNewAudioHandler()); chromeos::AudioHandler::GetInstance()->SetMuted(muted); } // Gets the volume level. The range is [0, 1.0]. float VolumeController::GetVolumeLevel() const { + DCHECK(!UseNewAudioHandler()); return chromeos::AudioHandler::GetInstance()->GetVolumePercent() / 100.f; } @@ -81,8 +121,30 @@ void VolumeController::SetVolumeLevel(float level) { } void VolumeController::SetVolumePercent(double percent) { + DCHECK(!UseNewAudioHandler()); chromeos::AudioHandler* audio_handler = chromeos::AudioHandler::GetInstance(); audio_handler->SetVolumePercent(percent); extensions::DispatchVolumeChangedEvent(audio_handler->GetVolumePercent(), audio_handler->IsMuted()); } + +void VolumeController::OnOutputVolumeChanged() { + DCHECK(UseNewAudioHandler()); + chromeos::CrasAudioHandler* audio_handler = chromeos::CrasAudioHandler::Get(); + extensions::DispatchVolumeChangedEvent( + audio_handler->GetOutputVolumePercent(), + audio_handler->IsOutputMuted()); +} + +void VolumeController::OnOutputMuteChanged() { + DCHECK(UseNewAudioHandler()); + chromeos::CrasAudioHandler* audio_handler = chromeos::CrasAudioHandler::Get(); + extensions::DispatchVolumeChangedEvent( + audio_handler->GetOutputVolumePercent(), + audio_handler->IsOutputMuted()); +} + +bool VolumeController::UseNewAudioHandler() const { + return CommandLine::ForCurrentProcess()-> + HasSwitch(ash::switches::kAshEnableNewAudioHandler); +} diff --git a/chrome/browser/ui/ash/volume_controller_chromeos.h b/chrome/browser/ui/ash/volume_controller_chromeos.h index 925d3d3..d3b2005 100644 --- a/chrome/browser/ui/ash/volume_controller_chromeos.h +++ b/chrome/browser/ui/ash/volume_controller_chromeos.h @@ -8,13 +8,15 @@ #include "ash/volume_control_delegate.h" #include "base/basictypes.h" #include "base/compiler_specific.h" +#include "chromeos/audio/cras_audio_handler.h" // A class which controls volume when F8-10 or a multimedia key for volume is // pressed. -class VolumeController : public ash::VolumeControlDelegate { +class VolumeController : public ash::VolumeControlDelegate, + public chromeos::CrasAudioHandler::AudioObserver { public: - VolumeController() {} - virtual ~VolumeController() {} + VolumeController(); + virtual ~VolumeController(); // Overridden from ash::VolumeControlDelegate: virtual bool HandleVolumeMute(const ui::Accelerator& accelerator) OVERRIDE; @@ -26,7 +28,13 @@ class VolumeController : public ash::VolumeControlDelegate { virtual void SetVolumeLevel(float level) OVERRIDE; virtual void SetVolumePercent(double percent) OVERRIDE; + // Overridden from chromeos::CrasAudioHandler::AudioObserver. + virtual void OnOutputVolumeChanged() OVERRIDE; + virtual void OnOutputMuteChanged() OVERRIDE; + private: + bool UseNewAudioHandler() const; + DISALLOW_COPY_AND_ASSIGN(VolumeController); }; diff --git a/chromeos/audio/audio_device.cc b/chromeos/audio/audio_device.cc new file mode 100644 index 0000000..f41acda --- /dev/null +++ b/chromeos/audio/audio_device.cc @@ -0,0 +1,85 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chromeos/audio/audio_device.h" + +#include "base/format_macros.h" +#include "base/stringprintf.h" +#include "base/utf_string_conversions.h" + +namespace { + +std::string GetTypeString(chromeos::AudioDeviceType type) { + if (type == chromeos::AUDIO_TYPE_INTERNAL) + return "INTERNAL"; + else if (type == chromeos::AUDIO_TYPE_HEADPHONE) + return "HEADPHONE"; + else if (type == chromeos::AUDIO_TYPE_USB) + return "USB"; + else if (type == chromeos::AUDIO_TYPE_BLUETOOTH) + return "BLUETOOTH"; + else if (type == chromeos::AUDIO_TYPE_HDMI) + return "HDMI"; + else + return "OTHER"; +} + +chromeos::AudioDeviceType GetAudioType(const std::string& node_type) { + if (node_type.find("INTERNAL_") != std::string::npos) + return chromeos::AUDIO_TYPE_INTERNAL; + else if (node_type.find("HEADPHONE") != std::string::npos) + return chromeos::AUDIO_TYPE_HEADPHONE; + else if (node_type.find("USB") != std::string::npos) + return chromeos::AUDIO_TYPE_USB; + else if (node_type.find("BLUETOOTH") != std::string::npos) + return chromeos::AUDIO_TYPE_BLUETOOTH; + else if (node_type.find("HDMI") != std::string::npos) + return chromeos::AUDIO_TYPE_HDMI; + else + return chromeos::AUDIO_TYPE_OTHER; +} + +} // namespace + +namespace chromeos { + +AudioDevice::AudioDevice() + : is_input(false), + id(0), + active(false) { +} + +AudioDevice::AudioDevice(const AudioNode& node) { + is_input = node.is_input; + id = node.id; + type = GetAudioType(node.type); + if (!node.name.empty() && node.name != "(default)") + display_name = UTF8ToUTF16(node.name); + else + display_name = UTF8ToUTF16(node.device_name); + active = node.active; +} + +std::string AudioDevice::ToString() const { + std::string result; + base::StringAppendF(&result, + "is_input = %s ", + is_input ? "true" : "false"); + base::StringAppendF(&result, + "id = %"PRIu64" ", + id); + base::StringAppendF(&result, + "display_name = %s ", + UTF16ToUTF8(display_name).c_str()); + base::StringAppendF(&result, + "type = %s ", + GetTypeString(type).c_str()); + base::StringAppendF(&result, + "active = %s ", + active ? "true" : "false"); + + return result; +} + +} // namespace chromeos diff --git a/chromeos/audio/audio_device.h b/chromeos/audio/audio_device.h new file mode 100644 index 0000000..891f030 --- /dev/null +++ b/chromeos/audio/audio_device.h @@ -0,0 +1,43 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROMEOS_AUDIO_AUDIO_DEVICE_H_ +#define CHROMEOS_AUDIO_AUDIO_DEVICE_H_ + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/string16.h" +#include "chromeos/chromeos_export.h" +#include "chromeos/dbus/audio_node.h" + +namespace chromeos { + +enum AudioDeviceType { + AUDIO_TYPE_INTERNAL, + AUDIO_TYPE_HEADPHONE, + AUDIO_TYPE_USB, + AUDIO_TYPE_BLUETOOTH, + AUDIO_TYPE_HDMI, + AUDIO_TYPE_OTHER, +}; + +struct CHROMEOS_EXPORT AudioDevice { + bool is_input; + uint64 id; + base::string16 display_name; + AudioDeviceType type; + bool active; + + AudioDevice(); + explicit AudioDevice(const AudioNode& node); + std::string ToString() const; +}; + +typedef std::vector<AudioDevice> AudioDeviceList; + +} // namespace chromeos + +#endif // CHROMEOS_AUDIO_AUDIO_DEVICE_H_ diff --git a/chromeos/audio/cras_audio_handler.cc b/chromeos/audio/cras_audio_handler.cc index e5bcec7..9666cc1 100644 --- a/chromeos/audio/cras_audio_handler.cc +++ b/chromeos/audio/cras_audio_handler.cc @@ -109,15 +109,31 @@ uint64 CrasAudioHandler::GetActiveInputNode() const { return active_input_node_id_; } +void CrasAudioHandler::GetAudioDevices(AudioDeviceList* device_list) const { + for (size_t i = 0; i < audio_devices_.size(); ++i) + device_list->push_back(audio_devices_[i]); +} + +bool CrasAudioHandler::GetActiveOutputDevice(AudioDevice* device) const { + for (size_t i = 0; i < audio_devices_.size(); ++i) { + if (audio_devices_[i].id == active_output_node_id_) { + *device = audio_devices_[i]; + return true; + } + } + NOTREACHED() << "Can't find active output audio device"; + return false; +} + void CrasAudioHandler::SetOutputVolumePercent(int volume_percent) { volume_percent = min(max(volume_percent, 0), 100); if (volume_percent <= kMuteThresholdPercent) volume_percent = 0; + SetOutputVolumeInternal(volume_percent); if (IsOutputMuted() && volume_percent > 0) SetOutputMute(false); if (!IsOutputMuted() && volume_percent == 0) SetOutputMute(true); - SetOutputVolumeInternal(volume_percent); } void CrasAudioHandler::AdjustOutputVolumeByPercent(int adjust_by_percent) { @@ -171,6 +187,8 @@ CrasAudioHandler::CrasAudioHandler( output_volume_(0), active_output_node_id_(0), active_input_node_id_(0), + has_alternative_input_(false), + has_alternative_output_(false), output_mute_locked_(false), input_mute_locked_(false) { chromeos::DBusThreadManager::Get()->GetCrasAudioClient()->AddObserver(this); @@ -222,12 +240,20 @@ void CrasAudioHandler::NodesChanged() { } void CrasAudioHandler::ActiveOutputNodeChanged(uint64 node_id) { + if (active_output_node_id_ == node_id) + return; + active_output_node_id_ = node_id; + GetNodes(); FOR_EACH_OBSERVER(AudioObserver, observers_, OnActiveOutputNodeChanged()); } void CrasAudioHandler::ActiveInputNodeChanged(uint64 node_id) { + if (active_input_node_id_ == node_id) + return; + active_input_node_id_ = node_id; + GetNodes(); FOR_EACH_OBSERVER(AudioObserver, observers_, OnActiveInputNodeChanged()); } @@ -241,8 +267,8 @@ void CrasAudioHandler::SetupInitialAudioState() { // Set the initial audio state to the ones read from audio prefs. output_mute_on_ = audio_pref_handler_->GetOutputMuteValue(); output_volume_ = audio_pref_handler_->GetOutputVolumeValue(); - SetOutputMute(output_mute_on_); SetOutputVolumeInternal(output_volume_); + SetOutputMute(output_mute_on_); // Get the initial audio data. GetNodes(); @@ -282,13 +308,28 @@ void CrasAudioHandler::HandleGetNodes(const chromeos::AudioNodeList& node_list, return; } - audio_nodes_.clear(); + audio_devices_.clear(); + active_input_node_id_ = 0; + active_output_node_id_ = 0; + has_alternative_input_ = false; + has_alternative_output_ = false; + for (size_t i = 0; i < node_list.size(); ++i) { if (node_list[i].is_input && node_list[i].active) active_input_node_id_ = node_list[i].id; else if (!node_list[i].is_input && node_list[i].active) active_output_node_id_ = node_list[i].id; - audio_nodes_.push_back(node_list[i]); + AudioDevice device(node_list[i]); + audio_devices_.push_back(device); + if (!has_alternative_input_ && + device.is_input && + device.type != AUDIO_TYPE_INTERNAL) { + has_alternative_input_ = true; + } else if (!has_alternative_output_ && + !device.is_input && + device.type != AUDIO_TYPE_INTERNAL) { + has_alternative_output_ = true; + } } FOR_EACH_OBSERVER(AudioObserver, observers_, OnAudioNodesChanged()); diff --git a/chromeos/audio/cras_audio_handler.h b/chromeos/audio/cras_audio_handler.h index 7e5f47a..61b11f5d 100644 --- a/chromeos/audio/cras_audio_handler.h +++ b/chromeos/audio/cras_audio_handler.h @@ -9,6 +9,7 @@ #include "base/memory/ref_counted.h" #include "base/memory/weak_ptr.h" #include "base/observer_list.h" +#include "chromeos/audio/audio_device.h" #include "chromeos/audio/audio_pref_observer.h" #include "chromeos/dbus/audio_node.h" #include "chromeos/dbus/cras_audio_client.h" @@ -83,6 +84,15 @@ class CHROMEOS_EXPORT CrasAudioHandler : public CrasAudioClient::Observer, // Returns the node_id of the active input node. uint64 GetActiveInputNode() const; + // Gets the audio devices back in |device_list|. + void GetAudioDevices(AudioDeviceList* device_list) const; + + bool GetActiveOutputDevice(AudioDevice* device) const; + + // Whether there is alternative input/output audio device. + bool has_alternative_input() const { return has_alternative_input_; } + bool has_alternative_output() const { return has_alternative_output_; } + // Sets volume level from 0-100%. If less than kMuteThresholdPercent, then // mutes the sound. If it was muted, and |volume_percent| is larger than // the threshold, then the sound is unmuted. @@ -141,13 +151,15 @@ class CHROMEOS_EXPORT CrasAudioHandler : public CrasAudioClient::Observer, ObserverList<AudioObserver> observers_; // Audio data and state. - AudioNodeList audio_nodes_; + AudioDeviceList audio_devices_; VolumeState volume_state_; bool output_mute_on_; bool input_mute_on_; int output_volume_; uint64 active_output_node_id_; uint64 active_input_node_id_; + bool has_alternative_input_; + bool has_alternative_output_; bool output_mute_locked_; bool input_mute_locked_; diff --git a/chromeos/chromeos.gyp b/chromeos/chromeos.gyp index 66d0461..2192796 100644 --- a/chromeos/chromeos.gyp +++ b/chromeos/chromeos.gyp @@ -31,6 +31,8 @@ 'CHROMEOS_IMPLEMENTATION', ], 'sources': [ + 'audio/audio_device.cc', + 'audio/audio_device.h', 'audio/audio_pref_observer.h', 'audio/audio_pref_handler.h', 'audio/cras_audio_handler.cc', diff --git a/chromeos/dbus/cras_audio_client.cc b/chromeos/dbus/cras_audio_client.cc index 342ce59..02260bb 100644 --- a/chromeos/dbus/cras_audio_client.cc +++ b/chromeos/dbus/cras_audio_client.cc @@ -384,25 +384,54 @@ class CrasAudioClientStubImpl : public CrasAudioClient { CrasAudioClientStubImpl() { VLOG(1) << "CrasAudioClientStubImpl is created"; - // Fake audio nodes. + // Fake audio output nodes. AudioNode node_1; node_1.is_input = false; node_1.id = 10001; - node_1.device_name = "Fake Audio Output"; + node_1.device_name = "Fake Speaker"; node_1.type = "INTERNAL_SPEAKER"; - node_1.name = "Internal Speaker"; - node_1.active = true; + node_1.name = "Speaker"; + node_1.active = false; + node_list_.push_back(node_1); AudioNode node_2; - node_2.is_input = true; + node_2.is_input = false; node_2.id = 10002; - node_2.device_name = "Fake Audio Input"; - node_2.type = "INTERNAL_MIC"; - node_2.name = "Internal Mic"; + node_2.device_name = "Fake Headphone"; + node_2.type = "HEADPHONE"; + node_2.name = "Headphone"; node_2.active = true; - - node_list_.push_back(node_1); node_list_.push_back(node_2); + active_output_node_id_ = node_2.id; + + AudioNode node_3; + node_3.is_input = false; + node_3.id = 10003; + node_3.device_name = "Fake Audio Output"; + node_3.type = "BLUETOOTH"; + node_3.name = "Bluetooth Headphone"; + node_3.active = false; + node_list_.push_back(node_3); + + // Fake audio input ndoes + AudioNode node_4; + node_4.is_input = true; + node_4.id = 10004; + node_4.device_name = "Fake Internal Mic"; + node_4.type = "INTERNAL_MIC"; + node_4.name = "Internal Mic"; + node_4.active = false; + node_list_.push_back(node_4); + + AudioNode node_5; + node_5.is_input = true; + node_5.id = 10005; + node_5.device_name = "Fake Internal Mic"; + node_5.type = "USB"; + node_5.name = "USB Mic"; + node_5.active = true; + node_list_.push_back(node_5); + active_input_node_id_ = node_5.id; } virtual ~CrasAudioClientStubImpl() { } @@ -459,6 +488,15 @@ class CrasAudioClientStubImpl : public CrasAudioClient { } virtual void SetActiveOutputNode(uint64 node_id) OVERRIDE { + if (active_output_node_id_ == node_id) + return; + + for (size_t i = 0; i < node_list_.size(); ++i) { + if (node_list_[i].id == active_output_node_id_) + node_list_[i].active = false; + else if (node_list_[i].id == node_id) + node_list_[i].active = true; + } active_output_node_id_ = node_id; FOR_EACH_OBSERVER(Observer, observers_, @@ -466,6 +504,15 @@ class CrasAudioClientStubImpl : public CrasAudioClient { } virtual void SetActiveInputNode(uint64 node_id) OVERRIDE { + if (active_input_node_id_ == node_id) + return; + + for (size_t i = 0; i < node_list_.size(); ++i) { + if (node_list_[i].id == active_input_node_id_) + node_list_[i].active = false; + else if (node_list_[i].id == node_id) + node_list_[i].active = true; + } active_input_node_id_ = node_id; FOR_EACH_OBSERVER(Observer, observers_, |