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 3# found in the LICENSE file. 4 5"""Provides a variety of device interactions based on adb. 6 7Eventually, this will be based on adb_wrapper. 8""" 9# pylint: disable=W0613 10 11import time 12 13import pylib.android_commands 14from pylib.device import adb_wrapper 15from pylib.device import decorators 16from pylib.device import device_errors 17from pylib.utils import apk_helper 18from pylib.utils import parallelizer 19 20_DEFAULT_TIMEOUT = 30 21_DEFAULT_RETRIES = 3 22 23 24@decorators.WithExplicitTimeoutAndRetries( 25 _DEFAULT_TIMEOUT, _DEFAULT_RETRIES) 26def GetAVDs(): 27 """Returns a list of Android Virtual Devices. 28 29 Returns: 30 A list containing the configured AVDs. 31 """ 32 return pylib.android_commands.GetAVDs() 33 34 35@decorators.WithExplicitTimeoutAndRetries( 36 _DEFAULT_TIMEOUT, _DEFAULT_RETRIES) 37def RestartServer(): 38 """Restarts the adb server. 39 40 Raises: 41 CommandFailedError if we fail to kill or restart the server. 42 """ 43 pylib.android_commands.AndroidCommands().RestartAdbServer() 44 45 46class DeviceUtils(object): 47 48 def __init__(self, device, default_timeout=_DEFAULT_TIMEOUT, 49 default_retries=_DEFAULT_RETRIES): 50 """DeviceUtils constructor. 51 52 Args: 53 device: Either a device serial, an existing AdbWrapper instance, an 54 an existing AndroidCommands instance, or nothing. 55 default_timeout: An integer containing the default number of seconds to 56 wait for an operation to complete if no explicit value 57 is provided. 58 default_retries: An integer containing the default number or times an 59 operation should be retried on failure if no explicit 60 value is provided. 61 """ 62 self.old_interface = None 63 if isinstance(device, basestring): 64 self.old_interface = pylib.android_commands.AndroidCommands(device) 65 elif isinstance(device, adb_wrapper.AdbWrapper): 66 self.old_interface = pylib.android_commands.AndroidCommands(str(device)) 67 elif isinstance(device, pylib.android_commands.AndroidCommands): 68 self.old_interface = device 69 elif not device: 70 self.old_interface = pylib.android_commands.AndroidCommands() 71 else: 72 raise ValueError('Unsupported type passed for argument "device"') 73 self._default_timeout = default_timeout 74 self._default_retries = default_retries 75 assert(hasattr(self, decorators.DEFAULT_TIMEOUT_ATTR)) 76 assert(hasattr(self, decorators.DEFAULT_RETRIES_ATTR)) 77 78 @decorators.WithTimeoutAndRetriesFromInstance() 79 def IsOnline(self, timeout=None, retries=None): 80 """Checks whether the device is online. 81 82 Args: 83 timeout: An integer containing the number of seconds to wait for the 84 operation to complete. 85 retries: An integer containing the number of times the operation should 86 be retried if it fails. 87 Returns: 88 True if the device is online, False otherwise. 89 """ 90 return self.old_interface.IsOnline() 91 92 @decorators.WithTimeoutAndRetriesFromInstance() 93 def HasRoot(self, timeout=None, retries=None): 94 """Checks whether or not adbd has root privileges. 95 96 Args: 97 timeout: Same as for |IsOnline|. 98 retries: Same as for |IsOnline|. 99 Returns: 100 True if adbd has root privileges, False otherwise. 101 """ 102 return self._HasRootImpl() 103 104 def _HasRootImpl(self): 105 """ Implementation of HasRoot. 106 107 This is split from HasRoot to allow other DeviceUtils methods to call 108 HasRoot without spawning a new timeout thread. 109 110 Returns: 111 Same as for |HasRoot|. 112 """ 113 return self.old_interface.IsRootEnabled() 114 115 @decorators.WithTimeoutAndRetriesFromInstance() 116 def EnableRoot(self, timeout=None, retries=None): 117 """Restarts adbd with root privileges. 118 119 Args: 120 timeout: Same as for |IsOnline|. 121 retries: Same as for |IsOnline|. 122 Raises: 123 CommandFailedError if root could not be enabled. 124 """ 125 if not self.old_interface.EnableAdbRoot(): 126 raise device_errors.CommandFailedError( 127 ['adb', 'root'], 'Could not enable root.') 128 129 @decorators.WithTimeoutAndRetriesFromInstance() 130 def GetExternalStoragePath(self, timeout=None, retries=None): 131 """Get the device's path to its SD card. 132 133 Args: 134 timeout: Same as for |IsOnline|. 135 retries: Same as for |IsOnline|. 136 Returns: 137 The device's path to its SD card. 138 """ 139 try: 140 return self.old_interface.GetExternalStorage() 141 except AssertionError as e: 142 raise device_errors.CommandFailedError( 143 ['adb', 'shell', 'echo', '$EXTERNAL_STORAGE'], str(e)) 144 145 @decorators.WithTimeoutAndRetriesFromInstance() 146 def WaitUntilFullyBooted(self, wifi=False, timeout=None, retries=None): 147 """Wait for the device to fully boot. 148 149 This means waiting for the device to boot, the package manager to be 150 available, and the SD card to be ready. It can optionally mean waiting 151 for wifi to come up, too. 152 153 Args: 154 wifi: A boolean indicating if we should wait for wifi to come up or not. 155 timeout: Same as for |IsOnline|. 156 retries: Same as for |IsOnline|. 157 Raises: 158 CommandTimeoutError if one of the component waits times out. 159 DeviceUnreachableError if the device becomes unresponsive. 160 """ 161 self._WaitUntilFullyBootedImpl(wifi=wifi, timeout=timeout) 162 163 def _WaitUntilFullyBootedImpl(self, wifi=False, timeout=None): 164 """ Implementation of WaitUntilFullyBooted. 165 166 This is split from WaitUntilFullyBooted to allow other DeviceUtils methods 167 to call WaitUntilFullyBooted without spawning a new timeout thread. 168 169 TODO(jbudorick) Remove the timeout parameter once this is no longer 170 implemented via AndroidCommands. 171 172 Args: 173 wifi: Same as for |WaitUntilFullyBooted|. 174 timeout: Same as for |IsOnline|. 175 Raises: 176 Same as for |WaitUntilFullyBooted|. 177 """ 178 if timeout is None: 179 timeout = self._default_timeout 180 self.old_interface.WaitForSystemBootCompleted(timeout) 181 self.old_interface.WaitForDevicePm() 182 self.old_interface.WaitForSdCardReady(timeout) 183 if wifi: 184 while not 'Wi-Fi is enabled' in ( 185 self._RunShellCommandImpl('dumpsys wifi')): 186 time.sleep(0.1) 187 188 @decorators.WithTimeoutAndRetriesDefaults( 189 10 * _DEFAULT_TIMEOUT, _DEFAULT_RETRIES) 190 def Reboot(self, block=True, timeout=None, retries=None): 191 """Reboot the device. 192 193 Args: 194 block: A boolean indicating if we should wait for the reboot to complete. 195 timeout: Same as for |IsOnline|. 196 retries: Same as for |IsOnline|. 197 """ 198 self.old_interface.Reboot() 199 if block: 200 self._WaitUntilFullyBootedImpl(timeout=timeout) 201 202 @decorators.WithTimeoutAndRetriesDefaults( 203 4 * _DEFAULT_TIMEOUT, _DEFAULT_RETRIES) 204 def Install(self, apk_path, reinstall=False, timeout=None, retries=None): 205 """Install an APK. 206 207 Noop if an identical APK is already installed. 208 209 Args: 210 apk_path: A string containing the path to the APK to install. 211 reinstall: A boolean indicating if we should keep any existing app data. 212 timeout: Same as for |IsOnline|. 213 retries: Same as for |IsOnline|. 214 Raises: 215 CommandFailedError if the installation fails. 216 CommandTimeoutError if the installation times out. 217 """ 218 package_name = apk_helper.GetPackageName(apk_path) 219 device_path = self.old_interface.GetApplicationPath(package_name) 220 if device_path is not None: 221 files_changed = self.old_interface.GetFilesChanged( 222 apk_path, device_path, ignore_filenames=True) 223 if len(files_changed) > 0: 224 should_install = True 225 if not reinstall: 226 out = self.old_interface.Uninstall(package_name) 227 for line in out.splitlines(): 228 if 'Failure' in line: 229 raise device_errors.CommandFailedError( 230 ['adb', 'uninstall', package_name], line.strip()) 231 else: 232 should_install = False 233 else: 234 should_install = True 235 if should_install: 236 try: 237 out = self.old_interface.Install(apk_path, reinstall=reinstall) 238 for line in out.splitlines(): 239 if 'Failure' in line: 240 raise device_errors.CommandFailedError( 241 ['adb', 'install', apk_path], line.strip()) 242 except AssertionError as e: 243 raise device_errors.CommandFailedError( 244 ['adb', 'install', apk_path], str(e)) 245 246 @decorators.WithTimeoutAndRetriesFromInstance() 247 def RunShellCommand(self, cmd, check_return=False, root=False, timeout=None, 248 retries=None): 249 """Run an ADB shell command. 250 251 TODO(jbudorick) Switch the default value of check_return to True after 252 AndroidCommands is gone. 253 254 Args: 255 cmd: A list containing the command to run on the device and any arguments. 256 check_return: A boolean indicating whether or not the return code should 257 be checked. 258 timeout: Same as for |IsOnline|. 259 retries: Same as for |IsOnline|. 260 Raises: 261 CommandFailedError if check_return is True and the return code is nozero. 262 Returns: 263 The output of the command. 264 """ 265 return self._RunShellCommandImpl(cmd, check_return=check_return, root=root, 266 timeout=timeout) 267 268 def _RunShellCommandImpl(self, cmd, check_return=False, root=False, 269 timeout=None): 270 """Implementation of RunShellCommand. 271 272 This is split from RunShellCommand to allow other DeviceUtils methods to 273 call RunShellCommand without spawning a new timeout thread. 274 275 TODO(jbudorick) Remove the timeout parameter once this is no longer 276 implemented via AndroidCommands. 277 278 Args: 279 cmd: Same as for |RunShellCommand|. 280 check_return: Same as for |RunShellCommand|. 281 timeout: Same as for |IsOnline|. 282 Raises: 283 Same as for |RunShellCommand|. 284 Returns: 285 Same as for |RunShellCommand|. 286 """ 287 if isinstance(cmd, list): 288 cmd = ' '.join(cmd) 289 if root and not self.HasRoot(): 290 cmd = 'su -c %s' % cmd 291 if check_return: 292 code, output = self.old_interface.GetShellCommandStatusAndOutput( 293 cmd, timeout_time=timeout) 294 if int(code) != 0: 295 raise device_errors.CommandFailedError( 296 cmd, 'Nonzero exit code (%d)' % code) 297 else: 298 output = self.old_interface.RunShellCommand(cmd, timeout_time=timeout) 299 return output 300 301 def __str__(self): 302 """Returns the device serial.""" 303 return self.old_interface.GetDevice() 304 305 @staticmethod 306 def parallel(devices=None, async=False): 307 """Creates a Parallelizer to operate over the provided list of devices. 308 309 If |devices| is either |None| or an empty list, the Parallelizer will 310 operate over all attached devices. 311 312 Args: 313 devices: A list of either DeviceUtils instances or objects from 314 from which DeviceUtils instances can be constructed. If None, 315 all attached devices will be used. 316 async: If true, returns a Parallelizer that runs operations 317 asynchronously. 318 Returns: 319 A Parallelizer operating over |devices|. 320 """ 321 if not devices or len(devices) == 0: 322 devices = pylib.android_commands.GetAttachedDevices() 323 parallelizer_type = (parallelizer.Parallelizer if async 324 else parallelizer.SyncParallelizer) 325 return parallelizer_type([ 326 d if isinstance(d, DeviceUtils) else DeviceUtils(d) 327 for d in devices]) 328 329