AHAL: support volume control for USB audio HAL.

Use mixer control to support master mute, master volume and hardware
volume for USB audio HAL.

Bug: 266216550
Test: atest VtsHalAudioCoreTargetTest
Change-Id: Iad544ba517cbfc778ebdf96dd161944886383b73
diff --git a/audio/aidl/android/hardware/audio/core/IModule.aidl b/audio/aidl/android/hardware/audio/core/IModule.aidl
index edfb9f2..ce0ac23 100644
--- a/audio/aidl/android/hardware/audio/core/IModule.aidl
+++ b/audio/aidl/android/hardware/audio/core/IModule.aidl
@@ -596,6 +596,7 @@
      * @param mute Whether the output from the module is muted.
      * @throws EX_UNSUPPORTED_OPERATION If muting of combined output
      *                                  is not supported by the module.
+     * @throws EX_ILLEGAL_STATE If any error happens while muting of combined output.
      */
     void setMasterMute(boolean mute);
 
@@ -627,6 +628,8 @@
      *                             accepted range.
      * @throws EX_UNSUPPORTED_OPERATION If attenuation of combined output
      *                                  is not supported by the module.
+     * @throws EX_ILLEGAL_STATE If any error happens while updating attenuation of
+                                combined output.
      */
     void setMasterVolume(float volume);
 
diff --git a/audio/aidl/android/hardware/audio/core/IStreamOut.aidl b/audio/aidl/android/hardware/audio/core/IStreamOut.aidl
index 0e58add..825553d 100644
--- a/audio/aidl/android/hardware/audio/core/IStreamOut.aidl
+++ b/audio/aidl/android/hardware/audio/core/IStreamOut.aidl
@@ -85,7 +85,8 @@
      * @throws EX_ILLEGAL_ARGUMENT If the number of elements in the provided
      *                             array does not match the channel count, or
      *                             attenuation values are out of range.
-     * @throws EX_ILLEGAL_STATE If the stream is closed.
+     * @throws EX_ILLEGAL_STATE If the stream is closed or there is any error happens
+                                when applying hardware volume.
      * @throws EX_UNSUPPORTED_OPERATION If hardware volume control is not supported.
      */
     void setHwVolume(in float[] channelVolumes);
diff --git a/audio/aidl/default/Android.bp b/audio/aidl/default/Android.bp
index 21616be..93362c6 100644
--- a/audio/aidl/default/Android.bp
+++ b/audio/aidl/default/Android.bp
@@ -75,6 +75,7 @@
         "Telephony.cpp",
         "usb/ModuleUsb.cpp",
         "usb/StreamUsb.cpp",
+        "usb/UsbAlsaMixerControl.cpp",
         "usb/UsbAlsaUtils.cpp",
     ],
     generated_sources: [
diff --git a/audio/aidl/default/Module.cpp b/audio/aidl/default/Module.cpp
index 5440b8d..b546597 100644
--- a/audio/aidl/default/Module.cpp
+++ b/audio/aidl/default/Module.cpp
@@ -457,6 +457,7 @@
         connectedPort.profiles = connectedProfilesIt->second;
     }
     ports.push_back(connectedPort);
+    onExternalDeviceConnectionChanged(connectedPort, true /*connected*/);
     *_aidl_return = std::move(connectedPort);
 
     std::vector<AudioRoute> newRoutes;
@@ -510,6 +511,7 @@
                    << configIt->id;
         return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
     }
+    onExternalDeviceConnectionChanged(*portIt, false /*connected*/);
     ports.erase(portIt);
     mConnectedDevicePorts.erase(in_portId);
     LOG(DEBUG) << __func__ << ": connected device port " << in_portId << " released";
