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
5"""An adapter to access the local display facade."""
6
7import logging
8import tempfile
9from PIL import Image
10
11from autotest_lib.client.common_lib import error
12from autotest_lib.client.cros.multimedia import display_facade_native
13from autotest_lib.client.cros.multimedia import facade_resource
14from autotest_lib.client.cros.multimedia.display_info import DisplayInfo
15from autotest_lib.client.cros.power import sys_power
16
17
18class DisplayFacadeLocalAdapter(object):
19    """DisplayFacadeLocalAdapter is an adapter to control the local display.
20
21    Methods with non-native-type arguments go to this class and do some
22    conversion; otherwise, go to the DisplayFacadeNative class.
23    """
24
25    def __init__(self, chrome):
26        """Construct a DisplayFacadeLocalAdapter.
27
28        @param chrome: A Chrome object.
29        """
30        # Create a DisplayFacadeNative object as a component such that this
31        # class can expose and manipulate its interfaces.
32        self._display_component = display_facade_native.DisplayFacadeNative(
33                facade_resource.FacadeResource(chrome_object=chrome))
34
35
36    def get_external_connector_name(self):
37        """Gets the name of the external output connector.
38
39        @return The external output connector name as a string; False if nothing
40                is connected.
41        """
42        return self._display_component.get_external_connector_name()
43
44
45    def get_internal_connector_name(self):
46        """Gets the name of the internal output connector.
47
48        @return The internal output connector name as a string; False if nothing
49                is connected.
50        """
51        return self._display_component.get_internal_connector_name()
52
53
54    def move_to_display(self, display_id):
55        """Moves the current window to the indicated display.
56
57        @param display_id: The id of the indicated display.
58        """
59        self._display_component.move_to_display(display_id)
60
61
62    def create_window(self, url='chrome://newtab'):
63        """Creates a new window from chrome.windows.create API.
64
65        @param url: Optional URL for the new window.
66
67        @return Identifier for the new window.
68        """
69        return self._display_component.create_window(url)
70
71
72    def update_window(self, window_id, state=None, bounds=None):
73        """Updates an existing window using the chrome.windows.update API.
74
75        @param window_id: Identifier for the window to update.
76        @param state: Optional string to set the state such as 'normal',
77                      'maximized', or 'fullscreen'.
78        @param bounds: Optional dictionary with keys top, left, width, and
79                       height to reposition the window.
80        """
81        self._display_component.update_window(window_id, state, bounds)
82
83
84    def set_fullscreen(self, is_fullscreen):
85        """Sets the current window to full screen.
86
87        @param is_fullscreen: True or False to indicate fullscreen state.
88        @return True if success, False otherwise.
89        """
90        return self._display_component.set_fullscreen(is_fullscreen)
91
92
93    def load_url(self, url):
94        """Loads the given url in a new tab. The new tab will be active.
95
96        @param url: The url to load as a string.
97        @return a str, the tab descriptor of the opened tab.
98        """
99        return self._display_component.load_url(url)
100
101
102    def load_calibration_image(self, resolution):
103        """Load a full screen calibration image from the HTTP server.
104
105        @param resolution: A tuple (width, height) of resolution.
106        @return a str, the tab descriptor of the opened tab.
107        """
108        return self._display_component.load_calibration_image(resolution)
109
110
111    def load_color_sequence(self, tab_descriptor, color_sequence):
112        """Displays a series of colors on full screen on the tab.
113        tab_descriptor is returned by any open tab API of display facade.
114        e.g.,
115        tab_descriptor = load_url('about:blank')
116        load_color_sequence(tab_descriptor, color)
117
118        @param tab_descriptor: Indicate which tab to test.
119        @param color_sequence: An integer list for switching colors.
120        @return A list of the timestamp for each switch.
121        """
122        return self._display_component.load_color_sequence(tab_descriptor,
123                                                           color_sequence)
124
125
126    def close_tab(self, tab_descriptor):
127        """Disables fullscreen and closes the tab of the given tab descriptor.
128        tab_descriptor is returned by any open tab API of display facade.
129        e.g.,
130        1.
131        tab_descriptor = load_url(url)
132        close_tab(tab_descriptor)
133
134        2.
135        tab_descriptor = load_calibration_image(resolution)
136        close_tab(tab_descriptor)
137
138        @param tab_descriptor: Indicate which tab to close.
139        """
140        self._display_component.close_tab(tab_descriptor)
141
142
143    def is_mirrored_enabled(self):
144        """Checks the mirrored state.
145
146        @return True if mirrored mode is enabled.
147        """
148        return self._display_component.is_mirrored_enabled()
149
150
151    def set_mirrored(self, is_mirrored):
152        """Sets mirrored mode.
153
154        @param is_mirrored: True or False to indicate mirrored state.
155        @throws error.TestError when the call fails.
156        """
157        if not self._display_component.set_mirrored(is_mirrored):
158            raise error.TestError('Failed to set_mirrored(%s)' % is_mirrored)
159
160
161    def is_display_primary(self, internal=True):
162        """Checks if internal screen is primary display.
163
164        @param internal: is internal/external screen primary status requested
165        @return boolean True if internal display is primary.
166        """
167        return self._display_component.is_display_primary(internal)
168
169
170    def suspend_resume(self, suspend_time=10):
171        """Suspends the DUT for a given time in second.
172
173        @param suspend_time: Suspend time in second.
174        """
175        try:
176            self._display_component.suspend_resume(suspend_time)
177        except sys_power.SpuriousWakeupError as e:
178            # Log suspend/resume errors but continue the test.
179            logging.error('suspend_resume error: %s', str(e))
180
181
182    def suspend_resume_bg(self, suspend_time=10):
183        """Suspends the DUT for a given time in second in the background.
184
185        @param suspend_time: Suspend time in second, default: 10s.
186        """
187        self._display_component.suspend_resume_bg(suspend_time)
188
189
190    def wait_external_display_connected(self, display):
191        """Waits for the specified display to be connected.
192
193        @param display: The display name as a string, like 'HDMI1', or
194                        False if no external display is expected.
195        @return: True if display is connected; False otherwise.
196        """
197        return self._display_component.wait_external_display_connected(display)
198
199
200    def hide_cursor(self):
201        """Hides mouse cursor by sending a keystroke."""
202        self._display_component.hide_cursor()
203
204
205    def hide_typing_cursor(self):
206        """Hides typing cursor by moving outside typing bar."""
207        self._display_component.hide_typing_cursor()
208
209
210    def set_content_protection(self, state):
211        """Sets the content protection of the external screen.
212
213        @param state: One of the states 'Undesired', 'Desired', or 'Enabled'
214        """
215        self._display_component.set_content_protection(state)
216
217
218    def get_content_protection(self):
219        """Gets the state of the content protection.
220
221        @param output: The output name as a string.
222        @return: A string of the state, like 'Undesired', 'Desired', or 'Enabled'.
223                 False if not supported.
224        """
225        return self._display_component.get_content_protection()
226
227
228    def _take_screenshot(self, screenshot_func):
229        """Gets screenshot from frame buffer.
230
231        @param screenshot_func: function to take a screenshot and save the image
232                to specified path. Usage: screenshot_func(path).
233
234        @return: An Image object.
235                 Notice that the returned image may not be in RGB format,
236                 depending on PIL implementation.
237        """
238        with tempfile.NamedTemporaryFile(suffix='.png') as f:
239            screenshot_func(f.name)
240            return Image.open(f.name)
241
242
243    def capture_internal_screen(self):
244        """Captures the internal screen framebuffer.
245
246        @return: An Image object.
247        """
248        screenshot_func = self._display_component.take_internal_screenshot
249        return self._take_screenshot(screenshot_func)
250
251
252    # TODO(ihf): This function needs to be fixed for multiple screens.
253    def capture_external_screen(self):
254        """Captures the external screen framebuffer.
255
256        @return: An Image object.
257        """
258        screenshot_func = self._display_component.take_external_screenshot
259        return self._take_screenshot(screenshot_func)
260
261
262    def capture_calibration_image(self):
263        """Captures the calibration image.
264
265        @return: An Image object.
266        """
267        screenshot_func = self._display_component.save_calibration_image
268        return self._take_screenshot(screenshot_func)
269
270
271    def get_external_resolution(self):
272        """Gets the resolution of the external screen.
273
274        @return The resolution tuple (width, height) or None if no external
275                display is connected.
276        """
277        resolution = self._display_component.get_external_resolution()
278        return tuple(resolution) if resolution else None
279
280
281    def get_internal_resolution(self):
282        """Gets the resolution of the internal screen.
283
284        @return The resolution tuple (width, height) or None if no internal
285                display.
286        """
287        resolution = self._display_component.get_internal_resolution()
288        return tuple(resolution) if resolution else None
289
290
291    def set_resolution(self, display_id, width, height):
292        """Sets the resolution on the specified display.
293
294        @param display_id: id of the display to set resolutions for.
295        @param width: width of the resolution
296        @param height: height of the resolution
297        """
298        self._display_component.set_resolution(display_id, width, height)
299
300
301    def get_display_info(self):
302        """Gets the information of all the displays that are connected to the
303                DUT.
304
305        @return: list of object DisplayInfo for display informtion
306        """
307        return map(DisplayInfo, self._display_component.get_display_info())
308
309
310    def get_display_modes(self, display_id):
311        """Gets the display modes of the specified display.
312
313        @param display_id: id of the display to get modes from; the id is from
314            the DisplayInfo list obtained by get_display_info().
315
316        @return: list of DisplayMode dicts.
317        """
318        return self._display_component.get_display_modes(display_id)
319
320
321    def get_available_resolutions(self, display_id):
322        """Gets the resolutions from the specified display.
323
324        @param display_id: id of the display to get modes from.
325
326        @return a list of (width, height) tuples.
327        """
328        return [tuple(r) for r in
329                self._display_component.get_available_resolutions(display_id)]
330
331
332    def get_display_rotation(self, display_id):
333        """Gets the display rotation for the specified display.
334
335        @param display_id: id of the display to get modes from.
336
337        @return: Degree of rotation.
338        """
339        return self._display_component.get_display_rotation(display_id)
340
341
342    def set_display_rotation(self, display_id, rotation,
343                             delay_before_rotation=0, delay_after_rotation=0):
344        """Sets the display rotation for the specified display.
345
346        @param display_id: id of the display to get modes from.
347        @param rotation: degree of rotation
348        @param delay_before_rotation: time in second for delay before rotation
349        @param delay_after_rotation: time in second for delay after rotation
350        """
351        self._display_component.set_display_rotation(
352                display_id, rotation, delay_before_rotation,
353                delay_after_rotation)
354
355
356    def get_internal_display_id(self):
357        """Gets the internal display id.
358
359        @return the id of the internal display.
360        """
361        return self._display_component.get_internal_display_id()
362
363
364    def get_first_external_display_id(self):
365        """Gets the first external display id.
366
367        @return the id of the first external display; -1 if not found.
368        """
369        return self._display_component.get_first_external_display_id()
370
371
372    def reset_connector_if_applicable(self, connector_type):
373        """Resets Type-C video connector from host end if applicable.
374
375        It's the workaround sequence since sometimes Type-C dongle becomes
376        corrupted and needs to be re-plugged.
377
378        @param connector_type: A string, like "VGA", "DVI", "HDMI", or "DP".
379        """
380        return self._display_component.reset_connector_if_applicable(
381                connector_type)
382