blob: 28b535f7b3cbaf18ef4d0dde880d324ba8bf974d [file] [log] [blame]
# Copyright 2017 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.
"""Tests touchscreen or stylus by drawing in any order or in spiral pattern.
Description
-----------
In this test, screen area is segmented into `x_segments` x `y_segments` blocks.
If argument `spiral_mode` is True, the operator has to swipe the blocks in
clockwise spiral pattern. Otherwise the operator has to touch all the blocks in
arbitrary order.
In `spiral_mode`, the pattern is:
1. Starting from upper-left block, move to rightmost block.
2. Then move down, left, up, to draw a outer rectangular circle.
3. Move to the inner upper-left block (1, 1), repeat 1-2.
4. Until the center block is reached.
The index of block (x, y) is defined as::
index = x + y * x_segments (number of blocks in x-axis).
For, example, a 3x3 grid::
0 1 2
3 4 5
6 7 8
The clockwise spiral drawing sequence is: 0, 1, 2, 5, 8, 7, 6, 3, 4.
There are two modes available: end-to-end mode and evdev mode.
- End-to-end mode uses Chrome touch event API.
- Evdev mode uses Linux evdev.
Test logic is in touchscreen.js.
Test Procedure
--------------
1. Once the test started, it would be set to fullscreen and shows
`x_segments` x `y_segments` grey blocks.
2. Draw these blocks green by touching them (or move your stylus to make it
hovering on a block in hover mode) in specified order. Test will pass after
all blocks being green.
3. If there is any problem with the touch device, press Escape to abort and
mark this test failed.
4. If `timeout_secs` is set and the test couldn't be passed in `timeout_secs`
seconds, the test will fail automatically.
Dependency
----------
- End-to-end mode is based on Chrome touch event API.
- Non end-to-end mode is based on Linux evdev.
Examples
--------
To test touchscreen with 30x20 blocks, add this in test list::
{
"pytest_name": "touchscreen",
"args": {
"y_segments": 30,
"x_segments": 20
}
}
To test touchscreen in end-to-end mode and cancel the time limit::
{
"pytest_name": "touchscreen",
"args": {
"e2e_mode": true,
"timeout_secs": null
}
}
To test touchscreen without spiral order restriction::
{
"pytest_name": "touchscreen",
"args": {
"spiral_mode": false
}
}
To test stylus in hover mode::
{
"pytest_name": "touchscreen",
"args": {
"stylus": true,
"hover_mode": true
}
}
Trouble Shooting
----------------
If you find the spiral test keeps failing, here are some tips:
1. Use end-to-end mode to see if it helps.
2. Use the tool `evtest` to check touch events reported by driver.
If seeing unexpected touch events in `evtest`, here are some thoughts:
1. Check if the motherboard and operator are properly grounded.
2. Remove all external connections to the DUT (including power adaptor, ethernet
cable, usb hub).
3. Check if there are noises coming from the grounding line. Maybe move the DUT
out of the manufacturing line to see if it helps.
4. Flash touchscreen firmware to a different version. Maybe it's too sensitive.
"""
import unittest
import factory_common # pylint: disable=unused-import
from cros.factory.external.evdev import ecodes # pylint: disable=E0611
from cros.factory.test import countdown_timer
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
_ID_CONTAINER = 'touchscreen-test-container'
# The style is in touchscreen.css.
# The layout contains one div for touchscreen.
_HTML_TOUCHSCREEN = (
'<link rel="stylesheet" type="text/css" href="touchscreen.css">'
'<div id="%s"></div>\n' % _ID_CONTAINER)
class StylusMonitor(touch_monitor.SingleTouchMonitor):
"""Stylus monitor."""
def __init__(self, device, ui, code):
"""Initialize.
Args:
device: evdev.InputDevice
ui: test_ui.UI
code: Which key to monitor: BTN_TOUCH (for touching) or BTN_TOOL_PEN
(for hovering).
"""
super(StylusMonitor, self).__init__(device)
self._ui = ui
self._code = code
# A boolean flag indicating if BTN_TOUCH or BTN_TOOL_PEN is on.
self._flag = self._state.keys[code]
def _EmitEvent(self, receiver):
state = self.GetState()
self._ui.CallJSFunction('goofyTouchListener', receiver, state.x, state.y)
def OnKey(self, code):
"""Called by Handler after state of a key changed."""
if code == self._code:
self._flag = not self._flag
if self._flag:
self._EmitEvent('touchStartHandler')
else:
self._EmitEvent('touchEndHandler')
def OnMove(self):
"""Called by Handler after X or Y coordinate changes."""
if self._flag:
self._EmitEvent('touchMoveHandler')
class TouchscreenMonitor(touch_monitor.MultiTouchMonitor):
"""Touchscreen monitor."""
def __init__(self, device, ui):
"""Initialize.
Args:
device: evdev.InputDevice
ui: test_ui.UI
"""
super(TouchscreenMonitor, self).__init__(device)
self._ui = ui
def _EmitEvent(self, receiver, slot_id):
slot = self.GetState().slots[slot_id]
self._ui.CallJSFunction('goofyTouchListener', receiver, slot.x, slot.y)
def OnNew(self, slot_id):
"""Called by Handler after a new contact comes."""
self._EmitEvent('touchStartHandler', slot_id)
def OnMove(self, slot_id):
"""Called by Handler after a contact moved."""
self._EmitEvent('touchMoveHandler', slot_id)
def OnLeave(self, slot_id):
"""Called by Handler after a contact leaves."""
self._EmitEvent('touchEndHandler', slot_id)
class TouchscreenTest(unittest.TestCase):
"""Tests touchscreen by drawing blocks in sequence.
Properties:
self._device: evdev.InputDevice
self._dispatcher: evdev_utils.InputDeviceDispatcher
self._monitor: StylusMonitor or TouchscreenMonitor
self._ui: test_ui.UI
"""
ARGS = [
Arg('x_segments', int, 'Number of segments in x-axis.', default=5),
Arg('y_segments', int, 'Number of segments in y-axis.', default=5),
Arg('retries', int, 'Number of retries (for spiral_mode).', default=5),
Arg('demo_interval_ms', int,
'Interval (ms) to show drawing pattern (for spiral mode). '
'<= 0 means no demo.',
default=150),
Arg('stylus', bool, 'Testing stylus or not.', default=False),
Arg('e2e_mode', bool,
'Perform end-to-end test or not (for touchscreen).',
default=False),
Arg('spiral_mode', bool,
'Do blocks need to be drawn in spiral order or not.',
default=True),
Arg('device_filter', (int, str), 'Evdev input event id or name.',
optional=True),
Arg('hover_mode', bool, 'Test hovering or touching (for stylus).',
default=False),
Arg('timeout_secs', (int, type(None)),
'Timeout for the test. None for no time limit.', default=20),
]
def setUp(self):
if self.args.stylus:
self._device = evdev_utils.FindDevice(self.args.device_filter,
evdev_utils.IsStylusDevice)
else:
if self.args.e2e_mode:
self._device = None
else:
self._device = evdev_utils.FindDevice(self.args.device_filter,
evdev_utils.IsTouchscreenDevice)
self._dispatcher = None
self._monitor = None
# Initialize frontend presentation.
self._ui = test_ui.UI()
self._ui.AppendHTML(_HTML_TOUCHSCREEN)
self._ui.CallJSFunction(
'setupTouchscreenTest', _ID_CONTAINER, self.args.x_segments,
self.args.y_segments, self.args.retries, self.args.demo_interval_ms,
self.args.e2e_mode, self.args.spiral_mode)
if self.args.timeout_secs:
countdown_timer.StartCountdownTimer(
self.args.timeout_secs, self.OnFailPressed, self._ui,
'touchscreen_countdown_timer')
def tearDown(self):
if self._dispatcher is not None:
self._dispatcher.close()
if self._device is not None:
self._device.ungrab()
def OnFailPressed(self):
"""Fails the test."""
self._ui.CallJSFunction('failTest')
def runTest(self):
if self._device is not None:
self._device = evdev_utils.DeviceReopen(self._device)
self._device.grab()
if self.args.stylus:
self._monitor = StylusMonitor(
self._device, self._ui,
ecodes.BTN_TOOL_PEN if self.args.hover_mode else ecodes.BTN_TOUCH)
else:
self._monitor = TouchscreenMonitor(self._device, self._ui)
self._dispatcher = evdev_utils.InputDeviceDispatcher(
self._device, self._monitor.Handler)
self._dispatcher.StartDaemon()
self._ui.BindKey(test_ui.ESCAPE_KEY, lambda _: self.OnFailPressed())
self._ui.Run()