audio-settings: add SetOutputMuted to CrosAudioConfig

- Remove "SetOutputMuted" from FakeCrosAudioConfig interface.
- Add SetOutputMuted to mojom.
- Add check for muted-by-policy before setting output mute state.
- Test output mute state updates when not muted by policy.

Note: UI side was completed with mock in 4077405 and 4076863.

Bug: b:260277007
Test: chromeos_unitttests --gtest_filter=*CrosAudioConfigImpl*
Change-Id: I57e848422f691c2174a7dd29e30c37e9be3df17d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4110038
Reviewed-by: Michael Checo <michaelcheco@google.com>
Reviewed-by: Daniel Cheng <dcheng@chromium.org>
Commit-Queue: Ashley Prasad <ashleydp@google.com>
Cr-Commit-Position: refs/heads/main@{#1089302}
diff --git a/chrome/browser/resources/settings/chromeos/device_page/fake_cros_audio_config.ts b/chrome/browser/resources/settings/chromeos/device_page/fake_cros_audio_config.ts
index 8ee227d..cdcdd73 100644
--- a/chrome/browser/resources/settings/chromeos/device_page/fake_cros_audio_config.ts
+++ b/chrome/browser/resources/settings/chromeos/device_page/fake_cros_audio_config.ts
@@ -106,7 +106,6 @@
 }
 
 export interface FakeCrosAudioConfigInterface extends CrosAudioConfigInterface {
-  setOutputMuted(muted: boolean): void;
   setInputMuted(muted: boolean): void;
   setInputVolumePercent(percent: number): void;
   setNoiseCancellationEnabled(enabled: boolean): void;
diff --git a/chromeos/ash/components/audio/cros_audio_config_impl.cc b/chromeos/ash/components/audio/cros_audio_config_impl.cc
index 5ca5457e..de270dad 100644
--- a/chromeos/ash/components/audio/cros_audio_config_impl.cc
+++ b/chromeos/ash/components/audio/cros_audio_config_impl.cc
@@ -115,6 +115,15 @@
   return mojom::MuteState::kNotMuted;
 }
 
+void CrosAudioConfigImpl::SetOutputMuted(bool muted) {
+  CrasAudioHandler* audio_handler = CrasAudioHandler::Get();
+  if (audio_handler->IsOutputMutedByPolicy()) {
+    return;
+  }
+
+  audio_handler->SetOutputMute(muted);
+}
+
 void CrosAudioConfigImpl::SetOutputVolumePercent(int8_t volume) {
   CrasAudioHandler* audio_handler = CrasAudioHandler::Get();
   audio_handler->SetOutputVolumePercent(volume);
diff --git a/chromeos/ash/components/audio/cros_audio_config_impl.h b/chromeos/ash/components/audio/cros_audio_config_impl.h
index c3763e2..a5e0563 100644
--- a/chromeos/ash/components/audio/cros_audio_config_impl.h
+++ b/chromeos/ash/components/audio/cros_audio_config_impl.h
@@ -26,6 +26,7 @@
       std::vector<mojom::AudioDevicePtr>* output_devices_out,
       std::vector<mojom::AudioDevicePtr>* input_devices_out) const override;
   mojom::MuteState GetInputMuteState() const override;
+  void SetOutputMuted(bool muted) override;
   void SetOutputVolumePercent(int8_t volume) override;
   void SetActiveDevice(uint64_t device_id) override;
 
diff --git a/chromeos/ash/components/audio/cros_audio_config_impl_unittest.cc b/chromeos/ash/components/audio/cros_audio_config_impl_unittest.cc
index a68c2479..bddb449d 100644
--- a/chromeos/ash/components/audio/cros_audio_config_impl_unittest.cc
+++ b/chromeos/ash/components/audio/cros_audio_config_impl_unittest.cc
@@ -116,11 +116,20 @@
     return fake_observer;
   }
 
+  bool GetDeviceMuted(const uint64_t id) {
+    return cras_audio_handler_->IsOutputMutedForDevice(id);
+  }
+
   void SimulateSetActiveDevice(const uint64_t& device_id) {
     remote_->SetActiveDevice(device_id);
     base::RunLoop().RunUntilIdle();
   }
 
