• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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