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