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