Implement volume control on default audio HAL

Implemented volume control based on setting audio port config.

Bug: 336370745
Test: atest VtsHalAudioCoreTargetTest
(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:122597a5e96c873239540a53523caf67fe806d90)
Merged-In: Ia590974e61aa3a1c3f70afdb54ce87c85e9a1b3c
Change-Id: Ia590974e61aa3a1c3f70afdb54ce87c85e9a1b3c
diff --git a/audio/aidl/default/Module.cpp b/audio/aidl/default/Module.cpp
index c14d06e..45ce5ef 100644
--- a/audio/aidl/default/Module.cpp
+++ b/audio/aidl/default/Module.cpp
@@ -47,6 +47,7 @@
 using aidl::android::media::audio::common::AudioDeviceType;
 using aidl::android::media::audio::common::AudioFormatDescription;
 using aidl::android::media::audio::common::AudioFormatType;
+using aidl::android::media::audio::common::AudioGainConfig;
 using aidl::android::media::audio::common::AudioInputFlags;
 using aidl::android::media::audio::common::AudioIoFlags;
 using aidl::android::media::audio::common::AudioMMapPolicy;
@@ -1200,7 +1201,9 @@
     }
 
     if (in_requested.gain.has_value()) {
-        // Let's pretend that gain can always be applied.
+        if (!setAudioPortConfigGain(*portIt, in_requested.gain.value())) {
+            return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+        }
         out_suggested->gain = in_requested.gain.value();
     }
 
@@ -1242,6 +1245,52 @@
     return ndk::ScopedAStatus::ok();
 }
 
+bool Module::setAudioPortConfigGain(const AudioPort& port, const AudioGainConfig& gainRequested) {
+    auto& ports = getConfig().ports;
+    if (gainRequested.index < 0 || gainRequested.index >= (int)port.gains.size()) {
+        LOG(ERROR) << __func__ << ": gains for port " << port.id << " is undefined";
+        return false;
+    }
+    int stepValue = port.gains[gainRequested.index].stepValue;
+    if (stepValue == 0) {
+        LOG(ERROR) << __func__ << ": port gain step value is 0";
+        return false;
+    }
+    int minValue = port.gains[gainRequested.index].minValue;
+    int maxValue = port.gains[gainRequested.index].maxValue;
+    if (gainRequested.values[0] > maxValue || gainRequested.values[0] < minValue) {
+        LOG(ERROR) << __func__ << ": gain value " << gainRequested.values[0]
+                   << " out of range of min and max gain config";
+        return false;
+    }
+    int gainIndex = (gainRequested.values[0] - minValue) / stepValue;
+    int totalSteps = (maxValue - minValue) / stepValue;
+    if (totalSteps == 0) {
+        LOG(ERROR) << __func__ << ": difference between port gain min value " << minValue
+                   << " and max value " << maxValue << " is less than step value " << stepValue;
+        return false;
+    }
+    // Root-power quantities are used in curve:
+    // 10^((minMb / 100 + (maxMb / 100 - minMb / 100) * gainIndex / totalSteps) / (10 * 2))
+    // where 100 is the conversion from mB to dB, 10 comes from the log 10 conversion from power
+    // ratios, and 2 means are the square of amplitude.
+    float gain =
+            pow(10, (minValue + (maxValue - minValue) * (gainIndex / (float)totalSteps)) / 2000);
+    if (gain < 0) {
+        LOG(ERROR) << __func__ << ": gain " << gain << " is less than 0";
+        return false;
+    }
+    for (const auto& route : getConfig().routes) {
+        if (route.sinkPortId != port.id) {
+            continue;
+        }
+        for (const auto sourcePortId : route.sourcePortIds) {
+            mStreams.setGain(sourcePortId, gain);
+        }
+    }
+    return true;
+}
+
 ndk::ScopedAStatus Module::resetAudioPatch(int32_t in_patchId) {
     auto& patches = getConfig().patches;
     auto patchIt = findById<AudioPatch>(patches, in_patchId);