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