input_playback: Add emulated stylus support

This patch introduces a new emulated stylus device which matches the
internal display resolution and is mainly for click emulation.

BUG=chromium:702532
TEST=none

Change-Id: Ia84437c12d52535d5f7dd634fa7e9be38b55b729
Signed-off-by: Chung-yih Wang <cywang@google.com>
Reviewed-on: https://chromium-review.googlesource.com/469509
Reviewed-by: Chi-Ngai Wan <cnwan@google.com>
Reviewed-by: Ben Cheng <bccheng@chromium.org>
diff --git a/client/cros/input_playback/click_events b/client/cros/input_playback/click_events
new file mode 100644
index 0000000..c054d52
--- /dev/null
+++ b/client/cros/input_playback/click_events
@@ -0,0 +1,302 @@
+E: 1490950038.399275 0001 0140 1
+E: 1490950038.399275 0003 0000 POSITION_X
+E: 1490950038.399275 0003 0001 POSITION_Y
+E: 1490950038.399275 0000 0000 0
+E: 1490950038.468959 0003 0000 POSITION_X
+E: 1490950038.468959 0003 0001 POSITION_Y
+E: 1490950038.468959 0000 0000 0
+E: 1490950038.509237 0003 0000 POSITION_X
+E: 1490950038.509237 0003 0001 POSITION_Y
+E: 1490950038.509237 0000 0000 0
+E: 1490950038.585664 0003 0000 POSITION_X
+E: 1490950038.585664 0003 0001 POSITION_Y
+E: 1490950038.585664 0000 0000 0
+E: 1490950038.615662 0003 0000 POSITION_X
+E: 1490950038.615662 0003 0001 POSITION_Y
+E: 1490950038.615662 0000 0000 0
+E: 1490950038.635643 0003 0001 POSITION_Y
+E: 1490950038.635643 0000 0000 0
+E: 1490950038.655748 0003 0000 POSITION_X
+E: 1490950038.655748 0003 0001 POSITION_Y
+E: 1490950038.655748 0000 0000 0
+E: 1490950038.665654 0003 0000 POSITION_X
+E: 1490950038.665654 0003 0001 POSITION_Y
+E: 1490950038.665654 0000 0000 0
+E: 1490950038.675640 0003 0000 POSITION_X
+E: 1490950038.675640 0003 0001 POSITION_Y
+E: 1490950038.675640 0000 0000 0
+E: 1490950038.685640 0003 0000 POSITION_X
+E: 1490950038.685640 0003 0001 POSITION_Y
+E: 1490950038.685640 0000 0000 0
+E: 1490950038.695550 0003 0000 POSITION_X
+E: 1490950038.695550 0003 0001 POSITION_Y
+E: 1490950038.695550 0000 0000 0
+E: 1490950038.709263 0003 0000 POSITION_X
+E: 1490950038.709263 0003 0001 POSITION_Y
+E: 1490950038.709263 0000 0000 0
+E: 1490950038.735774 0003 0000 POSITION_X
+E: 1490950038.735774 0003 0001 POSITION_Y
+E: 1490950038.735774 0000 0000 0
+E: 1490950038.759245 0003 0000 POSITION_X
+E: 1490950038.759245 0003 0001 POSITION_Y
+E: 1490950038.759245 0000 0000 0
+E: 1490950038.775621 0003 0000 POSITION_X
+E: 1490950038.775621 0003 0001 POSITION_Y
+E: 1490950038.775621 0000 0000 0
+E: 1490950038.779183 0003 0000 POSITION_X
+E: 1490950038.779183 0003 0001 POSITION_Y
+E: 1490950038.779183 0000 0000 0
+E: 1490950038.785640 0003 0000 POSITION_X
+E: 1490950038.785640 0003 0001 POSITION_Y
+E: 1490950038.785640 0000 0000 0
+E: 1490950038.789062 0003 0000 POSITION_X
+E: 1490950038.789062 0003 0001 POSITION_Y
+E: 1490950038.789062 0000 0000 0
+E: 1490950038.795743 0003 0000 POSITION_X
+E: 1490950038.795743 0003 0001 POSITION_Y
+E: 1490950038.795743 0000 0000 0
+E: 1490950038.805503 0004 0004 852034
+E: 1490950038.805503 0001 014a 1
+E: 1490950038.805503 0003 0018 844
+E: 1490950038.805503 0003 001a 12
+E: 1490950038.805503 0003 001b 19
+E: 1490950038.805503 0000 0000 0
+E: 1490950038.808801 0003 0018 907
+E: 1490950038.808801 0000 0000 0
+E: 1490950038.815469 0003 0000 POSITION_X
+E: 1490950038.815469 0003 0001 POSITION_Y
+E: 1490950038.815469 0003 0018 961
+E: 1490950038.815469 0003 001b 18
+E: 1490950038.815469 0000 0000 0
+E: 1490950038.819078 0003 0018 1000
+E: 1490950038.819078 0003 001b 19
+E: 1490950038.819078 0000 0000 0
+E: 1490950038.825493 0003 0018 1017
+E: 1490950038.825493 0000 0000 0
+E: 1490950038.828894 0003 0018 997
+E: 1490950038.828894 0000 0000 0
+E: 1490950038.835499 0003 0018 971
+E: 1490950038.835499 0000 0000 0
+E: 1490950038.838925 0003 0018 952
+E: 1490950038.838925 0000 0000 0
+E: 1490950038.845329 0003 0018 947
+E: 1490950038.845329 0000 0000 0
+E: 1490950038.848999 0003 0018 960
+E: 1490950038.848999 0000 0000 0
+E: 1490950038.855448 0003 0000 POSITION_X
+E: 1490950038.855448 0003 0001 POSITION_Y
+E: 1490950038.855448 0003 0018 994
+E: 1490950038.855448 0000 0000 0
+E: 1490950038.858837 0003 0018 1039
+E: 1490950038.858837 0000 0000 0
+E: 1490950038.865321 0003 0000 POSITION_X
+E: 1490950038.865321 0003 0001 POSITION_Y
+E: 1490950038.865321 0003 0018 1080
+E: 1490950038.865321 0000 0000 0
+E: 1490950038.868854 0003 0018 1122
+E: 1490950038.868854 0000 0000 0
+E: 1490950038.875347 0003 0018 1157
+E: 1490950038.875347 0000 0000 0
+E: 1490950038.878876 0003 0000 POSITION_X
+E: 1490950038.878876 0003 0001 POSITION_Y
+E: 1490950038.878876 0003 0018 1182
+E: 1490950038.878876 0000 0000 0
+E: 1490950038.885392 0003 0018 1211
+E: 1490950038.885392 0000 0000 0
+E: 1490950038.888881 0003 0018 1230
+E: 1490950038.888881 0003 001b 20
+E: 1490950038.888881 0000 0000 0
+E: 1490950038.895400 0003 0000 POSITION_X
+E: 1490950038.895400 0003 0001 POSITION_Y
+E: 1490950038.895400 0003 0018 1241
+E: 1490950038.895400 0000 0000 0
+E: 1490950038.898901 0003 0018 1245
+E: 1490950038.898901 0000 0000 0
+E: 1490950038.905305 0003 0000 POSITION_X
+E: 1490950038.905305 0003 0018 1247
+E: 1490950038.905305 0000 0000 0
+E: 1490950038.908876 0003 0018 1249
+E: 1490950038.908876 0003 001a 14
+E: 1490950038.908876 0000 0000 0
+E: 1490950038.915396 0003 0018 1250
+E: 1490950038.915396 0000 0000 0
+E: 1490950038.918854 0003 0000 POSITION_X
+E: 1490950038.918854 0003 0001 POSITION_Y
+E: 1490950038.918854 0000 0000 0
+E: 1490950038.925349 0003 0018 1252
+E: 1490950038.925349 0000 0000 0
+E: 1490950038.928933 0003 0018 1255
+E: 1490950038.928933 0000 0000 0
+E: 1490950038.935384 0003 0000 POSITION_X
+E: 1490950038.935384 0003 0001 POSITION_Y
+E: 1490950038.935384 0003 0018 1261
+E: 1490950038.935384 0000 0000 0
+E: 1490950038.938962 0003 0018 1267
+E: 1490950038.938962 0000 0000 0
+E: 1490950038.945381 0003 0018 1274
+E: 1490950038.945381 0000 0000 0
+E: 1490950038.948911 0003 0018 1280
+E: 1490950038.948911 0000 0000 0
+E: 1490950038.955369 0003 0018 1282
+E: 1490950038.955369 0000 0000 0
+E: 1490950038.958943 0003 0000 POSITION_X
+E: 1490950038.958943 0003 0001 POSITION_Y
+E: 1490950038.958943 0003 0018 1283
+E: 1490950038.958943 0000 0000 0
+E: 1490950038.975377 0003 0018 1285
+E: 1490950038.975377 0000 0000 0
+E: 1490950038.995189 0003 0018 1286
+E: 1490950038.995189 0000 0000 0
+E: 1490950038.998792 0003 0018 1288
+E: 1490950038.998792 0000 0000 0
+E: 1490950039.005277 0003 0018 1293
+E: 1490950039.005277 0000 0000 0
+E: 1490950039.008761 0003 0018 1297
+E: 1490950039.008761 0000 0000 0
+E: 1490950039.015251 0003 0018 1302
+E: 1490950039.015251 0000 0000 0
+E: 1490950039.018789 0003 0018 1307
+E: 1490950039.018789 0000 0000 0
+E: 1490950039.025210 0003 0018 1312
+E: 1490950039.025210 0000 0000 0
+E: 1490950039.028797 0003 0018 1315
+E: 1490950039.028797 0000 0000 0
+E: 1490950039.035415 0003 0018 1320
+E: 1490950039.035415 0000 0000 0
+E: 1490950039.038905 0003 0018 1326
+E: 1490950039.038905 0000 0000 0
+E: 1490950039.045454 0003 0018 1329
+E: 1490950039.045454 0000 0000 0
+E: 1490950039.048844 0003 0018 1334
+E: 1490950039.048844 0000 0000 0
+E: 1490950039.058860 0003 0018 1333
+E: 1490950039.058860 0000 0000 0
+E: 1490950039.065358 0003 0018 1331
+E: 1490950039.065358 0000 0000 0
+E: 1490950039.075343 0003 0018 1336
+E: 1490950039.075343 0000 0000 0
+E: 1490950039.078904 0003 0018 1339
+E: 1490950039.078904 0000 0000 0
+E: 1490950039.085466 0003 0018 1344
+E: 1490950039.085466 0000 0000 0
+E: 1490950039.089052 0003 0018 1347
+E: 1490950039.089052 0000 0000 0
+E: 1490950039.115488 0003 0018 1346
+E: 1490950039.115488 0000 0000 0
+E: 1490950039.125491 0003 0018 1344
+E: 1490950039.125491 0000 0000 0
+E: 1490950039.135456 0003 0018 1342
+E: 1490950039.135456 0000 0000 0
+E: 1490950039.145449 0003 0000 POSITION_X
+E: 1490950039.145449 0003 0001 POSITION_Y
+E: 1490950039.145449 0003 0018 1341
+E: 1490950039.145449 0000 0000 0
+E: 1490950039.148907 0003 0018 1339
+E: 1490950039.148907 0000 0000 0
+E: 1490950039.155365 0003 0018 1338
+E: 1490950039.155365 0000 0000 0
+E: 1490950039.165273 0003 0000 POSITION_X
+E: 1490950039.165273 0003 0001 POSITION_Y
+E: 1490950039.165273 0000 0000 0
+E: 1490950039.175291 0003 0018 1341
+E: 1490950039.175291 0000 0000 0
+E: 1490950039.178775 0003 0018 1339
+E: 1490950039.178775 0000 0000 0
+E: 1490950039.188892 0003 0000 POSITION_X
+E: 1490950039.188892 0003 0001 POSITION_Y
+E: 1490950039.188892 0000 0000 0
+E: 1490950039.208899 0003 0018 1338
+E: 1490950039.208899 0000 0000 0
+E: 1490950039.215309 0003 0018 1336
+E: 1490950039.215309 0000 0000 0
+E: 1490950039.218835 0003 0018 1333
+E: 1490950039.218835 0000 0000 0
+E: 1490950039.225211 0003 0000 POSITION_X
+E: 1490950039.225211 0003 0001 POSITION_Y
+E: 1490950039.225211 0000 0000 0
+E: 1490950039.228882 0003 0018 1329
+E: 1490950039.228882 0000 0000 0
+E: 1490950039.235339 0003 0018 1325
+E: 1490950039.235339 0000 0000 0
+E: 1490950039.238892 0003 0018 1320
+E: 1490950039.238892 0000 0000 0
+E: 1490950039.245381 0003 0018 1310
+E: 1490950039.245381 0000 0000 0
+E: 1490950039.248830 0003 0018 1297
+E: 1490950039.248830 0000 0000 0
+E: 1490950039.255334 0003 0018 1277
+E: 1490950039.255334 0000 0000 0
+E: 1490950039.258981 0003 0018 1247
+E: 1490950039.258981 0000 0000 0
+E: 1490950039.265516 0003 0018 1198
+E: 1490950039.265516 0000 0000 0
+E: 1490950039.269051 0003 0000 POSITION_X
+E: 1490950039.269051 0003 0001 POSITION_Y
+E: 1490950039.269051 0003 0018 1124
+E: 1490950039.269051 0000 0000 0
+E: 1490950039.275379 0003 0018 1008
+E: 1490950039.275379 0000 0000 0
+E: 1490950039.278880 0003 0018 835
+E: 1490950039.278880 0003 001a 15
+E: 1490950039.278880 0000 0000 0
+E: 1490950039.285330 0003 0018 563
+E: 1490950039.285330 0003 001a 14
+E: 1490950039.285330 0000 0000 0
+E: 1490950039.288955 0003 0018 296
+E: 1490950039.288955 0000 0000 0
+E: 1490950039.295333 0003 0000 POSITION_X
+E: 1490950039.295333 0003 0001 POSITION_Y
+E: 1490950039.295333 0003 0018 58
+E: 1490950039.295333 0000 0000 0
+E: 1490950039.298835 0004 0004 852034
+E: 1490950039.298835 0001 014a 0
+E: 1490950039.298835 0003 0018 0
+E: 1490950039.298835 0003 001a 0
+E: 1490950039.298835 0003 001b 0
+E: 1490950039.298835 0000 0000 0
+E: 1490950039.315303 0003 0000 POSITION_X
+E: 1490950039.315303 0003 0001 POSITION_Y
+E: 1490950039.315303 0000 0000 0
+E: 1490950039.325370 0003 0000 POSITION_X
+E: 1490950039.325370 0003 0001 POSITION_Y
+E: 1490950039.325370 0000 0000 0
+E: 1490950039.335246 0003 0000 POSITION_X
+E: 1490950039.335246 0003 0001 POSITION_Y
+E: 1490950039.335246 0000 0000 0
+E: 1490950039.338751 0003 0000 POSITION_X
+E: 1490950039.338751 0003 0001 POSITION_Y
+E: 1490950039.338751 0000 0000 0
+E: 1490950039.348912 0003 0000 POSITION_X
+E: 1490950039.348912 0003 0001 POSITION_Y
+E: 1490950039.348912 0000 0000 0
+E: 1490950039.355272 0003 0000 POSITION_X
+E: 1490950039.355272 0003 0001 POSITION_Y
+E: 1490950039.355272 0000 0000 0
+E: 1490950039.358818 0003 0000 POSITION_X
+E: 1490950039.358818 0003 0001 POSITION_Y
+E: 1490950039.358818 0000 0000 0
+E: 1490950039.365260 0003 0000 POSITION_X
+E: 1490950039.365260 0003 0001 POSITION_Y
+E: 1490950039.365260 0000 0000 0
+E: 1490950039.368866 0003 0000 POSITION_X
+E: 1490950039.368866 0003 0001 POSITION_Y
+E: 1490950039.368866 0000 0000 0
+E: 1490950039.375425 0003 0000 POSITION_X
+E: 1490950039.375425 0003 0001 POSITION_Y
+E: 1490950039.375425 0000 0000 0
+E: 1490950039.378914 0003 0000 POSITION_X
+E: 1490950039.378914 0003 0001 POSITION_Y
+E: 1490950039.378914 0000 0000 0
+E: 1490950039.384546 0003 0000 POSITION_X
+E: 1490950039.384546 0003 0001 POSITION_Y
+E: 1490950039.384546 0000 0000 0
+E: 1490950039.388043 0003 0000 POSITION_X
+E: 1490950039.388043 0003 0001 POSITION_Y
+E: 1490950039.388043 0000 0000 0
+E: 1490950039.394978 0003 0000 POSITION_X
+E: 1490950039.394978 0003 0001 POSITION_Y
+E: 1490950039.394978 0000 0000 0
+E: 1490950039.395727 0003 0001 POSITION_Y
+E: 1490950039.395727 0000 0000 0
+E: 1490950039.453667 0001 0140 0
+E: 1490950039.453667 0000 0000 0
diff --git a/client/cros/input_playback/stylus.prop b/client/cros/input_playback/stylus.prop
new file mode 100644
index 0000000..e7efe65
--- /dev/null
+++ b/client/cros/input_playback/stylus.prop
@@ -0,0 +1,29 @@
+N: Emulated Stylus
+I: 0018 18d1 0000 0100
+P: 00 00 00 00 00 00 00 00
+B: 00 1b 00 00 00 00 00 00 00
+B: 01 00 00 00 00 00 00 00 00
+B: 01 00 00 00 00 00 00 00 00
+B: 01 00 00 00 00 00 00 00 00
+B: 01 00 00 00 00 00 00 00 00
+B: 01 03 00 00 00 00 00 00 00
+B: 01 07 1c 00 00 00 00 00 00
+B: 01 00 00 00 00 00 00 00 00
+B: 01 00 00 00 00 00 00 00 00
+B: 01 00 00 00 00 00 00 00 00
+B: 01 00 00 00 00 00 00 00 00
+B: 01 00 00 00 00 00 00 00 00
+B: 01 00 00 00 00 00 00 00 00
+B: 02 00 00 00 00 00 00 00 00
+B: 03 03 00 00 0d 00 00 00 00
+B: 04 10 00 00 00 00 00 00 00
+B: 05 00 00 00 00 00 00 00 00
+B: 11 00 00 00 00 00 00 00 00
+B: 12 00 00 00 00 00 00 00 00
+B: 15 00 00 00 00 00 00 00 00
+B: 15 00 00 00 00 00 00 00 00
+A: 00 0 RESOLUTION_X 0 0 100
+A: 01 0 RESOLUTION_Y 0 0 100
+A: 18 0 2047 0 0 0
+A: 1a -63 63 0 0 57
+A: 1b -63 63 0 0 57
diff --git a/client/cros/input_playback/stylus.py b/client/cros/input_playback/stylus.py
new file mode 100644
index 0000000..c2798cb
--- /dev/null
+++ b/client/cros/input_playback/stylus.py
@@ -0,0 +1,105 @@
+# Copyright 2017 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.
+
+import logging
+import os
+
+from autotest_lib.client.common_lib import error
+from autotest_lib.client.cros.graphics import graphics_utils
+from autotest_lib.client.cros.input_playback import input_playback
+
+_CLICK_EVENTS = '/tmp/click_events'
+_CLICK_TEMPLATE = 'click_events'
+_PREFIX_RESOLUTION = 'RESOLUTION'
+_PREFIX_POSITION = 'POSITION'
+_STYLUS_DEVICE = 'stylus'
+_STYLUS_PROPERTY = '/tmp/stylus.prop'
+_STYLUS_TEMPLATE = 'stylus.prop'
+
+
+class Stylus(object):
+    """An emulated stylus device used for UI automation."""
+
+    def __init__(self):
+        """Prepare an emulated stylus device based on the internal display."""
+        self.dirname = os.path.dirname(__file__)
+        width, height = graphics_utils.get_internal_resolution()
+        logging.info('internal display W = %d H = %d ', width, height)
+        # Skip the test if there is no internal display
+        if width == -1:
+            raise error.TestNAError('No internal display')
+
+        # Enlarge resolution of the emulated stylus.
+        self.width = width * 10
+        self.height = height * 10
+        stylus_template = os.path.join(self.dirname, _STYLUS_TEMPLATE)
+        self.replace_with_prefix(stylus_template, _STYLUS_PROPERTY,
+                                 _PREFIX_RESOLUTION, self.width, self.height)
+        # Create an emulated stylus device.
+        self.stylus = input_playback.InputPlayback()
+        self.stylus.emulate(input_type=_STYLUS_DEVICE,
+                            property_file=_STYLUS_PROPERTY)
+        self.stylus.find_connected_inputs()
+
+    def replace_with_prefix(self, in_file, out_file, prefix, x_value, y_value):
+        """Substitute with the real positions and write to an output file.
+
+        Replace the keywords in template file with the real values and save
+        the results into a file.
+
+        @param in_file: the template file containing keywords for substitution.
+        @param out_file: the generated file after substitution.
+        @param prefix: the prefix of the keywords for substituion.
+        @param x_value: the target value of X.
+        @param y_value: the target value of Y.
+
+        """
+        with open(in_file) as infile:
+            content = infile.readlines()
+
+        with open(out_file, 'w') as outfile:
+            for line in content:
+                if line.find(prefix + '_X') > 0:
+                    line = line.replace(prefix + '_X', str(x_value))
+                    x_value += 1
+                else:
+                    if line.find(prefix + '_Y') > 0:
+                        line = line.replace(prefix + '_Y', str(y_value))
+                        y_value += 1
+                outfile.write(line)
+
+    def click(self, position_x, position_y):
+        """Click the point(x,y) on the emulated stylus panel.
+
+        @param position_x: the X position of the click point.
+        @param position_y: the Y position of the click point.
+
+        """
+        click_template = os.path.join(self.dirname, _CLICK_TEMPLATE)
+        self.replace_with_prefix(click_template,
+                                 _CLICK_EVENTS,
+                                 _PREFIX_POSITION,
+                                 position_x * 10,
+                                 position_y * 10)
+        self.stylus.blocking_playback(_CLICK_EVENTS, input_type=_STYLUS_DEVICE)
+
+    def click_with_percentage(self, percent_x, percent_y):
+        """Click a point based on the percentage of the display.
+
+        @param percent_x: the percentage of X position over display width.
+        @param percent_y: the percentage of Y position over display height.
+
+        """
+        position_x = int(percent_x * self.width / 10)
+        position_y = int(percent_y * self.height / 10)
+        self.click(position_x, position_y)
+
+    def close(self):
+        """Clean up the files/handles created in the class."""
+        if self.stylus:
+            self.stylus.close()
+        if os.path.exists(_STYLUS_PROPERTY):
+            os.remove(_STYLUS_PROPERTY)
+        if os.path.exists(_CLICK_EVENTS):
+            os.remove(_CLICK_EVENTS)