blob: 5239b7eacc3c96dba14d77508e2a9687b40749d0 [file] [log] [blame]
# Copyright 2016 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.
"""Test stylus functionality.
Description
-----------
Verifies if stylus is functional by asking operator to draw specified lines or
shapes using stylus.
For EMR stylus, drawing a diagonal line from left-bottom to right-top corner
should be sufficient to validate all scan lines. But for clamshells with hall
sensor, the magnet may cause EMR stylus to be non-functional in particular area.
To test that, set argument `endpoints_ratio` to build the lines for operator to
draw.
Test Procedure
--------------
1. When started, a diagonal line is displayed on screen.
2. Operator must use stylus to draw and follow the displayed line.
3. If the stylus moved too far (specified in argument `error_margin`) from the
requested path, test will fail.
Dependency
----------
- Based on Linux evdev.
Examples
--------
To check stylus functionality by drawing a diagonal line, add this in test
list::
{
"pytest_name": "stylus"
}
To check if the magnet in left side will cause problems, add this in test list
to draw a line from left-top to left-bottom::
{
"pytest_name": "stylus",
"args": {
"endpoints_ratio": [
[0, 0],
[0, 1]
]
}
}
"""
import threading
import factory_common # pylint: disable=unused-import
from cros.factory.external import evdev
from cros.factory.test.i18n import _
from cros.factory.test import test_case
from cros.factory.test import test_ui
from cros.factory.test.utils import evdev_utils
from cros.factory.test.utils import touch_monitor
from cros.factory.utils.arg_utils import Arg
from cros.factory.utils import sync_utils
class StylusMonitor(touch_monitor.SingleTouchMonitor):
def __init__(self, device, ui):
super(StylusMonitor, self).__init__(device)
self._ui = ui
self._lock = threading.RLock()
self._buffer = []
@sync_utils.Synchronized
def OnMove(self):
"""See SingleTouchMonitor.OnMove."""
state = self.GetState()
if state.keys[evdev.ecodes.BTN_TOUCH]:
# Instead of directly call JavaScript function 'handler' here, we buffer
# the events to reduce the latency from CallJSFunction.
self._buffer.append([state.x, state.y])
@sync_utils.Synchronized
def Flush(self):
if self._buffer:
self._ui.CallJSFunction('handler', self._buffer)
self._buffer = []
class StylusTest(test_case.TestCase):
"""Stylus factory test."""
ARGS = [
Arg('device_filter', (int, str), 'Stylus input event id or evdev name.',
default=None),
Arg('error_margin', int,
'Maximum tolerable distance to the diagonal line (in pixel).',
default=25),
Arg('begin_ratio', float,
'The beginning position of the diagonal line segment to check. '
'Should be in (0, 1).',
default=0.01),
Arg('end_ratio', float,
'The ending position of the diagonal line segment to check. '
'Should be in (0, 1).',
default=0.99),
Arg('step_ratio', float,
'If the distance between an input event to the latest accepted '
'input event is larger than this size, it would be ignored. '
'Should be in (0, 1).',
default=0.01),
Arg('endpoints_ratio', list,
'A list of two pairs, each pair contains the X and Y coordinates '
'ratio of an endpoint of the line segment for operator to draw. '
'Both endpoints must be on the border '
'(e.g., X=0 or X=1 or Y=0 or Y=1).',
default=[[0, 1], [1, 0]]),
Arg('autostart', bool,
'Starts the test automatically without prompting. Operators can '
'still press ESC to fail the test.',
default=False),
Arg('flush_interval', float,
'The time interval of flushing event buffers.',
default=0.1)
]
def setUp(self):
self._device = evdev_utils.FindDevice(self.args.device_filter,
evdev_utils.IsStylusDevice)
self._monitor = None
self._dispatcher = None
assert self.args.error_margin >= 0
assert 0 < self.args.begin_ratio < self.args.end_ratio < 1
assert 0 < self.args.step_ratio < 1
assert len(self.args.endpoints_ratio) == 2
assert self.args.endpoints_ratio[0] != self.args.endpoints_ratio[1]
for point in self.args.endpoints_ratio:
assert isinstance(point, list) and len(point) == 2
assert all(0 <= x_or_y <= 1 for x_or_y in point)
assert any(x_or_y in [0, 1] for x_or_y in point)
assert self.args.flush_interval > 0
def tearDown(self):
if self._dispatcher is not None:
self._dispatcher.close()
self._device.ungrab()
def runTest(self):
self.ui.BindStandardFailKeys()
if not self.args.autostart:
self.ui.SetHTML(
_('Please extend the green line with stylus to the other end.<br>'
'Stay between the two red lines.<br>'
'Press SPACE to start; Esc to fail.'),
id='msg')
self.ui.WaitKeysOnce(test_ui.SPACE_KEY)
self.ui.CallJSFunction('setupStylusTest',
self.args.error_margin, self.args.begin_ratio,
self.args.end_ratio, self.args.step_ratio,
self.args.endpoints_ratio)
self._device = evdev_utils.DeviceReopen(self._device)
self._device.grab()
self._monitor = StylusMonitor(self._device, self.ui)
self._dispatcher = evdev_utils.InputDeviceDispatcher(self._device,
self._monitor.Handler)
self._dispatcher.StartDaemon()
while True:
self._monitor.Flush()
self.Sleep(self.args.flush_interval)