1# Copyright (c) 2014 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
7import time
8
9from autotest_lib.client.bin import test
10from autotest_lib.client.bin import utils
11from autotest_lib.client.common_lib import error
12from autotest_lib.client.cros.input_playback import input_playback
13
14
15class touch_playback_test_base(test.test):
16    """Base class for touch tests involving playback."""
17    version = 1
18
19    _INPUTCONTROL = '/opt/google/input/inputcontrol'
20    _DEFAULT_SCROLL = 5000
21
22
23    @property
24    def _has_touchpad(self):
25        """True if device under test has a touchpad; else False."""
26        return self.player.has('touchpad')
27
28
29    @property
30    def _has_touchscreen(self):
31        """True if device under test has a touchscreen; else False."""
32        return self.player.has('touchscreen')
33
34
35    @property
36    def _has_mouse(self):
37        """True if device under test has or emulates a USB mouse; else False."""
38        return self.player.has('mouse')
39
40
41    def warmup(self, mouse_props=None):
42        """Test setup.
43
44        Instantiate player object to find touch devices, if any.
45        These devices can be used for playback later.
46        Emulate a USB mouse if a property file is provided.
47        Check if the inputcontrol script is avaiable on the disk.
48
49        @param mouse_props: optional property file for a mouse to emulate.
50                            Created using 'evemu-describe /dev/input/X'.
51
52        """
53        self.player = input_playback.InputPlayback()
54        if mouse_props:
55            self.player.emulate(input_type='mouse', property_file=mouse_props)
56        self.player.find_connected_inputs()
57
58        self._autotest_ext = None
59        self._has_inputcontrol = os.path.isfile(self._INPUTCONTROL)
60        self._platform = utils.get_board()
61
62
63    def _find_test_files(self, input_type, gestures):
64        """Determine where the test files are.
65
66        Expected file format is: <boardname>_<input type>_<hwid>_<gesture name>
67            e.g. samus_touchpad_164.17_scroll_down
68
69        @param input_type: device type, e.g. 'touchpad'
70        @param gestures: list of gesture name strings used in filename
71
72        @returns: None if not all files are found.  Dictionary of filepaths if
73                  they are found, indexed by gesture names as given.
74        @raises: error.TestError if no hw_id is found.
75
76        """
77        hw_id = self.player.devices[input_type].hw_id
78        if not hw_id:
79            raise error.TestError('No valid hw_id for this %s!' % input_type)
80
81        filepaths = {}
82        gesture_dir = os.path.join(self.bindir, 'gestures')
83        for gesture in gestures:
84            filename = '%s_%s_%s_%s' % (self._platform, input_type, hw_id,
85                                        gesture)
86            filepath = os.path.join(gesture_dir, filename)
87            if not os.path.exists(filepath):
88                logging.info('Did not find %s!', filepath)
89                return None
90            filepaths[gesture] = filepath
91
92        return filepaths
93
94
95    def _find_test_files_from_directions(self, input_type, fmt_str, directions):
96        """Find test files given a list of directions and gesture name format.
97
98        @param input_type: device type, e.g. 'touchpad'
99        @param fmt_str: format string for filename, e.g. 'scroll-%s'
100        @param directions: list of directions for fmt_string
101
102        @returns: None if not all files are found.  Dictionary of filepaths if
103                  they are found, indexed by directions as given.
104        @raises: error.TestError if no hw_id is found.
105
106        """
107        gestures = [fmt_str % d for d in directions]
108        temp_filepaths = self._find_test_files(input_type, gestures)
109
110        filepaths = {}
111        if temp_filepaths:
112            filepaths = {d: temp_filepaths[fmt_str % d] for d in directions}
113
114        return filepaths
115
116
117    def _emulate_mouse(self, property_file=None):
118        """Emulate a mouse with the given property file.
119
120        player will use default mouse if no file is provided.
121
122        """
123        self.player.emulate(input_type='mouse', property_file=property_file)
124        self.player.find_connected_inputs()
125        if not self._has_mouse:
126            raise error.TestError('Mouse emulation failed!')
127
128
129    def _playback(self, filepath, touch_type='touchpad'):
130        """Playback a given input file on the given input."""
131        self.player.playback(filepath, touch_type)
132
133
134    def _blocking_playback(self, filepath, touch_type='touchpad'):
135        """Playback a given input file on the given input; block until done."""
136        self.player.blocking_playback(filepath, touch_type)
137
138
139    def _set_touch_setting_by_inputcontrol(self, setting, value):
140        """Set a given touch setting the given value by inputcontrol.
141
142        @param setting: Name of touch setting, e.g. 'tapclick'.
143        @param value: True for enabled, False for disabled.
144
145        """
146        cmd_value = 1 if value else 0
147        utils.run('%s --%s %d' % (self._INPUTCONTROL, setting, cmd_value))
148        logging.info('%s turned %s.', setting, 'on' if value else 'off')
149
150
151    def _set_touch_setting(self, inputcontrol_setting, autotest_ext_setting,
152                           value):
153        """Set a given touch setting the given value.
154
155        @param inputcontrol_setting: Name of touch setting for the inputcontrol
156                                     script, e.g. 'tapclick'.
157        @param autotest_ext_setting: Name of touch setting for the autotest
158                                     extension, e.g. 'TapToClick'.
159        @param value: True for enabled, False for disabled.
160
161        """
162        if self._has_inputcontrol:
163            self._set_touch_setting_by_inputcontrol(inputcontrol_setting, value)
164        elif self._autotest_ext is not None:
165            self._autotest_ext.EvaluateJavaScript(
166                    'chrome.autotestPrivate.set%s(%s);'
167                    % (autotest_ext_setting, ("%s" % value).lower()))
168            # TODO: remove this sleep once checking for value is available.
169            time.sleep(1)
170        else:
171            raise error.TestFail('Both inputcontrol and the autotest '
172                                 'extension are not availble.')
173
174
175    def _set_australian_scrolling(self, value):
176        """Set australian scrolling to the given value.
177
178        @param value: True for enabled, False for disabled.
179
180        """
181        self._set_touch_setting('australian_scrolling', 'NaturalScroll', value)
182
183
184    def _set_tap_to_click(self, value):
185        """Set tap-to-click to the given value.
186
187        @param value: True for enabled, False for disabled.
188
189        """
190        self._set_touch_setting('tapclick', 'TapToClick', value)
191
192
193    def _set_tap_dragging(self, value):
194        """Set tap dragging to the given value.
195
196        @param value: True for enabled, False for disabled.
197
198        """
199        self._set_touch_setting('tapdrag', 'TapDragging', value)
200
201
202    def _reload_page(self):
203        """Reloads test page.  Presuposes self._tab.
204
205        @raise: TestError if page is not reset.
206
207        """
208        self._tab.Navigate(self._tab.url)
209        self._wait_for_page_ready()
210
211
212    def _set_autotest_ext(self, ext):
213        """Set the autotest extension.
214
215        @ext: the autotest extension object.
216
217        """
218        self._autotest_ext = ext
219
220
221    def _open_test_page(self, cr, filename='test_page.html'):
222        """Prepare test page for testing.  Set self._tab with page.
223
224        @param cr: chrome.Chrome() object
225        @param filename: name of file in self.bindir to open
226
227        """
228        cr.browser.platform.SetHTTPServerDirectories(self.bindir)
229        self._tab = cr.browser.tabs[0]
230        self._tab.Navigate(cr.browser.platform.http_server.UrlOf(
231                os.path.join(self.bindir, filename)))
232        self._wait_for_page_ready()
233
234
235    def _wait_for_page_ready(self):
236        """Wait for a variable pageReady on the test page to be true.
237
238        Presuposes self._tab and a pageReady variable.
239
240        @raises error.TestError if page is not ready after timeout.
241
242        """
243        self._tab.WaitForDocumentReadyStateToBeComplete()
244        utils.poll_for_condition(
245                lambda: self._tab.EvaluateJavaScript('pageReady'),
246                exception=error.TestError('Test page is not ready!'))
247
248
249    def _center_cursor(self):
250        """Playback mouse movement to center cursor.
251
252        Requres that self._emulate_mouse() has been called.
253
254        """
255        self.player.blocking_playback_of_default_file(
256                'mouse_center_cursor_gesture', input_type='mouse')
257
258
259    def _set_scroll(self, value, scroll_vertical=True):
260        """Set scroll position to given value.  Presuposes self._tab.
261
262        @param scroll_vertical: True for vertical scroll,
263                                False for horizontal Scroll.
264        @param value: True for enabled, False for disabled.
265
266         """
267        if scroll_vertical:
268            self._tab.ExecuteJavaScript(
269                'document.body.scrollTop=%s' % value)
270        else:
271            self._tab.ExecuteJavaScript(
272                'document.body.scrollLeft=%s' % value)
273
274
275    def _set_default_scroll_position(self, scroll_vertical=True):
276        """Set scroll position of page to default.  Presuposes self._tab.
277
278        @param scroll_vertical: True for vertical scroll,
279                                False for horizontal Scroll.
280        @raise: TestError if page is not set to default scroll position
281
282        """
283        total_tries = 2
284        for i in xrange(total_tries):
285            try:
286                self._set_scroll(self._DEFAULT_SCROLL, scroll_vertical)
287                self._wait_for_default_scroll_position(scroll_vertical)
288            except error.TestError as e:
289                if i == total_tries - 1:
290                   pos = self._get_scroll_position(scroll_vertical)
291                   logging.error('SCROLL POSITION: %s', pos)
292                   raise e
293            else:
294                 break
295
296
297    def _get_scroll_position(self, scroll_vertical=True):
298        """Return current scroll position of page.  Presuposes self._tab.
299
300        @param scroll_vertical: True for vertical scroll,
301                                False for horizontal Scroll.
302
303        """
304        if scroll_vertical:
305            return int(self._tab.EvaluateJavaScript('document.body.scrollTop'))
306        else:
307            return int(self._tab.EvaluateJavaScript('document.body.scrollLeft'))
308
309
310    def _wait_for_default_scroll_position(self, scroll_vertical=True):
311        """Wait for page to be at the default scroll position.
312
313        @param scroll_vertical: True for vertical scroll,
314                                False for horizontal scroll.
315
316        @raise: TestError if page either does not move or does not stop moving.
317
318        """
319        utils.poll_for_condition(
320                lambda: self._get_scroll_position(
321                        scroll_vertical) == self._DEFAULT_SCROLL,
322                exception=error.TestError('Page not set to default scroll!'))
323
324
325    def _wait_for_scroll_position_to_settle(self, scroll_vertical=True):
326        """Wait for page to move and then stop moving.
327
328        @param scroll_vertical: True for Vertical scroll and
329                                False for horizontal scroll.
330
331        @raise: TestError if page either does not move or does not stop moving.
332
333        """
334        # Wait until page starts moving.
335        utils.poll_for_condition(
336                lambda: self._get_scroll_position(
337                        scroll_vertical) != self._DEFAULT_SCROLL,
338                exception=error.TestError('No scrolling occurred!'), timeout=30)
339
340        # Wait until page has stopped moving.
341        self._previous = self._DEFAULT_SCROLL
342        def _movement_stopped():
343            current = self._get_scroll_position()
344            result = current == self._previous
345            self._previous = current
346            return result
347
348        utils.poll_for_condition(
349                lambda: _movement_stopped(), sleep_interval=1,
350                exception=error.TestError('Page did not stop moving!'),
351                timeout=30)
352
353
354    def cleanup(self):
355        self.player.close()
356