@@ -980,8 +982,17 @@
 
 ndk::ScopedAStatus Module::setMasterMute(bool in_mute) {
     LOG(DEBUG) << __func__ << ": " << in_mute;
-    mMasterMute = in_mute;
-    return ndk::ScopedAStatus::ok();
+    auto result = mDebug.simulateDeviceConnections ? ndk::ScopedAStatus::ok()
+                                                   : onMasterMuteChanged(in_mute);
+    if (result.isOk()) {
+        mMasterMute = in_mute;
+    } else {
+        LOG(ERROR) << __func__ << ": failed calling onMasterMuteChanged(" << in_mute
+                   << "), error=" << result;
+        // Reset master mute if it failed.
+        onMasterMuteChanged(mMasterMute);
+    }
+    return std::move(result);
 }
 
 ndk::ScopedAStatus Module::getMasterVolume(float* _aidl_return) {
@@ -993,8 +1004,17 @@
 ndk::ScopedAStatus Module::setMasterVolume(float in_volume) {
     LOG(DEBUG) << __func__ << ": " << in_volume;
     if (in_volume >= 0.0f && in_volume <= 1.0f) {
-        mMasterVolume = in_volume;
-        return ndk::ScopedAStatus::ok();
+        auto result = mDebug.simulateDeviceConnections ? ndk::ScopedAStatus::ok()
+                                                       : onMasterVolumeChanged(in_volume);
+        if (result.isOk()) {
+            mMasterVolume = in_volume;
+        } else {
+            // Reset master volume if it failed.
+            LOG(ERROR) << __func__ << ": failed calling onMasterVolumeChanged(" << in_volume
+                       << "), error=" << result;
+            onMasterVolumeChanged(mMasterVolume);
+        }
+        return std::move(result);
     }
     LOG(ERROR) << __func__ << ": invalid master volume value: " << in_volume;
     return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
@@ -1262,4 +1282,20 @@
     return ndk::ScopedAStatus::ok();
 }
 
+void Module::onExternalDeviceConnectionChanged(
+        const ::aidl::android::media::audio::common::AudioPort& audioPort __unused,
+        bool connected __unused) {
+    LOG(DEBUG) << __func__ << ": do nothing and return";
+}
+
+ndk::ScopedAStatus Module::onMasterMuteChanged(bool mute __unused) {
+    LOG(VERBOSE) << __func__ << ": do nothing and return ok";
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Module::onMasterVolumeChanged(float volume __unused) {
+    LOG(VERBOSE) << __func__ << ": do nothing and return ok";
+    return ndk::ScopedAStatus::ok();
+}
+
 }  // namespace aidl::android::hardware::audio::core
diff --git a/audio/aidl/default/include/core-impl/Module.h b/audio/aidl/default/include/core-impl/Module.h
index 8365b34..a5b3182 100644
--- a/audio/aidl/default/include/core-impl/Module.h
+++ b/audio/aidl/default/include/core-impl/Module.h
@@ -163,8 +163,6 @@
     // Maps port ids and port config ids to patch ids.
     // Multimap because both ports and configs can be used by multiple patches.
     std::multimap<int32_t, int32_t> mPatches;
-    bool mMasterMute = false;
-    float mMasterVolume = 1.0f;
     bool mMicMute = false;
     std::shared_ptr<sounddose::ISoundDose> mSoundDose;
     ndk::SpAIBinder mSoundDoseBinder;
@@ -180,6 +178,13 @@
     virtual ndk::ScopedAStatus checkAudioPatchEndpointsMatch(
             const std::vector<::aidl::android::media::audio::common::AudioPortConfig*>& sources,
             const std::vector<::aidl::android::media::audio::common::AudioPortConfig*>& sinks);
+    virtual void onExternalDeviceConnectionChanged(
+            const ::aidl::android::media::audio::common::AudioPort& audioPort, bool connected);
+    virtual ndk::ScopedAStatus onMasterMuteChanged(bool mute);
+    virtual ndk::ScopedAStatus onMasterVolumeChanged(float volume);
+
+    bool mMasterMute = false;
+    float mMasterVolume = 1.0f;
 };
 
 }  // namespace aidl::android::hardware::audio::core
diff --git a/audio/aidl/default/include/core-impl/ModuleUsb.h b/audio/aidl/default/include/core-impl/ModuleUsb.h
index 7b177e8..1aa2244 100644
--- a/audio/aidl/default/include/core-impl/ModuleUsb.h
+++ b/audio/aidl/default/include/core-impl/ModuleUsb.h
@@ -28,10 +28,6 @@
     // IModule interfaces
     ndk::ScopedAStatus getTelephony(std::shared_ptr<ITelephony>* _aidl_return) override;
     ndk::ScopedAStatus getBluetooth(std::shared_ptr<IBluetooth>* _aidl_return) override;
