1# This Source Code Form is subject to the terms of the Mozilla Public 2# License, v. 2.0. If a copy of the MPL was not distributed with this file, 3# You can obtain one at http://mozilla.org/MPL/2.0/. 4 5import os 6import re 7import time 8 9from abc import ABCMeta 10 11import version_codes 12 13from adb import ADBDevice, ADBError 14 15 16class ADBAndroid(ADBDevice): 17 """ADBAndroid implements :class:`ADBDevice` providing Android-specific 18 functionality. 19 20 :: 21 22 from mozdevice import ADBAndroid 23 24 adbdevice = ADBAndroid() 25 print adbdevice.list_files("/mnt/sdcard") 26 if adbdevice.process_exist("org.mozilla.fennec"): 27 print "Fennec is running" 28 """ 29 __metaclass__ = ABCMeta 30 31 def __init__(self, 32 device=None, 33 adb='adb', 34 adb_host=None, 35 adb_port=None, 36 test_root='', 37 logger_name='adb', 38 timeout=300, 39 verbose=False, 40 device_ready_retry_wait=20, 41 device_ready_retry_attempts=3): 42 """Initializes the ADBAndroid object. 43 44 :param device: When a string is passed, it is interpreted as the 45 device serial number. This form is not compatible with 46 devices containing a ":" in the serial; in this case 47 ValueError will be raised. 48 When a dictionary is passed it must have one or both of 49 the keys "device_serial" and "usb". This is compatible 50 with the dictionaries in the list returned by 51 ADBHost.devices(). If the value of device_serial is a 52 valid serial not containing a ":" it will be used to 53 identify the device, otherwise the value of the usb key, 54 prefixed with "usb:" is used. 55 If None is passed and there is exactly one device attached 56 to the host, that device is used. If there is more than one 57 device attached, ValueError is raised. If no device is 58 attached the constructor will block until a device is 59 attached or the timeout is reached. 60 :type device: dict, str or None 61 :param adb_host: host of the adb server to connect to. 62 :type adb_host: str or None 63 :param adb_port: port of the adb server to connect to. 64 :type adb_port: integer or None 65 :param str logger_name: logging logger name. Defaults to 'adb'. 66 :param integer device_ready_retry_wait: number of seconds to wait 67 between attempts to check if the device is ready after a 68 reboot. 69 :param integer device_ready_retry_attempts: number of attempts when 70 checking if a device is ready. 71 72 :raises: * ADBError 73 * ADBTimeoutError 74 * ValueError 75 """ 76 ADBDevice.__init__(self, device=device, adb=adb, 77 adb_host=adb_host, adb_port=adb_port, 78 test_root=test_root, 79 logger_name=logger_name, timeout=timeout, 80 verbose=verbose, 81 device_ready_retry_wait=device_ready_retry_wait, 82 device_ready_retry_attempts=device_ready_retry_attempts) 83 # https://source.android.com/devices/tech/security/selinux/index.html 84 # setenforce 85 # usage: setenforce [ Enforcing | Permissive | 1 | 0 ] 86 # getenforce returns either Enforcing or Permissive 87 88 try: 89 self.selinux = True 90 if self.shell_output('getenforce', timeout=timeout) != 'Permissive': 91 self._logger.info('Setting SELinux Permissive Mode') 92 self.shell_output("setenforce Permissive", timeout=timeout, root=True) 93 except ADBError: 94 self.selinux = False 95 96 self.version = int(self.shell_output("getprop ro.build.version.sdk", 97 timeout=timeout)) 98 99 def reboot(self, timeout=None): 100 """Reboots the device. 101 102 :param timeout: optional integer specifying the maximum time in 103 seconds for any spawned adb process to complete before 104 throwing an ADBTimeoutError. 105 This timeout is per adb call. The total time spent 106 may exceed this value. If it is not specified, the value 107 set in the ADB constructor is used. 108 :raises: * ADBTimeoutError 109 * ADBError 110 111 reboot() reboots the device, issues an adb wait-for-device in order to 112 wait for the device to complete rebooting, then calls is_device_ready() 113 to determine if the device has completed booting. 114 115 If the device supports running adbd as root, adbd will be 116 restarted running as root. Then, if the device supports 117 SELinux, setenforce Permissive will be called to change 118 SELinux to permissive. This must be done after adbd is 119 restarted in order for the SELinux Permissive setting to 120 persist. 121 122 """ 123 ready = ADBDevice.reboot(self, timeout=timeout) 124 self._check_adb_root(timeout=timeout) 125 return ready 126 127 # Informational methods 128 129 def get_battery_percentage(self, timeout=None): 130 """Returns the battery charge as a percentage. 131 132 :param timeout: The maximum time in 133 seconds for any spawned adb process to complete before 134 throwing an ADBTimeoutError. 135 This timeout is per adb call. The total time spent 136 may exceed this value. If it is not specified, the value 137 set in the ADBDevice constructor is used. 138 :type timeout: integer or None 139 :returns: battery charge as a percentage. 140 :raises: * ADBTimeoutError 141 * ADBError 142 """ 143 level = None 144 scale = None 145 percentage = 0 146 cmd = "dumpsys battery" 147 re_parameter = re.compile(r'\s+(\w+):\s+(\d+)') 148 lines = self.shell_output(cmd, timeout=timeout).split('\r') 149 for line in lines: 150 match = re_parameter.match(line) 151 if match: 152 parameter = match.group(1) 153 value = match.group(2) 154 if parameter == 'level': 155 level = float(value) 156 elif parameter == 'scale': 157 scale = float(value) 158 if parameter is not None and scale is not None: 159 percentage = 100.0 * level / scale 160 break 161 return percentage 162 163 # System control methods 164 165 def is_device_ready(self, timeout=None): 166 """Checks if a device is ready for testing. 167 168 This method uses the android only package manager to check for 169 readiness. 170 171 :param timeout: The maximum time 172 in seconds for any spawned adb process to complete before 173 throwing an ADBTimeoutError. 174 This timeout is per adb call. The total time spent 175 may exceed this value. If it is not specified, the value 176 set in the ADB constructor is used. 177 :type timeout: integer or None 178 :raises: * ADBTimeoutError 179 * ADBError 180 """ 181 # command_output automatically inserts a 'wait-for-device' 182 # argument to adb. Issuing an empty command is the same as adb 183 # -s <device> wait-for-device. We don't send an explicit 184 # 'wait-for-device' since that would add duplicate 185 # 'wait-for-device' arguments which is an error in newer 186 # versions of adb. 187 self.command_output([], timeout=timeout) 188 pm_error_string = "Error: Could not access the Package Manager" 189 pm_list_commands = ["packages", "permission-groups", "permissions", 190 "instrumentation", "features", "libraries"] 191 ready_path = os.path.join(self.test_root, "ready") 192 for attempt in range(self._device_ready_retry_attempts): 193 failure = 'Unknown failure' 194 success = True 195 try: 196 state = self.get_state(timeout=timeout) 197 if state != 'device': 198 failure = "Device state: %s" % state 199 success = False 200 else: 201 if (self.selinux and self.shell_output('getenforce', 202 timeout=timeout) != 'Permissive'): 203 self._logger.info('Setting SELinux Permissive Mode') 204 self.shell_output("setenforce Permissive", timeout=timeout, root=True) 205 if self.is_dir(ready_path, timeout=timeout, root=True): 206 self.rmdir(ready_path, timeout=timeout, root=True) 207 self.mkdir(ready_path, timeout=timeout, root=True) 208 self.rmdir(ready_path, timeout=timeout, root=True) 209 # Invoke the pm list commands to see if it is up and 210 # running. 211 for pm_list_cmd in pm_list_commands: 212 data = self.shell_output("pm list %s" % pm_list_cmd, 213 timeout=timeout) 214 if pm_error_string in data: 215 failure = data 216 success = False 217 break 218 except ADBError as e: 219 success = False 220 failure = e.message 221 222 if not success: 223 self._logger.debug('Attempt %s of %s device not ready: %s' % ( 224 attempt + 1, self._device_ready_retry_attempts, 225 failure)) 226 time.sleep(self._device_ready_retry_wait) 227 228 return success 229 230 def power_on(self, timeout=None): 231 """Sets the device's power stayon value. 232 233 :param timeout: The maximum time in 234 seconds for any spawned adb process to complete before 235 throwing an ADBTimeoutError. 236 This timeout is per adb call. The total time spent 237 may exceed this value. If it is not specified, the value 238 set in the ADB constructor is used. 239 :type timeout: integer or None 240 :raises: * ADBTimeoutError 241 * ADBError 242 """ 243 try: 244 self.shell_output('svc power stayon true', 245 timeout=timeout, 246 root=True) 247 except ADBError as e: 248 # Executing this via adb shell errors, but not interactively. 249 # Any other exitcode is a real error. 250 if 'exitcode: 137' not in e.message: 251 raise 252 self._logger.warning('Unable to set power stayon true: %s' % e) 253 254 # Application management methods 255 256 def install_app(self, apk_path, timeout=None): 257 """Installs an app on the device. 258 259 :param str apk_path: The apk file name to be installed. 260 :param timeout: The maximum time in 261 seconds for any spawned adb process to complete before 262 throwing an ADBTimeoutError. 263 This timeout is per adb call. The total time spent 264 may exceed this value. If it is not specified, the value 265 set in the ADB constructor is used. 266 :type timeout: integer or None 267 :raises: * ADBTimeoutError 268 * ADBError 269 """ 270 cmd = ["install"] 271 if self.version >= version_codes.M: 272 cmd.append("-g") 273 cmd.append(apk_path) 274 data = self.command_output(cmd, timeout=timeout) 275 if data.find('Success') == -1: 276 raise ADBError("install failed for %s. Got: %s" % 277 (apk_path, data)) 278 279 def is_app_installed(self, app_name, timeout=None): 280 """Returns True if an app is installed on the device. 281 282 :param str app_name: The name of the app to be checked. 283 :param timeout: The maximum time in 284 seconds for any spawned adb process to complete before 285 throwing an ADBTimeoutError. 286 This timeout is per adb call. The total time spent 287 may exceed this value. If it is not specified, the value 288 set in the ADB constructor is used. 289 :type timeout: integer or None 290 :raises: * ADBTimeoutError 291 * ADBError 292 """ 293 pm_error_string = 'Error: Could not access the Package Manager' 294 data = self.shell_output("pm list package %s" % app_name, timeout=timeout) 295 if pm_error_string in data: 296 raise ADBError(pm_error_string) 297 if app_name not in data: 298 return False 299 return True 300 301 def launch_application(self, app_name, activity_name, intent, url=None, 302 extras=None, wait=True, fail_if_running=True, 303 timeout=None): 304 """Launches an Android application 305 306 :param str app_name: Name of application (e.g. `com.android.chrome`) 307 :param str activity_name: Name of activity to launch (e.g. `.Main`) 308 :param str intent: Intent to launch application with 309 :param url: URL to open 310 :type url: str or None 311 :param extras: Extra arguments for application. 312 :type extras: dict or None 313 :param bool wait: If True, wait for application to start before 314 returning. 315 :param bool fail_if_running: Raise an exception if instance of 316 application is already running. 317 :param timeout: The maximum time in 318 seconds for any spawned adb process to complete before 319 throwing an ADBTimeoutError. 320 This timeout is per adb call. The total time spent 321 may exceed this value. If it is not specified, the value 322 set in the ADB constructor is used. 323 :type timeout: integer or None 324 :raises: * ADBTimeoutError 325 * ADBError 326 """ 327 # If fail_if_running is True, we throw an exception here. Only one 328 # instance of an application can be running at once on Android, 329 # starting a new instance may not be what we want depending on what 330 # we want to do 331 if fail_if_running and self.process_exist(app_name, timeout=timeout): 332 raise ADBError("Only one instance of an application may be running " 333 "at once") 334 335 acmd = ["am", "start"] + \ 336 ["-W" if wait else '', "-n", "%s/%s" % (app_name, activity_name)] 337 338 if intent: 339 acmd.extend(["-a", intent]) 340 341 if extras: 342 for (key, val) in extras.iteritems(): 343 if isinstance(val, int): 344 extra_type_param = "--ei" 345 elif isinstance(val, bool): 346 extra_type_param = "--ez" 347 else: 348 extra_type_param = "--es" 349 acmd.extend([extra_type_param, str(key), str(val)]) 350 351 if url: 352 acmd.extend(["-d", url]) 353 354 cmd = self._escape_command_line(acmd) 355 self.shell_output(cmd, timeout=timeout) 356 357 def launch_fennec(self, app_name, intent="android.intent.action.VIEW", 358 moz_env=None, extra_args=None, url=None, wait=True, 359 fail_if_running=True, timeout=None): 360 """Convenience method to launch Fennec on Android with various 361 debugging arguments 362 363 :param str app_name: Name of fennec application (e.g. 364 `org.mozilla.fennec`) 365 :param str intent: Intent to launch application. 366 :param moz_env: Mozilla specific environment to pass into 367 application. 368 :type moz_env: str or None 369 :param extra_args: Extra arguments to be parsed by fennec. 370 :type extra_args: str or None 371 :param url: URL to open 372 :type url: str or None 373 :param bool wait: If True, wait for application to start before 374 returning. 375 :param bool fail_if_running: Raise an exception if instance of 376 application is already running. 377 :param timeout: The maximum time in 378 seconds for any spawned adb process to complete before 379 throwing an ADBTimeoutError. 380 This timeout is per adb call. The total time spent 381 may exceed this value. If it is not specified, the value 382 set in the ADB constructor is used. 383 :type timeout: integer or None 384 :raises: * ADBTimeoutError 385 * ADBError 386 """ 387 extras = {} 388 389 if moz_env: 390 # moz_env is expected to be a dictionary of environment variables: 391 # Fennec itself will set them when launched 392 for (env_count, (env_key, env_val)) in enumerate(moz_env.iteritems()): 393 extras["env" + str(env_count)] = env_key + "=" + env_val 394 395 # Additional command line arguments that fennec will read and use (e.g. 396 # with a custom profile) 397 if extra_args: 398 extras['args'] = " ".join(extra_args) 399 400 self.launch_application(app_name, "org.mozilla.gecko.BrowserApp", 401 intent, url=url, extras=extras, 402 wait=wait, fail_if_running=fail_if_running, 403 timeout=timeout) 404 405 def stop_application(self, app_name, timeout=None, root=False): 406 """Stops the specified application 407 408 For Android 3.0+, we use the "am force-stop" to do this, which 409 is reliable and does not require root. For earlier versions of 410 Android, we simply try to manually kill the processes started 411 by the app repeatedly until none is around any more. This is 412 less reliable and does require root. 413 414 :param str app_name: Name of application (e.g. `com.android.chrome`) 415 :param timeout: The maximum time in 416 seconds for any spawned adb process to complete before 417 throwing an ADBTimeoutError. 418 This timeout is per adb call. The total time spent 419 may exceed this value. If it is not specified, the value 420 set in the ADB constructor is used. 421 :type timeout: integer or None 422 :param bool root: Flag specifying if the command should be 423 executed as root. 424 :raises: * ADBTimeoutError 425 * ADBError 426 """ 427 if self.version >= version_codes.HONEYCOMB: 428 self.shell_output("am force-stop %s" % app_name, 429 timeout=timeout, root=root) 430 else: 431 num_tries = 0 432 max_tries = 5 433 while self.process_exist(app_name, timeout=timeout): 434 if num_tries > max_tries: 435 raise ADBError("Couldn't successfully kill %s after %s " 436 "tries" % (app_name, max_tries)) 437 self.pkill(app_name, timeout=timeout, root=root) 438 num_tries += 1 439 440 # sleep for a short duration to make sure there are no 441 # additional processes in the process of being launched 442 # (this is not 100% guaranteed to work since it is inherently 443 # racey, but it's the best we can do) 444 time.sleep(1) 445 446 def uninstall_app(self, app_name, reboot=False, timeout=None): 447 """Uninstalls an app on the device. 448 449 :param str app_name: The name of the app to be 450 uninstalled. 451 :param bool reboot: Flag indicating that the device should 452 be rebooted after the app is uninstalled. No reboot occurs 453 if the app is not installed. 454 :param timeout: The maximum time in 455 seconds for any spawned adb process to complete before 456 throwing an ADBTimeoutError. 457 This timeout is per adb call. The total time spent 458 may exceed this value. If it is not specified, the value 459 set in the ADB constructor is used. 460 :type timeout: integer or None 461 :raises: * ADBTimeoutError 462 * ADBError 463 """ 464 if self.is_app_installed(app_name, timeout=timeout): 465 data = self.command_output(["uninstall", app_name], timeout=timeout) 466 if data.find('Success') == -1: 467 self._logger.debug('uninstall_app failed: %s' % data) 468 raise ADBError("uninstall failed for %s. Got: %s" % (app_name, data)) 469 if reboot: 470 self.reboot(timeout=timeout) 471 472 def update_app(self, apk_path, timeout=None): 473 """Updates an app on the device and reboots. 474 475 :param str apk_path: The apk file name to be 476 updated. 477 :param timeout: The maximum time in 478 seconds for any spawned adb process to complete before 479 throwing an ADBTimeoutError. 480 This timeout is per adb call. The total time spent 481 may exceed this value. If it is not specified, the value 482 set in the ADB constructor is used. 483 :type timeout: integer or None 484 :raises: * ADBTimeoutError 485 * ADBError 486 """ 487 cmd = ["install", "-r"] 488 if self.version >= version_codes.M: 489 cmd.append("-g") 490 cmd.append(apk_path) 491 output = self.command_output(cmd, timeout=timeout) 492 self.reboot(timeout=timeout) 493 return output 494