Feedback client interface.

This includes the interface definition (classes and constants) as
described in the design doc.

BUG=b:26162591
TEST=None

Change-Id: Iff67506e4e5d02d04186f53c72df00c45c6e43df
Reviewed-on: https://chromium-review.googlesource.com/319197
Commit-Ready: Gilad Arnold <garnold@chromium.org>
Tested-by: Gilad Arnold <garnold@chromium.org>
Reviewed-by: Simran Basi <sbasi@chromium.org>
diff --git a/client/common_lib/feedback/client.py b/client/common_lib/feedback/client.py
new file mode 100644
index 0000000..19e8240
--- /dev/null
+++ b/client/common_lib/feedback/client.py
@@ -0,0 +1,255 @@
+# Copyright 2016 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.
+
+"""Interactive feedback layer abstraction."""
+
+from autotest_lib.client.common_lib import error
+
+
+# All known queries.
+#
+# Audio playback and recording testing.
+QUERY_AUDIO_PLAYBACK_SILENT = 0
+QUERY_AUDIO_PLAYBACK_AUDIBLE = 1
+QUERY_AUDIO_RECORDING = 2
+# Motion sensor testing.
+QUERY_MOTION_RESTING = 10
+QUERY_MOTION_MOVING = 11
+# USB keyboard plugging and typing.
+QUERY_KEYBOARD_PLUG = 20
+QUERY_KEYBOARD_TYPE = 21
+# GPIO write/read testing.
+QUERY_GPIO_WRITE = 30
+QUERY_GPIO_READ = 31
+# On-board light testing.
+QUERY_LIGHT_ON = 40
+# TODO(garnold) Camera controls testing.
+#QUERY_CAMERA_???
+# Power management testing.
+QUERY_POWER_WAKEUP = 60
+
+
+# Feedback client definition.
+#
+class Client(object):
+    """Interface for an interactive feedback layer."""
+
+    def __init__(self):
+        self._initialized = False
+        self._finalized = False
+
+
+    def _check_active(self):
+        """Ensure that the client was initialized and not finalized."""
+        if not self._initialized:
+            raise error.TestError('Client was not initialized')
+        if self._finalized:
+            raise error.TestError('Client was already finalized')
+
+
+    def __enter__(self):
+        self._check_active()
+        return self
+
+
+    def __exit__(self, ex_type, ex_val, ex_tb):
+        self.finalize()
+
+
+    def initialize(self, test, host):
+        """Initializes the feedback object.
+
+        This method should be called once prior to any other call.
+
+        @param test: An object representing the test case.
+        @param host: An object representing the DUT.
+
+        @raise TestError: There was an error during initialization.
+        """
+        if self._initialized:
+            raise error.TestError('Client was already initialized')
+        if self._finalized:
+            raise error.TestError('Client was already finalized')
+        self._initialize_impl(test, host)
+        self._initialized = True
+        return self
+
+
+    def _initialize_impl(self, test, host):
+        """Implementation of feedback client initialization.
+
+        This should be implemented in concrete subclasses.
+        """
+        raise NotImplementedError
+
+
+    def new_query(self, query_id):
+        """Instantiates a new query.
+
+        @param query_id: A query identifier (see QUERY_ constants above).
+
+        @return A query object.
+
+        @raise TestError: Query is invalid or not supported.
+        """
+        self._check_active()
+        return self._new_query_impl(query_id)
+
+
+    def _new_query_impl(self, query_id):
+        """Implementation of new query instantiation.
+
+        This should be implemented in concrete subclasses.
+        """
+        raise NotImplementedError
+
+
+    def finalize(self):
+        """Finalizes the feedback object.
+
+        This method should be called once when done using the client.
+
+        @raise TestError: There was an error while finalizing the client.
+        """
+        self._check_active()
+        self._finalize_impl()
+        self._finalized = True
+
+
+    def _finalize_impl(self):
+        """Implementation of feedback client finalization.
+
+        This should be implemented in concrete subclasses.
+        """
+        raise NotImplementedError
+
+
+# Feedback query definitions.
+#
+class _Query(object):
+    """Interactive feedback query base class.
+
+    This class is further derived and should not be inherited directly.
+    """
+
+    def __init__(self):
+        self._prepare_called = False
+        self._validate_called = False
+
+
+    def prepare(self, **kwargs):
+        """Prepares the tester for providing or capturing feedback.
+
+        @raise TestError: Query preparation failed.
+        """
+        if self._prepare_called:
+            raise error.TestError('Prepare was already called')
+        self._prepare_impl(**kwargs)
+        self._prepare_called = True
+
+
+    def _prepare_impl(self, **kwargs):
+        """Implementation of query preparation logic.
+
+        This should be implemented in concrete subclasses.
+        """
+        raise NotImplementedError
+
+
+    def validate(self, **kwargs):
+        """Validates the interactive input/output result.
+
+        This enforces that the method is called at most once, then delegates
+        to an underlying implementation method.
+
+        @raise TestError: An error occurred during validation.
+        @raise TestFail: Query validation failed.
+        """
+        if self._validate_called:
+            raise error.TestError('Validate was already called')
+        self._validate_impl(**kwargs)
+        self._validate_called = True
+
+
+    def _validate_impl(self, **kwargs):
+        """Implementation of query validation logic.
+
+        This should be implemented in concrete subclasses.
+        """
+        raise NotImplementedError
+
+
+class OutputQuery(_Query):
+    """Interface for an output interactive feedback query.
+
+    This class mandates that prepare() is called prior to validate().
+    Subclasses should override implementations of _prepare_impl() and
+    _validate_impl().
+    """
+
+    def __init__(self):
+        super(OutputQuery, self).__init__()
+
+
+    def validate(self, **kwargs):
+        """Validates the interactive input/output result.
+
+        This enforces the precondition and delegates to the base method.
+
+        @raise TestError: An error occurred during validation.
+        @raise TestFail: Query validation failed.
+        """
+        if not self._prepare_called:
+            raise error.TestError('Prepare was not called')
+        super(OutputQuery, self).validate(**kwargs)
+
+
+class InputQuery(_Query):
+    """Interface for an input interactive feedback query.
+
+    This class mandates that prepare() is called first, then emit(), and
+    finally validate(). Subclasses should override implementations of
+    _prepare_impl(), _emit_impl() and _validate_impl().
+    """
+
+    def __init__(self):
+        super(InputQuery, self).__init__()
+        self._emit_called = False
+
+
+    def validate(self, **kwargs):
+        """Validates the interactive input/output result.
+
+        This enforces the precondition and delegates to the base method.
+
+        @raise TestError: An error occurred during validation.
+        @raise TestFail: Query validation failed.
+        """
+        if not self._emit_called:
+            raise error.TestError('Emit was not called')
+        super(InputQuery, self).validate(**kwargs)
+
+
+    def emit(self):
+        """Instructs the tester to emit a feedback to be captured by the test.
+
+        This enforces the precondition and ensures the method is called at most
+        once, then delegates to an underlying implementation method.
+
+        @raise TestError: An error occurred during emission.
+        """
+        if not self._prepare_called:
+            raise error.TestError('Prepare was not called')
+        if self._emit_called:
+            raise error.TestError('Emit was already called')
+        self._emit_impl()
+        self._emit_called = True
+
+
+    def _emit_impl(self):
+        """Implementation of query emission logic.
+
+        This should be implemented in concrete subclasses.
+        """
+        raise NotImplementedError