+  void SimulateSetOutputMuted(bool muted) {
+    remote_->SetOutputMuted(muted);
+    base::RunLoop().RunUntilIdle();
+  }
+
   void SetOutputVolumePercent(uint8_t volume_percent) {
     remote_->SetOutputVolumePercent(volume_percent);
     base::RunLoop().RunUntilIdle();
@@ -336,7 +345,7 @@
       fake_observer->last_audio_system_properties_.value()->output_mute_state);
 }
 
-TEST_F(CrosAudioConfigImplTest, GetOutputMuteStateMutedByPolicy) {
+TEST_F(CrosAudioConfigImplTest, HandleOutputMuteStateMutedByPolicy) {
   SetOutputMuteState(mojom::MuteState::kMutedByPolicy);
   std::unique_ptr<FakeAudioSystemPropertiesObserver> fake_observer = Observe();
   ASSERT_EQ(1u, fake_observer->num_properties_updated_calls_);
@@ -344,6 +353,15 @@
   EXPECT_EQ(
       mojom::MuteState::kMutedByPolicy,
       fake_observer->last_audio_system_properties_.value()->output_mute_state);
+
+  // Simulate attempting to change mute state while policy is enabled.
+  SimulateSetOutputMuted(/*muted=*/true);
+  SimulateSetOutputMuted(/*muted=*/false);
+  ASSERT_EQ(1u, fake_observer->num_properties_updated_calls_);
+  ASSERT_TRUE(fake_observer->last_audio_system_properties_.has_value());
+  EXPECT_EQ(
+      mojom::MuteState::kMutedByPolicy,
+      fake_observer->last_audio_system_properties_.value()->output_mute_state);
 }
 
 TEST_F(CrosAudioConfigImplTest, GetOutputAudioDevices) {
@@ -576,4 +594,31 @@
       fake_observer->last_audio_system_properties_.value()->input_mute_state);
 }
 
+TEST_F(CrosAudioConfigImplTest, SetOutputMuted) {
+  std::unique_ptr<FakeAudioSystemPropertiesObserver> fake_observer = Observe();
+
+  // Test default audio node list.
+  SetAudioNodes({kInternalSpeaker, kHDMIOutput});
+  SetActiveOutputNodes({kInternalSpeakerId});
+  EXPECT_EQ(
+      mojom::MuteState::kNotMuted,
+      fake_observer->last_audio_system_properties_.value()->output_mute_state);
+  EXPECT_FALSE(GetDeviceMuted(kInternalSpeakerId));
+  EXPECT_FALSE(GetDeviceMuted(kHDMIOutputId));
+
+  SimulateSetOutputMuted(/*muted=*/true);
+  EXPECT_EQ(
+      mojom::MuteState::kMutedByUser,
+      fake_observer->last_audio_system_properties_.value()->output_mute_state);
+  EXPECT_TRUE(GetDeviceMuted(kInternalSpeakerId));
+  EXPECT_FALSE(GetDeviceMuted(kHDMIOutputId));
+
+  SimulateSetOutputMuted(/*muted=*/false);
+  EXPECT_EQ(
+      mojom::MuteState::kNotMuted,
+      fake_observer->last_audio_system_properties_.value()->output_mute_state);
+  EXPECT_FALSE(GetDeviceMuted(kInternalSpeakerId));
+  EXPECT_FALSE(GetDeviceMuted(kHDMIOutputId));
+}
+
 }  // namespace ash::audio_config
diff --git a/chromeos/ash/components/audio/public/mojom/cros_audio_config.mojom b/chromeos/ash/components/audio/public/mojom/cros_audio_config.mojom
index 7b3c303..060fe70 100644
--- a/chromeos/ash/components/audio/public/mojom/cros_audio_config.mojom
+++ b/chromeos/ash/components/audio/public/mojom/cros_audio_config.mojom
@@ -98,6 +98,10 @@
   ObserveAudioSystemProperties(
     pending_remote<AudioSystemPropertiesObserver> observer);
 
+  // Sets the mute state of the active output device to |muted|. Ignored if
+  // device is muted by policy.
+  SetOutputMuted(bool muted);
+
   // Sets the volume of the active output device. If |volume| is above a
   // threshold and the device is muted, it's unmuted.
   SetOutputVolumePercent(int8 volume);