1# Copyright 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 time
7
8from autotest_lib.client.bin import utils
9from autotest_lib.client.cros.chameleon import screen_utility_factory
10
11
12class ChameleonScreenTest(object):
13    """Utility to test the screen between Chameleon and CrOS.
14
15    This class contains the screen-related testing operations.
16
17    """
18    # Time in seconds to wait for notation bubbles, including bubbles for
19    # external detection, mirror mode and fullscreen, to disappear.
20    _TEST_IMAGE_STABILIZE_TIME = 10
21
22    def __init__(self, chameleon_port, display_facade, output_dir):
23        """Initializes the ScreenUtilityFactory objects."""
24        self._display_facade = display_facade
25        factory = screen_utility_factory.ScreenUtilityFactory(
26                chameleon_port, display_facade)
27        self._resolution_comparer = factory.create_resolution_comparer()
28        self._screen_comparer = factory.create_screen_comparer(output_dir)
29        self._mirror_comparer = factory.create_mirror_comparer(output_dir)
30        self._calibration_image_tab_descriptor = None
31
32
33    def test_resolution(self, expected_resolution):
34        """Tests if the resolution of Chameleon matches with the one of CrOS.
35
36        @param expected_resolution: A tuple (width, height) for the expected
37                                    resolution.
38        @return: None if the check passes; otherwise, a string of error message.
39        """
40        return self._resolution_comparer.compare(expected_resolution)
41
42
43    def test_screen(self, expected_resolution, test_mirrored=None,
44                    error_list=None):
45        """Tests if the screen of Chameleon matches with the one of CrOS.
46
47        @param expected_resolution: A tuple (width, height) for the expected
48                                    resolution.
49        @param test_mirrored: True to test mirrored mode. False not to. None
50                              to test mirrored mode iff the current mode is
51                              mirrored.
52        @param error_list: A list to append the error message to or None.
53        @return: None if the check passes; otherwise, a string of error message.
54        """
55        if test_mirrored is None:
56            test_mirrored = self._display_facade.is_mirrored_enabled()
57
58        error = self._resolution_comparer.compare(expected_resolution)
59        if not error:
60            # Do two screen comparisons with and without hiding cursor, to
61            # work-around some devices still showing cursor on CrOS FB.
62            # TODO: Remove this work-around once crosbug/p/34524 got fixed.
63            error = self._screen_comparer.compare()
64            if error:
65                logging.info('Hide cursor and do screen comparison again...')
66                self._display_facade.hide_cursor()
67                error = self._screen_comparer.compare()
68        if not error and test_mirrored:
69            error = self._mirror_comparer.compare()
70        if error and error_list is not None:
71            error_list.append(error)
72        return error
73
74
75    def load_test_image(self, image_size, test_mirrored=None):
76        """Loads calibration image on the CrOS with logging
77
78        @param image_size: A tuple (width, height) conforms the resolution.
79        @param test_mirrored: True to test mirrored mode. False not to. None
80                              to test mirrored mode iff the current mode is
81                              mirrored.
82        """
83        if test_mirrored is None:
84            test_mirrored = self._display_facade.is_mirrored_enabled()
85        self._calibration_image_tab_descriptor = \
86            self._display_facade.load_calibration_image(image_size)
87        if not test_mirrored:
88            self._display_facade.move_to_display(
89                    self._display_facade.get_first_external_display_index())
90        self._display_facade.set_fullscreen(True)
91        logging.info('Waiting for calibration image to stabilize...')
92        time.sleep(self._TEST_IMAGE_STABILIZE_TIME)
93
94
95    def unload_test_image(self):
96        """Closes the tab in browser to unload the fullscreen test image."""
97        self._display_facade.close_tab(self._calibration_image_tab_descriptor)
98
99
100    def test_screen_with_image(self, expected_resolution, test_mirrored=None,
101                               error_list=None, chameleon_supported=True,
102                               retry_count=2):
103        """Tests the screen with image loaded.
104
105        @param expected_resolution: A tuple (width, height) for the expected
106                                    resolution.
107        @param test_mirrored: True to test mirrored mode. False not to. None
108                              to test mirrored mode iff the current mode is
109                              mirrored.
110        @param error_list: A list to append the error message to or None.
111        @param retry_count: A count to retry the screen test.
112        @param chameleon_supported: Whether resolution is supported by
113                                    chameleon. The DP RX doesn't support
114                                    4K resolution. The max supported resolution
115                                    is 2560x1600. See crbug/585900.
116        @return: None if the check passes; otherwise, a string of error message.
117        """
118        if test_mirrored is None:
119            test_mirrored = self._display_facade.is_mirrored_enabled()
120
121        if test_mirrored:
122            test_image_size = self._display_facade.get_internal_resolution()
123        else:
124            # DUT needs time to respond to the plug event
125            test_image_size = utils.wait_for_value_changed(
126                    self._display_facade.get_external_resolution,
127                    old_value=None)
128        error = None
129        if test_image_size != expected_resolution:
130            error = ('Screen size %s is not as expected %s!'
131                     % (str(test_image_size), str(expected_resolution)))
132            if test_mirrored:
133                # For the case of mirroring, depending on hardware vs
134                # software mirroring, screen size can be different.
135                logging.info('Warning: %s', error)
136                error = None
137            else:
138                error_list.append(error)
139
140        if chameleon_supported:
141            error = self._resolution_comparer.compare(expected_resolution)
142            if not error:
143                while retry_count:
144                    retry_count = retry_count - 1
145                    try:
146                        self.load_test_image(test_image_size)
147                        error = self.test_screen(expected_resolution, test_mirrored)
148                        if error is None:
149                            return error
150                        elif retry_count > 0:
151                            logging.info('Retry screen comparison again...')
152                    finally:
153                        self.unload_test_image()
154
155            if error and error_list is not None:
156                error_list.append(error)
157        return error
158
159
160    def check_external_display_connected(self, expected_display,
161                                         error_list=None):
162        """Checks the given external display connected.
163
164        @param expected_display: Name of the expected display or False
165                if no external display is expected.
166        @param error_list: A list to append the error message to or None.
167        @return: None if the check passes; otherwise, a string of error message.
168        """
169        error = None
170        if not self._display_facade.wait_external_display_connected(
171                expected_display):
172            error = 'Waited for display %s but timed out' % expected_display
173
174        if error and error_list is not None:
175            logging.error(error)
176            error_list.append(error)
177        return error
178