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