blob: 50b0c1d371ffe6b51d8e9849600685f52a74ee1c [file] [log] [blame]
# Copyright (c) 2014 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.
from collections import defaultdict
import logging
import os
import tempfile
import subprocess
from autotest_lib.client.bin import test
from autotest_lib.client.bin import utils
from autotest_lib.client.common_lib import error
class touch_playback_test_base(test.test):
"""Base class for touch tests involving playback."""
version = 1
_PLAYBACK_COMMAND = 'evemu-play --insert-slot0 %s < %s'
_INPUTCONTROL = '/opt/google/input/inputcontrol'
_DEFAULT_SCROLL = 5000
@property
def _has_touchpad(self):
"""True if device under test has a touchpad; else False."""
return self._has_inputs['touchpad']
@property
def _has_touchscreen(self):
"""True if device under test has a touchscreen; else False."""
return self._has_inputs['touchscreen']
@property
def _has_mouse(self):
"""True if device under test has or emulates a USB mouse; else False."""
return self._has_inputs['mouse']
def _find_device_properties(self, device):
"""Given device (e.g. /dev/input/event7), return a string of properties.
@return: string of properties.
"""
temp_file = tempfile.NamedTemporaryFile()
filename = temp_file.name
evtest_process = subprocess.Popen(['evtest', device], stdout=temp_file)
def find_exit():
"""Polling function for end of output."""
interrupt_cmd = 'grep "interrupt to exit" %s | wc -l' % filename
line_count = utils.run(interrupt_cmd).stdout.strip()
return line_count != '0'
utils.poll_for_condition(find_exit)
evtest_process.kill()
temp_file.seek(0)
props = temp_file.read()
temp_file.close() #deletes the temporary file
return props
def _determine_input_type(self, event):
"""Find event's list of propertiles and return input type (if any)."""
props = self._find_device_properties(event)
if props.find('REL_X') >= 0 and props.find('REL_Y') >= 0:
if (props.find('ABS_MT_POSITION_X') >= 0 and
props.find('ABS_MT_POSITION_Y') >= 0):
return 'multitouch_mouse'
else:
return 'mouse'
if props.find('ABS_X') >= 0 and props.find('ABS_Y') >= 0:
if (props.find('BTN_STYLUS') >= 0 or
props.find('BTN_STYLUS2') >= 0 or
props.find('BTN_TOOL_PEN') >= 0):
return 'tablet'
if (props.find('ABS_PRESSURE') >= 0 or
props.find('BTN_TOUCH') >= 0):
if (props.find('BTN_LEFT') >= 0 or
props.find('BTN_MIDDLE') >= 0 or
props.find('BTN_RIGHT') >= 0 or
props.find('BTN_TOOL_FINGER') >= 0):
return 'touchpad'
else:
return 'touchscreen'
if props.find('BTN_LEFT') >= 0:
return 'touchscreen'
return
def warmup(self, mouse_props=None, mouse_name=''):
"""Determine the nodes of all present touch devices, if any.
Cycle through all possible /dev/input/event* and find which ones
are touchpads, touchscreens, mice, etc.
These events can be used for playback later.
Emulate a USB mouse if a property file is provided.
Check if the inputcontrol script is avaiable on the disk.
@param mouse_props: property file for a mouse to emulate. Created
using 'evemu-describe /dev/input/X'.
@param mouse_name: name of expected mouse.
"""
self._has_inputs = defaultdict(bool)
self._nodes = defaultdict(str)
self._names = defaultdict(str)
self._device_emulation_process = None
self._autotest_ext = None
self._has_inputcontrol = os.path.isfile(self._INPUTCONTROL)
# Emulate mouse if property file was provided.
if mouse_props:
logging.info('Emulating mouse: %s', mouse_props)
self._device_emulation_process = subprocess.Popen(
['evemu-device', mouse_props], stdout=subprocess.PIPE)
self._names['mouse'] = mouse_name
# Cycle through all possible input devices.
input_events = utils.run('ls /dev/input/event*').stdout.strip().split()
for event in input_events:
input_type = self._determine_input_type(event)
if input_type:
logging.info('Found %s at %s.', input_type, event)
class_folder = event.replace('dev', 'sys/class')
name_file = os.path.join(class_folder, 'device', 'name')
name = 'unknown'
if os.path.isfile(name_file):
name = utils.run('cat %s' % name_file).stdout.strip()
# If a particular device is expected, make sure this matches.
if self._names[input_type]:
if self._names[input_type] != name:
continue
# Save this device information for later use.
self._has_inputs[input_type] = True
self._nodes[input_type] = event
self._names[input_type] = name
logging.info('%s is %s.', input_type, name)
def _playback(self, filepath, touch_type='touchpad'):
"""Playback a given set of touch movements.
@param filepath: path to the movements file on the DUT.
@param touch_type: name of device type; 'touchpad' by default.
Types are returned by the _determine_input_type()
function.
self._has_inputs[touch_type] must be True.
"""
assert(self._has_inputs[touch_type])
node = self._nodes[touch_type]
logging.info('Playing back finger-movement on %s, file=%s.', node,
filepath)
utils.run(self._PLAYBACK_COMMAND % (node, filepath))
def _set_touch_setting_by_inputcontrol(self, setting, value):
"""Set a given touch setting the given value by inputcontrol.
@param setting: Name of touch setting, e.g. 'tapclick'.
@param value: True for enabled, False for disabled.
"""
cmd_value = 1 if value else 0
utils.run('%s --%s %d' % (self._INPUTCONTROL, setting, cmd_value))
logging.info('%s turned %s.', setting, 'on' if value else 'off')
def _set_touch_setting(self, inputcontrol_setting, autotest_ext_setting,
value):
"""Set a given touch setting the given value.
@param inputcontrol_setting: Name of touch setting for the inputcontrol
script, e.g. 'tapclick'.
@param autotest_ext_setting: Name of touch setting for the autotest
extension, e.g. 'TapToClick'.
@param value: True for enabled, False for disabled.
"""
if self._has_inputcontrol:
self._set_touch_setting_by_inputcontrol(inputcontrol_setting, value)
elif self._autotest_ext is not None:
self._autotest_ext.EvaluateJavaScript(
'chrome.autotestPrivate.set%s(%s);'
% (autotest_ext_setting, ("%s" % value).lower()))
else:
raise error.TestFail('Both the inputcontrol and the autotest '
'extension are not availble')
def _set_australian_scrolling(self, value):
"""Set australian scrolling to the given value.
@param value: True for enabled, False for disabled.
"""
self._set_touch_setting('australian_scrolling', 'NaturalScroll', value)
def _set_tap_to_click(self, value):
"""Set tap-to-click to the given value.
@param value: True for enabled, False for disabled.
"""
self._set_touch_setting('tapclick', 'TapToClick', value)
def _set_tap_dragging(self, value):
"""Set tap dragging to the given value.
@param value: True for enabled, False for disabled.
"""
self._set_touch_setting('tapdrag', 'TapDragging', value)
def cleanup(self):
if self._device_emulation_process:
self._device_emulation_process.kill()
def _reload_page(self):
"""Reloads test page. Presuposes self._tab.
@raise: TestError if page is not reset.
"""
self._tab.Navigate(self._tab.url)
self._tab.WaitForDocumentReadyStateToBeComplete()
def _set_autotest_ext(self, ext):
"""Set the autotest extension.
@ext: the autotest extension object.
"""
self._autotest_ext = ext
def _get_scroll_position(self):
"""Return current scroll position of page. Presuposes self._tab."""
return int(self._tab.EvaluateJavaScript('document.body.scrollTop'))
def _wait_for_default_scroll_position(self):
"""Wait for page to be the default scroll position.
@raise: TestError if page either does not move or does not stop moving.
"""
utils.poll_for_condition(
lambda: self._get_scroll_position() == self._DEFAULT_SCROLL,
exception=error.TestError('Page not set to default scroll!'))
def _wait_for_scroll_position_to_settle(self):
"""Wait for page to move and then stop moving.
@raise: TestError if page either does not move or does not stop moving.
"""
# Wait until page starts moving.
utils.poll_for_condition(
lambda: self._get_scroll_position() != self._DEFAULT_SCROLL,
exception=error.TestError('No scrolling occurred!'))
# Wait until page has stopped moving.
self._previous = self._DEFAULT_SCROLL
def _movement_stopped():
current = self._get_scroll_position()
result = current == self._previous
self._previous = current
return result
utils.poll_for_condition(
lambda: _movement_stopped(), sleep_interval=1,
exception=error.TestError('Page did not stop moving!'))