[settings] Add utility function to round volume levels to 1%

This utility will also be used by the volume policy, which performs some
calculations on volume levels of its own.

Bug: 67148
Test: fx test setui_service_tests
Change-Id: I34dbb682fb4a9a5611bc7f533a4ab97f783e4357
Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/470570
Commit-Queue: William Xiao <wxyz@google.com>
Reviewed-by: Tom Robinson <robinsontom@google.com>
Reviewed-by: Bryce Lee <brycelee@google.com>
diff --git a/garnet/bin/setui/BUILD.gn b/garnet/bin/setui/BUILD.gn
index 41ff74a..07b87d9 100644
--- a/garnet/bin/setui/BUILD.gn
+++ b/garnet/bin/setui/BUILD.gn
@@ -106,6 +106,7 @@
     "src/audio/policy/audio_policy_handler.rs",
     "src/audio/policy/volume_policy_fidl_handler.rs",
     "src/audio/stream_volume_control.rs",
+    "src/audio/utils.rs",
     "src/base.rs",
     "src/clock.rs",
     "src/config.rs",
diff --git a/garnet/bin/setui/src/audio.rs b/garnet/bin/setui/src/audio.rs
index 4267797..f226581 100644
--- a/garnet/bin/setui/src/audio.rs
+++ b/garnet/bin/setui/src/audio.rs
@@ -15,3 +15,6 @@
 mod audio_default_settings;
 mod audio_fidl_handler;
 mod stream_volume_control;
+
+/// Mod containing utility functions for audio-related functionality.
+mod utils;
diff --git a/garnet/bin/setui/src/audio/stream_volume_control.rs b/garnet/bin/setui/src/audio/stream_volume_control.rs
index b721845..85bf247 100644
--- a/garnet/bin/setui/src/audio/stream_volume_control.rs
+++ b/garnet/bin/setui/src/audio/stream_volume_control.rs
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 use {
+    crate::audio::utils::round_volume_level,
     crate::base::SettingType,
     crate::call,
     crate::handler::setting_handler::ControllerError,
@@ -76,9 +77,9 @@
             self.bind_volume_control().await?;
         }
 
-        // Round to 1%.
+        // Round volume level from user input.
         let mut new_stream_value = stream.clone();
-        new_stream_value.user_volume_level = (stream.user_volume_level * 100.0).floor() / 100.0;
+        new_stream_value.user_volume_level = round_volume_level(stream.user_volume_level);
 
         let proxy = self.proxy.as_ref().unwrap();
 
diff --git a/garnet/bin/setui/src/audio/utils.rs b/garnet/bin/setui/src/audio/utils.rs
new file mode 100644
index 0000000..7b55057
--- /dev/null
+++ b/garnet/bin/setui/src/audio/utils.rs
@@ -0,0 +1,45 @@
+// Copyright 2021 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/// Rounds the given volume level to the nearest 1%. Input should be a float between 0.0 and 1.0 and
+/// the result will also be clamped to this range.
+///
+/// Rounding should be applied to audio levels handled by settings service that come from external
+/// sources.
+pub fn round_volume_level(volume: f32) -> f32 {
+    return ((volume * 100.0).round() / 100.0).max(0.0).min(1.0);
+}
+
+#[cfg(test)]
+mod tests {
+    use super::round_volume_level;
+
+    /// Various tests to verify rounding works as expected.
+    #[test]
+    fn test_round_volume() {
+        assert_eq!(round_volume_level(1.0), 1.0);
+        assert_eq!(round_volume_level(0.0), 0.0);
+        assert_eq!(round_volume_level(0.222222), 0.22);
+        assert_eq!(round_volume_level(0.349), 0.35);
+        assert_eq!(round_volume_level(0.995), 1.0);
+        assert_eq!(round_volume_level(0.994), 0.99);
+    }
+
+    /// Verifies that values below 0.0 round to 0.0.
+    #[test]
+    fn test_round_volume_below_range() {
+        assert_eq!(round_volume_level(-1.0), 0.0);
+        assert_eq!(round_volume_level(-0.1), 0.0);
+        assert_eq!(round_volume_level(-0.0), 0.0);
+        assert_eq!(round_volume_level(std::f32::MIN), 0.0);
+    }
+
+    /// Verifies that values above 1.0 round to 1.0.
+    #[test]
+    fn test_round_volume_above_range() {
+        assert_eq!(round_volume_level(2.0), 1.0);
+        assert_eq!(round_volume_level(1.1), 1.0);
+        assert_eq!(round_volume_level(std::f32::MAX), 1.0);
+    }
+}