-    ndk::ScopedAStatus getMasterMute(bool* _aidl_return) override;
-    ndk::ScopedAStatus setMasterMute(bool in_mute) override;
-    ndk::ScopedAStatus getMasterVolume(float* _aidl_return) override;
-    ndk::ScopedAStatus setMasterVolume(float in_volume) override;
     ndk::ScopedAStatus getMicMute(bool* _aidl_return) override;
     ndk::ScopedAStatus setMicMute(bool in_mute) override;
 
@@ -42,6 +38,11 @@
             const std::vector<::aidl::android::media::audio::common::AudioPortConfig*>& sources,
             const std::vector<::aidl::android::media::audio::common::AudioPortConfig*>& sinks)
             override;
+    void onExternalDeviceConnectionChanged(
+            const ::aidl::android::media::audio::common::AudioPort& audioPort,
+            bool connected) override;
+    ndk::ScopedAStatus onMasterMuteChanged(bool mute) override;
+    ndk::ScopedAStatus onMasterVolumeChanged(float volume) override;
 };
 
 }  // namespace aidl::android::hardware::audio::core
diff --git a/audio/aidl/default/include/core-impl/StreamUsb.h b/audio/aidl/default/include/core-impl/StreamUsb.h
index c04dc66..f1815dd 100644
--- a/audio/aidl/default/include/core-impl/StreamUsb.h
+++ b/audio/aidl/default/include/core-impl/StreamUsb.h
@@ -17,6 +17,7 @@
 #pragma once
 
 #include <mutex>
+#include <vector>
 
 #include <aidl/android/media/audio/common/AudioChannelLayout.h>
 
@@ -93,6 +94,12 @@
                  StreamContext&& context,
                  const std::optional<::aidl::android::media::audio::common::AudioOffloadInfo>&
                          offloadInfo);
+
+    ndk::ScopedAStatus getHwVolume(std::vector<float>* _aidl_return) override;
+    ndk::ScopedAStatus setHwVolume(const std::vector<float>& in_channelVolumes) override;
+
+    int mChannelCount;
+    std::vector<float> mHwVolumes;
 };
 
 }  // namespace aidl::android::hardware::audio::core
diff --git a/audio/aidl/default/usb/ModuleUsb.cpp b/audio/aidl/default/usb/ModuleUsb.cpp
index e803420..511ba74 100644
--- a/audio/aidl/default/usb/ModuleUsb.cpp
+++ b/audio/aidl/default/usb/ModuleUsb.cpp
@@ -22,6 +22,7 @@
 #include <android-base/logging.h>
 #include <tinyalsa/asoundlib.h>
 
+#include "UsbAlsaMixerControl.h"
 #include "UsbAlsaUtils.h"
 #include "core-impl/ModuleUsb.h"
 
@@ -86,26 +87,6 @@
     return ndk::ScopedAStatus::ok();
 }
 
-ndk::ScopedAStatus ModuleUsb::getMasterMute(bool* _aidl_return __unused) {
-    LOG(DEBUG) << __func__ << ": is not supported";
-    return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
-}
-
-ndk::ScopedAStatus ModuleUsb::setMasterMute(bool in_mute __unused) {
-    LOG(DEBUG) << __func__ << ": is not supported";
-    return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
-}
-
-ndk::ScopedAStatus ModuleUsb::getMasterVolume(float* _aidl_return __unused) {
-    LOG(DEBUG) << __func__ << ": is not supported";
-    return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
-}
-
-ndk::ScopedAStatus ModuleUsb::setMasterVolume(float in_volume __unused) {
-    LOG(DEBUG) << __func__ << ": is not supported";
-    return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
-}
-
 ndk::ScopedAStatus ModuleUsb::getMicMute(bool* _aidl_return __unused) {
     LOG(DEBUG) << __func__ << ": is not supported";
     return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
@@ -180,4 +161,26 @@
     return ndk::ScopedAStatus::ok();
 }
 
