xref: /aosp_15_r20/external/autotest/client/cros/input_playback/stylus.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
1*9c5db199SXin Li# Copyright 2017 The Chromium OS Authors. All rights reserved.
2*9c5db199SXin Li# Use of this source code is governed by a BSD-style license that can be
3*9c5db199SXin Li# found in the LICENSE file.
4*9c5db199SXin Li
5*9c5db199SXin Liimport logging
6*9c5db199SXin Liimport os
7*9c5db199SXin Li
8*9c5db199SXin Lifrom autotest_lib.client.common_lib import error
9*9c5db199SXin Lifrom autotest_lib.client.cros.graphics import graphics_utils
10*9c5db199SXin Lifrom autotest_lib.client.cros.input_playback import input_playback
11*9c5db199SXin Li
12*9c5db199SXin Li_CLICK_EVENTS = '/tmp/click_events'
13*9c5db199SXin Li_CLICK_TEMPLATE = 'click_events.template'
14*9c5db199SXin Li_PREFIX_RESOLUTION = 'RESOLUTION'
15*9c5db199SXin Li_PREFIX_POSITION = 'POSITION'
16*9c5db199SXin Li_STYLUS_DEVICE = 'stylus'
17*9c5db199SXin Li_STYLUS_PROPERTY = '/tmp/stylus.prop'
18*9c5db199SXin Li_STYLUS_TEMPLATE = 'stylus.prop.template'
19*9c5db199SXin Li
20*9c5db199SXin Li
21*9c5db199SXin Liclass Stylus(object):
22*9c5db199SXin Li    """An emulated stylus device used for UI automation."""
23*9c5db199SXin Li
24*9c5db199SXin Li    def __init__(self):
25*9c5db199SXin Li        """Prepare an emulated stylus device based on the internal display."""
26*9c5db199SXin Li        self.dirname = os.path.dirname(__file__)
27*9c5db199SXin Li        width, height = graphics_utils.get_internal_resolution()
28*9c5db199SXin Li        logging.info('internal display W = %d H = %d ', width, height)
29*9c5db199SXin Li        # Skip the test if there is no internal display
30*9c5db199SXin Li        if width == -1:
31*9c5db199SXin Li            raise error.TestNAError('No internal display')
32*9c5db199SXin Li
33*9c5db199SXin Li        # Enlarge resolution of the emulated stylus.
34*9c5db199SXin Li        self.width = width * 10
35*9c5db199SXin Li        self.height = height * 10
36*9c5db199SXin Li        stylus_template = os.path.join(self.dirname, _STYLUS_TEMPLATE)
37*9c5db199SXin Li        self.replace_with_prefix(stylus_template, _STYLUS_PROPERTY,
38*9c5db199SXin Li                                 _PREFIX_RESOLUTION, self.width, self.height)
39*9c5db199SXin Li        # Create an emulated stylus device.
40*9c5db199SXin Li        self.stylus = input_playback.InputPlayback()
41*9c5db199SXin Li        self.stylus.emulate(input_type=_STYLUS_DEVICE,
42*9c5db199SXin Li                            property_file=_STYLUS_PROPERTY)
43*9c5db199SXin Li        self.stylus.find_connected_inputs()
44*9c5db199SXin Li
45*9c5db199SXin Li    def replace_with_prefix(self, in_file, out_file, prefix, x_value, y_value):
46*9c5db199SXin Li        """Substitute with the real positions and write to an output file.
47*9c5db199SXin Li
48*9c5db199SXin Li        Replace the keywords in template file with the real values and save
49*9c5db199SXin Li        the results into a file.
50*9c5db199SXin Li
51*9c5db199SXin Li        @param in_file: the template file containing keywords for substitution.
52*9c5db199SXin Li        @param out_file: the generated file after substitution.
53*9c5db199SXin Li        @param prefix: the prefix of the keywords for substituion.
54*9c5db199SXin Li        @param x_value: the target value of X.
55*9c5db199SXin Li        @param y_value: the target value of Y.
56*9c5db199SXin Li
57*9c5db199SXin Li        """
58*9c5db199SXin Li        with open(in_file) as infile:
59*9c5db199SXin Li            content = infile.readlines()
60*9c5db199SXin Li
61*9c5db199SXin Li        with open(out_file, 'w') as outfile:
62*9c5db199SXin Li            for line in content:
63*9c5db199SXin Li                if line.find(prefix + '_X') > 0:
64*9c5db199SXin Li                    line = line.replace(prefix + '_X', str(x_value))
65*9c5db199SXin Li                    x_value += 1
66*9c5db199SXin Li                else:
67*9c5db199SXin Li                    if line.find(prefix + '_Y') > 0:
68*9c5db199SXin Li                        line = line.replace(prefix + '_Y', str(y_value))
69*9c5db199SXin Li                        y_value += 1
70*9c5db199SXin Li                outfile.write(line)
71*9c5db199SXin Li
72*9c5db199SXin Li    def click(self, position_x, position_y):
73*9c5db199SXin Li        """Click the point(x,y) on the emulated stylus panel.
74*9c5db199SXin Li
75*9c5db199SXin Li        @param position_x: the X position of the click point.
76*9c5db199SXin Li        @param position_y: the Y position of the click point.
77*9c5db199SXin Li
78*9c5db199SXin Li        """
79*9c5db199SXin Li        click_template = os.path.join(self.dirname, _CLICK_TEMPLATE)
80*9c5db199SXin Li        self.replace_with_prefix(click_template,
81*9c5db199SXin Li                                 _CLICK_EVENTS,
82*9c5db199SXin Li                                 _PREFIX_POSITION,
83*9c5db199SXin Li                                 position_x * 10,
84*9c5db199SXin Li                                 position_y * 10)
85*9c5db199SXin Li        self.stylus.blocking_playback(_CLICK_EVENTS, input_type=_STYLUS_DEVICE)
86*9c5db199SXin Li
87*9c5db199SXin Li    def click_with_percentage(self, percent_x, percent_y):
88*9c5db199SXin Li        """Click a point based on the percentage of the display.
89*9c5db199SXin Li
90*9c5db199SXin Li        @param percent_x: the percentage of X position over display width.
91*9c5db199SXin Li        @param percent_y: the percentage of Y position over display height.
92*9c5db199SXin Li
93*9c5db199SXin Li        """
94*9c5db199SXin Li        position_x = int(percent_x * self.width / 10)
95*9c5db199SXin Li        position_y = int(percent_y * self.height / 10)
96*9c5db199SXin Li        self.click(position_x, position_y)
97*9c5db199SXin Li
98*9c5db199SXin Li    def close(self):
99*9c5db199SXin Li        """Clean up the files/handles created in the class."""
100*9c5db199SXin Li        if self.stylus:
101*9c5db199SXin Li            self.stylus.close()
102*9c5db199SXin Li        if os.path.exists(_STYLUS_PROPERTY):
103*9c5db199SXin Li            os.remove(_STYLUS_PROPERTY)
104*9c5db199SXin Li        if os.path.exists(_CLICK_EVENTS):
105*9c5db199SXin Li            os.remove(_CLICK_EVENTS)
106*9c5db199SXin Li
107*9c5db199SXin Li    def __enter__(self):
108*9c5db199SXin Li        """Allow usage in 'with' statements."""
109*9c5db199SXin Li        return self
110*9c5db199SXin Li
111*9c5db199SXin Li    def __exit__(self, exc_type, exc_val, exc_tb):
112*9c5db199SXin Li        """Release resources on completion of a 'with' statement."""
113*9c5db199SXin Li        self.close()
114