Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "chromeos/audio/cras_audio_handler.h" | 5 #include "chromeos/audio/cras_audio_handler.h" |
| 6 | 6 |
| 7 #include <stddef.h> | 7 #include <stddef.h> |
| 8 #include <stdint.h> | 8 #include <stdint.h> |
| 9 | 9 |
| 10 #include <algorithm> | 10 #include <algorithm> |
| 11 #include <cmath> | 11 #include <cmath> |
| 12 | 12 |
| 13 #include "base/bind.h" | 13 #include "base/bind.h" |
| 14 #include "base/bind_helpers.h" | 14 #include "base/bind_helpers.h" |
| 15 #include "base/logging.h" | 15 #include "base/logging.h" |
| 16 #include "chromeos/audio/audio_devices_pref_handler.h" | 16 #include "base/sys_info.h" |
| 17 #include "chromeos/audio/audio_devices_pref_handler_stub.h" | 17 #include "chromeos/audio/audio_devices_pref_handler_stub.h" |
| 18 #include "chromeos/dbus/dbus_thread_manager.h" | 18 #include "chromeos/dbus/dbus_thread_manager.h" |
| 19 | 19 |
| 20 using std::max; | 20 using std::max; |
| 21 using std::min; | 21 using std::min; |
| 22 | 22 |
| 23 namespace chromeos { | 23 namespace chromeos { |
| 24 | 24 |
| 25 namespace { | 25 namespace { |
| 26 | 26 |
| 27 // Default value for unmuting, as a percent in the range [0, 100]. | 27 // Default value for unmuting, as a percent in the range [0, 100]. |
| 28 // Used when sound is unmuted, but volume was less than kMuteThresholdPercent. | 28 // Used when sound is unmuted, but volume was less than kMuteThresholdPercent. |
| 29 const int kDefaultUnmuteVolumePercent = 4; | 29 const int kDefaultUnmuteVolumePercent = 4; |
| 30 | 30 |
| 31 // Volume value which should be considered as muted in range [0, 100]. | 31 // Volume value which should be considered as muted in range [0, 100]. |
| 32 const int kMuteThresholdPercent = 1; | 32 const int kMuteThresholdPercent = 1; |
| 33 | 33 |
| 34 // The duration of HDMI output re-discover grace period in milliseconds. | 34 // The duration of HDMI output re-discover grace period in milliseconds. |
| 35 const int kHDMIRediscoverGracePeriodDurationInMs = 2000; | 35 const int kHDMIRediscoverGracePeriodDurationInMs = 2000; |
| 36 | 36 |
| 37 static CrasAudioHandler* g_cras_audio_handler = NULL; | 37 static CrasAudioHandler* g_cras_audio_handler = NULL; |
| 38 | 38 |
| 39 bool IsSameAudioDevice(const AudioDevice& a, const AudioDevice& b) { | 39 bool IsSameAudioDevice(const AudioDevice& a, const AudioDevice& b) { |
| 40 return a.id == b.id && a.is_input == b.is_input && a.type == b.type | 40 return a.stable_device_id == b.stable_device_id && a.is_input == b.is_input && |
| 41 && a.device_name == b.device_name; | 41 a.type == b.type && a.device_name == b.device_name; |
| 42 } | 42 } |
| 43 | 43 |
| 44 bool IsInNodeList(uint64_t node_id, | 44 bool IsInNodeList(uint64_t node_id, |
| 45 const CrasAudioHandler::NodeIdList& id_list) { | 45 const CrasAudioHandler::NodeIdList& id_list) { |
| 46 return std::find(id_list.begin(), id_list.end(), node_id) != id_list.end(); | 46 return std::find(id_list.begin(), id_list.end(), node_id) != id_list.end(); |
| 47 } | 47 } |
| 48 | 48 |
| 49 bool IsDeviceInList(const AudioDevice& device, const AudioNodeList& node_list) { | |
| 50 for (const AudioNode& node : node_list) { | |
| 51 if (device.stable_device_id == node.stable_device_id) | |
| 52 return true; | |
| 53 } | |
| 54 return false; | |
| 55 } | |
| 56 | |
| 49 } // namespace | 57 } // namespace |
| 50 | 58 |
| 51 CrasAudioHandler::AudioObserver::AudioObserver() { | 59 CrasAudioHandler::AudioObserver::AudioObserver() { |
| 52 } | 60 } |
| 53 | 61 |
| 54 CrasAudioHandler::AudioObserver::~AudioObserver() { | 62 CrasAudioHandler::AudioObserver::~AudioObserver() { |
| 55 } | 63 } |
| 56 | 64 |
| 57 void CrasAudioHandler::AudioObserver::OnOutputNodeVolumeChanged( | 65 void CrasAudioHandler::AudioObserver::OnOutputNodeVolumeChanged( |
| 58 uint64_t /* node_id */, | 66 uint64_t /* node_id */, |
| (...skipping 167 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 226 const AudioDevice* device = GetDeviceFromId(node_id); | 234 const AudioDevice* device = GetDeviceFromId(node_id); |
| 227 if (!device) { | 235 if (!device) { |
| 228 VLOG(1) << "AddActiveInputNode: Cannot find device id=" | 236 VLOG(1) << "AddActiveInputNode: Cannot find device id=" |
| 229 << "0x" << std::hex << node_id; | 237 << "0x" << std::hex << node_id; |
| 230 return; | 238 return; |
| 231 } | 239 } |
| 232 | 240 |
| 233 // If there is no primary active device, set |node_id| to primary active node. | 241 // If there is no primary active device, set |node_id| to primary active node. |
| 234 if ((device->is_input && !active_input_node_id_) || | 242 if ((device->is_input && !active_input_node_id_) || |
| 235 (!device->is_input && !active_output_node_id_)) { | 243 (!device->is_input && !active_output_node_id_)) { |
| 236 SwitchToDevice(*device, notify); | 244 SwitchToDevice(*device, notify, ACTIVATE_BY_USER); |
| 237 return; | 245 return; |
| 238 } | 246 } |
| 239 | 247 |
| 240 AddAdditionalActiveNode(node_id, notify); | 248 AddAdditionalActiveNode(node_id, notify); |
| 241 } | 249 } |
| 242 | 250 |
| 243 void CrasAudioHandler::ChangeActiveNodes(const NodeIdList& new_active_ids) { | 251 void CrasAudioHandler::ChangeActiveNodes(const NodeIdList& new_active_ids) { |
| 244 // Flags for whether there are input or output nodes passed in from | 252 // Flags for whether there are input or output nodes passed in from |
| 245 // |new_active_ids|. If there are no input nodes passed in, we will not | 253 // |new_active_ids|. If there are no input nodes passed in, we will not |
| 246 // make any change for input nodes; same for the output nodes. | 254 // make any change for input nodes; same for the output nodes. |
| (...skipping 134 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 381 SetOutputVolumePercent(kDefaultUnmuteVolumePercent); | 389 SetOutputVolumePercent(kDefaultUnmuteVolumePercent); |
| 382 } | 390 } |
| 383 } | 391 } |
| 384 | 392 |
| 385 void CrasAudioHandler::SetInputMute(bool mute_on) { | 393 void CrasAudioHandler::SetInputMute(bool mute_on) { |
| 386 SetInputMuteInternal(mute_on); | 394 SetInputMuteInternal(mute_on); |
| 387 FOR_EACH_OBSERVER(AudioObserver, observers_, | 395 FOR_EACH_OBSERVER(AudioObserver, observers_, |
| 388 OnInputMuteChanged(input_mute_on_)); | 396 OnInputMuteChanged(input_mute_on_)); |
| 389 } | 397 } |
| 390 | 398 |
| 391 void CrasAudioHandler::SetActiveOutputNode(uint64_t node_id, bool notify) { | 399 void CrasAudioHandler::SetActiveDevice(const AudioDevice& active_device, |
| 392 chromeos::DBusThreadManager::Get()->GetCrasAudioClient()-> | 400 bool notify, |
| 393 SetActiveOutputNode(node_id); | 401 DeviceActivateType activate_by) { |
| 402 if (active_device.is_input) { | |
| 403 chromeos::DBusThreadManager::Get() | |
| 404 ->GetCrasAudioClient() | |
| 405 ->SetActiveInputNode(active_device.id); | |
| 406 } else { | |
| 407 chromeos::DBusThreadManager::Get() | |
| 408 ->GetCrasAudioClient() | |
| 409 ->SetActiveOutputNode(active_device.id); | |
| 410 } | |
| 411 | |
| 394 if (notify) | 412 if (notify) |
| 395 NotifyActiveNodeChanged(false); | 413 NotifyActiveNodeChanged(active_device.is_input); |
| 396 | 414 |
| 397 // Save state for all output nodes. | 415 // Save active state for the nodes. |
| 398 for (AudioDeviceMap::iterator it = audio_devices_.begin(); | 416 for (AudioDeviceMap::iterator it = audio_devices_.begin(); |
| 399 it != audio_devices_.end(); ++it) { | 417 it != audio_devices_.end(); ++it) { |
| 400 if (it->second.is_input) | 418 const AudioDevice& device = it->second; |
| 419 if (device.is_input != active_device.is_input) | |
| 401 continue; | 420 continue; |
| 402 audio_pref_handler_->SetDeviceState(it->second, it->second.active | 421 SaveDeviceState(device, device.active, activate_by); |
| 403 ? AUDIO_STATE_ACTIVE | |
| 404 : AUDIO_STATE_INACTIVE); | |
| 405 } | 422 } |
| 406 } | 423 } |
| 407 | 424 |
| 408 void CrasAudioHandler::SetActiveInputNode(uint64_t node_id, bool notify) { | 425 void CrasAudioHandler::SaveDeviceState(const AudioDevice& device, |
| 409 chromeos::DBusThreadManager::Get()->GetCrasAudioClient()-> | 426 bool active, |
| 410 SetActiveInputNode(node_id); | 427 DeviceActivateType activate_by) { |
| 411 if (notify) | 428 if (!active) { |
| 412 NotifyActiveNodeChanged(true); | 429 audio_pref_handler_->SetDeviceActive(device, false, false); |
| 413 | 430 } else { |
| 414 // Save state for all input nodes. | 431 switch (activate_by) { |
| 415 for (AudioDeviceMap::iterator it = audio_devices_.begin(); | 432 case ACTIVATE_BY_USER: |
| 416 it != audio_devices_.end(); ++it) { | 433 audio_pref_handler_->SetDeviceActive(device, true, true); |
| 417 if (!it->second.is_input) | 434 break; |
| 418 continue; | 435 case ACTIVATE_BY_PRIORITY: |
| 419 audio_pref_handler_->SetDeviceState(it->second, it->second.active | 436 audio_pref_handler_->SetDeviceActive(device, true, false); |
| 420 ? AUDIO_STATE_ACTIVE | 437 break; |
| 421 : AUDIO_STATE_INACTIVE); | 438 default: |
| 439 // The device is made active due to its previous active state in prefs, | |
| 440 // don't change its active state settings in prefs. | |
| 441 break; | |
| 442 } | |
| 422 } | 443 } |
| 423 } | 444 } |
| 424 | 445 |
| 425 void CrasAudioHandler::SetVolumeGainPercentForDevice(uint64_t device_id, | 446 void CrasAudioHandler::SetVolumeGainPercentForDevice(uint64_t device_id, |
| 426 int value) { | 447 int value) { |
| 427 const AudioDevice* device = GetDeviceFromId(device_id); | 448 const AudioDevice* device = GetDeviceFromId(device_id); |
| 428 if (!device) | 449 if (!device) |
| 429 return; | 450 return; |
| 430 | 451 |
| 431 if (device->is_input) | 452 if (device->is_input) |
| (...skipping 93 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 525 } | 546 } |
| 526 | 547 |
| 527 void CrasAudioHandler::AudioClientRestarted() { | 548 void CrasAudioHandler::AudioClientRestarted() { |
| 528 // Make sure the logging is enabled in case cras server | 549 // Make sure the logging is enabled in case cras server |
| 529 // restarts after crashing. | 550 // restarts after crashing. |
| 530 LogErrors(); | 551 LogErrors(); |
| 531 InitializeAudioState(); | 552 InitializeAudioState(); |
| 532 } | 553 } |
| 533 | 554 |
| 534 void CrasAudioHandler::NodesChanged() { | 555 void CrasAudioHandler::NodesChanged() { |
| 535 // Refresh audio nodes data. | 556 if (cras_service_available_) |
| 536 GetNodes(); | 557 GetNodes(); |
| 537 } | 558 } |
| 538 | 559 |
| 539 void CrasAudioHandler::ActiveOutputNodeChanged(uint64_t node_id) { | 560 void CrasAudioHandler::ActiveOutputNodeChanged(uint64_t node_id) { |
| 540 if (active_output_node_id_ == node_id) | 561 if (active_output_node_id_ == node_id) |
| 541 return; | 562 return; |
| 542 | 563 |
| 543 // Active audio output device should always be changed by chrome. | 564 // Active audio output device should always be changed by chrome. |
| 544 // During system boot, cras may change active input to unknown device 0x1, | 565 // During system boot, cras may change active input to unknown device 0x1, |
| 545 // we don't need to log it, since it is not an valid device. | 566 // we don't need to log it, since it is not an valid device. |
| 546 if (GetDeviceFromId(node_id)) { | 567 if (GetDeviceFromId(node_id)) { |
| (...skipping 28 matching lines...) Expand all Loading... | |
| 575 } | 596 } |
| 576 | 597 |
| 577 const AudioDevice* CrasAudioHandler::GetDeviceFromId(uint64_t device_id) const { | 598 const AudioDevice* CrasAudioHandler::GetDeviceFromId(uint64_t device_id) const { |
| 578 AudioDeviceMap::const_iterator it = audio_devices_.find(device_id); | 599 AudioDeviceMap::const_iterator it = audio_devices_.find(device_id); |
| 579 if (it == audio_devices_.end()) | 600 if (it == audio_devices_.end()) |
| 580 return NULL; | 601 return NULL; |
| 581 | 602 |
| 582 return &(it->second); | 603 return &(it->second); |
| 583 } | 604 } |
| 584 | 605 |
| 606 const AudioDevice* CrasAudioHandler::GetDeviceFromStableDeviceId( | |
| 607 uint64_t stable_device_id) const { | |
| 608 for (AudioDeviceMap::const_iterator it = audio_devices_.begin(); | |
| 609 it != audio_devices_.end(); ++it) { | |
| 610 if (it->second.stable_device_id == stable_device_id) | |
| 611 return &(it->second); | |
| 612 } | |
| 613 return NULL; | |
| 614 } | |
| 615 | |
| 585 const AudioDevice* CrasAudioHandler::GetKeyboardMic() const { | 616 const AudioDevice* CrasAudioHandler::GetKeyboardMic() const { |
| 586 for (AudioDeviceMap::const_iterator it = audio_devices_.begin(); | 617 for (AudioDeviceMap::const_iterator it = audio_devices_.begin(); |
| 587 it != audio_devices_.end(); it++) { | 618 it != audio_devices_.end(); it++) { |
| 588 if (it->second.is_input && it->second.type == AUDIO_TYPE_KEYBOARD_MIC) | 619 if (it->second.is_input && it->second.type == AUDIO_TYPE_KEYBOARD_MIC) |
| 589 return &(it->second); | 620 return &(it->second); |
| 590 } | 621 } |
| 591 return NULL; | 622 return NULL; |
| 592 } | 623 } |
| 593 | 624 |
| 594 void CrasAudioHandler::SetupAudioInputState() { | 625 void CrasAudioHandler::SetupAudioInputState() { |
| (...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 648 // TODO(jennyz): crbug.com/417418, track the status for the decison if | 679 // TODO(jennyz): crbug.com/417418, track the status for the decison if |
| 649 // we should persist input gain value in prefs. | 680 // we should persist input gain value in prefs. |
| 650 if (!device->is_input) { | 681 if (!device->is_input) { |
| 651 audio_pref_handler_->SetMuteValue(*device, IsOutputMuted()); | 682 audio_pref_handler_->SetMuteValue(*device, IsOutputMuted()); |
| 652 SetOutputNodeVolumePercent(node_id, GetOutputVolumePercent()); | 683 SetOutputNodeVolumePercent(node_id, GetOutputVolumePercent()); |
| 653 } | 684 } |
| 654 } | 685 } |
| 655 | 686 |
| 656 void CrasAudioHandler::InitializeAudioState() { | 687 void CrasAudioHandler::InitializeAudioState() { |
| 657 ApplyAudioPolicy(); | 688 ApplyAudioPolicy(); |
| 689 | |
| 690 // Defer querying cras for GetNodes until cras service becomes available. | |
| 691 cras_service_available_ = false; | |
| 692 chromeos::DBusThreadManager::Get() | |
| 693 ->GetCrasAudioClient() | |
| 694 ->WaitForServiceToBeAvailable(base::Bind( | |
| 695 &CrasAudioHandler::InitializeAudioAfterCrasServiceAvailable, | |
| 696 weak_ptr_factory_.GetWeakPtr())); | |
| 697 } | |
| 698 | |
| 699 void CrasAudioHandler::InitializeAudioAfterCrasServiceAvailable( | |
| 700 bool service_is_available) { | |
| 701 if (!service_is_available) { | |
| 702 LOG(ERROR) << "Cras service is not available"; | |
| 703 cras_service_available_ = false; | |
| 704 return; | |
| 705 } | |
| 706 | |
| 707 cras_service_available_ = true; | |
| 658 GetNodes(); | 708 GetNodes(); |
| 659 } | 709 } |
| 660 | 710 |
| 661 void CrasAudioHandler::ApplyAudioPolicy() { | 711 void CrasAudioHandler::ApplyAudioPolicy() { |
| 662 output_mute_locked_ = false; | 712 output_mute_locked_ = false; |
| 663 if (!audio_pref_handler_->GetAudioOutputAllowedValue()) { | 713 if (!audio_pref_handler_->GetAudioOutputAllowedValue()) { |
| 664 // Mute the device, but do not update the preference. | 714 // Mute the device, but do not update the preference. |
| 665 SetOutputMuteInternal(true); | 715 SetOutputMuteInternal(true); |
| 666 output_mute_locked_ = true; | 716 output_mute_locked_ = true; |
| 667 } else { | 717 } else { |
| (...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 743 } | 793 } |
| 744 | 794 |
| 745 void CrasAudioHandler::GetNodes() { | 795 void CrasAudioHandler::GetNodes() { |
| 746 chromeos::DBusThreadManager::Get()->GetCrasAudioClient()->GetNodes( | 796 chromeos::DBusThreadManager::Get()->GetCrasAudioClient()->GetNodes( |
| 747 base::Bind(&CrasAudioHandler::HandleGetNodes, | 797 base::Bind(&CrasAudioHandler::HandleGetNodes, |
| 748 weak_ptr_factory_.GetWeakPtr()), | 798 weak_ptr_factory_.GetWeakPtr()), |
| 749 base::Bind(&CrasAudioHandler::HandleGetNodesError, | 799 base::Bind(&CrasAudioHandler::HandleGetNodesError, |
| 750 weak_ptr_factory_.GetWeakPtr())); | 800 weak_ptr_factory_.GetWeakPtr())); |
| 751 } | 801 } |
| 752 | 802 |
| 753 bool CrasAudioHandler::ChangeActiveDevice(const AudioDevice& new_active_device, | 803 bool CrasAudioHandler::ChangeActiveDevice( |
| 754 uint64_t* current_active_node_id) { | 804 const AudioDevice& new_active_device) { |
| 805 uint64_t& current_active_node_id = new_active_device.is_input | |
| 806 ? active_input_node_id_ | |
| 807 : active_output_node_id_; | |
| 755 // If the device we want to switch to is already the current active device, | 808 // If the device we want to switch to is already the current active device, |
| 756 // do nothing. | 809 // do nothing. |
| 757 if (new_active_device.active && | 810 if (new_active_device.active && |
| 758 new_active_device.id == *current_active_node_id) { | 811 new_active_device.id == current_active_node_id) { |
| 759 return false; | 812 return false; |
| 760 } | 813 } |
| 761 | 814 |
| 815 bool found_new_active_device = false; | |
| 762 // Reset all other input or output devices' active status. The active audio | 816 // Reset all other input or output devices' active status. The active audio |
| 763 // device from the previous user session can be remembered by cras, but not | 817 // device from the previous user session can be remembered by cras, but not |
| 764 // in chrome. see crbug.com/273271. | 818 // in chrome. see crbug.com/273271. |
| 765 for (AudioDeviceMap::iterator it = audio_devices_.begin(); | 819 for (AudioDeviceMap::iterator it = audio_devices_.begin(); |
| 766 it != audio_devices_.end(); ++it) { | 820 it != audio_devices_.end(); ++it) { |
| 767 if (it->second.is_input == new_active_device.is_input && | 821 if (it->second.is_input == new_active_device.is_input && |
| 768 it->second.id != new_active_device.id) | 822 it->second.id != new_active_device.id) { |
| 769 it->second.active = false; | 823 it->second.active = false; |
| 824 } else if (it->second.is_input == new_active_device.is_input && | |
| 825 it->second.id == new_active_device.id) { | |
| 826 found_new_active_device = true; | |
| 827 } | |
| 828 } | |
| 829 | |
| 830 if (!found_new_active_device) { | |
| 831 LOG(ERROR) << "Invalid new active device: " << new_active_device.ToString(); | |
| 832 return false; | |
| 770 } | 833 } |
| 771 | 834 |
| 772 // Set the current active input/output device to the new_active_device. | 835 // Set the current active input/output device to the new_active_device. |
| 773 *current_active_node_id = new_active_device.id; | 836 current_active_node_id = new_active_device.id; |
| 774 audio_devices_[*current_active_node_id].active = true; | 837 audio_devices_[current_active_node_id].active = true; |
| 775 return true; | 838 return true; |
| 776 } | 839 } |
| 777 | 840 |
| 778 bool CrasAudioHandler::NonActiveDeviceUnplugged(size_t old_devices_size, | 841 void CrasAudioHandler::SwitchToDevice(const AudioDevice& device, |
| 779 size_t new_devices_size, | 842 bool notify, |
| 780 uint64_t current_active_node) { | 843 DeviceActivateType activate_by) { |
| 781 return (new_devices_size < old_devices_size && | 844 if (!ChangeActiveDevice(device)) |
| 782 GetDeviceFromId(current_active_node)); | 845 return; |
| 846 | |
| 847 if (device.is_input) | |
| 848 SetupAudioInputState(); | |
| 849 else | |
| 850 SetupAudioOutputState(); | |
| 851 | |
| 852 SetActiveDevice(device, notify, activate_by); | |
| 783 } | 853 } |
| 784 | 854 |
| 785 void CrasAudioHandler::SwitchToDevice(const AudioDevice& device, bool notify) { | 855 bool CrasAudioHandler::HasDeviceChange(const AudioNodeList& new_nodes, |
| 786 if (device.is_input) { | 856 bool is_input, |
| 787 if (!ChangeActiveDevice(device, &active_input_node_id_)) | 857 AudioDevicePriorityQueue* new_discovered, |
| 788 return; | 858 bool* device_removed, |
| 789 SetupAudioInputState(); | 859 bool* active_device_removed) { |
| 790 SetActiveInputNode(active_input_node_id_, notify); | 860 *device_removed = false; |
| 791 } else { | |
| 792 if (!ChangeActiveDevice(device, &active_output_node_id_)) | |
| 793 return; | |
| 794 SetupAudioOutputState(); | |
| 795 SetActiveOutputNode(active_output_node_id_, notify); | |
| 796 } | |
| 797 } | |
| 798 | |
| 799 bool CrasAudioHandler::HasDeviceChange( | |
| 800 const AudioNodeList& new_nodes, | |
| 801 bool is_input, | |
| 802 AudioDevicePriorityQueue* new_discovered) { | |
| 803 size_t num_old_devices = 0; | |
| 804 size_t num_new_devices = 0; | |
| 805 for (AudioDeviceMap::const_iterator it = audio_devices_.begin(); | 861 for (AudioDeviceMap::const_iterator it = audio_devices_.begin(); |
| 806 it != audio_devices_.end(); ++it) { | 862 it != audio_devices_.end(); ++it) { |
| 807 if (is_input == it->second.is_input) | 863 const AudioDevice& device = it->second; |
| 808 ++num_old_devices; | 864 if (is_input != device.is_input) |
| 865 continue; | |
| 866 if (!IsDeviceInList(device, new_nodes)) { | |
| 867 *device_removed = true; | |
| 868 if ((is_input && device.id == active_input_node_id_) || | |
| 869 (!is_input && device.id == active_output_node_id_)) { | |
| 870 *active_device_removed = true; | |
| 871 } | |
| 872 } | |
| 809 } | 873 } |
| 810 | 874 |
| 811 bool new_or_changed_device = false; | 875 bool new_or_changed_device = false; |
| 812 while (!new_discovered->empty()) | 876 while (!new_discovered->empty()) |
| 813 new_discovered->pop(); | 877 new_discovered->pop(); |
| 814 for (AudioNodeList::const_iterator it = new_nodes.begin(); | 878 for (AudioNodeList::const_iterator it = new_nodes.begin(); |
| 815 it != new_nodes.end(); ++it) { | 879 it != new_nodes.end(); ++it) { |
| 816 if (is_input == it->is_input) { | 880 if (is_input != it->is_input) |
| 817 ++num_new_devices; | 881 continue; |
| 818 // Look to see if the new device not in the old device list. | 882 // Check if the new device is not in the old device list. |
| 819 AudioDevice device(*it); | 883 AudioDevice device(*it); |
| 820 DeviceStatus status = CheckDeviceStatus(device); | 884 DeviceStatus status = CheckDeviceStatus(device); |
| 821 if (status == NEW_DEVICE) | 885 if (status == NEW_DEVICE) |
| 822 new_discovered->push(device); | 886 new_discovered->push(device); |
| 823 if (status == NEW_DEVICE || status == CHANGED_DEVICE) { | 887 if (status == NEW_DEVICE || status == CHANGED_DEVICE) { |
| 824 new_or_changed_device = true; | 888 new_or_changed_device = true; |
| 825 } | |
| 826 } | 889 } |
| 827 } | 890 } |
| 828 return new_or_changed_device || (num_old_devices != num_new_devices); | 891 return new_or_changed_device || *device_removed; |
| 829 } | 892 } |
| 830 | 893 |
| 831 CrasAudioHandler::DeviceStatus CrasAudioHandler::CheckDeviceStatus( | 894 CrasAudioHandler::DeviceStatus CrasAudioHandler::CheckDeviceStatus( |
| 832 const AudioDevice& device) { | 895 const AudioDevice& device) { |
| 833 const AudioDevice* device_found = GetDeviceFromId(device.id); | 896 const AudioDevice* device_found = |
| 897 GetDeviceFromStableDeviceId(device.stable_device_id); | |
| 834 if (!device_found) | 898 if (!device_found) |
| 835 return NEW_DEVICE; | 899 return NEW_DEVICE; |
| 836 | 900 |
| 837 if (!IsSameAudioDevice(device, *device_found)) { | 901 if (!IsSameAudioDevice(device, *device_found)) { |
| 838 LOG(WARNING) << "Different Audio devices with same id:" | 902 LOG(ERROR) << "Different Audio devices with same stable device id:" |
| 839 << " new device: " << device.ToString() | 903 << " new device: " << device.ToString() |
| 840 << " old device: " << device_found->ToString(); | 904 << " old device: " << device_found->ToString(); |
| 841 return CHANGED_DEVICE; | 905 return CHANGED_DEVICE; |
| 842 } else if (device.active != device_found->active) { | 906 } else if (device.active != device_found->active) { |
| 843 return CHANGED_DEVICE; | 907 return CHANGED_DEVICE; |
| 844 } | 908 } |
| 845 | 909 |
| 846 return OLD_DEVICE; | 910 return OLD_DEVICE; |
| 847 } | 911 } |
| 848 | 912 |
| 849 void CrasAudioHandler::NotifyActiveNodeChanged(bool is_input) { | 913 void CrasAudioHandler::NotifyActiveNodeChanged(bool is_input) { |
| 850 if (is_input) | 914 if (is_input) |
| 851 FOR_EACH_OBSERVER(AudioObserver, observers_, OnActiveInputNodeChanged()); | 915 FOR_EACH_OBSERVER(AudioObserver, observers_, OnActiveInputNodeChanged()); |
| 852 else | 916 else |
| 853 FOR_EACH_OBSERVER(AudioObserver, observers_, OnActiveOutputNodeChanged()); | 917 FOR_EACH_OBSERVER(AudioObserver, observers_, OnActiveOutputNodeChanged()); |
| 854 } | 918 } |
| 855 | 919 |
| 920 bool CrasAudioHandler::GetActiveDeviceFromUserPref(bool is_input, | |
| 921 AudioDevice* active_device) { | |
| 922 bool found_active_device = false; | |
| 923 bool last_active_device_activate_by_user = false; | |
| 924 for (AudioDeviceMap::const_iterator it = audio_devices_.begin(); | |
| 925 it != audio_devices_.end(); ++it) { | |
| 926 AudioDevice device = it->second; | |
| 927 if (device.is_input != is_input) | |
| 928 continue; | |
| 929 | |
| 930 bool active = false; | |
| 931 bool activate_by_user = false; | |
| 932 if (!audio_pref_handler_->GetDeviceActive(device, &active, | |
| 933 &activate_by_user) || | |
| 934 !active) { | |
| 935 continue; | |
| 936 } | |
| 937 | |
| 938 if (!found_active_device) { | |
| 939 found_active_device = true; | |
| 940 *active_device = device; | |
| 941 last_active_device_activate_by_user = activate_by_user; | |
| 942 continue; | |
| 943 } | |
| 944 | |
| 945 // Choose the best one among multiple active devices from prefs. | |
| 946 if (activate_by_user) { | |
| 947 if (!last_active_device_activate_by_user) { | |
| 948 // Device activated by user has higher priority than the one | |
| 949 // is not activated by user. | |
| 950 *active_device = device; | |
| 951 last_active_device_activate_by_user = true; | |
| 952 } else { | |
| 953 // If there are more than one active devices activated by user in the | |
| 954 // prefs, most likely, after the device was shut down, and before it | |
| 955 // is rebooted, user has plugged in some previously unplugged audio | |
| 956 // devices. For such case, it does not make sense to honor the active | |
| 957 // states in the prefs. | |
| 958 VLOG(1) << "Found more than one user activated devices in the prefs."; | |
| 959 return false; | |
| 960 } | |
| 961 } else if (!last_active_device_activate_by_user) { | |
| 962 // If there are more than one active devices activated by priority in the | |
| 963 // prefs, most likely, cras is still enumerating the audio devices | |
| 964 // progressively. For such case, it does not make sense to honor the | |
| 965 // active states in the prefs. | |
| 966 VLOG(1) << "Found more than one active devices by priority in the prefs."; | |
| 967 return false; | |
| 968 } | |
| 969 } | |
| 970 | |
| 971 if (found_active_device && !active_device->is_for_simple_usage()) { | |
| 972 // This is an odd case which is rare but possible to happen during cras | |
| 973 // initialization depeneding the audio device enumation process. The only | |
| 974 // audio node coming from cras is an internal audio device not visible | |
| 975 // to user, such as AUDIO_TYPE_POST_MIX_LOOPBACK. | |
| 976 return false; | |
| 977 } | |
| 978 | |
| 979 return found_active_device; | |
| 980 } | |
| 981 | |
| 982 void CrasAudioHandler::HandleNonHotplugNodesChange( | |
| 983 bool is_input, | |
| 984 const AudioDevicePriorityQueue& hotplug_nodes, | |
| 985 bool has_device_change, | |
| 986 bool has_device_removed, | |
| 987 bool active_device_removed) { | |
| 988 bool has_current_active_node = | |
| 989 is_input ? active_input_node_id_ : active_output_node_id_; | |
| 990 | |
| 991 // No device change, extra NodesChanged signal received. | |
| 992 if (!has_device_change && has_current_active_node) | |
| 993 return; | |
| 994 | |
| 995 if (hotplug_nodes.empty()) { | |
| 996 // Unplugged a non-active device. | |
| 997 if (has_device_removed) { | |
| 998 if (!active_device_removed && has_current_active_node) { | |
| 999 // Removed a non-active device, keep the current active device. | |
| 1000 return; | |
| 1001 } | |
| 1002 | |
| 1003 if (active_device_removed) { | |
| 1004 // Unplugged the current active device. | |
| 1005 SwitchToTopPriorityDevice(is_input); | |
| 1006 return; | |
| 1007 } | |
| 1008 } | |
| 1009 | |
| 1010 // Some unexpected error happens on cras side. See crbug.com/586026. | |
| 1011 // Either cras sent stale nodes to chrome again or cras triggered some | |
| 1012 // error. Restore the previously selected active. | |
| 1013 VLOG(1) << "Odd case from cras, the active node is lost unexpectedly. "; | |
| 1014 SwitchToPreviousActiveDeviceIfAvailable(is_input); | |
| 1015 } else { | |
| 1016 // Looks like a new chrome session starts. | |
| 1017 SwitchToPreviousActiveDeviceIfAvailable(is_input); | |
| 1018 } | |
| 1019 } | |
| 1020 | |
| 1021 void CrasAudioHandler::HandleHotPlugDevice( | |
| 1022 const AudioDevice& hotplug_device, | |
| 1023 const AudioDevicePriorityQueue& device_priority_queue) { | |
| 1024 bool last_state_active = false; | |
| 1025 bool last_activate_by_user = false; | |
| 1026 if (!audio_pref_handler_->GetDeviceActive(hotplug_device, &last_state_active, | |
| 1027 &last_activate_by_user)) { | |
| 1028 // |hotplug_device} is plugged in for the first time, activate it if it | |
|
hychao
2016/03/02 15:34:05
|hotplug_device|
jennyz
2016/03/02 19:33:37
Done.
| |
| 1029 // is of the highest priority. | |
| 1030 if (device_priority_queue.top().id == hotplug_device.id) { | |
| 1031 VLOG(1) << "Hotplug a device for the first time: " | |
| 1032 << hotplug_device.ToString(); | |
| 1033 SwitchToDevice(hotplug_device, true, ACTIVATE_BY_PRIORITY); | |
| 1034 } | |
| 1035 } else if (last_state_active) { | |
| 1036 VLOG(1) << "Hotplug a device, restore to active: " | |
| 1037 << hotplug_device.ToString(); | |
| 1038 SwitchToDevice(hotplug_device, true, ACTIVATE_BY_RESTORE_PREVIOUS_STATE); | |
| 1039 } else { | |
| 1040 // Do not active the device if its previous state is inactive. | |
| 1041 VLOG(1) << "Hotplug device remains inactive as its previous state:" | |
| 1042 << hotplug_device.ToString(); | |
| 1043 } | |
| 1044 } | |
| 1045 | |
| 1046 void CrasAudioHandler::SwitchToTopPriorityDevice(bool is_input) { | |
| 1047 AudioDevice top_device = | |
| 1048 is_input ? input_devices_pq_.top() : output_devices_pq_.top(); | |
| 1049 SwitchToDevice(top_device, true, ACTIVATE_BY_PRIORITY); | |
| 1050 } | |
| 1051 | |
| 1052 void CrasAudioHandler::SwitchToPreviousActiveDeviceIfAvailable(bool is_input) { | |
| 1053 AudioDevice previous_active_device; | |
| 1054 if (GetActiveDeviceFromUserPref(is_input, &previous_active_device)) { | |
| 1055 // Switch to previous active device stored in user prefs. | |
| 1056 SwitchToDevice(previous_active_device, true, | |
| 1057 ACTIVATE_BY_RESTORE_PREVIOUS_STATE); | |
| 1058 } else { | |
| 1059 // No previous active device, switch to the top priority device. | |
| 1060 SwitchToTopPriorityDevice(is_input); | |
| 1061 } | |
| 1062 } | |
| 1063 | |
| 856 void CrasAudioHandler::UpdateDevicesAndSwitchActive( | 1064 void CrasAudioHandler::UpdateDevicesAndSwitchActive( |
| 857 const AudioNodeList& nodes) { | 1065 const AudioNodeList& nodes) { |
| 858 size_t old_output_device_size = 0; | 1066 size_t old_output_device_size = 0; |
| 859 size_t old_input_device_size = 0; | 1067 size_t old_input_device_size = 0; |
| 860 for (AudioDeviceMap::const_iterator it = audio_devices_.begin(); | 1068 for (AudioDeviceMap::const_iterator it = audio_devices_.begin(); |
| 861 it != audio_devices_.end(); ++it) { | 1069 it != audio_devices_.end(); ++it) { |
| 862 if (it->second.is_input) | 1070 if (it->second.is_input) |
| 863 ++old_input_device_size; | 1071 ++old_input_device_size; |
| 864 else | 1072 else |
| 865 ++old_output_device_size; | 1073 ++old_output_device_size; |
| 866 } | 1074 } |
| 867 | 1075 |
| 868 AudioDevicePriorityQueue hotplug_output_nodes; | 1076 AudioDevicePriorityQueue hotplug_output_nodes; |
| 869 AudioDevicePriorityQueue hotplug_input_nodes; | 1077 AudioDevicePriorityQueue hotplug_input_nodes; |
| 1078 bool has_output_removed = false; | |
| 1079 bool has_input_removed = false; | |
| 1080 bool active_output_removed = false; | |
| 1081 bool active_input_removed = false; | |
| 870 bool output_devices_changed = | 1082 bool output_devices_changed = |
| 871 HasDeviceChange(nodes, false, &hotplug_output_nodes); | 1083 HasDeviceChange(nodes, false, &hotplug_output_nodes, &has_output_removed, |
| 1084 &active_output_removed); | |
| 872 bool input_devices_changed = | 1085 bool input_devices_changed = |
| 873 HasDeviceChange(nodes, true, &hotplug_input_nodes); | 1086 HasDeviceChange(nodes, true, &hotplug_input_nodes, &has_input_removed, |
| 1087 &active_input_removed); | |
| 874 audio_devices_.clear(); | 1088 audio_devices_.clear(); |
| 875 has_alternative_input_ = false; | 1089 has_alternative_input_ = false; |
| 876 has_alternative_output_ = false; | 1090 has_alternative_output_ = false; |
| 877 | 1091 |
| 878 while (!input_devices_pq_.empty()) | 1092 while (!input_devices_pq_.empty()) |
| 879 input_devices_pq_.pop(); | 1093 input_devices_pq_.pop(); |
| 880 while (!output_devices_pq_.empty()) | 1094 while (!output_devices_pq_.empty()) |
| 881 output_devices_pq_.pop(); | 1095 output_devices_pq_.pop(); |
| 882 | 1096 |
| 883 size_t new_output_device_size = 0; | 1097 size_t new_output_device_size = 0; |
| 884 size_t new_input_device_size = 0; | 1098 size_t new_input_device_size = 0; |
| 885 for (size_t i = 0; i < nodes.size(); ++i) { | 1099 for (size_t i = 0; i < nodes.size(); ++i) { |
| 886 AudioDevice device(nodes[i]); | 1100 AudioDevice device(nodes[i]); |
| 887 audio_devices_[device.id] = device; | 1101 audio_devices_[device.id] = device; |
| 888 | |
| 889 if (!has_alternative_input_ && | 1102 if (!has_alternative_input_ && |
| 890 device.is_input && | 1103 device.is_input && |
| 891 device.type != AUDIO_TYPE_INTERNAL_MIC && | 1104 device.type != AUDIO_TYPE_INTERNAL_MIC && |
| 892 device.type != AUDIO_TYPE_KEYBOARD_MIC) { | 1105 device.type != AUDIO_TYPE_KEYBOARD_MIC) { |
| 893 has_alternative_input_ = true; | 1106 has_alternative_input_ = true; |
| 894 } else if (!has_alternative_output_ && | 1107 } else if (!has_alternative_output_ && |
| 895 !device.is_input && | 1108 !device.is_input && |
| 896 device.type != AUDIO_TYPE_INTERNAL_SPEAKER) { | 1109 device.type != AUDIO_TYPE_INTERNAL_SPEAKER) { |
| 897 has_alternative_output_ = true; | 1110 has_alternative_output_ = true; |
| 898 } | 1111 } |
| 899 | 1112 |
| 900 if (device.is_input) { | 1113 if (device.is_input) { |
| 901 input_devices_pq_.push(device); | 1114 input_devices_pq_.push(device); |
| 902 ++new_input_device_size; | 1115 ++new_input_device_size; |
| 903 } else { | 1116 } else { |
| 904 output_devices_pq_.push(device); | 1117 output_devices_pq_.push(device); |
| 905 ++new_output_device_size; | 1118 ++new_output_device_size; |
| 906 } | 1119 } |
| 907 } | 1120 } |
| 908 | 1121 |
| 1122 // Handle output device changes. | |
| 1123 HandleAudioDeviceChange(false, output_devices_pq_, hotplug_output_nodes, | |
| 1124 output_devices_changed, has_output_removed, | |
| 1125 active_output_removed); | |
| 1126 | |
| 1127 // Handle input device changes. | |
| 1128 HandleAudioDeviceChange(true, input_devices_pq_, hotplug_input_nodes, | |
| 1129 input_devices_changed, has_input_removed, | |
| 1130 active_input_removed); | |
| 1131 } | |
| 1132 | |
| 1133 void CrasAudioHandler::HandleAudioDeviceChange( | |
| 1134 bool is_input, | |
| 1135 const AudioDevicePriorityQueue& devices_pq, | |
| 1136 const AudioDevicePriorityQueue& hotplug_nodes, | |
| 1137 bool has_device_change, | |
| 1138 bool has_device_removed, | |
| 1139 bool active_device_removed) { | |
| 1140 uint64_t& active_node_id = | |
| 1141 is_input ? active_input_node_id_ : active_output_node_id_; | |
| 1142 | |
| 1143 // No audio devices found. | |
| 1144 if (devices_pq.empty()) { | |
| 1145 VLOG(1) << "No " << (is_input ? "input" : "output") << " devices found"; | |
| 1146 active_node_id = 0; | |
| 1147 NotifyActiveNodeChanged(is_input); | |
| 1148 return; | |
| 1149 } | |
| 1150 | |
| 909 // If the previous active device is removed from the new node list, | 1151 // If the previous active device is removed from the new node list, |
| 910 // or changed to inactive by cras, reset active_output_node_id_. | 1152 // or changed to inactive by cras, reset active_node_id. |
| 911 // See crbug.com/478968. | 1153 // See crbug.com/478968. |
| 912 const AudioDevice* active_output = GetDeviceFromId(active_output_node_id_); | 1154 const AudioDevice* active_device = GetDeviceFromId(active_node_id); |
| 913 if (!active_output || !active_output->active) | 1155 if (!active_device || !active_device->active) |
| 914 active_output_node_id_ = 0; | 1156 active_node_id = 0; |
| 915 const AudioDevice* active_input = GetDeviceFromId(active_input_node_id_); | |
| 916 if (!active_input || !active_input->active) | |
| 917 active_input_node_id_ = 0; | |
| 918 | 1157 |
| 919 // If audio nodes change is caused by unplugging some non-active audio | 1158 if (!active_node_id || hotplug_nodes.empty() || hotplug_nodes.size() > 1) { |
| 920 // devices, we wont't change the current active audio devices. | 1159 HandleNonHotplugNodesChange(is_input, hotplug_nodes, has_device_change, |
| 921 if (input_devices_changed && | 1160 has_device_removed, active_device_removed); |
| 922 !NonActiveDeviceUnplugged(old_input_device_size, | 1161 } else { |
| 923 new_input_device_size, | 1162 // Typical user hotplug case. |
| 924 active_input_node_id_)) { | 1163 HandleHotPlugDevice(hotplug_nodes.top(), devices_pq); |
| 925 // Some devices like chromeboxes don't have the internal audio input. In | |
| 926 // that case the active input node id should be reset. | |
| 927 if (input_devices_pq_.empty()) { | |
| 928 active_input_node_id_ = 0; | |
| 929 NotifyActiveNodeChanged(true); | |
| 930 } else { | |
| 931 // If there is no current active node, or, no new hot plugged node, select | |
| 932 // the active node by their priorities. | |
| 933 if (!active_input_node_id_ || hotplug_input_nodes.empty()) { | |
| 934 SwitchToDevice(input_devices_pq_.top(), true); | |
| 935 } else { | |
| 936 // If user has hot plugged any input nodes, look at the one with highest | |
| 937 // priority (normally, there is only one hot plugged input node), and | |
| 938 // consider switch to it depend on its last state stored in preference. | |
| 939 AudioDeviceState last_state = | |
| 940 audio_pref_handler_->GetDeviceState(hotplug_input_nodes.top()); | |
| 941 switch (last_state) { | |
| 942 case AUDIO_STATE_ACTIVE: | |
| 943 // This node was plugged in before and was selected as the active | |
| 944 // one | |
| 945 // before it was unplugged last time, switch to this device. | |
| 946 SwitchToDevice(hotplug_input_nodes.top(), true); | |
| 947 break; | |
| 948 case AUDIO_STATE_NOT_AVAILABLE: | |
| 949 // This is a new node, not plugged in before, with the highest | |
| 950 // priority. Switch to this device. | |
| 951 if (input_devices_pq_.top().id == hotplug_input_nodes.top().id) | |
| 952 SwitchToDevice(hotplug_input_nodes.top(), true); | |
| 953 break; | |
| 954 case AUDIO_STATE_INACTIVE: | |
| 955 // This node was NOT selected as the active one last time before it | |
| 956 // got unplugged, so don't switch to it. | |
| 957 default: | |
| 958 break; | |
| 959 } | |
| 960 } | |
| 961 } | |
| 962 } | |
| 963 if (output_devices_changed && | |
| 964 !NonActiveDeviceUnplugged(old_output_device_size, | |
| 965 new_output_device_size, | |
| 966 active_output_node_id_)) { | |
| 967 // ditto input node logic. | |
| 968 if (output_devices_pq_.empty()) { | |
| 969 active_output_node_id_ = 0; | |
| 970 NotifyActiveNodeChanged(false); | |
| 971 } else { | |
| 972 if (!active_output_node_id_ || hotplug_output_nodes.empty()) { | |
| 973 SwitchToDevice(output_devices_pq_.top(), true); | |
| 974 } else { | |
| 975 AudioDeviceState last_state = | |
| 976 audio_pref_handler_->GetDeviceState(hotplug_output_nodes.top()); | |
| 977 switch (last_state) { | |
| 978 case AUDIO_STATE_ACTIVE: | |
| 979 SwitchToDevice(hotplug_output_nodes.top(), true); | |
| 980 break; | |
| 981 case AUDIO_STATE_NOT_AVAILABLE: | |
| 982 if (output_devices_pq_.top().id == hotplug_output_nodes.top().id) | |
| 983 SwitchToDevice(hotplug_output_nodes.top(), true); | |
| 984 break; | |
| 985 case AUDIO_STATE_INACTIVE: | |
| 986 default: | |
| 987 break; | |
| 988 } | |
| 989 } | |
| 990 } | |
| 991 } | 1164 } |
| 992 } | 1165 } |
| 993 | 1166 |
| 994 void CrasAudioHandler::HandleGetNodes(const chromeos::AudioNodeList& node_list, | 1167 void CrasAudioHandler::HandleGetNodes(const chromeos::AudioNodeList& node_list, |
| 995 bool success) { | 1168 bool success) { |
| 996 if (!success) { | 1169 if (!success) { |
| 997 LOG_IF(ERROR, log_errors_) << "Failed to retrieve audio nodes data"; | 1170 LOG_IF(ERROR, log_errors_) << "Failed to retrieve audio nodes data"; |
| 998 return; | 1171 return; |
| 999 } | 1172 } |
| 1000 | 1173 |
| (...skipping 92 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1093 hdmi_rediscover_grace_period_duration_in_ms_), | 1266 hdmi_rediscover_grace_period_duration_in_ms_), |
| 1094 this, &CrasAudioHandler::UpdateAudioAfterHDMIRediscoverGracePeriod); | 1267 this, &CrasAudioHandler::UpdateAudioAfterHDMIRediscoverGracePeriod); |
| 1095 } | 1268 } |
| 1096 | 1269 |
| 1097 void CrasAudioHandler::SetHDMIRediscoverGracePeriodForTesting( | 1270 void CrasAudioHandler::SetHDMIRediscoverGracePeriodForTesting( |
| 1098 int duration_in_ms) { | 1271 int duration_in_ms) { |
| 1099 hdmi_rediscover_grace_period_duration_in_ms_ = duration_in_ms; | 1272 hdmi_rediscover_grace_period_duration_in_ms_ = duration_in_ms; |
| 1100 } | 1273 } |
| 1101 | 1274 |
| 1102 } // namespace chromeos | 1275 } // namespace chromeos |
| OLD | NEW |