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