OLD | NEW |
---|---|
1 # Copyright 2014 The Chromium Authors. All rights reserved. | 1 # Copyright 2014 The Chromium Authors. All rights reserved. |
2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
4 | 4 |
5 """Provides a variety of device interactions based on adb. | 5 """Provides a variety of device interactions based on adb. |
6 | 6 |
7 Eventually, this will be based on adb_wrapper. | 7 Eventually, this will be based on adb_wrapper. |
8 """ | 8 """ |
9 # pylint: disable=W0613 | 9 # pylint: disable=W0613 |
10 | 10 |
(...skipping 108 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
119 self.old_interface = pylib.android_commands.AndroidCommands(str(device)) | 119 self.old_interface = pylib.android_commands.AndroidCommands(str(device)) |
120 elif isinstance(device, pylib.android_commands.AndroidCommands): | 120 elif isinstance(device, pylib.android_commands.AndroidCommands): |
121 self.adb = adb_wrapper.AdbWrapper(device.GetDevice()) | 121 self.adb = adb_wrapper.AdbWrapper(device.GetDevice()) |
122 self.old_interface = device | 122 self.old_interface = device |
123 else: | 123 else: |
124 raise ValueError('Unsupported device value: %r' % device) | 124 raise ValueError('Unsupported device value: %r' % device) |
125 self._commands_installed = None | 125 self._commands_installed = None |
126 self._default_timeout = default_timeout | 126 self._default_timeout = default_timeout |
127 self._default_retries = default_retries | 127 self._default_retries = default_retries |
128 self._cache = {} | 128 self._cache = {} |
129 self._cache['application_paths'] = {} | |
129 assert hasattr(self, decorators.DEFAULT_TIMEOUT_ATTR) | 130 assert hasattr(self, decorators.DEFAULT_TIMEOUT_ATTR) |
130 assert hasattr(self, decorators.DEFAULT_RETRIES_ATTR) | 131 assert hasattr(self, decorators.DEFAULT_RETRIES_ATTR) |
131 | 132 |
132 @decorators.WithTimeoutAndRetriesFromInstance() | 133 @decorators.WithTimeoutAndRetriesFromInstance() |
133 def IsOnline(self, timeout=None, retries=None): | 134 def IsOnline(self, timeout=None, retries=None): |
134 """Checks whether the device is online. | 135 """Checks whether the device is online. |
135 | 136 |
136 Args: | 137 Args: |
137 timeout: timeout in seconds | 138 timeout: timeout in seconds |
138 retries: number of retries | 139 retries: number of retries |
(...skipping 133 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
272 Args: | 273 Args: |
273 package: Name of the package. | 274 package: Name of the package. |
274 | 275 |
275 Returns: | 276 Returns: |
276 Path to the apk on the device if it exists, None otherwise. | 277 Path to the apk on the device if it exists, None otherwise. |
277 """ | 278 """ |
278 # 'pm path' is liable to incorrectly exit with a nonzero number starting | 279 # 'pm path' is liable to incorrectly exit with a nonzero number starting |
279 # in Lollipop. | 280 # in Lollipop. |
280 # TODO(jbudorick): Check if this is fixed as new Android versions are | 281 # TODO(jbudorick): Check if this is fixed as new Android versions are |
281 # released to put an upper bound on this. | 282 # released to put an upper bound on this. |
283 application_paths = self._cache['application_paths'] | |
jbudorick
2015/01/21 22:54:09
If we stay with the cache, please move this above
| |
284 if package in application_paths: | |
285 return application_paths[package] | |
286 | |
282 should_check_return = (self.build_version_sdk < | 287 should_check_return = (self.build_version_sdk < |
jbudorick
2015/01/21 22:54:09
w.r.t. my tuning suggestion: we already have some
| |
283 constants.ANDROID_SDK_VERSION_CODES.LOLLIPOP) | 288 constants.ANDROID_SDK_VERSION_CODES.LOLLIPOP) |
284 output = self.RunShellCommand(['pm', 'path', package], single_line=True, | 289 output = self.RunShellCommand(['pm', 'path', package], single_line=True, |
285 check_return=should_check_return) | 290 check_return=should_check_return) |
286 if not output: | 291 if not output: |
287 return None | 292 return None |
288 if not output.startswith('package:'): | 293 if not output.startswith('package:'): |
289 raise device_errors.CommandFailedError('pm path returned: %r' % output, | 294 raise device_errors.CommandFailedError('pm path returned: %r' % output, |
290 str(self)) | 295 str(self)) |
291 return output[len('package:'):] | 296 application_paths[package] = output[len('package:'):] |
297 return application_paths[package] | |
292 | 298 |
293 @decorators.WithTimeoutAndRetriesFromInstance() | 299 @decorators.WithTimeoutAndRetriesFromInstance() |
294 def WaitUntilFullyBooted(self, wifi=False, timeout=None, retries=None): | 300 def WaitUntilFullyBooted(self, wifi=False, timeout=None, retries=None): |
295 """Wait for the device to fully boot. | 301 """Wait for the device to fully boot. |
296 | 302 |
297 This means waiting for the device to boot, the package manager to be | 303 This means waiting for the device to boot, the package manager to be |
298 available, and the SD card to be ready. It can optionally mean waiting | 304 available, and the SD card to be ready. It can optionally mean waiting |
299 for wifi to come up, too. | 305 for wifi to come up, too. |
300 | 306 |
301 Args: | 307 Args: |
(...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
352 | 358 |
353 Raises: | 359 Raises: |
354 CommandTimeoutError on timeout. | 360 CommandTimeoutError on timeout. |
355 DeviceUnreachableError on missing device. | 361 DeviceUnreachableError on missing device. |
356 """ | 362 """ |
357 def device_offline(): | 363 def device_offline(): |
358 return not self.IsOnline() | 364 return not self.IsOnline() |
359 | 365 |
360 self.adb.Reboot() | 366 self.adb.Reboot() |
361 self._cache = {} | 367 self._cache = {} |
368 self._cache['application_paths'] = {} | |
362 timeout_retry.WaitFor(device_offline, wait_period=1) | 369 timeout_retry.WaitFor(device_offline, wait_period=1) |
363 if block: | 370 if block: |
364 self.WaitUntilFullyBooted() | 371 self.WaitUntilFullyBooted() |
365 | 372 |
366 INSTALL_DEFAULT_TIMEOUT = 4 * _DEFAULT_TIMEOUT | 373 INSTALL_DEFAULT_TIMEOUT = 4 * _DEFAULT_TIMEOUT |
367 INSTALL_DEFAULT_RETRIES = _DEFAULT_RETRIES | 374 INSTALL_DEFAULT_RETRIES = _DEFAULT_RETRIES |
368 | 375 |
369 @decorators.WithTimeoutAndRetriesDefaults( | 376 @decorators.WithTimeoutAndRetriesDefaults( |
370 INSTALL_DEFAULT_TIMEOUT, | 377 INSTALL_DEFAULT_TIMEOUT, |
371 INSTALL_DEFAULT_RETRIES) | 378 INSTALL_DEFAULT_RETRIES) |
(...skipping 17 matching lines...) Expand all Loading... | |
389 device_path = self.GetApplicationPath(package_name) | 396 device_path = self.GetApplicationPath(package_name) |
390 if device_path is not None: | 397 if device_path is not None: |
391 should_install = bool(self._GetChangedFilesImpl(apk_path, device_path)) | 398 should_install = bool(self._GetChangedFilesImpl(apk_path, device_path)) |
392 if should_install and not reinstall: | 399 if should_install and not reinstall: |
393 self.adb.Uninstall(package_name) | 400 self.adb.Uninstall(package_name) |
394 else: | 401 else: |
395 should_install = True | 402 should_install = True |
396 if should_install: | 403 if should_install: |
397 self.adb.Install(apk_path, reinstall=reinstall) | 404 self.adb.Install(apk_path, reinstall=reinstall) |
398 | 405 |
406 application_paths = self._cache['application_paths'] | |
407 if package_name in application_paths: | |
408 del application_paths[package_name] | |
409 | |
399 @decorators.WithTimeoutAndRetriesFromInstance() | 410 @decorators.WithTimeoutAndRetriesFromInstance() |
400 def RunShellCommand(self, cmd, check_return=False, cwd=None, env=None, | 411 def RunShellCommand(self, cmd, check_return=False, cwd=None, env=None, |
401 as_root=False, single_line=False, | 412 as_root=False, single_line=False, |
402 timeout=None, retries=None): | 413 timeout=None, retries=None): |
403 """Run an ADB shell command. | 414 """Run an ADB shell command. |
404 | 415 |
405 The command to run |cmd| should be a sequence of program arguments or else | 416 The command to run |cmd| should be a sequence of program arguments or else |
406 a single string. | 417 a single string. |
407 | 418 |
408 When |cmd| is a sequence, it is assumed to contain the name of the command | 419 When |cmd| is a sequence, it is assumed to contain the name of the command |
409 to run followed by its arguments. In this case, arguments are passed to the | 420 to run followed by its arguments. In this case, arguments are passed to the |
410 command exactly as given, without any further processing by the shell. This | 421 command exactly as given, without any further processing by the shell. This |
411 allows to easily pass arguments containing spaces or special characters | 422 allows to easily pass arguments containing spaces or special characters |
412 without having to worry about getting quoting right. Whenever possible, it | 423 without having to worry about getting quoting right. Whenever possible, it |
413 is recomended to pass |cmd| as a sequence. | 424 is recomended to pass |cmd| as a sequence. |
414 | 425 |
415 When |cmd| is given as a string, it will be interpreted and run by the | 426 When |cmd| is given as a string, it will be interpreted and run by the |
416 shell on the device. | 427 shell on the device. |
417 | 428 |
418 This behaviour is consistent with that of command runners in cmd_helper as | 429 This behaviour is consistent with that of command runners in cmd_helper as |
419 well as Python's own subprocess.Popen. | 430 well as Python's own subprocess.Popen. |
420 | 431 |
421 TODO(perezju) Change the default of |check_return| to True when callers | 432 TODO(perezju) Change the default of |check_return| to True when callers |
422 have switched to the new behaviour. | 433 have switched to the new behaviour. |
423 | 434 |
435 TODO(jaekyun) Should clear some of non-RO cache items if this command | |
436 affects them. | |
437 | |
424 Args: | 438 Args: |
425 cmd: A string with the full command to run on the device, or a sequence | 439 cmd: A string with the full command to run on the device, or a sequence |
426 containing the command and its arguments. | 440 containing the command and its arguments. |
427 check_return: A boolean indicating whether or not the return code should | 441 check_return: A boolean indicating whether or not the return code should |
428 be checked. | 442 be checked. |
429 cwd: The device directory in which the command should be run. | 443 cwd: The device directory in which the command should be run. |
430 env: The environment variables with which the command should be run. | 444 env: The environment variables with which the command should be run. |
431 as_root: A boolean indicating whether the shell command should be run | 445 as_root: A boolean indicating whether the shell command should be run |
432 with root privileges. | 446 with root privileges. |
433 single_line: A boolean indicating if only a single line of output is | 447 single_line: A boolean indicating if only a single line of output is |
(...skipping 15 matching lines...) Expand all Loading... | |
449 DeviceUnreachableError on missing device. | 463 DeviceUnreachableError on missing device. |
450 """ | 464 """ |
451 def env_quote(key, value): | 465 def env_quote(key, value): |
452 if not DeviceUtils._VALID_SHELL_VARIABLE.match(key): | 466 if not DeviceUtils._VALID_SHELL_VARIABLE.match(key): |
453 raise KeyError('Invalid shell variable name %r' % key) | 467 raise KeyError('Invalid shell variable name %r' % key) |
454 # using double quotes here to allow interpolation of shell variables | 468 # using double quotes here to allow interpolation of shell variables |
455 return '%s=%s' % (key, cmd_helper.DoubleQuote(value)) | 469 return '%s=%s' % (key, cmd_helper.DoubleQuote(value)) |
456 | 470 |
457 if not isinstance(cmd, basestring): | 471 if not isinstance(cmd, basestring): |
458 cmd = ' '.join(cmd_helper.SingleQuote(s) for s in cmd) | 472 cmd = ' '.join(cmd_helper.SingleQuote(s) for s in cmd) |
473 | |
474 if cmd.startswith('pm install') or cmd.startswith('pm uninstall'): | |
jbudorick
2015/01/21 22:54:09
O_O
This looks _extremely_ dangerous / flaky / te
| |
475 self._cache['application_paths'] = {} | |
476 | |
459 if env: | 477 if env: |
460 env = ' '.join(env_quote(k, v) for k, v in env.iteritems()) | 478 env = ' '.join(env_quote(k, v) for k, v in env.iteritems()) |
461 cmd = '%s %s' % (env, cmd) | 479 cmd = '%s %s' % (env, cmd) |
462 if cwd: | 480 if cwd: |
463 cmd = 'cd %s && %s' % (cmd_helper.SingleQuote(cwd), cmd) | 481 cmd = 'cd %s && %s' % (cmd_helper.SingleQuote(cwd), cmd) |
464 if as_root and self.NeedsSU(): | 482 if as_root and self.NeedsSU(): |
465 # "su -c sh -c" allows using shell features in |cmd| | 483 # "su -c sh -c" allows using shell features in |cmd| |
466 cmd = 'su -c sh -c %s' % cmd_helper.SingleQuote(cmd) | 484 cmd = 'su -c sh -c %s' % cmd_helper.SingleQuote(cmd) |
467 if timeout is None: | 485 if timeout is None: |
468 timeout = self._default_timeout | 486 timeout = self._default_timeout |
(...skipping 816 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1285 Returns: | 1303 Returns: |
1286 A Parallelizer operating over |devices|. | 1304 A Parallelizer operating over |devices|. |
1287 """ | 1305 """ |
1288 if not devices: | 1306 if not devices: |
1289 devices = adb_wrapper.AdbWrapper.GetDevices() | 1307 devices = adb_wrapper.AdbWrapper.GetDevices() |
1290 devices = [d if isinstance(d, cls) else cls(d) for d in devices] | 1308 devices = [d if isinstance(d, cls) else cls(d) for d in devices] |
1291 if async: | 1309 if async: |
1292 return parallelizer.Parallelizer(devices) | 1310 return parallelizer.Parallelizer(devices) |
1293 else: | 1311 else: |
1294 return parallelizer.SyncParallelizer(devices) | 1312 return parallelizer.SyncParallelizer(devices) |
OLD | NEW |