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