1 // Copyright 2014 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 "extensions/shell/browser/shell_audio_controller_chromeos.h"
6 
7 #include <stdint.h>
8 
9 #include <memory>
10 
11 #include "base/macros.h"
12 #include "base/memory/ptr_util.h"
13 #include "chromeos/audio/audio_device.h"
14 #include "chromeos/audio/audio_devices_pref_handler.h"
15 #include "chromeos/audio/cras_audio_handler.h"
16 #include "chromeos/dbus/audio/audio_node.h"
17 #include "chromeos/dbus/audio/fake_cras_audio_client.h"
18 #include "testing/gtest/include/gtest/gtest.h"
19 
20 using chromeos::AudioDevice;
21 using chromeos::AudioNode;
22 using chromeos::AudioNodeList;
23 
24 namespace extensions {
25 
26 class ShellAudioControllerTest : public testing::Test {
27  public:
ShellAudioControllerTest()28   ShellAudioControllerTest() : next_node_id_(1) {
29     chromeos::CrasAudioClient::InitializeFake();
30     audio_client()->SetAudioNodesForTesting(AudioNodeList());
31     chromeos::CrasAudioHandler::InitializeForTesting();
32 
33     controller_.reset(new ShellAudioController());
34   }
35 
~ShellAudioControllerTest()36   ~ShellAudioControllerTest() override {
37     controller_.reset();
38     chromeos::CrasAudioHandler::Shutdown();
39     chromeos::CrasAudioClient::Shutdown();
40   }
41 
42  protected:
43   // Fills a AudioNode for use by tests.
CreateNode(chromeos::AudioDeviceType type)44   AudioNode CreateNode(chromeos::AudioDeviceType type) {
45     AudioNode node;
46     node.is_input =
47         type == chromeos::AUDIO_TYPE_MIC ||
48         type == chromeos::AUDIO_TYPE_INTERNAL_MIC ||
49         type == chromeos::AUDIO_TYPE_KEYBOARD_MIC;
50     node.id = next_node_id_++;
51     node.type = AudioDevice::GetTypeString(type);
52     return node;
53   }
54 
55   // Changes the active state of the node with |id| in |nodes|.
SetNodeActive(AudioNodeList * nodes,uint64_t id,bool active)56   void SetNodeActive(AudioNodeList* nodes, uint64_t id, bool active) {
57     for (AudioNodeList::iterator it = nodes->begin();
58          it != nodes->end(); ++it) {
59       if (it->id == id) {
60         it->active = active;
61         return;
62       }
63     }
64     ASSERT_TRUE(false) << "Didn't find ID " << id;
65   }
66 
audio_client()67   chromeos::FakeCrasAudioClient* audio_client() {
68     return chromeos::FakeCrasAudioClient::Get();
69   }
70 
audio_handler()71   chromeos::CrasAudioHandler* audio_handler() {
72     return chromeos::CrasAudioHandler::Get();
73   }
74 
75   std::unique_ptr<ShellAudioController> controller_;
76 
77   // Next audio node ID to be returned by CreateNode().
78   uint64_t next_node_id_;
79 
80  private:
81   DISALLOW_COPY_AND_ASSIGN(ShellAudioControllerTest);
82 };
83 
84 // Tests that higher-priority devices are activated as soon as they're
85 // connected.
TEST_F(ShellAudioControllerTest,SelectBestDevices)86 TEST_F(ShellAudioControllerTest, SelectBestDevices) {
87   AudioNode internal_speaker =
88       CreateNode(chromeos::AUDIO_TYPE_INTERNAL_SPEAKER);
89   AudioNode internal_mic = CreateNode(chromeos::AUDIO_TYPE_INTERNAL_MIC);
90   AudioNode headphone = CreateNode(chromeos::AUDIO_TYPE_HEADPHONE);
91   AudioNode external_mic = CreateNode(chromeos::AUDIO_TYPE_MIC);
92 
93   // AudioDevice gives the headphone jack a higher priority than the internal
94   // speaker and an external mic a higher priority than the internal mic, so we
95   // should start out favoring headphones and the external mic.
96   AudioNodeList all_nodes;
97   all_nodes.push_back(internal_speaker);
98   all_nodes.push_back(internal_mic);
99   all_nodes.push_back(headphone);
100   all_nodes.push_back(external_mic);
101   audio_client()->SetAudioNodesAndNotifyObserversForTesting(all_nodes);
102   EXPECT_EQ(headphone.id, audio_handler()->GetPrimaryActiveOutputNode());
103   EXPECT_EQ(external_mic.id, audio_handler()->GetPrimaryActiveInputNode());
104 
105   // Unplug the headphones and mic and check that we switch to the internal
106   // devices.
107   AudioNodeList internal_nodes;
108   internal_nodes.push_back(internal_speaker);
109   internal_nodes.push_back(internal_mic);
110   audio_client()->SetAudioNodesAndNotifyObserversForTesting(internal_nodes);
111   EXPECT_EQ(internal_speaker.id, audio_handler()->GetPrimaryActiveOutputNode());
112   EXPECT_EQ(internal_mic.id, audio_handler()->GetPrimaryActiveInputNode());
113 
114   // Switch back to the external devices. Mark the previously-activated internal
115   // devices as being active so CrasAudioHandler doesn't complain.
116   SetNodeActive(&all_nodes, internal_speaker.id, true);
117   SetNodeActive(&all_nodes, internal_mic.id, true);
118   audio_client()->SetAudioNodesAndNotifyObserversForTesting(all_nodes);
119   EXPECT_EQ(headphone.id, audio_handler()->GetPrimaryActiveOutputNode());
120   EXPECT_EQ(external_mic.id, audio_handler()->GetPrimaryActiveInputNode());
121 }
122 
123 // Tests that active audio devices are unmuted and have correct initial volume.
TEST_F(ShellAudioControllerTest,InitialVolume)124 TEST_F(ShellAudioControllerTest, InitialVolume) {
125   AudioNodeList nodes;
126   nodes.push_back(CreateNode(chromeos::AUDIO_TYPE_INTERNAL_SPEAKER));
127   nodes.push_back(CreateNode(chromeos::AUDIO_TYPE_INTERNAL_MIC));
128   audio_client()->SetAudioNodesAndNotifyObserversForTesting(nodes);
129 
130   EXPECT_FALSE(audio_handler()->IsOutputMuted());
131   EXPECT_FALSE(audio_handler()->IsInputMuted());
132   EXPECT_EQ(static_cast<double>(
133                 chromeos::AudioDevicesPrefHandler::kDefaultOutputVolumePercent),
134             audio_handler()->GetOutputVolumePercent());
135 
136   // TODO(rkc): The default value for gain is wrong. http://crbug.com/442489
137   EXPECT_EQ(75.0, audio_handler()->GetInputGainPercent());
138 }
139 
140 }  // namespace extensions
141