autotest: add test audio_CrasGetNodes

Check if the values of active, volume, and max supported channels of
ALSA loopback input and output nodes are correct by dbus GetNodes API.

For this test, kernel config is required to have CONFIG_SND_ALOOP=m
which makes snd-aloop is manually probeable.

BUG=b:152282570
TEST=test_that audio_CrasGetNodes

Change-Id: I28080381397256fbfde092a7859c57837ef3a02a
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/third_party/autotest/+/2208479
Commit-Queue: Pin-chih Lin <johnylin@chromium.org>
Tested-by: Pin-chih Lin <johnylin@chromium.org>
Auto-Submit: Pin-chih Lin <johnylin@chromium.org>
Reviewed-by: En-Shuo Hsu <enshuo@chromium.org>
diff --git a/client/cros/audio/cras_utils.py b/client/cros/audio/cras_utils.py
index 85bbc99..f175c6f 100644
--- a/client/cros/audio/cras_utils.py
+++ b/client/cros/audio/cras_utils.py
@@ -317,7 +317,7 @@
 
 # Cras node types reported from Cras DBus control API.
 CRAS_OUTPUT_NODE_TYPES = ['HEADPHONE', 'INTERNAL_SPEAKER', 'HDMI', 'USB',
-                          'BLUETOOTH', 'LINEOUT', 'UNKNOWN']
+                          'BLUETOOTH', 'LINEOUT', 'UNKNOWN', 'ALSA_LOOPBACK']
 CRAS_INPUT_NODE_TYPES = ['MIC', 'INTERNAL_MIC', 'USB', 'BLUETOOTH',
                          'POST_DSP_LOOPBACK', 'POST_MIX_LOOPBACK', 'UNKNOWN',
                          'KEYBOARD_MIC', 'HOTWORD', 'FRONT_MIC', 'REAR_MIC',
@@ -656,6 +656,20 @@
     raise CrasUtilsError('Cannot find active node volume from nodes.')
 
 
+def get_active_output_node_max_supported_channels():
+    """Returns max supported channels from active output node.
+
+    @returns: int for max supported channels.
+
+    @raises: CrasUtilsError: if node cannot be found.
+    """
+    nodes = get_cras_nodes()
+    for node in nodes:
+        if node['Active'] == 1 and node['IsInput'] == 0:
+            return int(node['MaxSupportedChannels'])
+    raise CrasUtilsError('Cannot find active output node.')
+
+
 class CrasTestClient(object):
     """An object to perform cras_test_client functions."""
 
diff --git a/client/site_tests/audio_CrasGetNodes/audio_CrasGetNodes.py b/client/site_tests/audio_CrasGetNodes/audio_CrasGetNodes.py
new file mode 100644
index 0000000..259b49b
--- /dev/null
+++ b/client/site_tests/audio_CrasGetNodes/audio_CrasGetNodes.py
@@ -0,0 +1,79 @@
+# Copyright 2020 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import logging
+import time
+
+from autotest_lib.client.bin import test
+from autotest_lib.client.bin import utils
+from autotest_lib.client.common_lib import error
+from autotest_lib.client.common_lib.cros import chrome
+from autotest_lib.client.cros import constants as cros_constants
+from autotest_lib.client.cros.audio import cras_utils
+from autotest_lib.client.cros.multimedia import audio_facade_native
+
+class audio_CrasGetNodes(test.test):
+    """Verifies dbus GetNodes API of CRAS."""
+    version = 1
+
+    ALOOP_CRAS_NODE_TYPE = 'ALSA_LOOPBACK'
+    ALOOP_MODULE_NAME = 'snd-aloop'
+
+    def run_once(self):
+        """Entry point of this test."""
+        # Check CRAS server is alive. If not, restart it and wait a second to
+        # get server ready.
+        if utils.get_service_pid('cras') == 0:
+            logging.debug('CRAS server is down. Restart it.')
+            utils.start_service('cras', ignore_status=True)
+            time.sleep(1)
+
+        utils.load_module(self.ALOOP_MODULE_NAME)
+
+        try:
+            with chrome.Chrome(
+                    extension_paths=[cros_constants.AUDIO_TEST_EXTENSION],
+                    autotest_ext=True) as cr:
+                audio_facade = audio_facade_native.AudioFacadeNative(cr)
+                audio_facade.set_chrome_active_node_type(
+                        self.ALOOP_CRAS_NODE_TYPE, self.ALOOP_CRAS_NODE_TYPE)
+
+            # Checks active output and input node types are correct.
+            active_output_types, active_input_types = (
+                cras_utils.get_selected_node_types())
+            if len(active_output_types) != 1:
+                raise error.TestFail(
+                    'Length of active output types is not 1, got: %d',
+                    len(active_output_types))
+            if active_output_types[0] != self.ALOOP_CRAS_NODE_TYPE:
+                raise error.TestFail(
+                    'Active output device is not %s, got: %d',
+                    self.ALOOP_CRAS_NODE_TYPE, active_output_types[0])
+            if len(active_input_types) != 1:
+                raise error.TestFail(
+                    'Length of active input types is not 1, got: %d',
+                    len(active_input_types))
+            if active_input_types[0] != self.ALOOP_CRAS_NODE_TYPE:
+                raise error.TestFail(
+                    'Active input device is not %s, got: %d',
+                    self.ALOOP_CRAS_NODE_TYPE, active_input_types[0])
+
+            # Checks active node volume is correct.
+            for target_volume in [25, 75]:
+                cras_utils.set_selected_output_node_volume(target_volume)
+                volume = cras_utils.get_active_node_volume()
+                if volume != target_volume:
+                    raise error.TestFail('Volume is as expected: %d, got: %d',
+                                         target_volume, volume)
+
+            # Checks max supported channels value is correct.
+            max_supported_channels = (
+                    cras_utils.get_active_output_node_max_supported_channels())
+            if max_supported_channels != 8:
+                raise error.TestFail('Max supported channels is not 8, got: %d',
+                                     max_supported_channels)
+        finally:
+            utils.stop_service('cras', ignore_status=True)
+            utils.unload_module(self.ALOOP_MODULE_NAME)
+            utils.start_service('cras', ignore_status=True)
diff --git a/client/site_tests/audio_CrasGetNodes/control b/client/site_tests/audio_CrasGetNodes/control
new file mode 100644
index 0000000..1b990a4
--- /dev/null
+++ b/client/site_tests/audio_CrasGetNodes/control
@@ -0,0 +1,26 @@
+# Copyright 2020 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+AUTHOR = 'The Chromium OS Audio Team, chromeos-audio@google.com'
+NAME = 'audio_CrasGetNodes'
+PURPOSE = 'Test that dbus GetNodes API of CRAS is working.'
+CRITERIA = '''
+Check if the values of active, volume, and max supported channels of ALSA
+loopback input and output nodes are correct by dbus GetNodes API.
+'''
+ATTRIBUTES = 'suite:audio_essential'
+TIME = 'SHORT'
+TEST_CATEGORY = 'Functional'
+TEST_CLASS = 'audio'
+TEST_TYPE = 'client'
+DEPENDENCIES = ''
+
+DOC = '''
+Test that dbus GetNodes API of CRAS is working.
+
+NOTE: For this test kernel config is required to have CONFIG_SND_ALOOP=m which
+makes snd-aloop is manually probeable.
+'''
+
+job.run_test('audio_CrasGetNodes')