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

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

Issue 1166113002: Add InstallSplitApk function to device utils. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 5 years, 6 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
« no previous file with comments | « build/android/gyp/util/build_device.py ('k') | build/android/pylib/device/device_utils_test.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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
(...skipping 184 matching lines...) Expand 10 before | Expand all | Expand 10 after
195 other: The instance of DeviceUtils to compare to. 195 other: The instance of DeviceUtils to compare to.
196 Returns: 196 Returns:
197 Whether |self| is less than |other|. 197 Whether |self| is less than |other|.
198 """ 198 """
199 return self.adb.GetDeviceSerial() < other.adb.GetDeviceSerial() 199 return self.adb.GetDeviceSerial() < other.adb.GetDeviceSerial()
200 200
201 def __str__(self): 201 def __str__(self):
202 """Returns the device serial.""" 202 """Returns the device serial."""
203 return self.adb.GetDeviceSerial() 203 return self.adb.GetDeviceSerial()
204 204
205 # 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.
206 def RequiresSdkLevel(requiredSdkLevel):
207 """Returns a decorator that checks that the device has the required
208 SDK level.
209 """
210 def decorator(f):
211 def wrapper(self, *args, **kwargs):
212 if self.build_version_sdk < requiredSdkLevel:
213 raise device_errors.DeviceVersionError(
214 ('%s requires SDK level %s, device is SDK level %s' %
215 (f.__name__, requiredSdkLevel, self.build_version_sdk)),
216 device_serial=self.adb.GetDeviceSerial())
217 f(self, *args, **kwargs)
218 return wrapper
219 return decorator
220
205 @decorators.WithTimeoutAndRetriesFromInstance() 221 @decorators.WithTimeoutAndRetriesFromInstance()
206 def IsOnline(self, timeout=None, retries=None): 222 def IsOnline(self, timeout=None, retries=None):
207 """Checks whether the device is online. 223 """Checks whether the device is online.
208 224
209 Args: 225 Args:
210 timeout: timeout in seconds 226 timeout: timeout in seconds
211 retries: number of retries 227 retries: number of retries
212 228
213 Returns: 229 Returns:
214 True if the device is online, False otherwise. 230 True if the device is online, False otherwise.
(...skipping 117 matching lines...) Expand 10 before | Expand all | Expand 10 after
332 value = self.RunShellCommand('echo $EXTERNAL_STORAGE', 348 value = self.RunShellCommand('echo $EXTERNAL_STORAGE',
333 single_line=True, 349 single_line=True,
334 check_return=True) 350 check_return=True)
335 if not value: 351 if not value:
336 raise device_errors.CommandFailedError('$EXTERNAL_STORAGE is not set', 352 raise device_errors.CommandFailedError('$EXTERNAL_STORAGE is not set',
337 str(self)) 353 str(self))
338 self._cache['external_storage'] = value 354 self._cache['external_storage'] = value
339 return value 355 return value
340 356
341 @decorators.WithTimeoutAndRetriesFromInstance() 357 @decorators.WithTimeoutAndRetriesFromInstance()
342 def GetApplicationPath(self, package, timeout=None, retries=None): 358 def GetApplicationPaths(self, package, timeout=None, retries=None):
343 """Get the path of the installed apk on the device for the given package. 359 """Get the paths of the installed apks on the device for the given package.
344 360
345 Args: 361 Args:
346 package: Name of the package. 362 package: Name of the package.
347 363
348 Returns: 364 Returns:
349 Path to the apk on the device if it exists, None otherwise. 365 List of paths to the apks on the device if they exists, None otherwise.
350 """ 366 """
351 # 'pm path' is liable to incorrectly exit with a nonzero number starting 367 # 'pm path' is liable to incorrectly exit with a nonzero number starting
352 # in Lollipop. 368 # in Lollipop.
353 # TODO(jbudorick): Check if this is fixed as new Android versions are 369 # TODO(jbudorick): Check if this is fixed as new Android versions are
354 # released to put an upper bound on this. 370 # released to put an upper bound on this.
355 should_check_return = (self.build_version_sdk < 371 should_check_return = (self.build_version_sdk <
356 constants.ANDROID_SDK_VERSION_CODES.LOLLIPOP) 372 constants.ANDROID_SDK_VERSION_CODES.LOLLIPOP)
357 output = self.RunShellCommand(['pm', 'path', package], single_line=True, 373 output = self.RunShellCommand(
358 check_return=should_check_return) 374 ['pm', 'path', package], check_return=should_check_return)
359 if not output: 375 if not output:
360 return None 376 return None
361 if not output.startswith('package:'): 377 if not all([s.startswith('package:') for s in output]):
362 raise device_errors.CommandFailedError('pm path returned: %r' % output, 378 raise device_errors.CommandFailedError(
363 str(self)) 379 'pm path returned: %r' % '\n'.join(output), str(self))
364 return output[len('package:'):] 380 return [s[len('package:'):] for s in output]
365 381
366 @decorators.WithTimeoutAndRetriesFromInstance() 382 @decorators.WithTimeoutAndRetriesFromInstance()
367 def WaitUntilFullyBooted(self, wifi=False, timeout=None, retries=None): 383 def WaitUntilFullyBooted(self, wifi=False, timeout=None, retries=None):
368 """Wait for the device to fully boot. 384 """Wait for the device to fully boot.
369 385
370 This means waiting for the device to boot, the package manager to be 386 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 387 available, and the SD card to be ready. It can optionally mean waiting
372 for wifi to come up, too. 388 for wifi to come up, too.
373 389
374 Args: 390 Args:
375 wifi: A boolean indicating if we should wait for wifi to come up or not. 391 wifi: A boolean indicating if we should wait for wifi to come up or not.
376 timeout: timeout in seconds 392 timeout: timeout in seconds
377 retries: number of retries 393 retries: number of retries
378 394
379 Raises: 395 Raises:
380 CommandFailedError on failure. 396 CommandFailedError on failure.
381 CommandTimeoutError if one of the component waits times out. 397 CommandTimeoutError if one of the component waits times out.
382 DeviceUnreachableError if the device becomes unresponsive. 398 DeviceUnreachableError if the device becomes unresponsive.
383 """ 399 """
384 def sd_card_ready(): 400 def sd_card_ready():
385 try: 401 try:
386 self.RunShellCommand(['test', '-d', self.GetExternalStoragePath()], 402 self.RunShellCommand(['test', '-d', self.GetExternalStoragePath()],
387 check_return=True) 403 check_return=True)
388 return True 404 return True
389 except device_errors.AdbCommandFailedError: 405 except device_errors.AdbCommandFailedError:
390 return False 406 return False
391 407
392 def pm_ready(): 408 def pm_ready():
393 try: 409 try:
394 return self.GetApplicationPath('android') 410 return self.GetApplicationPaths('android')
395 except device_errors.CommandFailedError: 411 except device_errors.CommandFailedError:
396 return False 412 return False
397 413
398 def boot_completed(): 414 def boot_completed():
399 return self.GetProp('sys.boot_completed') == '1' 415 return self.GetProp('sys.boot_completed') == '1'
400 416
401 def wifi_enabled(): 417 def wifi_enabled():
402 return 'Wi-Fi is enabled' in self.RunShellCommand(['dumpsys', 'wifi'], 418 return 'Wi-Fi is enabled' in self.RunShellCommand(['dumpsys', 'wifi'],
403 check_return=False) 419 check_return=False)
404 420
(...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. 470 reinstall: A boolean indicating if we should keep any existing app data.
455 timeout: timeout in seconds 471 timeout: timeout in seconds
456 retries: number of retries 472 retries: number of retries
457 473
458 Raises: 474 Raises:
459 CommandFailedError if the installation fails. 475 CommandFailedError if the installation fails.
460 CommandTimeoutError if the installation times out. 476 CommandTimeoutError if the installation times out.
461 DeviceUnreachableError on missing device. 477 DeviceUnreachableError on missing device.
462 """ 478 """
463 package_name = apk_helper.GetPackageName(apk_path) 479 package_name = apk_helper.GetPackageName(apk_path)
464 device_path = self.GetApplicationPath(package_name) 480 device_paths = self.GetApplicationPaths(package_name)
465 if device_path is not None: 481 if device_paths:
466 should_install = bool(self._GetChangedFilesImpl(apk_path, device_path)) 482 should_install = bool(
483 self._GetChangedFilesImpl(apk_path, device_paths[0]))
467 if should_install and not reinstall: 484 if should_install and not reinstall:
468 self.adb.Uninstall(package_name) 485 self.adb.Uninstall(package_name)
469 else: 486 else:
470 should_install = True 487 should_install = True
471 if should_install: 488 if should_install:
472 self.adb.Install(apk_path, reinstall=reinstall) 489 self.adb.Install(apk_path, reinstall=reinstall)
473 490
491 @RequiresSdkLevel(constants.ANDROID_SDK_VERSION_CODES.LOLLIPOP)
492 @decorators.WithTimeoutAndRetriesDefaults(
493 INSTALL_DEFAULT_TIMEOUT,
494 INSTALL_DEFAULT_RETRIES)
495 def InstallSplitApk(self, base_apk, split_apks, reinstall=False,
496 timeout=None, retries=None):
497 """Install a split APK.
498
499 Noop if all of the APK splits are already installed.
500
501 Args:
502 base_apk: A string of the path to the base APK.
503 split_apks: A list of strings of paths of all of the APK splits.
504 reinstall: A boolean indicating if we should keep any existing app data.
505 timeout: timeout in seconds
506 retries: number of retries
507
508 Raises:
509 CommandFailedError if the installation fails.
510 CommandTimeoutError if the installation times out.
511 DeviceUnreachableError on missing device.
512 DeviceVersionError if device SDK is less than Android L.
513 """
514 def select_splits():
jbudorick 2015/06/08 19:38:26 extract to its own module
mikecase (-- gone --) 2015/06/17 22:00:42 done
515 split_config = ('%s-r%s-%s:%s' %
516 (self.langauge_setting,
517 self.country_setting,
518 self.screen_density,
519 self.product_cpu_abi))
520 cmd = [os.path.join(constants.ANDROID_SDK_TOOLS, 'split-select'),
521 '--target', split_config, '--base', base_apk]
522 for split in split_apks:
523 cmd.extend(['--split', split])
524 return cmd_helper.GetCmdOutput(cmd).splitlines()
525
526 required_apk_splits = select_splits()
527 package_name = apk_helper.GetPackageName(base_apk)
528 device_apk_paths = self.GetApplicationPaths(package_name)
529 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
530 device_checksums = md5sum.CalculateDeviceMd5Sums(
531 device_apk_paths, self).values()
532 host_checksums = md5sum.CalculateHostMd5Sums(
533 [base_apk] + required_apk_splits).values()
534 should_install = sorted(host_checksums) != sorted(device_checksums)
535 if should_install and not reinstall:
536 self.adb.Uninstall(package_name)
537 else:
538 should_install = True
539 if should_install:
540 self.adb.InstallMultiple([base_apk] + required_apk_splits,
541 reinstall=reinstall)
542
474 @decorators.WithTimeoutAndRetriesFromInstance() 543 @decorators.WithTimeoutAndRetriesFromInstance()
475 def RunShellCommand(self, cmd, check_return=False, cwd=None, env=None, 544 def RunShellCommand(self, cmd, check_return=False, cwd=None, env=None,
476 as_root=False, single_line=False, large_output=False, 545 as_root=False, single_line=False, large_output=False,
477 timeout=None, retries=None): 546 timeout=None, retries=None):
478 """Run an ADB shell command. 547 """Run an ADB shell command.
479 548
480 The command to run |cmd| should be a sequence of program arguments or else 549 The command to run |cmd| should be a sequence of program arguments or else
481 a single string. 550 a single string.
482 551
483 When |cmd| is a sequence, it is assumed to contain the name of the command 552 When |cmd| is a sequence, it is assumed to contain the name of the command
(...skipping 291 matching lines...) Expand 10 before | Expand all | Expand 10 after
775 844
776 Raises: 845 Raises:
777 CommandTimeoutError on timeout. 846 CommandTimeoutError on timeout.
778 DeviceUnreachableError on missing device. 847 DeviceUnreachableError on missing device.
779 """ 848 """
780 # Check that the package exists before clearing it for android builds below 849 # 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 850 # JB MR2. Necessary because calling pm clear on a package that doesn't exist
782 # may never return. 851 # may never return.
783 if ((self.build_version_sdk >= 852 if ((self.build_version_sdk >=
784 constants.ANDROID_SDK_VERSION_CODES.JELLY_BEAN_MR2) 853 constants.ANDROID_SDK_VERSION_CODES.JELLY_BEAN_MR2)
785 or self.GetApplicationPath(package)): 854 or self.GetApplicationPaths(package)):
786 self.RunShellCommand(['pm', 'clear', package], check_return=True) 855 self.RunShellCommand(['pm', 'clear', package], check_return=True)
787 856
788 @decorators.WithTimeoutAndRetriesFromInstance() 857 @decorators.WithTimeoutAndRetriesFromInstance()
789 def SendKeyEvent(self, keycode, timeout=None, retries=None): 858 def SendKeyEvent(self, keycode, timeout=None, retries=None):
790 """Sends a keycode to the device. 859 """Sends a keycode to the device.
791 860
792 See the pylib.constants.keyevent module for suitable keycode values. 861 See the pylib.constants.keyevent module for suitable keycode values.
793 862
794 Args: 863 Args:
795 keycode: A integer keycode to send to the device. 864 keycode: A integer keycode to send to the device.
(...skipping 441 matching lines...) Expand 10 before | Expand all | Expand 10 after
1237 1306
1238 # Next, check the current runtime value is what we need, and 1307 # Next, check the current runtime value is what we need, and
1239 # if not, set it and report that a reboot is required. 1308 # if not, set it and report that a reboot is required.
1240 value = self.GetProp(self.JAVA_ASSERT_PROPERTY) 1309 value = self.GetProp(self.JAVA_ASSERT_PROPERTY)
1241 if new_value != value: 1310 if new_value != value:
1242 self.SetProp(self.JAVA_ASSERT_PROPERTY, new_value) 1311 self.SetProp(self.JAVA_ASSERT_PROPERTY, new_value)
1243 return True 1312 return True
1244 else: 1313 else:
1245 return False 1314 return False
1246 1315
1316 @property
1317 def langauge_setting(self):
1318 """Returns the language setting on the device."""
1319 return self.GetProp('persist.sys.language', cache=False)
1320
1321 @property
1322 def country_setting(self):
1323 """Returns the country setting on the device."""
1324 return self.GetProp('persist.sys.country', cache=False)
1325
1326 @property
1327 def screen_density(self):
1328 """Returns the screen density of the device."""
1329 DPI_TO_DENSITY = {
1330 120: 'ldpi',
1331 160: 'mdpi',
1332 240: 'hdpi',
1333 320: 'xhdpi',
1334 480: 'xxhdpi',
1335 }
1336 dpi = int(self.GetProp('ro.sf.lcd_density', cache=True))
1337 return DPI_TO_DENSITY.get(dpi, 'tvdpi')
1247 1338
1248 @property 1339 @property
1249 def build_description(self): 1340 def build_description(self):
1250 """Returns the build description of the system. 1341 """Returns the build description of the system.
1251 1342
1252 For example: 1343 For example:
1253 nakasi-user 4.4.4 KTU84P 1227136 release-keys 1344 nakasi-user 4.4.4 KTU84P 1227136 release-keys
1254 """ 1345 """
1255 return self.GetProp('ro.build.description', cache=True) 1346 return self.GetProp('ro.build.description', cache=True)
1256 1347
(...skipping 320 matching lines...) Expand 10 before | Expand all | Expand 10 after
1577 @classmethod 1668 @classmethod
1578 def HealthyDevices(cls): 1669 def HealthyDevices(cls):
1579 blacklist = device_blacklist.ReadBlacklist() 1670 blacklist = device_blacklist.ReadBlacklist()
1580 def blacklisted(adb): 1671 def blacklisted(adb):
1581 if adb.GetDeviceSerial() in blacklist: 1672 if adb.GetDeviceSerial() in blacklist:
1582 logging.warning('Device %s is blacklisted.', adb.GetDeviceSerial()) 1673 logging.warning('Device %s is blacklisted.', adb.GetDeviceSerial())
1583 return True 1674 return True
1584 return False 1675 return False
1585 1676
1586 return [cls(adb) for adb in adb_wrapper.AdbWrapper.Devices() 1677 return [cls(adb) for adb in adb_wrapper.AdbWrapper.Devices()
1587 if not blacklisted(adb)] 1678 if not blacklisted(adb)]
1588
OLDNEW
« no previous file with comments | « build/android/gyp/util/build_device.py ('k') | build/android/pylib/device/device_utils_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698