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"""Facade to access the display-related functionality.""" 7 8from __future__ import absolute_import 9from __future__ import division 10from __future__ import print_function 11import logging 12import multiprocessing 13import numpy 14import os 15import re 16import shutil 17import time 18import json 19from autotest_lib.client.bin import utils 20from autotest_lib.client.common_lib import error 21from autotest_lib.client.common_lib import utils as common_utils 22from autotest_lib.client.common_lib.cros import retry 23from autotest_lib.client.cros import constants 24from autotest_lib.client.cros.graphics import graphics_utils 25from autotest_lib.client.cros.multimedia import facade_resource 26from autotest_lib.client.cros.multimedia import image_generator 27from autotest_lib.client.cros.power import sys_power 28from six.moves import range 29from telemetry.internal.browser import web_contents 30 31class TimeoutException(Exception): 32 """Timeout Exception class.""" 33 pass 34 35 36_FLAKY_CALL_RETRY_TIMEOUT_SEC = 60 37_FLAKY_DISPLAY_CALL_RETRY_DELAY_SEC = 2 38 39_retry_display_call = retry.retry( 40 (KeyError, error.CmdError), 41 timeout_min=_FLAKY_CALL_RETRY_TIMEOUT_SEC / 60.0, 42 delay_sec=_FLAKY_DISPLAY_CALL_RETRY_DELAY_SEC) 43 44 45class DisplayFacadeNative(object): 46 """Facade to access the display-related functionality. 47 48 The methods inside this class only accept Python native types. 49 """ 50 51 CALIBRATION_IMAGE_PATH = '/tmp/calibration.png' 52 MINIMUM_REFRESH_RATE_EXPECTED = 25.0 53 DELAY_TIME = 3 54 MAX_TYPEC_PORT = 6 55 56 def __init__(self, resource): 57 """Initializes a DisplayFacadeNative. 58 59 @param resource: A FacadeResource object. 60 """ 61 self._resource = resource 62 self._image_generator = image_generator.ImageGenerator() 63 64 65 @facade_resource.retry_chrome_call 66 def get_display_info(self): 67 """Gets the display info from Chrome.system.display API. 68 69 @return array of dict for display info. 70 """ 71 extension = self._resource.get_extension( 72 constants.DISPLAY_TEST_EXTENSION) 73 extension.ExecuteJavaScript('window.__display_info = null;') 74 extension.ExecuteJavaScript( 75 "chrome.system.display.getInfo(function(info) {" 76 "window.__display_info = info;})") 77 utils.wait_for_value(lambda: ( 78 extension.EvaluateJavaScript("window.__display_info") != None), 79 expected_value=True) 80 return extension.EvaluateJavaScript("window.__display_info") 81 82 83 @facade_resource.retry_chrome_call 84 def get_window_info(self): 85 """Gets the current window info from Chrome.system.window API. 86 87 @return a dict for the information of the current window. 88 """ 89 extension = self._resource.get_extension() 90 extension.ExecuteJavaScript('window.__window_info = null;') 91 extension.ExecuteJavaScript( 92 "chrome.windows.getCurrent(function(info) {" 93 "window.__window_info = info;})") 94 utils.wait_for_value(lambda: ( 95 extension.EvaluateJavaScript("window.__window_info") != None), 96 expected_value=True) 97 return extension.EvaluateJavaScript("window.__window_info") 98 99 100 @facade_resource.retry_chrome_call 101 def create_window(self, url='chrome://newtab'): 102 """Creates a new window from chrome.windows.create API. 103 104 @param url: Optional URL for the new window. 105 106 @return Identifier for the new window. 107 108 @raise TimeoutException if it fails. 109 """ 110 extension = self._resource.get_extension() 111 112 extension.ExecuteJavaScript( 113 """ 114 var __new_window_id = null; 115 chrome.windows.create( 116 {url: '%s'}, 117 function(win) { 118 __new_window_id = win.id}); 119 """ % (url) 120 ) 121 extension.WaitForJavaScriptCondition( 122 "__new_window_id !== null", 123 timeout=web_contents.DEFAULT_WEB_CONTENTS_TIMEOUT) 124 125 return extension.EvaluateJavaScript("__new_window_id") 126 127 128 @facade_resource.retry_chrome_call 129 def update_window(self, window_id, state=None, bounds=None): 130 """Updates an existing window using the chrome.windows.update API. 131 132 @param window_id: Identifier for the window to update. 133 @param state: Optional string to set the state such as 'normal', 134 'maximized', or 'fullscreen'. 135 @param bounds: Optional dictionary with keys top, left, width, and 136 height to reposition the window. 137 138 @return True if success. 139 140 @raise TimeoutException if it fails. 141 """ 142 extension = self._resource.get_extension() 143 params = {} 144 145 if state: 146 params['state'] = state 147 if bounds: 148 params['top'] = bounds['top'] 149 params['left'] = bounds['left'] 150 params['width'] = bounds['width'] 151 params['height'] = bounds['height'] 152 153 if not params: 154 logging.info('Nothing to update for window_id={}'.format(window_id)) 155 return True 156 157 extension.ExecuteJavaScript( 158 """ 159 var __status = 'Running'; 160 chrome.windows.update(%d, %s, 161 function(win) { 162 __status = 'Done'}); 163 """ % (window_id, json.dumps(params)) 164 ) 165 extension.WaitForJavaScriptCondition( 166 "__status == 'Done'", 167 timeout=web_contents.DEFAULT_WEB_CONTENTS_TIMEOUT) 168 169 return True 170 171 172 def _get_display_by_id(self, display_id): 173 """Gets a display by ID. 174 175 @param display_id: id of the display. 176 177 @return: A dict of various display info. 178 """ 179 for display in self.get_display_info(): 180 if display['id'] == display_id: 181 return display 182 raise RuntimeError('Cannot find display ' + display_id) 183 184 185 def get_display_modes(self, display_id): 186 """Gets all the display modes for the specified display. 187 188 @param display_id: id of the display to get modes from. 189 190 @return: A list of DisplayMode dicts. 191 """ 192 display = self._get_display_by_id(display_id) 193 return display['modes'] 194 195 196 def get_display_rotation(self, display_id): 197 """Gets the display rotation for the specified display. 198 199 @param display_id: id of the display to get modes from. 200 201 @return: Degree of rotation. 202 """ 203 display = self._get_display_by_id(display_id) 204 return display['rotation'] 205 206 207 def get_display_notifications(self): 208 """Gets the display notifications 209 210 @return: Returns a list of display related notifications only. 211 """ 212 display_notifications = [] 213 for notification in self._resource.get_visible_notifications(): 214 if notification['id'] == 'chrome://settings/display': 215 display_notifications.append(notification) 216 return display_notifications 217 218 219 def set_display_rotation(self, display_id, rotation, 220 delay_before_rotation=0, delay_after_rotation=0): 221 """Sets the display rotation for the specified display. 222 223 @param display_id: id of the display to get modes from. 224 @param rotation: degree of rotation 225 @param delay_before_rotation: time in second for delay before rotation 226 @param delay_after_rotation: time in second for delay after rotation 227 """ 228 time.sleep(delay_before_rotation) 229 extension = self._resource.get_extension( 230 constants.DISPLAY_TEST_EXTENSION) 231 extension.ExecuteJavaScript( 232 """ 233 window.__set_display_rotation_has_error = null; 234 chrome.system.display.setDisplayProperties('%(id)s', 235 {"rotation": %(rotation)d}, () => { 236 if (chrome.runtime.lastError) { 237 console.error('Failed to set display rotation', 238 chrome.runtime.lastError); 239 window.__set_display_rotation_has_error = "failure"; 240 } else { 241 window.__set_display_rotation_has_error = "success"; 242 } 243 }); 244 """ 245 % {'id': display_id, 'rotation': rotation} 246 ) 247 utils.wait_for_value(lambda: ( 248 extension.EvaluateJavaScript( 249 'window.__set_display_rotation_has_error') != None), 250 expected_value=True) 251 time.sleep(delay_after_rotation) 252 result = extension.EvaluateJavaScript( 253 'window.__set_display_rotation_has_error') 254 if result != 'success': 255 raise RuntimeError('Failed to set display rotation: %r' % result) 256 257 258 def get_available_resolutions(self, display_id): 259 """Gets the resolutions from the specified display. 260 261 @return a list of (width, height) tuples. 262 """ 263 display = self._get_display_by_id(display_id) 264 modes = display['modes'] 265 if 'widthInNativePixels' not in modes[0]: 266 raise RuntimeError('Cannot find widthInNativePixels attribute') 267 if display['isInternal']: 268 logging.info("Getting resolutions of internal display") 269 return list(set([(mode['width'], mode['height']) for mode in 270 modes])) 271 return list(set([(mode['widthInNativePixels'], 272 mode['heightInNativePixels']) for mode in modes])) 273 274 275 def has_internal_display(self): 276 """Returns whether the device has an internal display. 277 278 @return whether the device has an internal display 279 """ 280 return len([d for d in self.get_display_info() if d['isInternal']]) > 0 281 282 283 def get_internal_display_id(self): 284 """Gets the internal display id. 285 286 @return the id of the internal display. 287 """ 288 for display in self.get_display_info(): 289 if display['isInternal']: 290 return display['id'] 291 raise RuntimeError('Cannot find internal display') 292 293 294 def get_first_external_display_id(self): 295 """Gets the first external display id. 296 297 @return the id of the first external display; -1 if not found. 298 """ 299 # Get the first external and enabled display 300 for display in self.get_display_info(): 301 if display['isEnabled'] and not display['isInternal']: 302 return display['id'] 303 return -1 304 305 306 def set_resolution(self, display_id, width, height, timeout=3): 307 """Sets the resolution of the specified display. 308 309 @param display_id: id of the display to set resolution for. 310 @param width: width of the resolution 311 @param height: height of the resolution 312 @param timeout: maximal time in seconds waiting for the new resolution 313 to settle in. 314 @raise TimeoutException when the operation is timed out. 315 """ 316 317 extension = self._resource.get_extension( 318 constants.DISPLAY_TEST_EXTENSION) 319 extension.ExecuteJavaScript( 320 """ 321 window.__set_resolution_progress = null; 322 chrome.system.display.getInfo((info_array) => { 323 var mode; 324 for (var info of info_array) { 325 if (info['id'] == '%(id)s') { 326 for (var m of info['modes']) { 327 if (m['width'] == %(width)d && 328 m['height'] == %(height)d) { 329 mode = m; 330 break; 331 } 332 } 333 break; 334 } 335 } 336 if (mode === undefined) { 337 console.error('Failed to select the resolution ' + 338 '%(width)dx%(height)d'); 339 window.__set_resolution_progress = "mode not found"; 340 return; 341 } 342 343 chrome.system.display.setDisplayProperties('%(id)s', 344 {'displayMode': mode}, () => { 345 if (chrome.runtime.lastError) { 346 window.__set_resolution_progress = "failed: " + 347 chrome.runtime.lastError.message; 348 } else { 349 window.__set_resolution_progress = "succeeded"; 350 } 351 } 352 ); 353 }); 354 """ 355 % {'id': display_id, 'width': width, 'height': height} 356 ) 357 utils.wait_for_value(lambda: ( 358 extension.EvaluateJavaScript( 359 'window.__set_resolution_progress') != None), 360 expected_value=True) 361 result = extension.EvaluateJavaScript( 362 'window.__set_resolution_progress') 363 if result != 'succeeded': 364 raise RuntimeError('Failed to set resolution: %r' % result) 365 366 367 @_retry_display_call 368 def get_external_resolution(self): 369 """Gets the resolution of the external screen. 370 371 @return The resolution tuple (width, height) 372 """ 373 return graphics_utils.get_external_resolution() 374 375 def get_internal_resolution(self): 376 """Gets the resolution of the internal screen. 377 378 @return The resolution tuple (width, height) or None if internal screen 379 is not available 380 """ 381 for display in self.get_display_info(): 382 if display['isInternal']: 383 bounds = display['bounds'] 384 return (bounds['width'], bounds['height']) 385 return None 386 387 388 def set_content_protection(self, state): 389 """Sets the content protection of the external screen. 390 391 @param state: One of the states 'Undesired', 'Desired', or 'Enabled' 392 """ 393 connector = self.get_external_connector_name() 394 graphics_utils.set_content_protection(connector, state) 395 396 397 def get_content_protection(self): 398 """Gets the state of the content protection. 399 400 @param output: The output name as a string. 401 @return: A string of the state, like 'Undesired', 'Desired', or 'Enabled'. 402 False if not supported. 403 """ 404 connector = self.get_external_connector_name() 405 return graphics_utils.get_content_protection(connector) 406 407 408 def get_external_crtc_id(self): 409 """Gets the external crtc. 410 411 @return The id of the external crtc.""" 412 return graphics_utils.get_external_crtc_id() 413 414 415 def get_internal_crtc_id(self): 416 """Gets the internal crtc. 417 418 @retrun The id of the internal crtc.""" 419 return graphics_utils.get_internal_crtc_id() 420 421 422 def take_internal_screenshot(self, path): 423 """Takes internal screenshot. 424 425 @param path: path to image file. 426 """ 427 self.take_screenshot_crtc(path, self.get_internal_crtc_id()) 428 429 430 def take_external_screenshot(self, path): 431 """Takes external screenshot. 432 433 @param path: path to image file. 434 """ 435 self.take_screenshot_crtc(path, self.get_external_crtc_id()) 436 437 438 def take_screenshot_crtc(self, path, id): 439 """Captures the DUT screenshot, use id for selecting screen. 440 441 @param path: path to image file. 442 @param id: The id of the crtc to screenshot. 443 """ 444 445 graphics_utils.take_screenshot_crop(path, crtc_id=id) 446 return True 447 448 449 def save_calibration_image(self, path): 450 """Save the calibration image to the given path. 451 452 @param path: path to image file. 453 """ 454 shutil.copy(self.CALIBRATION_IMAGE_PATH, path) 455 return True 456 457 458 def take_tab_screenshot(self, output_path, url_pattern=None): 459 """Takes a screenshot of the tab specified by the given url pattern. 460 461 @param output_path: A path of the output file. 462 @param url_pattern: A string of url pattern used to search for tabs. 463 Default is to look for .svg image. 464 """ 465 if url_pattern is None: 466 # If no URL pattern is provided, defaults to capture the first 467 # tab that shows SVG image. 468 url_pattern = '.svg' 469 470 tabs = self._resource.get_tabs() 471 for i in range(0, len(tabs)): 472 if url_pattern in tabs[i].url: 473 data = tabs[i].Screenshot(timeout=5) 474 # Flip the colors from BGR to RGB. 475 data = numpy.fliplr(data.reshape(-1, 3)).reshape(data.shape) 476 data.tofile(output_path) 477 break 478 return True 479 480 481 def toggle_mirrored(self): 482 """Toggles mirrored.""" 483 graphics_utils.screen_toggle_mirrored() 484 return True 485 486 487 def hide_cursor(self): 488 """Hides mouse cursor.""" 489 graphics_utils.hide_cursor() 490 return True 491 492 493 def hide_typing_cursor(self): 494 """Hides typing cursor.""" 495 graphics_utils.hide_typing_cursor() 496 return True 497 498 499 def is_mirrored_enabled(self): 500 """Checks the mirrored state. 501 502 @return True if mirrored mode is enabled. 503 """ 504 return bool(self.get_display_info()[0]['mirroringSourceId']) 505 506 507 def set_mirrored(self, is_mirrored): 508 """Sets mirrored mode. 509 510 @param is_mirrored: True or False to indicate mirrored state. 511 @return True if success, False otherwise. 512 """ 513 if self.is_mirrored_enabled() == is_mirrored: 514 return True 515 516 retries = 4 517 while retries > 0: 518 self.toggle_mirrored() 519 result = utils.wait_for_value(self.is_mirrored_enabled, 520 expected_value=is_mirrored, 521 timeout_sec=3) 522 if result == is_mirrored: 523 return True 524 retries -= 1 525 return False 526 527 528 def is_display_primary(self, internal=True): 529 """Checks if internal screen is primary display. 530 531 @param internal: is internal/external screen primary status requested 532 @return boolean True if internal display is primary. 533 """ 534 for info in self.get_display_info(): 535 if info['isInternal'] == internal and info['isPrimary']: 536 return True 537 return False 538 539 540 def suspend_resume(self, suspend_time=10): 541 """Suspends the DUT for a given time in second. 542 543 @param suspend_time: Suspend time in second. 544 """ 545 sys_power.do_suspend(suspend_time) 546 return True 547 548 549 def suspend_resume_bg(self, suspend_time=10): 550 """Suspends the DUT for a given time in second in the background. 551 552 @param suspend_time: Suspend time in second. 553 """ 554 process = multiprocessing.Process(target=self.suspend_resume, 555 args=(suspend_time,)) 556 process.start() 557 return True 558 559 560 @_retry_display_call 561 def get_external_connector_name(self): 562 """Gets the name of the external output connector. 563 564 @return The external output connector name as a string, if any. 565 Otherwise, return False. 566 """ 567 return graphics_utils.get_external_connector_name() 568 569 570 def get_internal_connector_name(self): 571 """Gets the name of the internal output connector. 572 573 @return The internal output connector name as a string, if any. 574 Otherwise, return False. 575 """ 576 return graphics_utils.get_internal_connector_name() 577 578 579 def wait_external_display_connected(self, display): 580 """Waits for the specified external display to be connected. 581 582 @param display: The display name as a string, like 'HDMI1', or 583 False if no external display is expected. 584 @return: True if display is connected; False otherwise. 585 """ 586 result = utils.wait_for_value(self.get_external_connector_name, 587 expected_value=display) 588 return result == display 589 590 591 @facade_resource.retry_chrome_call 592 def move_to_display(self, display_id): 593 """Moves the current window to the indicated display. 594 595 @param display_id: The id of the indicated display. 596 @return True if success. 597 598 @raise TimeoutException if it fails. 599 """ 600 display_info = self._get_display_by_id(display_id) 601 if not display_info['isEnabled']: 602 raise RuntimeError('Cannot find the indicated display') 603 target_bounds = display_info['bounds'] 604 605 extension = self._resource.get_extension() 606 # If the area of bounds is empty (here we achieve this by setting 607 # width and height to zero), the window_sizer will automatically 608 # determine an area which is visible and fits on the screen. 609 # For more details, see chrome/browser/ui/window_sizer.cc 610 # Without setting state to 'normal', if the current state is 611 # 'minimized', 'maximized' or 'fullscreen', the setting of 612 # 'left', 'top', 'width' and 'height' will be ignored. 613 # For more details, see chrome/browser/extensions/api/tabs/tabs_api.cc 614 extension.ExecuteJavaScript( 615 """ 616 var __status = 'Running'; 617 chrome.windows.update( 618 chrome.windows.WINDOW_ID_CURRENT, 619 {left: %d, top: %d, width: 0, height: 0, 620 state: 'normal'}, 621 function(info) { 622 if (info.left == %d && info.top == %d && 623 info.state == 'normal') 624 __status = 'Done'; }); 625 """ 626 % (target_bounds['left'], target_bounds['top'], 627 target_bounds['left'], target_bounds['top']) 628 ) 629 extension.WaitForJavaScriptCondition( 630 "__status == 'Done'", 631 timeout=web_contents.DEFAULT_WEB_CONTENTS_TIMEOUT) 632 return True 633 634 635 def is_fullscreen_enabled(self): 636 """Checks the fullscreen state. 637 638 @return True if fullscreen mode is enabled. 639 """ 640 return self.get_window_info()['state'] == 'fullscreen' 641 642 643 def set_fullscreen(self, is_fullscreen): 644 """Sets the current window to full screen. 645 646 @param is_fullscreen: True or False to indicate fullscreen state. 647 @return True if success, False otherwise. 648 """ 649 extension = self._resource.get_extension() 650 if not extension: 651 raise RuntimeError('Autotest extension not found') 652 653 if is_fullscreen: 654 window_state = "fullscreen" 655 else: 656 window_state = "normal" 657 extension.ExecuteJavaScript( 658 """ 659 var __status = 'Running'; 660 chrome.windows.update( 661 chrome.windows.WINDOW_ID_CURRENT, 662 {state: '%s'}, 663 function() { __status = 'Done'; }); 664 """ 665 % window_state) 666 utils.wait_for_value(lambda: ( 667 extension.EvaluateJavaScript('__status') == 'Done'), 668 expected_value=True) 669 return self.is_fullscreen_enabled() == is_fullscreen 670 671 672 def load_url(self, url): 673 """Loads the given url in a new tab. The new tab will be active. 674 675 @param url: The url to load as a string. 676 @return a str, the tab descriptor of the opened tab. 677 """ 678 return self._resource.load_url(url) 679 680 681 def load_calibration_image(self, resolution): 682 """Opens a new tab and loads a full screen calibration 683 image from the HTTP server. 684 685 @param resolution: A tuple (width, height) of resolution. 686 @return a str, the tab descriptor of the opened tab. 687 """ 688 path = self.CALIBRATION_IMAGE_PATH 689 self._image_generator.generate_image(resolution[0], resolution[1], path) 690 os.chmod(path, 0o644) 691 tab_descriptor = self.load_url('file://%s' % path) 692 return tab_descriptor 693 694 695 def load_color_sequence(self, tab_descriptor, color_sequence): 696 """Displays a series of colors on full screen on the tab. 697 tab_descriptor is returned by any open tab API of display facade. 698 e.g., 699 tab_descriptor = load_url('about:blank') 700 load_color_sequence(tab_descriptor, color) 701 702 @param tab_descriptor: Indicate which tab to test. 703 @param color_sequence: An integer list for switching colors. 704 @return A list of the timestamp for each switch. 705 """ 706 tab = self._resource.get_tab_by_descriptor(tab_descriptor) 707 color_sequence_for_java_script = ( 708 'var color_sequence = [' + 709 ','.join("'#%06X'" % x for x in color_sequence) + 710 '];') 711 # Paints are synchronized to the fresh rate of the screen by 712 # window.requestAnimationFrame. 713 tab.ExecuteJavaScript(color_sequence_for_java_script + """ 714 function render(timestamp) { 715 window.timestamp_list.push(timestamp); 716 if (window.count < color_sequence.length) { 717 document.body.style.backgroundColor = 718 color_sequence[count]; 719 window.count++; 720 window.requestAnimationFrame(render); 721 } 722 } 723 window.count = 0; 724 window.timestamp_list = []; 725 window.requestAnimationFrame(render); 726 """) 727 728 # Waiting time is decided by following concerns: 729 # 1. MINIMUM_REFRESH_RATE_EXPECTED: the minimum refresh rate 730 # we expect it to be. Real refresh rate is related to 731 # not only hardware devices but also drivers and browsers. 732 # Most graphics devices support at least 60fps for a single 733 # monitor, and under mirror mode, since the both frames 734 # buffers need to be updated for an input frame, the refresh 735 # rate will decrease by half, so here we set it to be a 736 # little less than 30 (= 60/2) to make it more tolerant. 737 # 2. DELAY_TIME: extra wait time for timeout. 738 tab.WaitForJavaScriptCondition( 739 'window.count == color_sequence.length', 740 timeout=( 741 (len(color_sequence) / self.MINIMUM_REFRESH_RATE_EXPECTED) 742 + self.DELAY_TIME)) 743 return tab.EvaluateJavaScript("window.timestamp_list") 744 745 746 def close_tab(self, tab_descriptor): 747 """Disables fullscreen and closes the tab of the given tab descriptor. 748 tab_descriptor is returned by any open tab API of display facade. 749 e.g., 750 1. 751 tab_descriptor = load_url(url) 752 close_tab(tab_descriptor) 753 754 2. 755 tab_descriptor = load_calibration_image(resolution) 756 close_tab(tab_descriptor) 757 758 @param tab_descriptor: Indicate which tab to be closed. 759 """ 760 if tab_descriptor: 761 # set_fullscreen(False) is necessary here because currently there 762 # is a bug in tabs.Close(). If the current state is fullscreen and 763 # we call close_tab() without setting state back to normal, it will 764 # cancel fullscreen mode without changing system configuration, and 765 # so that the next time someone calls set_fullscreen(True), the 766 # function will find that current state is already 'fullscreen' 767 # (though it is not) and do nothing, which will break all the 768 # following tests. 769 self.set_fullscreen(False) 770 self._resource.close_tab(tab_descriptor) 771 else: 772 logging.error('close_tab: not a valid tab_descriptor') 773 774 return True 775 776 777 def reset_connector_if_applicable(self, connector_type): 778 """Resets Type-C video connector from host end if applicable. 779 780 It's the workaround sequence since sometimes Type-C dongle becomes 781 corrupted and needs to be re-plugged. 782 783 @param connector_type: A string, like "VGA", "DVI", "HDMI", or "DP". 784 """ 785 if connector_type != 'HDMI' and connector_type != 'DP': 786 return 787 # Decide if we need to add --name=cros_pd 788 usbpd_command = 'ectool --name=cros_pd usbpd' 789 try: 790 common_utils.run('%s 0' % usbpd_command) 791 except error.CmdError: 792 usbpd_command = 'ectool usbpd' 793 794 port = 0 795 while port < self.MAX_TYPEC_PORT: 796 # We use usbpd to get Role information and then power cycle the 797 # SRC one. 798 command = '%s %d' % (usbpd_command, port) 799 try: 800 output = common_utils.run(command).stdout 801 if re.compile('Role.*SRC').search(output): 802 logging.info('power-cycle Type-C port %d', port) 803 common_utils.run('%s sink' % command) 804 common_utils.run('%s auto' % command) 805 port += 1 806 except error.CmdError: 807 break 808