Chromium Code Reviews| Index: build/android/pylib/device/device_utils.py |
| diff --git a/build/android/pylib/device/device_utils.py b/build/android/pylib/device/device_utils.py |
| index 967809ff8e2860d5846f181b780e1ed7c341dc33..c08d24956ea4b13aa191c44cca8b1d88cedefb86 100644 |
| --- a/build/android/pylib/device/device_utils.py |
| +++ b/build/android/pylib/device/device_utils.py |
| @@ -202,6 +202,22 @@ class DeviceUtils(object): |
| """Returns the device serial.""" |
| return self.adb.GetDeviceSerial() |
| + # pylint: disable=no-self-argument |
|
mikecase (-- gone --)
2015/06/08 19:14:52
Guessing you won't like this function (kinda hacky
jbudorick
2015/06/08 19:38:26
should be in decorators.py
mikecase (-- gone --)
2015/06/17 22:00:42
Removed decorator. Added _CheckSdkLevel function.
|
| + def RequiresSdkLevel(requiredSdkLevel): |
| + """Returns a decorator that checks that the device has the required |
| + SDK level. |
| + """ |
| + def decorator(f): |
| + def wrapper(self, *args, **kwargs): |
| + if self.build_version_sdk < requiredSdkLevel: |
| + raise device_errors.DeviceVersionError( |
| + ('%s requires SDK level %s, device is SDK level %s' % |
| + (f.__name__, requiredSdkLevel, self.build_version_sdk)), |
| + device_serial=self.adb.GetDeviceSerial()) |
| + f(self, *args, **kwargs) |
| + return wrapper |
| + return decorator |
| + |
| @decorators.WithTimeoutAndRetriesFromInstance() |
| def IsOnline(self, timeout=None, retries=None): |
| """Checks whether the device is online. |
| @@ -339,14 +355,14 @@ class DeviceUtils(object): |
| return value |
| @decorators.WithTimeoutAndRetriesFromInstance() |
| - def GetApplicationPath(self, package, timeout=None, retries=None): |
| - """Get the path of the installed apk on the device for the given package. |
| + def GetApplicationPaths(self, package, timeout=None, retries=None): |
| + """Get the paths of the installed apks on the device for the given package. |
| Args: |
| package: Name of the package. |
| Returns: |
| - Path to the apk on the device if it exists, None otherwise. |
| + List of paths to the apks on the device if they exists, None otherwise. |
| """ |
| # 'pm path' is liable to incorrectly exit with a nonzero number starting |
| # in Lollipop. |
| @@ -354,14 +370,14 @@ class DeviceUtils(object): |
| # released to put an upper bound on this. |
| should_check_return = (self.build_version_sdk < |
| constants.ANDROID_SDK_VERSION_CODES.LOLLIPOP) |
| - output = self.RunShellCommand(['pm', 'path', package], single_line=True, |
| - check_return=should_check_return) |
| + output = self.RunShellCommand( |
| + ['pm', 'path', package], check_return=should_check_return) |
| if not output: |
| return None |
| - if not output.startswith('package:'): |
| - raise device_errors.CommandFailedError('pm path returned: %r' % output, |
| - str(self)) |
| - return output[len('package:'):] |
| + if not all([s.startswith('package:') for s in output]): |
| + raise device_errors.CommandFailedError( |
| + 'pm path returned: %r' % '\n'.join(output), str(self)) |
| + return [s[len('package:'):] for s in output] |
| @decorators.WithTimeoutAndRetriesFromInstance() |
| def WaitUntilFullyBooted(self, wifi=False, timeout=None, retries=None): |
| @@ -391,7 +407,7 @@ class DeviceUtils(object): |
| def pm_ready(): |
| try: |
| - return self.GetApplicationPath('android') |
| + return self.GetApplicationPaths('android') |
| except device_errors.CommandFailedError: |
| return False |
| @@ -461,9 +477,10 @@ class DeviceUtils(object): |
| DeviceUnreachableError on missing device. |
| """ |
| package_name = apk_helper.GetPackageName(apk_path) |
| - device_path = self.GetApplicationPath(package_name) |
| - if device_path is not None: |
| - should_install = bool(self._GetChangedFilesImpl(apk_path, device_path)) |
| + device_paths = self.GetApplicationPaths(package_name) |
| + if device_paths: |
| + should_install = bool( |
| + self._GetChangedFilesImpl(apk_path, device_paths[0])) |
| if should_install and not reinstall: |
| self.adb.Uninstall(package_name) |
| else: |
| @@ -471,6 +488,58 @@ class DeviceUtils(object): |
| if should_install: |
| self.adb.Install(apk_path, reinstall=reinstall) |
| + @RequiresSdkLevel(constants.ANDROID_SDK_VERSION_CODES.LOLLIPOP) |
| + @decorators.WithTimeoutAndRetriesDefaults( |
| + INSTALL_DEFAULT_TIMEOUT, |
| + INSTALL_DEFAULT_RETRIES) |
| + def InstallSplitApk(self, base_apk, split_apks, reinstall=False, |
| + timeout=None, retries=None): |
| + """Install a split APK. |
| + |
| + Noop if all of the APK splits are already installed. |
| + |
| + Args: |
| + base_apk: A string of the path to the base APK. |
| + split_apks: A list of strings of paths of all of the APK splits. |
| + reinstall: A boolean indicating if we should keep any existing app data. |
| + timeout: timeout in seconds |
| + retries: number of retries |
| + |
| + Raises: |
| + CommandFailedError if the installation fails. |
| + CommandTimeoutError if the installation times out. |
| + DeviceUnreachableError on missing device. |
| + DeviceVersionError if device SDK is less than Android L. |
| + """ |
| + def select_splits(): |
|
jbudorick
2015/06/08 19:38:26
extract to its own module
mikecase (-- gone --)
2015/06/17 22:00:42
done
|
| + split_config = ('%s-r%s-%s:%s' % |
| + (self.langauge_setting, |
| + self.country_setting, |
| + self.screen_density, |
| + self.product_cpu_abi)) |
| + cmd = [os.path.join(constants.ANDROID_SDK_TOOLS, 'split-select'), |
| + '--target', split_config, '--base', base_apk] |
| + for split in split_apks: |
| + cmd.extend(['--split', split]) |
| + return cmd_helper.GetCmdOutput(cmd).splitlines() |
| + |
| + required_apk_splits = select_splits() |
| + package_name = apk_helper.GetPackageName(base_apk) |
| + device_apk_paths = self.GetApplicationPaths(package_name) |
| + if device_apk_paths: |
|
jbudorick
2015/06/08 19:38:26
I'm not sure about this part. It looks like it's i
mikecase (-- gone --)
2015/06/17 22:00:42
I think you have to install them all or nothing. I
|
| + device_checksums = md5sum.CalculateDeviceMd5Sums( |
| + device_apk_paths, self).values() |
| + host_checksums = md5sum.CalculateHostMd5Sums( |
| + [base_apk] + required_apk_splits).values() |
| + should_install = sorted(host_checksums) != sorted(device_checksums) |
| + if should_install and not reinstall: |
| + self.adb.Uninstall(package_name) |
| + else: |
| + should_install = True |
| + if should_install: |
| + self.adb.InstallMultiple([base_apk] + required_apk_splits, |
| + reinstall=reinstall) |
| + |
| @decorators.WithTimeoutAndRetriesFromInstance() |
| def RunShellCommand(self, cmd, check_return=False, cwd=None, env=None, |
| as_root=False, single_line=False, large_output=False, |
| @@ -782,7 +851,7 @@ class DeviceUtils(object): |
| # may never return. |
| if ((self.build_version_sdk >= |
| constants.ANDROID_SDK_VERSION_CODES.JELLY_BEAN_MR2) |
| - or self.GetApplicationPath(package)): |
| + or self.GetApplicationPaths(package)): |
| self.RunShellCommand(['pm', 'clear', package], check_return=True) |
| @decorators.WithTimeoutAndRetriesFromInstance() |
| @@ -1244,6 +1313,28 @@ class DeviceUtils(object): |
| else: |
| return False |
| + @property |
| + def langauge_setting(self): |
| + """Returns the language setting on the device.""" |
| + return self.GetProp('persist.sys.language', cache=False) |
| + |
| + @property |
| + def country_setting(self): |
| + """Returns the country setting on the device.""" |
| + return self.GetProp('persist.sys.country', cache=False) |
| + |
| + @property |
| + def screen_density(self): |
| + """Returns the screen density of the device.""" |
| + DPI_TO_DENSITY = { |
| + 120: 'ldpi', |
| + 160: 'mdpi', |
| + 240: 'hdpi', |
| + 320: 'xhdpi', |
| + 480: 'xxhdpi', |
| + } |
| + dpi = int(self.GetProp('ro.sf.lcd_density', cache=True)) |
| + return DPI_TO_DENSITY.get(dpi, 'tvdpi') |
| @property |
| def build_description(self): |
| @@ -1584,5 +1675,4 @@ class DeviceUtils(object): |
| return False |
| return [cls(adb) for adb in adb_wrapper.AdbWrapper.Devices() |
| - if not blacklisted(adb)] |
| - |
| + if not blacklisted(adb)] |