+void ModuleUsb::onExternalDeviceConnectionChanged(
+        const ::aidl::android::media::audio::common::AudioPort& audioPort, bool connected) {
+    if (audioPort.ext.getTag() != AudioPortExt::Tag::device) {
+        return;
+    }
+    const auto& address = audioPort.ext.get<AudioPortExt::Tag::device>().device.address;
+    if (address.getTag() != AudioDeviceAddress::alsa) {
+        return;
+    }
+    const int card = address.get<AudioDeviceAddress::alsa>()[0];
+    usb::UsbAlsaMixerControl::getInstance().setDeviceConnectionState(card, mMasterMute,
+                                                                     mMasterVolume, connected);
+}
+
+ndk::ScopedAStatus ModuleUsb::onMasterMuteChanged(bool mute) {
+    return usb::UsbAlsaMixerControl::getInstance().setMasterMute(mute);
+}
+
+ndk::ScopedAStatus ModuleUsb::onMasterVolumeChanged(float volume) {
+    return usb::UsbAlsaMixerControl::getInstance().setMasterVolume(volume);
+}
+
 }  // namespace aidl::android::hardware::audio::core
diff --git a/audio/aidl/default/usb/StreamUsb.cpp b/audio/aidl/default/usb/StreamUsb.cpp
index bd53a0e..d6f757c 100644
--- a/audio/aidl/default/usb/StreamUsb.cpp
+++ b/audio/aidl/default/usb/StreamUsb.cpp
@@ -17,6 +17,9 @@
 #define LOG_TAG "AHAL_StreamUsb"
 #include <android-base/logging.h>
 
+#include <Utils.h>
+
+#include "UsbAlsaMixerControl.h"
 #include "UsbAlsaUtils.h"
 #include "core-impl/Module.h"
 #include "core-impl/StreamUsb.h"
@@ -30,8 +33,12 @@
 using aidl::android::media::audio::common::AudioDevice;
 using aidl::android::media::audio::common::AudioDeviceAddress;
 using aidl::android::media::audio::common::AudioOffloadInfo;
+using aidl::android::media::audio::common::AudioPortExt;
 using aidl::android::media::audio::common::MicrophoneDynamicInfo;
 using aidl::android::media::audio::common::MicrophoneInfo;
+using android::OK;
+using android::status_t;
+using android::hardware::audio::common::getChannelCount;
 
 namespace aidl::android::hardware::audio::core {
 
@@ -239,6 +246,31 @@
                   // The default worker implementation is used.
                   return new StreamOutWorker(ctx, driver);
               },
-              offloadInfo) {}
+              offloadInfo) {
+    mChannelCount = getChannelCount(mContext.getChannelLayout());
+}
+
+ndk::ScopedAStatus StreamOutUsb::getHwVolume(std::vector<float>* _aidl_return) {
+    *_aidl_return = mHwVolumes;
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus StreamOutUsb::setHwVolume(const std::vector<float>& in_channelVolumes) {
+    for (const auto& device : mConnectedDevices) {
+        if (device.address.getTag() != AudioDeviceAddress::alsa) {
+            LOG(DEBUG) << __func__ << ": skip as the device address is not alsa";
+            continue;
+        }
+        const int card = device.address.get<AudioDeviceAddress::alsa>()[0];
+        if (auto result =
+                    usb::UsbAlsaMixerControl::getInstance().setVolumes(card, in_channelVolumes);
+            !result.isOk()) {
+            LOG(ERROR) << __func__ << ": failed to set volume for device, card=" << card;
+            return result;
+        }
+    }
+    mHwVolumes = in_channelVolumes;
+    return ndk::ScopedAStatus::ok();
+}
 
 }  // namespace aidl::android::hardware::audio::core
