1 // Copyright (c) 2020 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/audio/unified_audio_detailed_view_controller.h"
6
7 #include "ash/public/cpp/ash_features.h"
8 #include "ash/system/audio/audio_detailed_view.h"
9 #include "ash/system/audio/mic_gain_slider_controller.h"
10 #include "ash/system/unified/unified_system_tray_controller.h"
11 #include "ash/system/unified/unified_system_tray_model.h"
12 #include "ash/test/ash_test_base.h"
13 #include "base/test/scoped_feature_list.h"
14 #include "chromeos/audio/audio_devices_pref_handler.h"
15 #include "chromeos/audio/audio_devices_pref_handler_stub.h"
16 #include "chromeos/dbus/audio/fake_cras_audio_client.h"
17 #include "mojo/public/cpp/bindings/receiver_set.h"
18 #include "services/media_session/public/mojom/media_controller.mojom-test-utils.h"
19 #include "testing/gmock/include/gmock/gmock.h"
20
21 using chromeos::AudioDevice;
22 using chromeos::AudioDeviceList;
23 using chromeos::AudioNode;
24 using chromeos::AudioNodeList;
25 using chromeos::CrasAudioHandler;
26
27 namespace ash {
28 namespace {
29
30 class FakeMediaControllerManager
31 : public media_session::mojom::MediaControllerManagerInterceptorForTesting {
32 public:
33 FakeMediaControllerManager() = default;
34
35 mojo::PendingRemote<media_session::mojom::MediaControllerManager>
MakeRemote()36 MakeRemote() {
37 mojo::PendingRemote<media_session::mojom::MediaControllerManager> remote;
38 receivers_.Add(this, remote.InitWithNewPipeAndPassReceiver());
39 return remote;
40 }
41
42 // media_session::mojom::MediaControllerManagerInterceptorForTesting:
GetForwardingInterface()43 media_session::mojom::MediaControllerManager* GetForwardingInterface()
44 override {
45 NOTREACHED();
46 return nullptr;
47 }
48
CreateActiveMediaController(mojo::PendingReceiver<media_session::mojom::MediaController> receiver)49 void CreateActiveMediaController(
50 mojo::PendingReceiver<media_session::mojom::MediaController> receiver)
51 override {}
52
53 MOCK_METHOD0(SuspendAllSessions, void());
54
55 private:
56 mojo::ReceiverSet<media_session::mojom::MediaControllerManager> receivers_;
57 };
58
59 constexpr uint64_t kMicJackId = 10010;
60 constexpr uint64_t kInternalMicId = 10003;
61 constexpr uint64_t kFrontMicId = 10012;
62 constexpr uint64_t kRearMicId = 10013;
63
64 struct AudioNodeInfo {
65 bool is_input;
66 uint64_t id;
67 const char* const device_name;
68 const char* const type;
69 const char* const name;
70 };
71
72 const uint32_t kInputMaxSupportedChannels = 1;
73 const uint32_t kOutputMaxSupportedChannels = 2;
74
75 const AudioNodeInfo kMicJack[] = {
76 {true, kMicJackId, "Fake Mic Jack", "MIC", "Mic Jack"}};
77
78 const AudioNodeInfo kInternalMic[] = {
79 {true, kInternalMicId, "Fake Mic", "INTERNAL_MIC", "Internal Mic"}};
80
81 const AudioNodeInfo kFrontMic[] = {
82 {true, kFrontMicId, "Fake Front Mic", "FRONT_MIC", "Front Mic"}};
83
84 const AudioNodeInfo kRearMic[] = {
85 {true, kRearMicId, "Fake Rear Mic", "REAR_MIC", "Rear Mic"}};
86
GenerateAudioNode(const AudioNodeInfo * node_info)87 AudioNode GenerateAudioNode(const AudioNodeInfo* node_info) {
88 uint64_t stable_device_id_v2 = 0;
89 uint64_t stable_device_id_v1 = node_info->id;
90 return AudioNode(node_info->is_input, node_info->id, false,
91 stable_device_id_v1, stable_device_id_v2,
92 node_info->device_name, node_info->type, node_info->name,
93 false /* is_active*/, 0 /* pluged_time */,
94 node_info->is_input ? kInputMaxSupportedChannels
95 : kOutputMaxSupportedChannels);
96 }
97
GenerateAudioNodeList(const std::vector<const AudioNodeInfo * > & nodes)98 AudioNodeList GenerateAudioNodeList(
99 const std::vector<const AudioNodeInfo*>& nodes) {
100 AudioNodeList node_list;
101 for (auto* node_info : nodes) {
102 node_list.push_back(GenerateAudioNode(node_info));
103 }
104 return node_list;
105 }
106
107 } // namespace
108
109 // Test param is the version of stabel device id used by audio node.
110 class UnifiedAudioDetailedViewControllerTest : public AshTestBase {
111 public:
UnifiedAudioDetailedViewControllerTest()112 UnifiedAudioDetailedViewControllerTest() {}
113 ~UnifiedAudioDetailedViewControllerTest() override = default;
114
115 // AshTestBase:
SetUp()116 void SetUp() override {
117 AshTestBase::SetUp();
118
119 scoped_feature_list_.InitAndEnableFeature(
120 features::kSystemTrayMicGainSetting);
121
122 fake_manager_ = std::make_unique<FakeMediaControllerManager>();
123 tray_model_ = std::make_unique<UnifiedSystemTrayModel>(nullptr);
124 tray_controller_ =
125 std::make_unique<UnifiedSystemTrayController>(tray_model_.get());
126 audio_detailed_view_controller_ =
127 std::make_unique<UnifiedAudioDetailedViewController>(
128 tray_controller_.get());
129
130 map_device_sliders_callback_ = base::BindRepeating(
131 &UnifiedAudioDetailedViewControllerTest::AddViewToSliderDeviceMap,
132 base::Unretained(this));
133 MicGainSliderController::SetMapDeviceSliderCallbackForTest(
134 &map_device_sliders_callback_);
135 }
136
TearDown()137 void TearDown() override {
138 MicGainSliderController::SetMapDeviceSliderCallbackForTest(nullptr);
139 audio_pref_handler_ = nullptr;
140
141 audio_detailed_view_controller_.reset();
142 tray_controller_.reset();
143 tray_model_.reset();
144
145 fake_manager_.reset();
146 AshTestBase::TearDown();
147 }
148
SetUpCrasAudioHandler(const AudioNodeList & audio_nodes)149 void SetUpCrasAudioHandler(const AudioNodeList& audio_nodes) {
150 // Shutdown previous instance in case there is one.
151 if (CrasAudioHandler::Get())
152 CrasAudioHandler::Shutdown();
153
154 fake_cras_audio_client()->SetAudioNodesForTesting(audio_nodes);
155 audio_pref_handler_ = new chromeos::AudioDevicesPrefHandlerStub();
156 CrasAudioHandler::Initialize(fake_manager_->MakeRemote(),
157 audio_pref_handler_);
158 cras_audio_handler_ = CrasAudioHandler::Get();
159 }
160
AddViewToSliderDeviceMap(uint64_t device_id,views::View * view)161 void AddViewToSliderDeviceMap(uint64_t device_id, views::View* view) {
162 sliders_map_[device_id] = view;
163 }
164
165 protected:
fake_cras_audio_client()166 chromeos::FakeCrasAudioClient* fake_cras_audio_client() {
167 return chromeos::FakeCrasAudioClient::Get();
168 }
169
170 std::map<uint64_t, views::View*> sliders_map_;
171 MicGainSliderController::MapDeviceSliderCallback map_device_sliders_callback_;
172 CrasAudioHandler* cras_audio_handler_ = nullptr; // Not owned.
173 scoped_refptr<chromeos::AudioDevicesPrefHandlerStub> audio_pref_handler_;
174 std::unique_ptr<FakeMediaControllerManager> fake_manager_;
175 std::unique_ptr<UnifiedAudioDetailedViewController>
176 audio_detailed_view_controller_;
177 std::unique_ptr<UnifiedSystemTrayModel> tray_model_;
178 std::unique_ptr<UnifiedSystemTrayController> tray_controller_;
179 base::test::ScopedFeatureList scoped_feature_list_;
180 };
181
TEST_F(UnifiedAudioDetailedViewControllerTest,OnlyOneVisibleSlider)182 TEST_F(UnifiedAudioDetailedViewControllerTest, OnlyOneVisibleSlider) {
183 SetUpCrasAudioHandler(GenerateAudioNodeList({kInternalMic, kMicJack}));
184 audio_detailed_view_controller_->CreateView();
185
186 // Only slider corresponding to the Internal Mic should be visible initially.
187 cras_audio_handler_->SwitchToDevice(
188 AudioDevice(GenerateAudioNode(kInternalMic)), true,
189 CrasAudioHandler::ACTIVATE_BY_USER);
190 EXPECT_EQ(kInternalMicId, cras_audio_handler_->GetPrimaryActiveInputNode());
191 EXPECT_TRUE(sliders_map_.find(kInternalMicId)->second->GetVisible());
192
193 EXPECT_FALSE(sliders_map_.find(kMicJackId)->second->GetVisible());
194
195 // Switching to Mic Jack should flip the visibility of the sliders.
196 cras_audio_handler_->SwitchToDevice(AudioDevice(GenerateAudioNode(kMicJack)),
197 true, CrasAudioHandler::ACTIVATE_BY_USER);
198 EXPECT_EQ(kMicJackId, cras_audio_handler_->GetPrimaryActiveInputNode());
199 EXPECT_TRUE(sliders_map_.find(kMicJackId)->second->GetVisible());
200
201 EXPECT_FALSE(sliders_map_.find(kInternalMicId)->second->GetVisible());
202 }
203
TEST_F(UnifiedAudioDetailedViewControllerTest,DualInternalMicHasSingleVisibleSlider)204 TEST_F(UnifiedAudioDetailedViewControllerTest,
205 DualInternalMicHasSingleVisibleSlider) {
206 SetUpCrasAudioHandler(GenerateAudioNodeList({kFrontMic, kRearMic}));
207
208 // Verify the device has dual internal mics.
209 EXPECT_TRUE(cras_audio_handler_->HasDualInternalMic());
210
211 audio_detailed_view_controller_->CreateView();
212
213 // Verify there is only 1 slider in the view.
214 EXPECT_EQ(sliders_map_.size(), 1u);
215
216 // Verify the slider is visible.
217 EXPECT_TRUE(sliders_map_.begin()->second->GetVisible());
218 }
219
220 } // namespace ash
221