1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "ash/system/chromeos/audio/tray_audio.h" 6 7 #include <cmath> 8 9 #include "ash/ash_constants.h" 10 #include "ash/ash_switches.h" 11 #include "ash/metrics/user_metrics_recorder.h" 12 #include "ash/shell.h" 13 #include "ash/system/tray/actionable_view.h" 14 #include "ash/system/tray/fixed_sized_scroll_view.h" 15 #include "ash/system/tray/hover_highlight_view.h" 16 #include "ash/system/tray/system_tray.h" 17 #include "ash/system/tray/system_tray_delegate.h" 18 #include "ash/system/tray/system_tray_notifier.h" 19 #include "ash/system/tray/tray_constants.h" 20 #include "ash/volume_control_delegate.h" 21 #include "base/strings/utf_string_conversions.h" 22 #include "chromeos/audio/cras_audio_handler.h" 23 #include "grit/ash_resources.h" 24 #include "grit/ash_strings.h" 25 #include "third_party/skia/include/core/SkCanvas.h" 26 #include "third_party/skia/include/core/SkPaint.h" 27 #include "third_party/skia/include/core/SkRect.h" 28 #include "third_party/skia/include/effects/SkGradientShader.h" 29 #include "ui/base/l10n/l10n_util.h" 30 #include "ui/base/resource/resource_bundle.h" 31 #include "ui/gfx/canvas.h" 32 #include "ui/gfx/image/image.h" 33 #include "ui/gfx/image/image_skia_operations.h" 34 #include "ui/views/controls/button/image_button.h" 35 #include "ui/views/controls/image_view.h" 36 #include "ui/views/controls/label.h" 37 #include "ui/views/controls/slider.h" 38 #include "ui/views/layout/box_layout.h" 39 #include "ui/views/view.h" 40 41 using chromeos::CrasAudioHandler; 42 43 namespace ash { 44 namespace internal { 45 46 namespace { 47 const int kVolumeImageWidth = 25; 48 const int kVolumeImageHeight = 25; 49 const int kBarSeparatorWidth = 25; 50 const int kBarSeparatorHeight = 30; 51 const int kSliderRightPaddingToVolumeViewEdge = 17; 52 const int kExtraPaddingBetweenBarAndMore = 10; 53 54 const int kNoAudioDeviceIcon = -1; 55 56 // IDR_AURA_UBER_TRAY_VOLUME_LEVELS contains 5 images, 57 // The one for mute is at the 0 index and the other 58 // four are used for ascending volume levels. 59 const int kVolumeLevels = 4; 60 61 bool IsAudioMuted() { 62 return CrasAudioHandler::Get()->IsOutputMuted(); 63 } 64 65 float GetVolumeLevel() { 66 return CrasAudioHandler::Get()->GetOutputVolumePercent() / 100.0f; 67 } 68 69 int GetAudioDeviceIconId(chromeos::AudioDeviceType type) { 70 if (type == chromeos::AUDIO_TYPE_HEADPHONE) 71 return IDR_AURA_UBER_TRAY_AUDIO_HEADPHONE; 72 else if (type == chromeos::AUDIO_TYPE_USB) 73 return IDR_AURA_UBER_TRAY_AUDIO_USB; 74 else if (type == chromeos::AUDIO_TYPE_BLUETOOTH) 75 return IDR_AURA_UBER_TRAY_AUDIO_BLUETOOTH; 76 else if (type == chromeos::AUDIO_TYPE_HDMI) 77 return IDR_AURA_UBER_TRAY_AUDIO_HDMI; 78 else 79 return kNoAudioDeviceIcon; 80 } 81 82 base::string16 GetAudioDeviceName(const chromeos::AudioDevice& device) { 83 switch(device.type) { 84 case chromeos::AUDIO_TYPE_HEADPHONE: 85 return l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_AUDIO_HEADPHONE); 86 case chromeos::AUDIO_TYPE_INTERNAL_SPEAKER: 87 return l10n_util::GetStringUTF16( 88 IDS_ASH_STATUS_TRAY_AUDIO_INTERNAL_SPEAKER); 89 case chromeos::AUDIO_TYPE_INTERNAL_MIC: 90 return l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_AUDIO_INTERNAL_MIC); 91 case chromeos::AUDIO_TYPE_USB: 92 return l10n_util::GetStringFUTF16( 93 IDS_ASH_STATUS_TRAY_AUDIO_USB_DEVICE, 94 UTF8ToUTF16(device.display_name)); 95 case chromeos::AUDIO_TYPE_BLUETOOTH: 96 return l10n_util::GetStringFUTF16( 97 IDS_ASH_STATUS_TRAY_AUDIO_BLUETOOTH_DEVICE, 98 UTF8ToUTF16(device.display_name)); 99 case chromeos::AUDIO_TYPE_HDMI: 100 return l10n_util::GetStringFUTF16( 101 IDS_ASH_STATUS_TRAY_AUDIO_HDMI_DEVICE, 102 UTF8ToUTF16(device.display_name)); 103 default: 104 return UTF8ToUTF16(device.display_name); 105 } 106 } 107 108 } // namespace 109 110 namespace tray { 111 112 class VolumeButton : public views::ToggleImageButton { 113 public: 114 explicit VolumeButton(views::ButtonListener* listener) 115 : views::ToggleImageButton(listener), 116 image_index_(-1) { 117 SetImageAlignment(ALIGN_CENTER, ALIGN_MIDDLE); 118 image_ = ui::ResourceBundle::GetSharedInstance().GetImageNamed( 119 IDR_AURA_UBER_TRAY_VOLUME_LEVELS); 120 SetPreferredSize(gfx::Size(kTrayPopupItemHeight, kTrayPopupItemHeight)); 121 Update(); 122 } 123 124 virtual ~VolumeButton() {} 125 126 void Update() { 127 float level = GetVolumeLevel(); 128 int image_index = IsAudioMuted() ? 129 0 : (level == 1.0 ? 130 kVolumeLevels : 131 std::max(1, int(std::ceil(level * (kVolumeLevels - 1))))); 132 if (image_index != image_index_) { 133 gfx::Rect region(0, image_index * kVolumeImageHeight, 134 kVolumeImageWidth, kVolumeImageHeight); 135 gfx::ImageSkia image_skia = gfx::ImageSkiaOperations::ExtractSubset( 136 *(image_.ToImageSkia()), region); 137 SetImage(views::CustomButton::STATE_NORMAL, &image_skia); 138 image_index_ = image_index; 139 } 140 SchedulePaint(); 141 } 142 143 private: 144 // Overridden from views::View. 145 virtual gfx::Size GetPreferredSize() OVERRIDE { 146 gfx::Size size = views::ToggleImageButton::GetPreferredSize(); 147 size.set_height(kTrayPopupItemHeight); 148 return size; 149 } 150 151 gfx::Image image_; 152 int image_index_; 153 154 DISALLOW_COPY_AND_ASSIGN(VolumeButton); 155 }; 156 157 class VolumeSlider : public views::Slider { 158 public: 159 explicit VolumeSlider(views::SliderListener* listener) 160 : views::Slider(listener, views::Slider::HORIZONTAL) { 161 set_focus_border_color(kFocusBorderColor); 162 SetValue(GetVolumeLevel()); 163 SetAccessibleName( 164 ui::ResourceBundle::GetSharedInstance().GetLocalizedString( 165 IDS_ASH_STATUS_TRAY_VOLUME)); 166 Update(); 167 } 168 virtual ~VolumeSlider() {} 169 170 void Update() { 171 UpdateState(!IsAudioMuted()); 172 } 173 174 DISALLOW_COPY_AND_ASSIGN(VolumeSlider); 175 }; 176 177 // Vertical bar separator that can be placed on the VolumeView. 178 class BarSeparator : public views::View { 179 public: 180 BarSeparator() {} 181 virtual ~BarSeparator() {} 182 183 // Overriden from views::View. 184 virtual gfx::Size GetPreferredSize() OVERRIDE { 185 return gfx::Size(kBarSeparatorWidth, kBarSeparatorHeight); 186 } 187 188 private: 189 virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE { 190 canvas->FillRect(gfx::Rect(width() / 2, 0, 1, height()), 191 kButtonStrokeColor); 192 } 193 194 DISALLOW_COPY_AND_ASSIGN(BarSeparator); 195 }; 196 197 class VolumeView : public ActionableView, 198 public views::ButtonListener, 199 public views::SliderListener { 200 public: 201 VolumeView(SystemTrayItem* owner, bool is_default_view) 202 : owner_(owner), 203 icon_(NULL), 204 slider_(NULL), 205 bar_(NULL), 206 device_type_(NULL), 207 more_(NULL), 208 is_default_view_(is_default_view) { 209 SetFocusable(false); 210 SetLayoutManager(new views::BoxLayout(views::BoxLayout::kHorizontal, 211 kTrayPopupPaddingHorizontal, 0, kTrayPopupPaddingBetweenItems)); 212 213 icon_ = new VolumeButton(this); 214 AddChildView(icon_); 215 216 slider_ = new VolumeSlider(this); 217 AddChildView(slider_); 218 219 bar_ = new BarSeparator; 220 AddChildView(bar_); 221 222 device_type_ = new views::ImageView; 223 AddChildView(device_type_); 224 225 more_ = new views::ImageView; 226 more_->EnableCanvasFlippingForRTLUI(true); 227 more_->SetImage(ui::ResourceBundle::GetSharedInstance().GetImageNamed( 228 IDR_AURA_UBER_TRAY_MORE).ToImageSkia()); 229 AddChildView(more_); 230 231 Update(); 232 } 233 234 virtual ~VolumeView() {} 235 236 void Update() { 237 icon_->Update(); 238 slider_->Update(); 239 UpdateDeviceTypeAndMore(); 240 Layout(); 241 } 242 243 // Sets volume level on slider_, |percent| is ranged from [0.00] to [1.00]. 244 void SetVolumeLevel(float percent) { 245 // Slider's value is in finer granularity than audio volume level(0.01), 246 // there will be a small discrepancy between slider's value and volume level 247 // on audio side. To avoid the jittering in slider UI, do not set change 248 // slider value if the change is less than 1%. 249 if (std::abs(percent-slider_->value()) < 0.01) 250 return; 251 // The change in volume will be reflected via accessibility system events, 252 // so we prevent the UI event from being sent here. 253 slider_->set_enable_accessibility_events(false); 254 slider_->SetValue(percent); 255 // It is possible that the volume was (un)muted, but the actual volume level 256 // did not change. In that case, setting the value of the slider won't 257 // trigger an update. So explicitly trigger an update. 258 Update(); 259 slider_->set_enable_accessibility_events(true); 260 } 261 262 private: 263 // Updates bar_, device_type_ icon, and more_ buttons. 264 void UpdateDeviceTypeAndMore() { 265 if (!ash::switches::ShowAudioDeviceMenu() || !is_default_view_) { 266 more_->SetVisible(false); 267 bar_->SetVisible(false); 268 device_type_->SetVisible(false); 269 return; 270 } 271 272 CrasAudioHandler* audio_handler = CrasAudioHandler::Get(); 273 bool show_more = audio_handler->has_alternative_output() || 274 audio_handler->has_alternative_input(); 275 more_->SetVisible(show_more); 276 277 // Show output device icon if necessary. 278 chromeos::AudioDevice device; 279 if (!audio_handler->GetActiveOutputDevice(&device)) 280 return; 281 int device_icon = GetAudioDeviceIconId(device.type); 282 bar_->SetVisible(show_more); 283 if (device_icon != kNoAudioDeviceIcon) { 284 device_type_->SetVisible(true); 285 device_type_->SetImage( 286 ui::ResourceBundle::GetSharedInstance().GetImageNamed( 287 device_icon).ToImageSkia()); 288 } else { 289 device_type_->SetVisible(false); 290 } 291 } 292 293 void HandleVolumeUp(int volume) { 294 CrasAudioHandler* audio_handler = CrasAudioHandler::Get(); 295 audio_handler->SetOutputVolumePercent(volume); 296 if (audio_handler->IsOutputMuted() && 297 !audio_handler->IsOutputVolumeBelowDefaultMuteLvel()) 298 audio_handler->SetOutputMute(false); 299 } 300 301 void HandleVolumeDown(int volume) { 302 CrasAudioHandler* audio_handler = CrasAudioHandler::Get(); 303 audio_handler->SetOutputVolumePercent(volume); 304 if (audio_handler->IsOutputVolumeBelowDefaultMuteLvel() && 305 !audio_handler->IsOutputMuted()) { 306 audio_handler->SetOutputMute(true); 307 } else if (!audio_handler->IsOutputVolumeBelowDefaultMuteLvel() && 308 audio_handler->IsOutputMuted()) { 309 audio_handler->SetOutputMute(false); 310 } 311 } 312 313 // Overridden from views::View. 314 virtual void Layout() OVERRIDE { 315 views::View::Layout(); 316 317 if (!more_->visible()) { 318 int w = width() - slider_->bounds().x() - 319 kSliderRightPaddingToVolumeViewEdge; 320 slider_->SetSize(gfx::Size(w, slider_->height())); 321 return; 322 } 323 324 // Make sure the chevron always has the full size. 325 gfx::Size size = more_->GetPreferredSize(); 326 gfx::Rect bounds(size); 327 bounds.set_x(width() - size.width() - kTrayPopupPaddingBetweenItems); 328 bounds.set_y((height() - size.height()) / 2); 329 more_->SetBoundsRect(bounds); 330 331 // Layout either bar_ or device_type_ at the left of the more_ button. 332 views::View* view_left_to_more; 333 if (device_type_->visible()) 334 view_left_to_more = device_type_; 335 else 336 view_left_to_more = bar_; 337 gfx::Size view_size = view_left_to_more->GetPreferredSize(); 338 gfx::Rect view_bounds(view_size); 339 view_bounds.set_x(more_->bounds().x() - view_size.width() - 340 kExtraPaddingBetweenBarAndMore); 341 view_bounds.set_y((height() - view_size.height()) / 2); 342 view_left_to_more->SetBoundsRect(view_bounds); 343 344 // Layout vertical bar next to view_left_to_more if device_type_ is visible. 345 if (device_type_->visible()) { 346 gfx::Size bar_size = bar_->GetPreferredSize(); 347 gfx::Rect bar_bounds(bar_size); 348 bar_bounds.set_x(view_left_to_more->bounds().x() - bar_size.width()); 349 bar_bounds.set_y((height() - bar_size.height()) / 2); 350 bar_->SetBoundsRect(bar_bounds); 351 } 352 353 // Layout slider, calculate slider width. 354 gfx::Rect slider_bounds = slider_->bounds(); 355 slider_bounds.set_width( 356 bar_->bounds().x() 357 - (device_type_->visible() ? 0 : kTrayPopupPaddingBetweenItems) 358 - slider_bounds.x()); 359 slider_->SetBoundsRect(slider_bounds); 360 } 361 362 // Overridden from views::ButtonListener. 363 virtual void ButtonPressed(views::Button* sender, 364 const ui::Event& event) OVERRIDE { 365 CHECK(sender == icon_); 366 bool mute_on = !IsAudioMuted(); 367 CrasAudioHandler::Get()->SetOutputMute(mute_on); 368 if (!mute_on) 369 CrasAudioHandler::Get()->AdjustOutputVolumeToAudibleLevel(); 370 } 371 372 // Overridden from views:SliderListener. 373 virtual void SliderValueChanged(views::Slider* sender, 374 float value, 375 float old_value, 376 views::SliderChangeReason reason) OVERRIDE { 377 if (reason == views::VALUE_CHANGED_BY_USER) { 378 int volume = value * 100.0f; 379 int old_volume = CrasAudioHandler::Get()->GetOutputVolumePercent(); 380 // Do not call change audio volume if the difference is less than 381 // 1%, which is beyond cras audio api's granularity for output volume. 382 if (std::abs(volume - old_volume) < 1) 383 return; 384 Shell::GetInstance()->metrics()->RecordUserMetricsAction( 385 is_default_view_ ? 386 ash::UMA_STATUS_AREA_CHANGED_VOLUME_MENU : 387 ash::UMA_STATUS_AREA_CHANGED_VOLUME_POPUP); 388 if (volume > old_volume) 389 HandleVolumeUp(volume); 390 else 391 HandleVolumeDown(volume); 392 } 393 icon_->Update(); 394 } 395 396 // Overriden from ActionableView. 397 virtual bool PerformAction(const ui::Event& event) OVERRIDE { 398 if (!more_->visible()) 399 return false; 400 owner_->TransitionDetailedView(); 401 return true; 402 } 403 404 SystemTrayItem* owner_; 405 VolumeButton* icon_; 406 VolumeSlider* slider_; 407 BarSeparator* bar_; 408 views::ImageView* device_type_; 409 views::ImageView* more_; 410 bool is_default_view_; 411 412 DISALLOW_COPY_AND_ASSIGN(VolumeView); 413 }; 414 415 class AudioDetailedView : public TrayDetailsView, 416 public ViewClickListener { 417 public: 418 AudioDetailedView(SystemTrayItem* owner, user::LoginStatus login) 419 : TrayDetailsView(owner), 420 login_(login) { 421 CreateItems(); 422 Update(); 423 } 424 425 virtual ~AudioDetailedView() { 426 } 427 428 void Update() { 429 UpdateAudioDevices(); 430 Layout(); 431 } 432 433 private: 434 void CreateItems() { 435 CreateScrollableList(); 436 CreateHeaderEntry(); 437 } 438 439 void CreateHeaderEntry() { 440 CreateSpecialRow(IDS_ASH_STATUS_TRAY_AUDIO, this); 441 } 442 443 void UpdateAudioDevices() { 444 output_devices_.clear(); 445 input_devices_.clear(); 446 chromeos::AudioDeviceList devices; 447 CrasAudioHandler::Get()->GetAudioDevices(&devices); 448 for (size_t i = 0; i < devices.size(); ++i) { 449 if (devices[i].is_input) 450 input_devices_.push_back(devices[i]); 451 else 452 output_devices_.push_back(devices[i]); 453 } 454 UpdateScrollableList(); 455 } 456 457 void UpdateScrollableList() { 458 scroll_content()->RemoveAllChildViews(true); 459 device_map_.clear(); 460 461 // Add audio output devices. 462 AddScrollListInfoItem( 463 l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_AUDIO_OUTPUT)); 464 for (size_t i = 0; i < output_devices_.size(); ++i) { 465 HoverHighlightView* container = AddScrollListItem( 466 GetAudioDeviceName(output_devices_[i]), 467 gfx::Font::NORMAL, 468 output_devices_[i].active); /* checkmark if active */ 469 device_map_[container] = output_devices_[i]; 470 } 471 472 AddScrollSeparator(); 473 474 // Add audio input devices. 475 AddScrollListInfoItem( 476 l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_AUDIO_INPUT)); 477 for (size_t i = 0; i < input_devices_.size(); ++i) { 478 HoverHighlightView* container = AddScrollListItem( 479 GetAudioDeviceName(input_devices_[i]), 480 gfx::Font::NORMAL, 481 input_devices_[i].active); /* checkmark if active */ 482 device_map_[container] = input_devices_[i]; 483 } 484 485 scroll_content()->SizeToPreferredSize(); 486 scroller()->Layout(); 487 } 488 489 void AddScrollListInfoItem(const string16& text) { 490 views::Label* label = new views::Label(text); 491 492 // Align info item with checkbox items 493 int margin = kTrayPopupPaddingHorizontal + 494 kTrayPopupDetailsLabelExtraLeftMargin; 495 int left_margin = 0; 496 int right_margin = 0; 497 if (base::i18n::IsRTL()) 498 right_margin = margin; 499 else 500 left_margin = margin; 501 502 label->set_border(views::Border::CreateEmptyBorder( 503 ash::kTrayPopupPaddingBetweenItems, 504 left_margin, 505 ash::kTrayPopupPaddingBetweenItems, 506 right_margin)); 507 label->SetHorizontalAlignment(gfx::ALIGN_LEFT); 508 label->SetEnabledColor(SkColorSetARGB(192, 0, 0, 0)); 509 label->SetFont(label->font().DeriveFont(0, gfx::Font::BOLD)); 510 511 scroll_content()->AddChildView(label); 512 } 513 514 HoverHighlightView* AddScrollListItem(const string16& text, 515 gfx::Font::FontStyle style, 516 bool checked) { 517 HoverHighlightView* container = new HoverHighlightView(this); 518 container->AddCheckableLabel(text, style, checked); 519 scroll_content()->AddChildView(container); 520 return container; 521 } 522 523 // Overridden from ViewClickListener. 524 virtual void OnViewClicked(views::View* sender) OVERRIDE { 525 if (sender == footer()->content()) { 526 TransitionToDefaultView(); 527 } else { 528 AudioDeviceMap::iterator iter = device_map_.find(sender); 529 if (iter == device_map_.end()) 530 return; 531 chromeos::AudioDevice& device = iter->second; 532 CrasAudioHandler::Get()->SwitchToDevice(device); 533 } 534 } 535 536 typedef std::map<views::View*, chromeos::AudioDevice> AudioDeviceMap; 537 538 user::LoginStatus login_; 539 chromeos::AudioDeviceList output_devices_; 540 chromeos::AudioDeviceList input_devices_; 541 AudioDeviceMap device_map_; 542 543 DISALLOW_COPY_AND_ASSIGN(AudioDetailedView); 544 }; 545 546 } // namespace tray 547 548 TrayAudio::TrayAudio(SystemTray* system_tray) 549 : TrayImageItem(system_tray, IDR_AURA_UBER_TRAY_VOLUME_MUTE), 550 volume_view_(NULL), 551 audio_detail_(NULL), 552 pop_up_volume_view_(false) { 553 CrasAudioHandler::Get()->AddAudioObserver(this); 554 } 555 556 TrayAudio::~TrayAudio() { 557 if (CrasAudioHandler::IsInitialized()) 558 CrasAudioHandler::Get()->RemoveAudioObserver(this); 559 } 560 561 bool TrayAudio::GetInitialVisibility() { 562 return IsAudioMuted(); 563 } 564 565 views::View* TrayAudio::CreateDefaultView(user::LoginStatus status) { 566 volume_view_ = new tray::VolumeView(this, true); 567 return volume_view_; 568 } 569 570 views::View* TrayAudio::CreateDetailedView(user::LoginStatus status) { 571 if (!ash::switches::ShowAudioDeviceMenu() || pop_up_volume_view_) { 572 volume_view_ = new tray::VolumeView(this, false); 573 return volume_view_; 574 } else { 575 Shell::GetInstance()->metrics()->RecordUserMetricsAction( 576 ash::UMA_STATUS_AREA_DETAILED_AUDIO_VIEW); 577 audio_detail_ = new tray::AudioDetailedView(this, status); 578 return audio_detail_; 579 } 580 } 581 582 void TrayAudio::DestroyDefaultView() { 583 volume_view_ = NULL; 584 } 585 586 void TrayAudio::DestroyDetailedView() { 587 if (audio_detail_) { 588 audio_detail_ = NULL; 589 } else if (volume_view_) { 590 volume_view_ = NULL; 591 pop_up_volume_view_ = false; 592 } 593 } 594 595 bool TrayAudio::ShouldHideArrow() const { 596 return true; 597 } 598 599 bool TrayAudio::ShouldShowLauncher() const { 600 return ash::switches::ShowAudioDeviceMenu() && !pop_up_volume_view_; 601 } 602 603 void TrayAudio::OnOutputVolumeChanged() { 604 float percent = GetVolumeLevel(); 605 if (tray_view()) 606 tray_view()->SetVisible(GetInitialVisibility()); 607 608 if (volume_view_) { 609 volume_view_->SetVolumeLevel(percent); 610 SetDetailedViewCloseDelay(kTrayPopupAutoCloseDelayInSeconds); 611 return; 612 } 613 pop_up_volume_view_ = true; 614 PopupDetailedView(kTrayPopupAutoCloseDelayInSeconds, false); 615 } 616 617 void TrayAudio::OnOutputMuteChanged() { 618 if (tray_view()) 619 tray_view()->SetVisible(GetInitialVisibility()); 620 621 if (volume_view_) { 622 volume_view_->Update(); 623 SetDetailedViewCloseDelay(kTrayPopupAutoCloseDelayInSeconds); 624 } else { 625 pop_up_volume_view_ = true; 626 PopupDetailedView(kTrayPopupAutoCloseDelayInSeconds, false); 627 } 628 } 629 630 void TrayAudio::OnInputGainChanged() { 631 } 632 633 void TrayAudio::OnInputMuteChanged() { 634 } 635 636 void TrayAudio::OnAudioNodesChanged() { 637 Update(); 638 } 639 640 void TrayAudio::OnActiveOutputNodeChanged() { 641 Update(); 642 } 643 644 void TrayAudio::OnActiveInputNodeChanged() { 645 Update(); 646 } 647 648 void TrayAudio::Update() { 649 if (tray_view()) 650 tray_view()->SetVisible(GetInitialVisibility()); 651 if (audio_detail_) 652 audio_detail_->Update(); 653 if (volume_view_) { 654 volume_view_->SetVolumeLevel(GetVolumeLevel()); 655 volume_view_->Update(); 656 } 657 } 658 659 } // namespace internal 660 } // namespace ash 661