Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 # Copyright 2015 The Chromium Authors. All rights reserved. | |
| 2 # Use of this source code is governed by a BSD-style license that can be | |
| 3 # found in the LICENSE file. | |
| 4 | |
| 5 import logging | |
| 6 import os | |
| 7 import subprocess | |
| 8 import sys | |
| 9 import time | |
| 10 | |
| 11 # Add the telemetry directory to Python's search paths. | |
| 12 current_directory = os.path.dirname(os.path.realpath(__file__)) | |
| 13 telemetry_dir = os.path.realpath( | |
| 14 os.path.join(current_directory, '..', '..', '..', 'tools', 'telemetry')) | |
| 15 if telemetry_dir not in sys.path: | |
| 16 sys.path.append(telemetry_dir) | |
| 17 | |
| 18 from telemetry.core import browser_options | |
| 19 from telemetry.core import browser_finder | |
| 20 from telemetry.core import extension_to_load | |
| 21 from telemetry.core import exceptions | |
| 22 from telemetry.core import util | |
| 23 from telemetry.core.platform import cros_interface | |
| 24 | |
| 25 logger = logging.getLogger('proximity_auth.%s' % __name__) | |
| 26 | |
| 27 | |
| 28 class AccountPickerScreen(object): | |
| 29 """ Wrapper for the ChromeOS account picker screen. | |
| 30 | |
| 31 The account picker screen is the WebContents page used for both the lock | |
| 32 screen and signin screen. | |
| 33 | |
| 34 Note: This class assumes the account picker screen only has one user. If there | |
| 35 are multiple user pods, the first one will be used. | |
| 36 """ | |
| 37 | |
| 38 class AuthType: | |
| 39 """ The authentication type expected for a user pod. """ | |
| 40 OFFLINE_PASSWORD = 0 | |
| 41 ONLINE_SIGN_IN = 1 | |
| 42 NUMERIC_PIN = 2 | |
| 43 USER_CLICK = 3 | |
| 44 EXPAND_THEN_USER_CLICK = 4 | |
| 45 FORCE_OFFLINE_PASSWORD = 5 | |
| 46 | |
| 47 class SmartLockState: | |
| 48 """ The state of the Smart Lock icon on a user pod. | |
| 49 """ | |
| 50 NOT_SHOWN = 'not_shown' | |
| 51 AUTHENTICATED = 'authenticated' | |
| 52 LOCKED = 'locked' | |
| 53 HARD_LOCKED = 'hardlocked' | |
| 54 TO_BE_ACTIVATED = 'to_be_activated' | |
| 55 SPINNER = 'spinner' | |
| 56 | |
| 57 # JavaScript expression for getting the user pod on the page | |
| 58 _GET_POD_JS = 'document.getElementById("pod-row").pods[0]' | |
| 59 | |
| 60 def __init__(self, oobe, chromeos): | |
| 61 """ | |
| 62 Args: | |
| 63 oobe: Inspector page of the OOBE WebContents. | |
| 64 chromeos: The parent Chrome wrapper. | |
| 65 """ | |
| 66 self._oobe = oobe | |
| 67 self._chromeos = chromeos | |
| 68 | |
| 69 @property | |
| 70 def is_lockscreen(self): | |
| 71 return self._oobe.EvaluateJavaScript( | |
| 72 '!document.getElementById("sign-out-user-item").hidden') | |
| 73 | |
| 74 @property | |
| 75 def auth_type(self): | |
| 76 return self._oobe.EvaluateJavaScript('%s.authType' % self._GET_POD_JS) | |
| 77 | |
| 78 @property | |
| 79 def smart_lock_state(self): | |
| 80 icon_shown = self._oobe.EvaluateJavaScript( | |
| 81 '!%s.customIconElement.hidden' % self._GET_POD_JS) | |
| 82 if not icon_shown: | |
| 83 return self.SmartLockState.NOT_SHOWN | |
| 84 class_list_dict = self._oobe.EvaluateJavaScript( | |
| 85 '%s.customIconElement.querySelector(".custom-icon")' | |
| 86 '.classList' % self._GET_POD_JS) | |
| 87 class_list = [v for k,v in class_list_dict.items() if k != 'length'] | |
| 88 | |
| 89 if 'custom-icon-unlocked' in class_list: | |
| 90 return self.SmartLockState.AUTHENTICATED | |
| 91 if 'custom-icon-locked' in class_list: | |
| 92 return self.SmartLockState.LOCKED | |
| 93 if 'custom-icon-hardlocked' in class_list: | |
| 94 return self.SmartLockState.HARD_LOCKED | |
| 95 if 'custom-icon-locked-to-be-activated' in class_list: | |
| 96 return self.SmartLockState.TO_BE_ACTIVATED | |
| 97 if 'custom-icon-spinner' in class_list: | |
| 98 return self.SmartLockState.SPINNER | |
| 99 | |
| 100 def WaitForSmartLockState(self, state, wait_time_secs=60): | |
| 101 """ Waits for the Smart Lock icon to reach the given state. | |
| 102 | |
| 103 Args: | |
| 104 state: A value in AccountPickerScreen.SmartLockState | |
| 105 wait_time_secs: The time to wait | |
| 106 Returns: | |
| 107 True if the state is reached within the wait time, else False. | |
| 108 """ | |
| 109 try: | |
| 110 util.WaitFor(lambda: self.smart_lock_state == state, wait_time_secs) | |
| 111 return True | |
| 112 except exceptions.TimeoutException: | |
| 113 return False | |
| 114 | |
| 115 def EnterPassword(self): | |
| 116 """ Enters the password to unlock or sign-in. | |
| 117 | |
| 118 Raises an exception if entering the password fails to enter/resume the user | |
| 119 session. | |
|
Ilya Sherman
2015/03/25 23:17:29
The Python style is to list "Raises:" just as you
Tim Song
2015/03/27 18:05:26
Done.
| |
| 120 """ | |
| 121 assert(self.auth_type == self.AuthType.OFFLINE_PASSWORD or | |
| 122 self.auth_type == self.AuthType.FORCE_OFFLINE_PASSWORD) | |
| 123 oobe = self._oobe | |
| 124 oobe.EvaluateJavaScript( | |
| 125 '%s.passwordElement.value = "%s"' % ( | |
| 126 self._GET_POD_JS, self._chromeos.password)) | |
| 127 oobe.EvaluateJavaScript( | |
| 128 '%s.activate()' % self._GET_POD_JS) | |
| 129 util.WaitFor(lambda: (self._chromeos.session_state == | |
| 130 ChromeOS.SessionState.IN_SESSION), | |
| 131 5) | |
| 132 | |
| 133 def UnlockWithClick(self): | |
| 134 """ Clicks the user pod to unlock or sign-in. """ | |
| 135 assert(self.auth_type == self.AuthType.USER_CLICK) | |
| 136 self._oobe.EvaluateJavaScript('%s.activate()' % self._GET_POD_JS) | |
| 137 | |
| 138 | |
| 139 class SmartLockSettings(object): | |
| 140 """ Wrapper for the Smart Lock settings in chromeos://settings. | |
| 141 """ | |
| 142 def __init__(self, tab, chromeos): | |
| 143 """ | |
| 144 Args: | |
| 145 tab: Inspector page of the chromeos://settings tag. | |
| 146 chromeos: The parent Chrome wrapper. | |
| 147 """ | |
| 148 self._tab = tab | |
| 149 self._chromeos = chromeos | |
| 150 | |
| 151 @property | |
| 152 def smart_lock_enabled(self): | |
| 153 ''' Returns true if the settings show that Smart Lock is enabled. ''' | |
| 154 return self._tab.EvaluateJavaScript( | |
| 155 '!document.getElementById("easy-unlock-enabled").hidden') | |
| 156 | |
| 157 def TurnOffSmartLock(self): | |
| 158 """ Turns off Smart Lock. | |
| 159 | |
| 160 Smart Lock is turned off by clicking the turn-off button and navigating | |
| 161 through the resulting overlay. | |
| 162 | |
| 163 Raises an exception if turning off Smart Lock fails. | |
| 164 """ | |
| 165 assert(self.smart_lock_enabled) | |
| 166 tab = self._tab | |
| 167 tab.EvaluateJavaScript( | |
| 168 'document.getElementById("easy-unlock-turn-off-button").click()') | |
| 169 util.WaitFor(lambda: tab.EvaluateJavaScript( | |
| 170 '!document.getElementById("easy-unlock-turn-off-overlay").hidden && ' | |
| 171 'document.getElementById("easy-unlock-turn-off-confirm") != null'), | |
| 172 10) | |
| 173 tab.EvaluateJavaScript( | |
| 174 'document.getElementById("easy-unlock-turn-off-confirm").click()') | |
| 175 util.WaitFor(lambda: tab.EvaluateJavaScript( | |
| 176 '!document.getElementById("easy-unlock-disabled").hidden'), 15) | |
| 177 | |
| 178 def StartSetup(self): | |
| 179 """ Starts the Smart Lock setup flow by clicking the button. | |
| 180 """ | |
| 181 assert(not self.smart_lock_enabled) | |
| 182 self._tab.EvaluateJavaScript( | |
| 183 'document.getElementById("easy-unlock-setup-button").click()') | |
| 184 | |
| 185 def StartSetupAndReturnApp(self): | |
| 186 """ Runs the setup and returns the wrapper to the setup app. | |
| 187 | |
| 188 After clicking the setup button in the settings page, enter the password to | |
| 189 reauthenticate the user before the app launches. | |
| 190 | |
| 191 Raises an exception if setup fails. | |
| 192 | |
| 193 Returns: | |
| 194 A SmartLockApp object of the app that was launched. | |
| 195 """ | |
| 196 self.StartSetup() | |
| 197 util.WaitFor(lambda: (self._chromeos.session_state == | |
| 198 ChromeOS.SessionState.LOCK_SCREEN), | |
| 199 5) | |
| 200 lock_screen = self._chromeos.GetAccountPickerScreen() | |
| 201 lock_screen.EnterPassword() | |
| 202 util.WaitFor(lambda: self._chromeos.GetSmartLockApp() is not None, 10) | |
| 203 return self._chromeos.GetSmartLockApp() | |
| 204 | |
| 205 | |
| 206 class SmartLockApp(object): | |
| 207 """ Wrapper for the Smart Lock setup dialog. | |
| 208 | |
| 209 Note: This does not include the app's background page. | |
| 210 """ | |
| 211 | |
| 212 class PairingState: | |
| 213 """ The current state of the setup flow. """ | |
| 214 SCAN = 'scan' | |
| 215 PAIR = 'pair' | |
| 216 CLICK_FOR_TRIAL_RUN = 'click_for_trial_run' | |
| 217 TRIAL_RUN_COMPLETED = 'trial_run_completed' | |
| 218 | |
| 219 def __init__(self, app_page, chromeos): | |
| 220 """ | |
| 221 Args: | |
| 222 app_page: Inspector page of the app window. | |
| 223 chromeos: The parent Chrome wrapper. | |
| 224 """ | |
| 225 self._app_page = app_page | |
| 226 self._chromeos = chromeos | |
| 227 | |
| 228 @property | |
| 229 def pairing_state(self): | |
| 230 ''' Returns the state the app is currently in. | |
| 231 | |
| 232 Raises an exception if the state is unknown. | |
| 233 ''' | |
| 234 state = self._app_page.EvaluateJavaScript( | |
| 235 'document.body.getAttribute("step")') | |
| 236 if state == 'scan': | |
| 237 return SmartLockApp.PairingState.SCAN | |
| 238 elif state == 'pair': | |
| 239 return SmartLockApp.PairingState.PAIR | |
| 240 elif state == 'complete': | |
| 241 button_text = self._app_page.EvaluateJavaScript( | |
| 242 'document.getElementById("pairing-button").textContent') | |
| 243 button_text = button_text.strip().lower() | |
| 244 if button_text == 'try it out': | |
| 245 return SmartLockApp.PairingState.CLICK_FOR_TRIAL_RUN | |
| 246 elif button_text == 'done': | |
| 247 return SmartLockApp.PairingState.TRIAL_RUN_COMPLETED | |
| 248 else: | |
| 249 raise ValueError('Unknown button text: %s', button_text) | |
| 250 else: | |
| 251 raise ValueError('Unknown pairing state: %s' % state) | |
| 252 | |
| 253 def FindPhone(self, retries=3): | |
| 254 """ Starts the initial step to find nearby phones. | |
| 255 | |
| 256 The app must be in the SCAN state. | |
| 257 | |
| 258 Args: | |
| 259 retries: The number of times to retry if no phones are found. | |
| 260 Returns: | |
| 261 True if a phone is found, else False. | |
| 262 """ | |
| 263 assert(self.pairing_state == self.PairingState.SCAN) | |
| 264 for _ in xrange(retries): | |
| 265 self._ClickPairingButton() | |
| 266 if self.pairing_state == self.PairingState.PAIR: | |
| 267 return True | |
| 268 # Wait a few seconds before retrying. | |
| 269 time.sleep(10) | |
| 270 return False | |
| 271 | |
| 272 def PairPhone(self): | |
| 273 """ Starts the step of finding nearby phones. | |
| 274 | |
| 275 The app must be in the PAIR state. | |
| 276 | |
| 277 Returns: | |
| 278 True if pairing succeeded, else False. | |
| 279 """ | |
| 280 assert(self.pairing_state == self.PairingState.PAIR) | |
| 281 self._ClickPairingButton() | |
| 282 return self.pairing_state == self.PairingState.CLICK_FOR_TRIAL_RUN | |
| 283 | |
| 284 def StartTrialRun(self): | |
| 285 """ Starts the trial run. | |
| 286 | |
| 287 The app must be in the CLICK_FOR_TRIAL_RUN state. | |
| 288 Raises an exception if starting the trial run fails. | |
| 289 """ | |
| 290 assert(self.pairing_state == self.PairingState.CLICK_FOR_TRIAL_RUN) | |
| 291 self._app_page.EvaluateJavaScript( | |
| 292 'document.getElementById("pairing-button").click()') | |
| 293 util.WaitFor(lambda: (self._chromeos.session_state == | |
| 294 ChromeOS.SessionState.LOCK_SCREEN), | |
| 295 10) | |
| 296 | |
| 297 def DismissApp(self): | |
| 298 """ Dismisses the app after setup is completed. | |
| 299 | |
| 300 The app must be in the TRIAL_RUN_COMPLETED state. | |
| 301 """ | |
| 302 assert(self.pairing_state == self.PairingState.TRIAL_RUN_COMPLETED) | |
| 303 self._app_page.EvaluateJavaScript( | |
| 304 'document.getElementById("pairing-button").click()') | |
| 305 | |
| 306 def _ClickPairingButton(self): | |
| 307 self._app_page.EvaluateJavaScript( | |
| 308 'document.getElementById("pairing-button").click()') | |
| 309 util.WaitFor(lambda: self._app_page.EvaluateJavaScript( | |
| 310 '!document.getElementById("pairing-button").disabled'), 60) | |
| 311 util.WaitFor(lambda: self._app_page.EvaluateJavaScript( | |
| 312 '!document.getElementById("pairing-button-title")' | |
| 313 '.classList.contains("animated-fade-out")'), 5) | |
| 314 util.WaitFor(lambda: self._app_page.EvaluateJavaScript( | |
| 315 '!document.getElementById("pairing-button-title")' | |
| 316 '.classList.contains("animated-fade-in")'), 5) | |
| 317 | |
| 318 | |
| 319 class ChromeOS(object): | |
| 320 """ Wrapper for a remote ChromeOS device. | |
| 321 | |
| 322 Operations performed through this wrapper are sent through the network to | |
| 323 Chrome using the Chrome DevTools API. Therefore, any function may throw an | |
| 324 exception if the communication to the remote device is severed. | |
| 325 """ | |
| 326 | |
| 327 class SessionState: | |
| 328 """ The state of the user session. | |
| 329 """ | |
| 330 SIGNIN_SCREEN = 'signin_screen' | |
| 331 IN_SESSION = 'in_session' | |
| 332 LOCK_SCREEN = 'lock_screen' | |
| 333 | |
| 334 _SMART_LOCK_SETTINGS_URL = 'chrome://settings/search#Smart%20Lock' | |
| 335 | |
| 336 def __init__(self, remote_address, username, password, ssh_port=None): | |
| 337 """ | |
| 338 Args: | |
| 339 remote_address: The remote address of the cros device. | |
| 340 username: The username of the account to test. | |
| 341 password: The password of the account to test. | |
| 342 ssh_port: The ssh port to connect to. | |
| 343 """ | |
| 344 self._remote_address = remote_address; | |
| 345 self._username = username | |
| 346 self._password = password | |
| 347 self._ssh_port = ssh_port | |
| 348 self._browser = None | |
| 349 self._cros_interface = None | |
| 350 self._background_page = None | |
| 351 self._processes = [] | |
| 352 | |
| 353 @property | |
| 354 def username(self): | |
| 355 ''' Returns the username of the user to login. ''' | |
| 356 return self._username | |
| 357 | |
| 358 @property | |
| 359 def password(self): | |
| 360 ''' Returns the password of the user to login. ''' | |
| 361 return self._password | |
| 362 | |
| 363 @property | |
| 364 def session_state(self): | |
| 365 ''' Returns the state of the user session. ''' | |
| 366 assert(self._browser is not None) | |
| 367 if self._browser.oobe_exists: | |
| 368 if self._cros_interface.IsCryptohomeMounted(self.username, False): | |
| 369 return self.SessionState.LOCK_SCREEN | |
| 370 else: | |
| 371 return self.SessionState.SIGNIN_SCREEN | |
| 372 else: | |
| 373 return self.SessionState.IN_SESSION; | |
| 374 | |
| 375 @property | |
| 376 def cryptauth_access_token(self): | |
| 377 try: | |
| 378 util.WaitFor(lambda: self._background_page.EvaluateJavaScript( | |
| 379 'var __token = __token || null; ' | |
| 380 'chrome.identity.getAuthToken(function(token) {' | |
| 381 ' __token = token;' | |
| 382 '}); ' | |
| 383 '__token != null'), 5) | |
| 384 return self._background_page.EvaluateJavaScript('__token'); | |
| 385 except exceptions.TimeoutException: | |
| 386 logger.error('Failed to get access token.'); | |
| 387 return '' | |
| 388 | |
| 389 def __enter__(self): | |
| 390 return self | |
| 391 | |
| 392 def __exit__(self, *args): | |
| 393 if self._browser is not None: | |
| 394 self._browser.Close() | |
| 395 if self._cros_interface is not None: | |
| 396 self._cros_interface.CloseConnection() | |
| 397 for process in self._processes: | |
| 398 process.terminate() | |
| 399 | |
| 400 def Start(self, local_app_path=None): | |
| 401 """ Connects to the ChromeOS device and logs in. | |
| 402 Args: | |
| 403 local_app_path: A path on the local device containing the Smart Lock app | |
| 404 to use instead of the app on the ChromeOS device. | |
| 405 Return: | |
| 406 |self| for using in a "with" statement. | |
| 407 """ | |
| 408 assert(self._browser is None) | |
| 409 | |
| 410 finder_opts = browser_options.BrowserFinderOptions('cros-chrome') | |
| 411 finder_opts.CreateParser().parse_args(args=[]) | |
| 412 finder_opts.cros_remote = self._remote_address | |
| 413 if self._ssh_port is not None: | |
| 414 finder_opts.cros_remote_ssh_port = self._ssh_port | |
| 415 finder_opts.verbosity = 1 | |
| 416 | |
| 417 browser_opts = finder_opts.browser_options | |
| 418 browser_opts.create_browser_with_oobe = True | |
| 419 browser_opts.disable_component_extensions_with_background_pages = False | |
| 420 browser_opts.gaia_login = True | |
| 421 browser_opts.username = self._username | |
| 422 browser_opts.password = self._password | |
| 423 browser_opts.auto_login = True | |
| 424 | |
| 425 self._cros_interface = cros_interface.CrOSInterface( | |
| 426 finder_opts.cros_remote, | |
| 427 finder_opts.cros_remote_ssh_port, | |
| 428 finder_opts.cros_ssh_identity) | |
| 429 | |
| 430 browser_opts.disable_default_apps = local_app_path is not None | |
| 431 if local_app_path is not None: | |
| 432 easy_unlock_app = extension_to_load.ExtensionToLoad( | |
| 433 path=local_app_path, | |
| 434 browser_type='cros-chrome', | |
| 435 is_component=True) | |
| 436 finder_opts.extensions_to_load.append(easy_unlock_app) | |
| 437 | |
| 438 retries = 3 | |
| 439 while self._browser is not None or retries > 0: | |
| 440 try: | |
| 441 browser_to_create = browser_finder.FindBrowser(finder_opts) | |
| 442 self._browser = browser_to_create.Create(finder_opts); | |
| 443 break; | |
| 444 except (exceptions.LoginException) as e: | |
| 445 logger.error('Timed out logging in: %s' % e); | |
| 446 if retries == 1: | |
| 447 raise | |
| 448 | |
| 449 bg_page_path = '/_generated_background_page.html' | |
| 450 util.WaitFor( | |
| 451 lambda: self._FindSmartLockAppPage(bg_page_path) is not None, | |
| 452 10); | |
| 453 self._background_page = self._FindSmartLockAppPage(bg_page_path) | |
| 454 return self | |
| 455 | |
| 456 def GetAccountPickerScreen(self): | |
| 457 """ Returns the wrapper for the lock screen or sign-in screen. | |
| 458 | |
| 459 Return: | |
| 460 An instance of AccountPickerScreen. | |
| 461 An exception will be raised if the wrapper can't be created. | |
| 462 """ | |
| 463 assert(self._browser is not None) | |
| 464 assert(self.session_state == self.SessionState.LOCK_SCREEN or | |
| 465 self.session_state == self.SessionState.SIGNIN_SCREEN) | |
| 466 oobe = self._browser.oobe | |
| 467 def IsLockScreenResponsive(): | |
| 468 return (oobe.EvaluateJavaScript("typeof Oobe == 'function'") and | |
| 469 oobe.EvaluateJavaScript( | |
| 470 "typeof Oobe.authenticateForTesting == 'function'")) | |
| 471 util.WaitFor(IsLockScreenResponsive, 10) | |
| 472 util.WaitFor(lambda: oobe.EvaluateJavaScript( | |
| 473 'document.getElementById("pod-row") && ' | |
| 474 'document.getElementById("pod-row").pods && ' | |
| 475 'document.getElementById("pod-row").pods.length > 0'), 10) | |
| 476 return AccountPickerScreen(oobe, self) | |
| 477 | |
| 478 def GetSmartLockSettings(self): | |
| 479 """ Returns the wrapper for the Smart Lock settings. | |
| 480 A tab will be navigated to chrome://settings if it does not exist. | |
| 481 | |
| 482 Return: | |
| 483 An instance of SmartLockSettings. | |
| 484 An exception will be raised if the wrapper can't be created. | |
| 485 """ | |
| 486 if not len(self._browser.tabs): | |
| 487 self._browser.New() | |
| 488 tab = self._browser.tabs[0] | |
| 489 url = tab.EvaluateJavaScript('document.location.href') | |
| 490 if url != self._SMART_LOCK_SETTINGS_URL: | |
| 491 tab.Navigate(self._SMART_LOCK_SETTINGS_URL) | |
| 492 | |
| 493 # Wait for settings page to be responsive. | |
| 494 util.WaitFor(lambda: tab.EvaluateJavaScript( | |
| 495 'document.getElementById("easy-unlock-disabled") && ' | |
| 496 'document.getElementById("easy-unlock-enabled") && ' | |
| 497 '(!document.getElementById("easy-unlock-disabled").hidden || ' | |
| 498 ' !document.getElementById("easy-unlock-enabled").hidden)'), 10) | |
| 499 settings = SmartLockSettings(tab, self) | |
| 500 logger.info('Started Smart Lock settings: enabled=%s' % | |
| 501 settings.smart_lock_enabled) | |
| 502 return settings | |
| 503 | |
| 504 def GetSmartLockApp(self): | |
| 505 """ Returns the wrapper for the Smart Lock setup app. | |
| 506 | |
| 507 Return: | |
| 508 An instance of SmartLockApp or None if the app window does not exist. | |
| 509 """ | |
| 510 app_page = self._FindSmartLockAppPage('/pairing.html') | |
| 511 if app_page is not None: | |
| 512 # Wait for app window to be responsive. | |
| 513 util.WaitFor(lambda: app_page.EvaluateJavaScript( | |
| 514 'document.getElementById("pairing-button") != null'), 10) | |
| 515 return SmartLockApp(app_page, self) | |
| 516 return None | |
| 517 | |
| 518 def RunBtmon(self): | |
| 519 """ Runs the btmon command. | |
| 520 Return: | |
| 521 A subprocess.Popen object of the btmon process. | |
| 522 """ | |
| 523 assert(self._cros_interface) | |
| 524 cmd = self._cros_interface.FormSSHCommandLine(['btmon']) | |
| 525 process = subprocess.Popen(args=cmd, stdout=subprocess.PIPE, | |
| 526 stderr=subprocess.PIPE) | |
| 527 self._processes.append(process) | |
| 528 return process | |
| 529 | |
| 530 def _FindSmartLockAppPage(self, page_name): | |
| 531 try: | |
| 532 extensions = self._browser.extensions.GetByExtensionId( | |
| 533 'mkaemigholebcgchlkbankmihknojeak') | |
| 534 except KeyError: | |
| 535 return None | |
| 536 for extension_page in extensions: | |
| 537 pathname = extension_page.EvaluateJavaScript('document.location.pathname') | |
| 538 if pathname == page_name: | |
| 539 return extension_page | |
| 540 return None | |
| OLD | NEW |