diff --git a/audio/aidl/default/usb/UsbAlsaMixerControl.cpp b/audio/aidl/default/usb/UsbAlsaMixerControl.cpp
new file mode 100644
index 0000000..b5337d1
--- /dev/null
+++ b/audio/aidl/default/usb/UsbAlsaMixerControl.cpp
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "AHAL_UsbAlsaMixerControl"
+#include <android-base/logging.h>
+
+#include <cmath>
+#include <string>
+#include <vector>
+
+#include <android/binder_status.h>
+
+#include "UsbAlsaMixerControl.h"
+
+namespace aidl::android::hardware::audio::core::usb {
+
+//-----------------------------------------------------------------------------
+
+MixerControl::MixerControl(struct mixer_ctl* ctl)
+    : mCtl(ctl),
+      mNumValues(mixer_ctl_get_num_values(ctl)),
+      mMinValue(mixer_ctl_get_range_min(ctl)),
+      mMaxValue(mixer_ctl_get_range_max(ctl)) {}
+
+unsigned int MixerControl::getNumValues() const {
+    return mNumValues;
+}
+
+int MixerControl::getMaxValue() const {
+    return mMaxValue;
+}
+
+int MixerControl::getMinValue() const {
+    return mMinValue;
+}
+
+int MixerControl::setArray(const void* array, size_t count) {
+    const std::lock_guard guard(mLock);
+    return mixer_ctl_set_array(mCtl, array, count);
+}
+
+//-----------------------------------------------------------------------------
+
+// static
+const std::map<AlsaMixer::Control, std::vector<AlsaMixer::ControlNamesAndExpectedCtlType>>
+        AlsaMixer::kPossibleControls = {
+                {AlsaMixer::MASTER_SWITCH, {{"Master Playback Switch", MIXER_CTL_TYPE_BOOL}}},
+                {AlsaMixer::MASTER_VOLUME, {{"Master Playback Volume", MIXER_CTL_TYPE_INT}}},
+                {AlsaMixer::HW_VOLUME,
+                 {{"Headphone Playback Volume", MIXER_CTL_TYPE_INT},
+                  {"Headset Playback Volume", MIXER_CTL_TYPE_INT},
+                  {"PCM Playback Volume", MIXER_CTL_TYPE_INT}}}};
+
+// static
+std::map<AlsaMixer::Control, std::shared_ptr<MixerControl>> AlsaMixer::initializeMixerControls(
+        struct mixer* mixer) {
+    std::map<AlsaMixer::Control, std::shared_ptr<MixerControl>> mixerControls;
+    std::string mixerCtlNames;
+    for (const auto& [control, possibleCtls] : kPossibleControls) {
+        for (const auto& [ctlName, expectedCtlType] : possibleCtls) {
+            struct mixer_ctl* ctl = mixer_get_ctl_by_name(mixer, ctlName.c_str());
+            if (ctl != nullptr && mixer_ctl_get_type(ctl) == expectedCtlType) {
+                mixerControls.emplace(control, std::make_unique<MixerControl>(ctl));
+                if (!mixerCtlNames.empty()) {
+                    mixerCtlNames += ",";
+                }
+                mixerCtlNames += ctlName;
+                break;
+            }
+        }
+    }
+    LOG(DEBUG) << __func__ << ": available mixer control names=[" << mixerCtlNames << "]";
+    return mixerControls;
+}
+
+AlsaMixer::AlsaMixer(struct mixer* mixer)
+    : mMixer(mixer), mMixerControls(initializeMixerControls(mMixer)) {}
+
+AlsaMixer::~AlsaMixer() {
+    mixer_close(mMixer);
+}
+
+namespace {
+
+int volumeFloatToInteger(float fValue, int maxValue, int minValue) {
+    return minValue + std::ceil((maxValue - minValue) * fValue);
+}
+
+float volumeIntegerToFloat(int iValue, int maxValue, int minValue) {
+    if (iValue > maxValue) {
+        return 1.0f;
+    }
+    if (iValue < minValue) {
+        return 0.0f;
+    }
+    return static_cast<float>(iValue - minValue) / (maxValue - minValue);
+}
+
+}  // namespace
+
+ndk::ScopedAStatus AlsaMixer::setMasterMute(bool muted) {
+    auto it = mMixerControls.find(AlsaMixer::MASTER_SWITCH);
+    if (it == mMixerControls.end()) {
+        return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+    }
+    const int numValues = it->second->getNumValues();
+    std::vector<int> values(numValues, muted ? 0 : 1);
+    if (int err = it->second->setArray(values.data(), numValues); err != 0) {
+        LOG(ERROR) << __func__ << ": failed to set master mute, err=" << err;
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+    }
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus AlsaMixer::setMasterVolume(float volume) {
+    auto it = mMixerControls.find(AlsaMixer::MASTER_VOLUME);
+    if (it == mMixerControls.end()) {
+        return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+    }
+    const int numValues = it->second->getNumValues();
+    std::vector<int> values(numValues, volumeFloatToInteger(volume, it->second->getMaxValue(),
+                                                            it->second->getMinValue()));
+    if (int err = it->second->setArray(values.data(), numValues); err != 0) {
+        LOG(ERROR) << __func__ << ": failed to set master volume, err=" << err;
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+    }
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus AlsaMixer::setVolumes(std::vector<float> volumes) {
+    auto it = mMixerControls.find(AlsaMixer::HW_VOLUME);
+    if (it == mMixerControls.end()) {
+        return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+    }
+    const int numValues = it->second->getNumValues();
+    const int maxValue = it->second->getMaxValue();
+    const int minValue = it->second->getMinValue();
+    std::vector<int> values;
+    size_t i = 0;
+    for (; i < numValues && i < values.size(); ++i) {
+        values.emplace_back(volumeFloatToInteger(volumes[i], maxValue, minValue));
+    }
+    if (int err = it->second->setArray(values.data(), values.size()); err != 0) {
+        LOG(ERROR) << __func__ << ": failed to set volume, err=" << err;
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+    }
+    return ndk::ScopedAStatus::ok();
+}
+
+//-----------------------------------------------------------------------------
+
+// static
+UsbAlsaMixerControl& UsbAlsaMixerControl::getInstance() {
+    static UsbAlsaMixerControl gInstance;
+    return gInstance;
+}
+
+void UsbAlsaMixerControl::setDeviceConnectionState(int card, bool masterMuted, float masterVolume,
+                                                   bool connected) {
+    LOG(DEBUG) << __func__ << ": card=" << card << ", connected=" << connected;
+    if (connected) {
+        struct mixer* mixer = mixer_open(card);
+        if (mixer == nullptr) {
+            PLOG(ERROR) << __func__ << ": failed to open mixer for card=" << card;
+            return;
+        }
+        auto alsaMixer = std::make_shared<AlsaMixer>(mixer);
+        alsaMixer->setMasterMute(masterMuted);
+        alsaMixer->setMasterVolume(masterVolume);
+        const std::lock_guard guard(mLock);
+        mMixerControls.emplace(card, alsaMixer);
+    } else {
+        const std::lock_guard guard(mLock);
+        mMixerControls.erase(card);
+    }
+}
+
+ndk::ScopedAStatus UsbAlsaMixerControl::setMasterMute(bool mute) {
+    auto alsaMixers = getAlsaMixers();
+    for (auto it = alsaMixers.begin(); it != alsaMixers.end(); ++it) {
+        if (auto result = it->second->setMasterMute(mute); !result.isOk()) {
+            // Return illegal state if there are multiple devices connected and one of them fails
+            // to set master mute. Otherwise, return the error from calling `setMasterMute`.
+            LOG(ERROR) << __func__ << ": failed to set master mute for card=" << it->first;
+            return alsaMixers.size() > 1 ? ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE)
+                                         : std::move(result);
+        }
+    }
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus UsbAlsaMixerControl::setMasterVolume(float volume) {
+    auto alsaMixers = getAlsaMixers();
+    for (auto it = alsaMixers.begin(); it != alsaMixers.end(); ++it) {
+        if (auto result = it->second->setMasterVolume(volume); !result.isOk()) {
+            // Return illegal state if there are multiple devices connected and one of them fails
+            // to set master volume. Otherwise, return the error from calling `setMasterVolume`.
+            LOG(ERROR) << __func__ << ": failed to set master volume for card=" << it->first;
+            return alsaMixers.size() > 1 ? ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE)
+                                         : std::move(result);
+        }
+    }
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus UsbAlsaMixerControl::setVolumes(int card, std::vector<float> volumes) {
+    auto alsaMixer = getAlsaMixer(card);
+    if (alsaMixer == nullptr) {
+        LOG(ERROR) << __func__ << ": no mixer control found for card=" << card;
+        return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+    }
+    return alsaMixer->setVolumes(volumes);
+}
+
+std::shared_ptr<AlsaMixer> UsbAlsaMixerControl::getAlsaMixer(int card) {
+    const std::lock_guard guard(mLock);
+    const auto it = mMixerControls.find(card);
+    return it == mMixerControls.end() ? nullptr : it->second;
+}
+
+std::map<int, std::shared_ptr<AlsaMixer>> UsbAlsaMixerControl::getAlsaMixers() {
+    const std::lock_guard guard(mLock);
+    return mMixerControls;
+}
+
+}  // namespace aidl::android::hardware::audio::core::usb
diff --git a/audio/aidl/default/usb/UsbAlsaMixerControl.h b/audio/aidl/default/usb/UsbAlsaMixerControl.h
new file mode 100644
index 0000000..cbcddd8
--- /dev/null
+++ b/audio/aidl/default/usb/UsbAlsaMixerControl.h
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <map>
+#include <memory>
+#include <mutex>
+#include <optional>
+#include <string>
+#include <vector>
+
+#include <android-base/thread_annotations.h>
+#include <android/binder_auto_utils.h>
+
+extern "C" {
+#include <tinyalsa/mixer.h>
+}
+
+namespace aidl::android::hardware::audio::core::usb {
+
+class MixerControl {
+  public:
+    explicit MixerControl(struct mixer_ctl* ctl);
+
+    unsigned int getNumValues() const;
+    int getMaxValue() const;
+    int getMinValue() const;
+    int setArray(const void* array, size_t count);
+
+  private:
+    std::mutex mLock;
+    // The mixer_ctl object is owned by ALSA and will be released when the mixer is closed.
+    struct mixer_ctl* mCtl GUARDED_BY(mLock);
+    const unsigned int mNumValues;
+    const int mMinValue;
+    const int mMaxValue;
+};
+
+class AlsaMixer {
+  public:
+    explicit AlsaMixer(struct mixer* mixer);
+
+    ~AlsaMixer();
+
+    bool isValid() const { return mMixer != nullptr; }
+
+    ndk::ScopedAStatus setMasterMute(bool muted);
+    ndk::ScopedAStatus setMasterVolume(float volume);
+    ndk::ScopedAStatus setVolumes(std::vector<float> volumes);
+
+  private:
+    enum Control {
+        MASTER_SWITCH,
+        MASTER_VOLUME,
+        HW_VOLUME,
+    };
+    using ControlNamesAndExpectedCtlType = std::pair<std::string, enum mixer_ctl_type>;
+    static const std::map<Control, std::vector<ControlNamesAndExpectedCtlType>> kPossibleControls;
+    static std::map<Control, std::shared_ptr<MixerControl>> initializeMixerControls(
+            struct mixer* mixer);
+
+    // The mixer object is owned by ALSA and will be released when the mixer is closed.
+    struct mixer* mMixer;
+    // `mMixerControls` will only be initialized in constructor. After that, it wil only be
+    // read but not be modified.
+    const std::map<Control, std::shared_ptr<MixerControl>> mMixerControls;
+};
+
+class UsbAlsaMixerControl {
+  public:
+    static UsbAlsaMixerControl& getInstance();
+
+    void setDeviceConnectionState(int card, bool masterMuted, float masterVolume, bool connected);
+
+    // Master volume settings will be applied to all sound cards, it is only set by the
+    // USB module.
+    ndk::ScopedAStatus setMasterMute(bool muted);
+    ndk::ScopedAStatus setMasterVolume(float volume);
+    // The volume settings can be different on sound cards. It is controlled by streams.
+    ndk::ScopedAStatus setVolumes(int card, std::vector<float> volumes);
+
+  private:
+    std::shared_ptr<AlsaMixer> getAlsaMixer(int card);
+    std::map<int, std::shared_ptr<AlsaMixer>> getAlsaMixers();
+
+    std::mutex mLock;
+    // A map whose key is the card number and value is a shared pointer to corresponding
+    // AlsaMixer object.
+    std::map<int, std::shared_ptr<AlsaMixer>> mMixerControls GUARDED_BY(mLock);
+};
+
+}  // namespace aidl::android::hardware::audio::core::usb