Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(79)

Side by Side Diff: build/android/pylib/device/device_utils.py

Issue 1222313015: Manual partial update from from https://crrev.com/337502 (Closed) Base URL: https://github.com/domokit/mojo.git@master
Patch Set: Created 5 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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=unused-argument 9 # pylint: disable=unused-argument
10 10
11 import collections 11 import collections
12 import contextlib 12 import contextlib
13 import itertools 13 import itertools
14 import logging 14 import logging
15 import multiprocessing 15 import multiprocessing
16 import os 16 import os
17 import posixpath 17 import posixpath
18 import re 18 import re
19 import shutil 19 import shutil
20 import sys 20 import sys
21 import tempfile 21 import tempfile
22 import time 22 import time
23 import zipfile 23 import zipfile
24 24
25 import pylib.android_commands 25 import pylib.android_commands
26 from pylib import cmd_helper 26 from pylib import cmd_helper
27 from pylib import constants 27 from pylib import constants
28 from pylib import device_signal 28 from pylib import device_signal
29 from pylib.constants import keyevent
29 from pylib.device import adb_wrapper 30 from pylib.device import adb_wrapper
30 from pylib.device import decorators 31 from pylib.device import decorators
31 from pylib.device import device_blacklist 32 from pylib.device import device_blacklist
32 from pylib.device import device_errors 33 from pylib.device import device_errors
33 from pylib.device import intent 34 from pylib.device import intent
34 from pylib.device import logcat_monitor 35 from pylib.device import logcat_monitor
35 from pylib.device.commands import install_commands 36 from pylib.device.commands import install_commands
37 from pylib.sdk import split_select
36 from pylib.utils import apk_helper 38 from pylib.utils import apk_helper
37 from pylib.utils import base_error 39 from pylib.utils import base_error
38 from pylib.utils import device_temp_file 40 from pylib.utils import device_temp_file
39 from pylib.utils import host_utils 41 from pylib.utils import host_utils
40 from pylib.utils import md5sum 42 from pylib.utils import md5sum
41 from pylib.utils import parallelizer 43 from pylib.utils import parallelizer
42 from pylib.utils import timeout_retry 44 from pylib.utils import timeout_retry
43 from pylib.utils import zip_utils 45 from pylib.utils import zip_utils
44 46
45 _DEFAULT_TIMEOUT = 30 47 _DEFAULT_TIMEOUT = 30
(...skipping 82 matching lines...) Expand 10 before | Expand all | Expand 10 after
128 # makes sure that the last line is also terminated, and is more memory 130 # makes sure that the last line is also terminated, and is more memory
129 # efficient than first appending an end-line to each line and then joining 131 # efficient than first appending an end-line to each line and then joining
130 # all of them together. 132 # all of them together.
131 return ''.join(s for line in lines for s in (line, '\n')) 133 return ''.join(s for line in lines for s in (line, '\n'))
132 134
133 135
134 class DeviceUtils(object): 136 class DeviceUtils(object):
135 137
136 _MAX_ADB_COMMAND_LENGTH = 512 138 _MAX_ADB_COMMAND_LENGTH = 512
137 _MAX_ADB_OUTPUT_LENGTH = 32768 139 _MAX_ADB_OUTPUT_LENGTH = 32768
140 _LAUNCHER_FOCUSED_RE = re.compile(
141 '\s*mCurrentFocus.*(Launcher|launcher).*')
138 _VALID_SHELL_VARIABLE = re.compile('^[a-zA-Z_][a-zA-Z0-9_]*$') 142 _VALID_SHELL_VARIABLE = re.compile('^[a-zA-Z_][a-zA-Z0-9_]*$')
139 143
140 # Property in /data/local.prop that controls Java assertions. 144 # Property in /data/local.prop that controls Java assertions.
141 JAVA_ASSERT_PROPERTY = 'dalvik.vm.enableassertions' 145 JAVA_ASSERT_PROPERTY = 'dalvik.vm.enableassertions'
142 146
143 def __init__(self, device, default_timeout=_DEFAULT_TIMEOUT, 147 def __init__(self, device, default_timeout=_DEFAULT_TIMEOUT,
144 default_retries=_DEFAULT_RETRIES): 148 default_retries=_DEFAULT_RETRIES):
145 """DeviceUtils constructor. 149 """DeviceUtils constructor.
146 150
147 Args: 151 Args:
(...skipping 184 matching lines...) Expand 10 before | Expand all | Expand 10 after
332 value = self.RunShellCommand('echo $EXTERNAL_STORAGE', 336 value = self.RunShellCommand('echo $EXTERNAL_STORAGE',
333 single_line=True, 337 single_line=True,
334 check_return=True) 338 check_return=True)
335 if not value: 339 if not value:
336 raise device_errors.CommandFailedError('$EXTERNAL_STORAGE is not set', 340 raise device_errors.CommandFailedError('$EXTERNAL_STORAGE is not set',
337 str(self)) 341 str(self))
338 self._cache['external_storage'] = value 342 self._cache['external_storage'] = value
339 return value 343 return value
340 344
341 @decorators.WithTimeoutAndRetriesFromInstance() 345 @decorators.WithTimeoutAndRetriesFromInstance()
342 def GetApplicationPath(self, package, timeout=None, retries=None): 346 def GetApplicationPaths(self, package, timeout=None, retries=None):
343 """Get the path of the installed apk on the device for the given package. 347 """Get the paths of the installed apks on the device for the given package.
344 348
345 Args: 349 Args:
346 package: Name of the package. 350 package: Name of the package.
347 351
348 Returns: 352 Returns:
349 Path to the apk on the device if it exists, None otherwise. 353 List of paths to the apks on the device for the given package.
350 """ 354 """
351 # 'pm path' is liable to incorrectly exit with a nonzero number starting 355 # 'pm path' is liable to incorrectly exit with a nonzero number starting
352 # in Lollipop. 356 # in Lollipop.
353 # TODO(jbudorick): Check if this is fixed as new Android versions are 357 # TODO(jbudorick): Check if this is fixed as new Android versions are
354 # released to put an upper bound on this. 358 # released to put an upper bound on this.
355 should_check_return = (self.build_version_sdk < 359 should_check_return = (self.build_version_sdk <
356 constants.ANDROID_SDK_VERSION_CODES.LOLLIPOP) 360 constants.ANDROID_SDK_VERSION_CODES.LOLLIPOP)
357 output = self.RunShellCommand(['pm', 'path', package], single_line=True, 361 output = self.RunShellCommand(
358 check_return=should_check_return) 362 ['pm', 'path', package], check_return=should_check_return)
359 if not output: 363 apks = []
360 return None 364 for line in output:
361 if not output.startswith('package:'): 365 if not line.startswith('package:'):
362 raise device_errors.CommandFailedError('pm path returned: %r' % output, 366 raise device_errors.CommandFailedError(
363 str(self)) 367 'pm path returned: %r' % '\n'.join(output), str(self))
364 return output[len('package:'):] 368 apks.append(line[len('package:'):])
369 return apks
370
371 @decorators.WithTimeoutAndRetriesFromInstance()
372 def GetApplicationDataDirectory(self, package, timeout=None, retries=None):
373 """Get the data directory on the device for the given package.
374
375 Args:
376 package: Name of the package.
377
378 Returns:
379 The package's data directory, or None if the package doesn't exist on the
380 device.
381 """
382 try:
383 output = self._RunPipedShellCommand(
384 'pm dump %s | grep dataDir=' % cmd_helper.SingleQuote(package))
385 for line in output:
386 _, _, dataDir = line.partition('dataDir=')
387 if dataDir:
388 return dataDir
389 except device_errors.CommandFailedError:
390 logging.exception('Could not find data directory for %s', package)
391 return None
365 392
366 @decorators.WithTimeoutAndRetriesFromInstance() 393 @decorators.WithTimeoutAndRetriesFromInstance()
367 def WaitUntilFullyBooted(self, wifi=False, timeout=None, retries=None): 394 def WaitUntilFullyBooted(self, wifi=False, timeout=None, retries=None):
368 """Wait for the device to fully boot. 395 """Wait for the device to fully boot.
369 396
370 This means waiting for the device to boot, the package manager to be 397 This means waiting for the device to boot, the package manager to be
371 available, and the SD card to be ready. It can optionally mean waiting 398 available, and the SD card to be ready. It can optionally mean waiting
372 for wifi to come up, too. 399 for wifi to come up, too.
373 400
374 Args: 401 Args:
375 wifi: A boolean indicating if we should wait for wifi to come up or not. 402 wifi: A boolean indicating if we should wait for wifi to come up or not.
376 timeout: timeout in seconds 403 timeout: timeout in seconds
377 retries: number of retries 404 retries: number of retries
378 405
379 Raises: 406 Raises:
380 CommandFailedError on failure. 407 CommandFailedError on failure.
381 CommandTimeoutError if one of the component waits times out. 408 CommandTimeoutError if one of the component waits times out.
382 DeviceUnreachableError if the device becomes unresponsive. 409 DeviceUnreachableError if the device becomes unresponsive.
383 """ 410 """
384 def sd_card_ready(): 411 def sd_card_ready():
385 try: 412 try:
386 self.RunShellCommand(['test', '-d', self.GetExternalStoragePath()], 413 self.RunShellCommand(['test', '-d', self.GetExternalStoragePath()],
387 check_return=True) 414 check_return=True)
388 return True 415 return True
389 except device_errors.AdbCommandFailedError: 416 except device_errors.AdbCommandFailedError:
390 return False 417 return False
391 418
392 def pm_ready(): 419 def pm_ready():
393 try: 420 try:
394 return self.GetApplicationPath('android') 421 return self.GetApplicationPaths('android')
395 except device_errors.CommandFailedError: 422 except device_errors.CommandFailedError:
396 return False 423 return False
397 424
398 def boot_completed(): 425 def boot_completed():
399 return self.GetProp('sys.boot_completed') == '1' 426 return self.GetProp('sys.boot_completed') == '1'
400 427
401 def wifi_enabled(): 428 def wifi_enabled():
402 return 'Wi-Fi is enabled' in self.RunShellCommand(['dumpsys', 'wifi'], 429 return 'Wi-Fi is enabled' in self.RunShellCommand(['dumpsys', 'wifi'],
403 check_return=False) 430 check_return=False)
404 431
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after
454 reinstall: A boolean indicating if we should keep any existing app data. 481 reinstall: A boolean indicating if we should keep any existing app data.
455 timeout: timeout in seconds 482 timeout: timeout in seconds
456 retries: number of retries 483 retries: number of retries
457 484
458 Raises: 485 Raises:
459 CommandFailedError if the installation fails. 486 CommandFailedError if the installation fails.
460 CommandTimeoutError if the installation times out. 487 CommandTimeoutError if the installation times out.
461 DeviceUnreachableError on missing device. 488 DeviceUnreachableError on missing device.
462 """ 489 """
463 package_name = apk_helper.GetPackageName(apk_path) 490 package_name = apk_helper.GetPackageName(apk_path)
464 device_path = self.GetApplicationPath(package_name) 491 device_paths = self.GetApplicationPaths(package_name)
465 if device_path is not None: 492 if device_paths:
466 should_install = bool(self._GetChangedFilesImpl(apk_path, device_path)) 493 if len(device_paths) > 1:
494 logging.warning(
495 'Installing single APK (%s) when split APKs (%s) are currently '
496 'installed.', apk_path, ' '.join(device_paths))
497 (files_to_push, _) = self._GetChangedAndStaleFiles(
498 apk_path, device_paths[0])
499 should_install = bool(files_to_push)
467 if should_install and not reinstall: 500 if should_install and not reinstall:
468 self.adb.Uninstall(package_name) 501 self.adb.Uninstall(package_name)
469 else: 502 else:
470 should_install = True 503 should_install = True
471 if should_install: 504 if should_install:
472 self.adb.Install(apk_path, reinstall=reinstall) 505 self.adb.Install(apk_path, reinstall=reinstall)
473 506
507 @decorators.WithTimeoutAndRetriesDefaults(
508 INSTALL_DEFAULT_TIMEOUT,
509 INSTALL_DEFAULT_RETRIES)
510 def InstallSplitApk(self, base_apk, split_apks, reinstall=False,
511 timeout=None, retries=None):
512 """Install a split APK.
513
514 Noop if all of the APK splits are already installed.
515
516 Args:
517 base_apk: A string of the path to the base APK.
518 split_apks: A list of strings of paths of all of the APK splits.
519 reinstall: A boolean indicating if we should keep any existing app data.
520 timeout: timeout in seconds
521 retries: number of retries
522
523 Raises:
524 CommandFailedError if the installation fails.
525 CommandTimeoutError if the installation times out.
526 DeviceUnreachableError on missing device.
527 DeviceVersionError if device SDK is less than Android L.
528 """
529 self._CheckSdkLevel(constants.ANDROID_SDK_VERSION_CODES.LOLLIPOP)
530
531 all_apks = [base_apk] + split_select.SelectSplits(
532 self, base_apk, split_apks)
533 package_name = apk_helper.GetPackageName(base_apk)
534 device_apk_paths = self.GetApplicationPaths(package_name)
535
536 if device_apk_paths:
537 partial_install_package = package_name
538 device_checksums = md5sum.CalculateDeviceMd5Sums(device_apk_paths, self)
539 host_checksums = md5sum.CalculateHostMd5Sums(all_apks)
540 apks_to_install = [k for (k, v) in host_checksums.iteritems()
541 if v not in device_checksums.values()]
542 if apks_to_install and not reinstall:
543 self.adb.Uninstall(package_name)
544 partial_install_package = None
545 apks_to_install = all_apks
546 else:
547 partial_install_package = None
548 apks_to_install = all_apks
549 if apks_to_install:
550 self.adb.InstallMultiple(
551 apks_to_install, partial=partial_install_package, reinstall=reinstall)
552
553 def _CheckSdkLevel(self, required_sdk_level):
554 """Raises an exception if the device does not have the required SDK level.
555 """
556 if self.build_version_sdk < required_sdk_level:
557 raise device_errors.DeviceVersionError(
558 ('Requires SDK level %s, device is SDK level %s' %
559 (required_sdk_level, self.build_version_sdk)),
560 device_serial=self.adb.GetDeviceSerial())
561
562
474 @decorators.WithTimeoutAndRetriesFromInstance() 563 @decorators.WithTimeoutAndRetriesFromInstance()
475 def RunShellCommand(self, cmd, check_return=False, cwd=None, env=None, 564 def RunShellCommand(self, cmd, check_return=False, cwd=None, env=None,
476 as_root=False, single_line=False, large_output=False, 565 as_root=False, single_line=False, large_output=False,
477 timeout=None, retries=None): 566 timeout=None, retries=None):
478 """Run an ADB shell command. 567 """Run an ADB shell command.
479 568
480 The command to run |cmd| should be a sequence of program arguments or else 569 The command to run |cmd| should be a sequence of program arguments or else
481 a single string. 570 a single string.
482 571
483 When |cmd| is a sequence, it is assumed to contain the name of the command 572 When |cmd| is a sequence, it is assumed to contain the name of the command
(...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after
550 with device_temp_file.DeviceTempFile(self.adb, suffix='.sh') as script: 639 with device_temp_file.DeviceTempFile(self.adb, suffix='.sh') as script:
551 self._WriteFileWithPush(script.name, cmd) 640 self._WriteFileWithPush(script.name, cmd)
552 logging.info('Large shell command will be run from file: %s ...', 641 logging.info('Large shell command will be run from file: %s ...',
553 cmd[:100]) 642 cmd[:100])
554 return handle_check_return('sh %s' % script.name_quoted) 643 return handle_check_return('sh %s' % script.name_quoted)
555 644
556 def handle_large_output(cmd, large_output_mode): 645 def handle_large_output(cmd, large_output_mode):
557 if large_output_mode: 646 if large_output_mode:
558 with device_temp_file.DeviceTempFile(self.adb) as large_output_file: 647 with device_temp_file.DeviceTempFile(self.adb) as large_output_file:
559 cmd = '%s > %s' % (cmd, large_output_file.name) 648 cmd = '%s > %s' % (cmd, large_output_file.name)
560 logging.info('Large output mode enabled. Will write output to device ' 649 logging.debug('Large output mode enabled. Will write output to '
561 'and read results from file.') 650 'device and read results from file.')
562 handle_large_command(cmd) 651 handle_large_command(cmd)
563 return self.ReadFile(large_output_file.name, force_pull=True) 652 return self.ReadFile(large_output_file.name, force_pull=True)
564 else: 653 else:
565 try: 654 try:
566 return handle_large_command(cmd) 655 return handle_large_command(cmd)
567 except device_errors.AdbCommandFailedError as exc: 656 except device_errors.AdbCommandFailedError as exc:
568 if exc.status is None: 657 if exc.status is None:
569 logging.exception('No output found for %s', cmd) 658 logging.exception('No output found for %s', cmd)
570 logging.warning('Attempting to run in large_output mode.') 659 logging.warning('Attempting to run in large_output mode.')
571 logging.warning('Use RunShellCommand(..., large_output=True) for ' 660 logging.warning('Use RunShellCommand(..., large_output=True) for '
(...skipping 135 matching lines...) Expand 10 before | Expand all | Expand 10 after
707 extras = {} 796 extras = {}
708 797
709 cmd = ['am', 'instrument'] 798 cmd = ['am', 'instrument']
710 if finish: 799 if finish:
711 cmd.append('-w') 800 cmd.append('-w')
712 if raw: 801 if raw:
713 cmd.append('-r') 802 cmd.append('-r')
714 for k, v in extras.iteritems(): 803 for k, v in extras.iteritems():
715 cmd.extend(['-e', str(k), str(v)]) 804 cmd.extend(['-e', str(k), str(v)])
716 cmd.append(component) 805 cmd.append(component)
717 return self.RunShellCommand(cmd, check_return=True) 806 return self.RunShellCommand(cmd, check_return=True, large_output=True)
718 807
719 @decorators.WithTimeoutAndRetriesFromInstance() 808 @decorators.WithTimeoutAndRetriesFromInstance()
720 def BroadcastIntent(self, intent_obj, timeout=None, retries=None): 809 def BroadcastIntent(self, intent_obj, timeout=None, retries=None):
721 """Send a broadcast intent. 810 """Send a broadcast intent.
722 811
723 Args: 812 Args:
724 intent: An Intent to broadcast. 813 intent: An Intent to broadcast.
725 timeout: timeout in seconds 814 timeout: timeout in seconds
726 retries: number of retries 815 retries: number of retries
727 816
728 Raises: 817 Raises:
729 CommandTimeoutError on timeout. 818 CommandTimeoutError on timeout.
730 DeviceUnreachableError on missing device. 819 DeviceUnreachableError on missing device.
731 """ 820 """
732 cmd = ['am', 'broadcast'] + intent_obj.am_args 821 cmd = ['am', 'broadcast'] + intent_obj.am_args
733 self.RunShellCommand(cmd, check_return=True) 822 self.RunShellCommand(cmd, check_return=True)
734 823
735 @decorators.WithTimeoutAndRetriesFromInstance() 824 @decorators.WithTimeoutAndRetriesFromInstance()
736 def GoHome(self, timeout=None, retries=None): 825 def GoHome(self, timeout=None, retries=None):
737 """Return to the home screen. 826 """Return to the home screen and obtain launcher focus.
827
828 This command launches the home screen and attempts to obtain
829 launcher focus until the timeout is reached.
738 830
739 Args: 831 Args:
740 timeout: timeout in seconds 832 timeout: timeout in seconds
741 retries: number of retries 833 retries: number of retries
742 834
743 Raises: 835 Raises:
744 CommandTimeoutError on timeout. 836 CommandTimeoutError on timeout.
745 DeviceUnreachableError on missing device. 837 DeviceUnreachableError on missing device.
746 """ 838 """
839 def is_launcher_focused():
840 output = self.RunShellCommand(['dumpsys', 'window', 'windows'],
841 check_return=True, large_output=True)
842 return any(self._LAUNCHER_FOCUSED_RE.match(l) for l in output)
843
844 def dismiss_popups():
845 # There is a dialog present; attempt to get rid of it.
846 # Not all dialogs can be dismissed with back.
847 self.SendKeyEvent(keyevent.KEYCODE_ENTER)
848 self.SendKeyEvent(keyevent.KEYCODE_BACK)
849 return is_launcher_focused()
850
851 # If Home is already focused, return early to avoid unnecessary work.
852 if is_launcher_focused():
853 return
854
747 self.StartActivity( 855 self.StartActivity(
748 intent.Intent(action='android.intent.action.MAIN', 856 intent.Intent(action='android.intent.action.MAIN',
749 category='android.intent.category.HOME'), 857 category='android.intent.category.HOME'),
750 blocking=True) 858 blocking=True)
751 859
860 if not is_launcher_focused():
861 timeout_retry.WaitFor(dismiss_popups, wait_period=1)
862
752 @decorators.WithTimeoutAndRetriesFromInstance() 863 @decorators.WithTimeoutAndRetriesFromInstance()
753 def ForceStop(self, package, timeout=None, retries=None): 864 def ForceStop(self, package, timeout=None, retries=None):
754 """Close the application. 865 """Close the application.
755 866
756 Args: 867 Args:
757 package: A string containing the name of the package to stop. 868 package: A string containing the name of the package to stop.
758 timeout: timeout in seconds 869 timeout: timeout in seconds
759 retries: number of retries 870 retries: number of retries
760 871
761 Raises: 872 Raises:
(...skipping 13 matching lines...) Expand all
775 886
776 Raises: 887 Raises:
777 CommandTimeoutError on timeout. 888 CommandTimeoutError on timeout.
778 DeviceUnreachableError on missing device. 889 DeviceUnreachableError on missing device.
779 """ 890 """
780 # Check that the package exists before clearing it for android builds below 891 # Check that the package exists before clearing it for android builds below
781 # JB MR2. Necessary because calling pm clear on a package that doesn't exist 892 # JB MR2. Necessary because calling pm clear on a package that doesn't exist
782 # may never return. 893 # may never return.
783 if ((self.build_version_sdk >= 894 if ((self.build_version_sdk >=
784 constants.ANDROID_SDK_VERSION_CODES.JELLY_BEAN_MR2) 895 constants.ANDROID_SDK_VERSION_CODES.JELLY_BEAN_MR2)
785 or self.GetApplicationPath(package)): 896 or self.GetApplicationPaths(package)):
786 self.RunShellCommand(['pm', 'clear', package], check_return=True) 897 self.RunShellCommand(['pm', 'clear', package], check_return=True)
787 898
788 @decorators.WithTimeoutAndRetriesFromInstance() 899 @decorators.WithTimeoutAndRetriesFromInstance()
789 def SendKeyEvent(self, keycode, timeout=None, retries=None): 900 def SendKeyEvent(self, keycode, timeout=None, retries=None):
790 """Sends a keycode to the device. 901 """Sends a keycode to the device.
791 902
792 See the pylib.constants.keyevent module for suitable keycode values. 903 See the pylib.constants.keyevent module for suitable keycode values.
793 904
794 Args: 905 Args:
795 keycode: A integer keycode to send to the device. 906 keycode: A integer keycode to send to the device.
796 timeout: timeout in seconds 907 timeout: timeout in seconds
797 retries: number of retries 908 retries: number of retries
798 909
799 Raises: 910 Raises:
800 CommandTimeoutError on timeout. 911 CommandTimeoutError on timeout.
801 DeviceUnreachableError on missing device. 912 DeviceUnreachableError on missing device.
802 """ 913 """
803 self.RunShellCommand(['input', 'keyevent', format(keycode, 'd')], 914 self.RunShellCommand(['input', 'keyevent', format(keycode, 'd')],
804 check_return=True) 915 check_return=True)
805 916
806 PUSH_CHANGED_FILES_DEFAULT_TIMEOUT = 10 * _DEFAULT_TIMEOUT 917 PUSH_CHANGED_FILES_DEFAULT_TIMEOUT = 10 * _DEFAULT_TIMEOUT
807 PUSH_CHANGED_FILES_DEFAULT_RETRIES = _DEFAULT_RETRIES 918 PUSH_CHANGED_FILES_DEFAULT_RETRIES = _DEFAULT_RETRIES
808 919
809 @decorators.WithTimeoutAndRetriesDefaults( 920 @decorators.WithTimeoutAndRetriesDefaults(
810 PUSH_CHANGED_FILES_DEFAULT_TIMEOUT, 921 PUSH_CHANGED_FILES_DEFAULT_TIMEOUT,
811 PUSH_CHANGED_FILES_DEFAULT_RETRIES) 922 PUSH_CHANGED_FILES_DEFAULT_RETRIES)
812 def PushChangedFiles(self, host_device_tuples, timeout=None, 923 def PushChangedFiles(self, host_device_tuples, timeout=None,
813 retries=None): 924 retries=None, delete_device_stale=False):
814 """Push files to the device, skipping files that don't need updating. 925 """Push files to the device, skipping files that don't need updating.
815 926
927 When a directory is pushed, it is traversed recursively on the host and
928 all files in it are pushed to the device as needed.
929 Additionally, if delete_device_stale option is True,
930 files that exist on the device but don't exist on the host are deleted.
931
816 Args: 932 Args:
817 host_device_tuples: A list of (host_path, device_path) tuples, where 933 host_device_tuples: A list of (host_path, device_path) tuples, where
818 |host_path| is an absolute path of a file or directory on the host 934 |host_path| is an absolute path of a file or directory on the host
819 that should be minimially pushed to the device, and |device_path| is 935 that should be minimially pushed to the device, and |device_path| is
820 an absolute path of the destination on the device. 936 an absolute path of the destination on the device.
821 timeout: timeout in seconds 937 timeout: timeout in seconds
822 retries: number of retries 938 retries: number of retries
939 delete_device_stale: option to delete stale files on device
823 940
824 Raises: 941 Raises:
825 CommandFailedError on failure. 942 CommandFailedError on failure.
826 CommandTimeoutError on timeout. 943 CommandTimeoutError on timeout.
827 DeviceUnreachableError on missing device. 944 DeviceUnreachableError on missing device.
828 """ 945 """
829 946
830 files = [] 947 all_changed_files = []
948 all_stale_files = []
831 for h, d in host_device_tuples: 949 for h, d in host_device_tuples:
832 if os.path.isdir(h): 950 if os.path.isdir(h):
833 self.RunShellCommand(['mkdir', '-p', d], check_return=True) 951 self.RunShellCommand(['mkdir', '-p', d], check_return=True)
834 files += self._GetChangedFilesImpl(h, d) 952 (changed_files, stale_files) = self._GetChangedAndStaleFiles(h, d)
953 all_changed_files += changed_files
954 all_stale_files += stale_files
835 955
836 if not files: 956 if delete_device_stale:
957 self.RunShellCommand(['rm', '-f'] + all_stale_files,
958 check_return=True)
959
960 if not all_changed_files:
837 return 961 return
838 962
963 self._PushFilesImpl(host_device_tuples, all_changed_files)
964
965 def _GetChangedAndStaleFiles(self, host_path, device_path):
966 """Get files to push and delete
967
968 Args:
969 host_path: an absolute path of a file or directory on the host
970 device_path: an absolute path of a file or directory on the device
971
972 Returns:
973 a two-element tuple
974 1st element: a list of (host_files_path, device_files_path) tuples to push
975 2nd element: a list of stale files under device_path
976 """
977 real_host_path = os.path.realpath(host_path)
978 try:
979 real_device_path = self.RunShellCommand(
980 ['realpath', device_path], single_line=True, check_return=True)
981 except device_errors.CommandFailedError:
982 real_device_path = None
983 if not real_device_path:
984 return ([(host_path, device_path)], [])
985
986 try:
987 host_checksums = md5sum.CalculateHostMd5Sums([real_host_path])
988 device_checksums = md5sum.CalculateDeviceMd5Sums(
989 [real_device_path], self)
990 except EnvironmentError as e:
991 logging.warning('Error calculating md5: %s', e)
992 return ([(host_path, device_path)], [])
993
994 if os.path.isfile(host_path):
995 host_checksum = host_checksums.get(real_host_path)
996 device_checksum = device_checksums.get(real_device_path)
997 if host_checksum != device_checksum:
998 return ([(host_path, device_path)], [])
999 else:
1000 return ([], [])
1001 else:
1002 to_push = []
1003 for host_abs_path, host_checksum in host_checksums.iteritems():
1004 device_abs_path = '%s/%s' % (
1005 real_device_path, os.path.relpath(host_abs_path, real_host_path))
1006 device_checksum = device_checksums.pop(device_abs_path, None)
1007 if device_checksum != host_checksum:
1008 to_push.append((host_abs_path, device_abs_path))
1009 to_delete = device_checksums.keys()
1010 return (to_push, to_delete)
1011
1012 def _PushFilesImpl(self, host_device_tuples, files):
839 size = sum(host_utils.GetRecursiveDiskUsage(h) for h, _ in files) 1013 size = sum(host_utils.GetRecursiveDiskUsage(h) for h, _ in files)
840 file_count = len(files) 1014 file_count = len(files)
841 dir_size = sum(host_utils.GetRecursiveDiskUsage(h) 1015 dir_size = sum(host_utils.GetRecursiveDiskUsage(h)
842 for h, _ in host_device_tuples) 1016 for h, _ in host_device_tuples)
843 dir_file_count = 0 1017 dir_file_count = 0
844 for h, _ in host_device_tuples: 1018 for h, _ in host_device_tuples:
845 if os.path.isdir(h): 1019 if os.path.isdir(h):
846 dir_file_count += sum(len(f) for _r, _d, f in os.walk(h)) 1020 dir_file_count += sum(len(f) for _r, _d, f in os.walk(h))
847 else: 1021 else:
848 dir_file_count += 1 1022 dir_file_count += 1
(...skipping 10 matching lines...) Expand all
859 dir_push_duration < zip_duration or not self._commands_installed): 1033 dir_push_duration < zip_duration or not self._commands_installed):
860 self._PushChangedFilesIndividually(host_device_tuples) 1034 self._PushChangedFilesIndividually(host_device_tuples)
861 elif push_duration < zip_duration or not self._commands_installed: 1035 elif push_duration < zip_duration or not self._commands_installed:
862 self._PushChangedFilesIndividually(files) 1036 self._PushChangedFilesIndividually(files)
863 else: 1037 else:
864 self._PushChangedFilesZipped(files) 1038 self._PushChangedFilesZipped(files)
865 self.RunShellCommand( 1039 self.RunShellCommand(
866 ['chmod', '-R', '777'] + [d for _, d in host_device_tuples], 1040 ['chmod', '-R', '777'] + [d for _, d in host_device_tuples],
867 as_root=True, check_return=True) 1041 as_root=True, check_return=True)
868 1042
869 def _GetChangedFilesImpl(self, host_path, device_path):
870 real_host_path = os.path.realpath(host_path)
871 try:
872 real_device_path = self.RunShellCommand(
873 ['realpath', device_path], single_line=True, check_return=True)
874 except device_errors.CommandFailedError:
875 real_device_path = None
876 if not real_device_path:
877 return [(host_path, device_path)]
878
879 try:
880 host_checksums = md5sum.CalculateHostMd5Sums([real_host_path])
881 device_paths_to_md5 = (
882 real_device_path if os.path.isfile(real_host_path)
883 else ('%s/%s' % (real_device_path, os.path.relpath(p, real_host_path))
884 for p in host_checksums.iterkeys()))
885 device_checksums = md5sum.CalculateDeviceMd5Sums(
886 device_paths_to_md5, self)
887 except EnvironmentError as e:
888 logging.warning('Error calculating md5: %s', e)
889 return [(host_path, device_path)]
890
891 if os.path.isfile(host_path):
892 host_checksum = host_checksums.get(real_host_path)
893 device_checksum = device_checksums.get(real_device_path)
894 if host_checksum != device_checksum:
895 return [(host_path, device_path)]
896 else:
897 return []
898 else:
899 to_push = []
900 for host_abs_path, host_checksum in host_checksums.iteritems():
901 device_abs_path = '%s/%s' % (
902 real_device_path, os.path.relpath(host_abs_path, real_host_path))
903 if (device_checksums.get(device_abs_path) != host_checksum):
904 to_push.append((host_abs_path, device_abs_path))
905 return to_push
906
907 def _InstallCommands(self): 1043 def _InstallCommands(self):
908 if self._commands_installed is None: 1044 if self._commands_installed is None:
909 try: 1045 try:
910 if not install_commands.Installed(self): 1046 if not install_commands.Installed(self):
911 install_commands.InstallCommands(self) 1047 install_commands.InstallCommands(self)
912 self._commands_installed = True 1048 self._commands_installed = True
913 except Exception as e: 1049 except Exception as e:
914 logging.warning('unzip not available: %s' % str(e)) 1050 logging.warning('unzip not available: %s' % str(e))
915 self._commands_installed = False 1051 self._commands_installed = False
916 1052
(...skipping 320 matching lines...) Expand 10 before | Expand all | Expand 10 after
1237 1373
1238 # Next, check the current runtime value is what we need, and 1374 # Next, check the current runtime value is what we need, and
1239 # if not, set it and report that a reboot is required. 1375 # if not, set it and report that a reboot is required.
1240 value = self.GetProp(self.JAVA_ASSERT_PROPERTY) 1376 value = self.GetProp(self.JAVA_ASSERT_PROPERTY)
1241 if new_value != value: 1377 if new_value != value:
1242 self.SetProp(self.JAVA_ASSERT_PROPERTY, new_value) 1378 self.SetProp(self.JAVA_ASSERT_PROPERTY, new_value)
1243 return True 1379 return True
1244 else: 1380 else:
1245 return False 1381 return False
1246 1382
1383 @property
1384 def language(self):
1385 """Returns the language setting on the device."""
1386 return self.GetProp('persist.sys.language', cache=False)
1387
1388 @property
1389 def country(self):
1390 """Returns the country setting on the device."""
1391 return self.GetProp('persist.sys.country', cache=False)
1392
1393 @property
1394 def screen_density(self):
1395 """Returns the screen density of the device."""
1396 DPI_TO_DENSITY = {
1397 120: 'ldpi',
1398 160: 'mdpi',
1399 240: 'hdpi',
1400 320: 'xhdpi',
1401 480: 'xxhdpi',
1402 640: 'xxxhdpi',
1403 }
1404 dpi = int(self.GetProp('ro.sf.lcd_density', cache=True))
1405 return DPI_TO_DENSITY.get(dpi, 'tvdpi')
1247 1406
1248 @property 1407 @property
1249 def build_description(self): 1408 def build_description(self):
1250 """Returns the build description of the system. 1409 """Returns the build description of the system.
1251 1410
1252 For example: 1411 For example:
1253 nakasi-user 4.4.4 KTU84P 1227136 release-keys 1412 nakasi-user 4.4.4 KTU84P 1227136 release-keys
1254 """ 1413 """
1255 return self.GetProp('ro.build.description', cache=True) 1414 return self.GetProp('ro.build.description', cache=True)
1256 1415
(...skipping 321 matching lines...) Expand 10 before | Expand all | Expand 10 after
1578 def HealthyDevices(cls): 1737 def HealthyDevices(cls):
1579 blacklist = device_blacklist.ReadBlacklist() 1738 blacklist = device_blacklist.ReadBlacklist()
1580 def blacklisted(adb): 1739 def blacklisted(adb):
1581 if adb.GetDeviceSerial() in blacklist: 1740 if adb.GetDeviceSerial() in blacklist:
1582 logging.warning('Device %s is blacklisted.', adb.GetDeviceSerial()) 1741 logging.warning('Device %s is blacklisted.', adb.GetDeviceSerial())
1583 return True 1742 return True
1584 return False 1743 return False
1585 1744
1586 return [cls(adb) for adb in adb_wrapper.AdbWrapper.Devices() 1745 return [cls(adb) for adb in adb_wrapper.AdbWrapper.Devices()
1587 if not blacklisted(adb)] 1746 if not blacklisted(adb)]
1588
OLDNEW
« no previous file with comments | « build/android/pylib/device/battery_utils_test.py ('k') | build/android/pylib/device/device_utils_device_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698