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, retry_count=2):
102        """Tests the screen with image loaded.
103
104        @param expected_resolution: A tuple (width, height) for the expected
105                                    resolution.
106        @param test_mirrored: True to test mirrored mode. False not to. None
107                              to test mirrored mode iff the current mode is
108                              mirrored.
109        @param error_list: A list to append the error message to or None.
110        @param retry_count: A count to retry the screen test.
111        @return: None if the check passes; otherwise, a string of error message.
112        """
113        if test_mirrored is None:
114            test_mirrored = self._display_facade.is_mirrored_enabled()
115
116        if test_mirrored:
117            test_image_size = self._display_facade.get_internal_resolution()
118        else:
119            # DUT needs time to respond to the plug event
120            test_image_size = utils.wait_for_value_changed(
121                    self._display_facade.get_external_resolution,
122                    old_value=None)
123
124        error = self._resolution_comparer.compare(expected_resolution)
125        if not error:
126            while retry_count:
127                retry_count = retry_count - 1
128                try:
129                    self.load_test_image(test_image_size)
130                    error = self.test_screen(expected_resolution, test_mirrored)
131                    if error is None:
132                        return error
133                    elif retry_count > 0:
134                        logging.info('Retry screen comparison again...')
135                finally:
136                    self.unload_test_image()
137
138        if error and error_list is not None:
139            error_list.append(error)
140        return error
141
142
143    def check_external_display_connected(self, expected_display,
144                                         error_list=None):
145        """Checks the given external display connected.
146
147        @param expected_display: Name of the expected display or False
148                if no external display is expected.
149        @param error_list: A list to append the error message to or None.
150        @return: None if the check passes; otherwise, a string of error message.
151        """
152        error = None
153        if not self._display_facade.wait_external_display_connected(
154                expected_display):
155            error = 'Waited for display %s but timed out' % expected_display
156
157        if error and error_list is not None:
158            logging.error(error)
159            error_list.append(error)
160        return error
161