Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / build / android / pylib / android_commands.py
1 # Copyright (c) 2012 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 an interface to communicate with the device via the adb command.
6
7 Assumes adb binary is currently on system path.
8 """
9
10 import collections
11 import datetime
12 import inspect
13 import logging
14 import os
15 import re
16 import shlex
17 import signal
18 import subprocess
19 import sys
20 import tempfile
21 import time
22
23 import cmd_helper
24 import constants
25 import screenshot
26 import system_properties
27
28 try:
29   from pylib import pexpect
30 except:
31   pexpect = None
32
33 sys.path.append(os.path.join(
34     constants.DIR_SOURCE_ROOT, 'third_party', 'android_testrunner'))
35 import adb_interface
36 import am_instrument_parser
37 import errors
38
39
40 # Pattern to search for the next whole line of pexpect output and capture it
41 # into a match group. We can't use ^ and $ for line start end with pexpect,
42 # see http://www.noah.org/python/pexpect/#doc for explanation why.
43 PEXPECT_LINE_RE = re.compile('\n([^\r]*)\r')
44
45 # Set the adb shell prompt to be a unique marker that will [hopefully] not
46 # appear at the start of any line of a command's output.
47 SHELL_PROMPT = '~+~PQ\x17RS~+~'
48
49 # Java properties file
50 LOCAL_PROPERTIES_PATH = '/data/local.prop'
51
52 # Property in /data/local.prop that controls Java assertions.
53 JAVA_ASSERT_PROPERTY = 'dalvik.vm.enableassertions'
54
55 MEMORY_INFO_RE = re.compile('^(?P<key>\w+):\s+(?P<usage_kb>\d+) kB$')
56 NVIDIA_MEMORY_INFO_RE = re.compile('^\s*(?P<user>\S+)\s*(?P<name>\S+)\s*'
57                                    '(?P<pid>\d+)\s*(?P<usage_bytes>\d+)$')
58
59 # Keycode "enum" suitable for passing to AndroidCommands.SendKey().
60 KEYCODE_HOME = 3
61 KEYCODE_BACK = 4
62 KEYCODE_DPAD_UP = 19
63 KEYCODE_DPAD_DOWN = 20
64 KEYCODE_DPAD_RIGHT = 22
65 KEYCODE_ENTER = 66
66 KEYCODE_MENU = 82
67
68 MD5SUM_DEVICE_FOLDER = constants.TEST_EXECUTABLE_DIR + '/md5sum/'
69 MD5SUM_DEVICE_PATH = MD5SUM_DEVICE_FOLDER + 'md5sum_bin'
70 MD5SUM_LD_LIBRARY_PATH = 'LD_LIBRARY_PATH=%s' % MD5SUM_DEVICE_FOLDER
71
72
73 def GetAVDs():
74   """Returns a list of AVDs."""
75   re_avd = re.compile('^[ ]+Name: ([a-zA-Z0-9_:.-]+)', re.MULTILINE)
76   avds = re_avd.findall(cmd_helper.GetCmdOutput(['android', 'list', 'avd']))
77   return avds
78
79
80 def GetAttachedDevices(hardware=True, emulator=True, offline=False):
81   """Returns a list of attached, android devices and emulators.
82
83   If a preferred device has been set with ANDROID_SERIAL, it will be first in
84   the returned list. The arguments specify what devices to include in the list.
85
86   Example output:
87
88     * daemon not running. starting it now on port 5037 *
89     * daemon started successfully *
90     List of devices attached
91     027c10494100b4d7        device
92     emulator-5554   offline
93
94   Args:
95     hardware: Include attached actual devices that are online.
96     emulator: Include emulators (i.e. AVD's) currently on host.
97     offline: Include devices and emulators that are offline.
98
99   Returns: List of devices.
100   """
101   adb_devices_output = cmd_helper.GetCmdOutput([constants.GetAdbPath(),
102                                                 'devices'])
103
104   re_device = re.compile('^([a-zA-Z0-9_:.-]+)\tdevice$', re.MULTILINE)
105   online_devices = re_device.findall(adb_devices_output)
106
107   re_device = re.compile('^(emulator-[0-9]+)\tdevice', re.MULTILINE)
108   emulator_devices = re_device.findall(adb_devices_output)
109
110   re_device = re.compile('^([a-zA-Z0-9_:.-]+)\toffline$', re.MULTILINE)
111   offline_devices = re_device.findall(adb_devices_output)
112
113   devices = []
114   # First determine list of online devices (e.g. hardware and/or emulator).
115   if hardware and emulator:
116     devices = online_devices
117   elif hardware:
118     devices = [device for device in online_devices
119                if device not in emulator_devices]
120   elif emulator:
121     devices = emulator_devices
122
123   # Now add offline devices if offline is true
124   if offline:
125     devices = devices + offline_devices
126
127   preferred_device = os.environ.get('ANDROID_SERIAL')
128   if preferred_device in devices:
129     devices.remove(preferred_device)
130     devices.insert(0, preferred_device)
131   return devices
132
133
134 def IsDeviceAttached(device):
135   """Return true if the device is attached and online."""
136   return device in GetAttachedDevices()
137
138
139 def _GetFilesFromRecursiveLsOutput(path, ls_output, re_file, utc_offset=None):
140   """Gets a list of files from `ls` command output.
141
142   Python's os.walk isn't used because it doesn't work over adb shell.
143
144   Args:
145     path: The path to list.
146     ls_output: A list of lines returned by an `ls -lR` command.
147     re_file: A compiled regular expression which parses a line into named groups
148         consisting of at minimum "filename", "date", "time", "size" and
149         optionally "timezone".
150     utc_offset: A 5-character string of the form +HHMM or -HHMM, where HH is a
151         2-digit string giving the number of UTC offset hours, and MM is a
152         2-digit string giving the number of UTC offset minutes. If the input
153         utc_offset is None, will try to look for the value of "timezone" if it
154         is specified in re_file.
155
156   Returns:
157     A dict of {"name": (size, lastmod), ...} where:
158       name: The file name relative to |path|'s directory.
159       size: The file size in bytes (0 for directories).
160       lastmod: The file last modification date in UTC.
161   """
162   re_directory = re.compile('^%s/(?P<dir>[^:]+):$' % re.escape(path))
163   path_dir = os.path.dirname(path)
164
165   current_dir = ''
166   files = {}
167   for line in ls_output:
168     directory_match = re_directory.match(line)
169     if directory_match:
170       current_dir = directory_match.group('dir')
171       continue
172     file_match = re_file.match(line)
173     if file_match:
174       filename = os.path.join(current_dir, file_match.group('filename'))
175       if filename.startswith(path_dir):
176         filename = filename[len(path_dir) + 1:]
177       lastmod = datetime.datetime.strptime(
178           file_match.group('date') + ' ' + file_match.group('time')[:5],
179           '%Y-%m-%d %H:%M')
180       if not utc_offset and 'timezone' in re_file.groupindex:
181         utc_offset = file_match.group('timezone')
182       if isinstance(utc_offset, str) and len(utc_offset) == 5:
183         utc_delta = datetime.timedelta(hours=int(utc_offset[1:3]),
184                                        minutes=int(utc_offset[3:5]))
185         if utc_offset[0:1] == '-':
186           utc_delta = -utc_delta
187         lastmod -= utc_delta
188       files[filename] = (int(file_match.group('size')), lastmod)
189   return files
190
191
192 def _ParseMd5SumOutput(md5sum_output):
193   """Returns a list of tuples from the provided md5sum output.
194
195   Args:
196     md5sum_output: output directly from md5sum binary.
197
198   Returns:
199     List of namedtuples with attributes |hash| and |path|, where |path| is the
200     absolute path to the file with an Md5Sum of |hash|.
201   """
202   HashAndPath = collections.namedtuple('HashAndPath', ['hash', 'path'])
203   split_lines = [line.split('  ') for line in md5sum_output]
204   return [HashAndPath._make(s) for s in split_lines if len(s) == 2]
205
206
207 def _HasAdbPushSucceeded(command_output):
208   """Returns whether adb push has succeeded from the provided output."""
209   # TODO(frankf): We should look at the return code instead of the command
210   # output for many of the commands in this file.
211   if not command_output:
212     return True
213   # Success looks like this: "3035 KB/s (12512056 bytes in 4.025s)"
214   # Errors look like this: "failed to copy  ... "
215   if not re.search('^[0-9]', command_output.splitlines()[-1]):
216     logging.critical('PUSH FAILED: ' + command_output)
217     return False
218   return True
219
220
221 def GetLogTimestamp(log_line, year):
222   """Returns the timestamp of the given |log_line| in the given year."""
223   try:
224     return datetime.datetime.strptime('%s-%s' % (year, log_line[:18]),
225                                       '%Y-%m-%d %H:%M:%S.%f')
226   except (ValueError, IndexError):
227     logging.critical('Error reading timestamp from ' + log_line)
228     return None
229
230
231 class AndroidCommands(object):
232   """Helper class for communicating with Android device via adb."""
233
234   def __init__(self, device=None, api_strict_mode=False):
235     """Constructor.
236
237     Args:
238       device: If given, adb commands are only send to the device of this ID.
239           Otherwise commands are sent to all attached devices.
240       api_strict_mode: A boolean indicating whether fatal errors should be
241           raised if this API is used improperly.
242     """
243     adb_dir = os.path.dirname(constants.GetAdbPath())
244     if adb_dir and adb_dir not in os.environ['PATH'].split(os.pathsep):
245       # Required by third_party/android_testrunner to call directly 'adb'.
246       os.environ['PATH'] += os.pathsep + adb_dir
247     self._adb = adb_interface.AdbInterface()
248     if device:
249       self._adb.SetTargetSerial(device)
250     self._device = device
251     self._logcat = None
252     self.logcat_process = None
253     self._logcat_tmpoutfile = None
254     self._pushed_files = []
255     self._device_utc_offset = None
256     self._potential_push_size = 0
257     self._actual_push_size = 0
258     self._external_storage = ''
259     self._util_wrapper = ''
260     self._api_strict_mode = api_strict_mode
261     self._system_properties = system_properties.SystemProperties(self.Adb())
262     self._push_if_needed_cache = {}
263
264     if not self._api_strict_mode:
265       logging.warning(
266           'API STRICT MODE IS DISABLED.\n'
267           'It should be enabled as soon as possible as it will eventually '
268           'become the default.')
269
270   @property
271   def system_properties(self):
272     return self._system_properties
273
274   def _LogShell(self, cmd):
275     """Logs the adb shell command."""
276     if self._device:
277       device_repr = self._device[-4:]
278     else:
279       device_repr = '????'
280     logging.info('[%s]> %s', device_repr, cmd)
281
282   def Adb(self):
283     """Returns our AdbInterface to avoid us wrapping all its methods."""
284     # TODO(tonyg): Disable this method when in _api_strict_mode.
285     return self._adb
286
287   def GetDevice(self):
288     """Returns the device serial."""
289     return self._device
290
291   def IsOnline(self):
292     """Checks whether the device is online.
293
294     Returns:
295       True if device is in 'device' mode, False otherwise.
296     """
297     out = self._adb.SendCommand('get-state')
298     return out.strip() == 'device'
299
300   def IsRootEnabled(self):
301     """Checks if root is enabled on the device."""
302     root_test_output = self.RunShellCommand('ls /root') or ['']
303     return not 'Permission denied' in root_test_output[0]
304
305   def EnableAdbRoot(self):
306     """Enables adb root on the device.
307
308     Returns:
309       True: if output from executing adb root was as expected.
310       False: otherwise.
311     """
312     if self.GetBuildType() == 'user':
313       logging.warning("Can't enable root in production builds with type user")
314       return False
315     else:
316       return_value = self._adb.EnableAdbRoot()
317       # EnableAdbRoot inserts a call for wait-for-device only when adb logcat
318       # output matches what is expected. Just to be safe add a call to
319       # wait-for-device.
320       self._adb.SendCommand('wait-for-device')
321       return return_value
322
323   def GetDeviceYear(self):
324     """Returns the year information of the date on device."""
325     return self.RunShellCommand('date +%Y')[0]
326
327   def GetExternalStorage(self):
328     if not self._external_storage:
329       self._external_storage = self.RunShellCommand('echo $EXTERNAL_STORAGE')[0]
330       assert self._external_storage, 'Unable to find $EXTERNAL_STORAGE'
331     return self._external_storage
332
333   def WaitForDevicePm(self):
334     """Blocks until the device's package manager is available.
335
336     To workaround http://b/5201039, we restart the shell and retry if the
337     package manager isn't back after 120 seconds.
338
339     Raises:
340       errors.WaitForResponseTimedOutError after max retries reached.
341     """
342     last_err = None
343     retries = 3
344     while retries:
345       try:
346         self._adb.WaitForDevicePm()
347         return  # Success
348       except errors.WaitForResponseTimedOutError as e:
349         last_err = e
350         logging.warning('Restarting and retrying after timeout: %s', e)
351         retries -= 1
352         self.RestartShell()
353     raise last_err  # Only reached after max retries, re-raise the last error.
354
355   def RestartShell(self):
356     """Restarts the shell on the device. Does not block for it to return."""
357     self.RunShellCommand('stop')
358     self.RunShellCommand('start')
359
360   def Reboot(self, full_reboot=True):
361     """Reboots the device and waits for the package manager to return.
362
363     Args:
364       full_reboot: Whether to fully reboot the device or just restart the shell.
365     """
366     # TODO(torne): hive can't reboot the device either way without breaking the
367     # connection; work out if we can handle this better
368     if os.environ.get('USING_HIVE'):
369       logging.warning('Ignoring reboot request as we are on hive')
370       return
371     if full_reboot or not self.IsRootEnabled():
372       self._adb.SendCommand('reboot')
373       self._system_properties = system_properties.SystemProperties(self.Adb())
374       timeout = 300
375       retries = 1
376       # Wait for the device to disappear.
377       while retries < 10 and self.IsOnline():
378         time.sleep(1)
379         retries += 1
380     else:
381       self.RestartShell()
382       timeout = 120
383     # To run tests we need at least the package manager and the sd card (or
384     # other external storage) to be ready.
385     self.WaitForDevicePm()
386     self.WaitForSdCardReady(timeout)
387
388   def Shutdown(self):
389     """Shuts down the device."""
390     self._adb.SendCommand('reboot -p')
391     self._system_properties = system_properties.SystemProperties(self.Adb())
392
393   def Uninstall(self, package):
394     """Uninstalls the specified package from the device.
395
396     Args:
397       package: Name of the package to remove.
398
399     Returns:
400       A status string returned by adb uninstall
401     """
402     uninstall_command = 'uninstall %s' % package
403
404     self._LogShell(uninstall_command)
405     return self._adb.SendCommand(uninstall_command, timeout_time=60)
406
407   def Install(self, package_file_path, reinstall=False):
408     """Installs the specified package to the device.
409
410     Args:
411       package_file_path: Path to .apk file to install.
412       reinstall: Reinstall an existing apk, keeping the data.
413
414     Returns:
415       A status string returned by adb install
416     """
417     assert os.path.isfile(package_file_path), ('<%s> is not file' %
418                                                package_file_path)
419
420     install_cmd = ['install']
421
422     if reinstall:
423       install_cmd.append('-r')
424
425     install_cmd.append(package_file_path)
426     install_cmd = ' '.join(install_cmd)
427
428     self._LogShell(install_cmd)
429     # FIXME(wang16): Change the timeout here to five minutes. Revert
430     # the change when slaves can run kvm enabled x86 android emulators.
431     return self._adb.SendCommand(install_cmd,
432                                  timeout_time=5 * 60,
433                                  retry_count=0)
434
435   def ManagedInstall(self, apk_path, keep_data=False, package_name=None,
436                      reboots_on_timeout=2):
437     """Installs specified package and reboots device on timeouts.
438
439     If package_name is supplied, checks if the package is already installed and
440     doesn't reinstall if the apk md5sums match.
441
442     Args:
443       apk_path: Path to .apk file to install.
444       keep_data: Reinstalls instead of uninstalling first, preserving the
445         application data.
446       package_name: Package name (only needed if keep_data=False).
447       reboots_on_timeout: number of time to reboot if package manager is frozen.
448     """
449     # Check if package is already installed and up to date.
450     if package_name:
451       installed_apk_path = self.GetApplicationPath(package_name)
452       if (installed_apk_path and
453           not self.GetFilesChanged(apk_path, installed_apk_path,
454                                    ignore_filenames=True)):
455         logging.info('Skipped install: identical %s APK already installed' %
456             package_name)
457         return
458     # Install.
459     reboots_left = reboots_on_timeout
460     while True:
461       try:
462         if not keep_data:
463           assert package_name
464           self.Uninstall(package_name)
465         install_status = self.Install(apk_path, reinstall=keep_data)
466         if 'Success' in install_status:
467           return
468         else:
469           raise Exception('Install failure: %s' % install_status)
470       except errors.WaitForResponseTimedOutError:
471         print '@@@STEP_WARNINGS@@@'
472         logging.info('Timeout on installing %s on device %s', apk_path,
473                      self._device)
474
475         if reboots_left <= 0:
476           raise Exception('Install timed out')
477
478         # Force a hard reboot on last attempt
479         self.Reboot(full_reboot=(reboots_left == 1))
480         reboots_left -= 1
481
482   def MakeSystemFolderWritable(self):
483     """Remounts the /system folder rw."""
484     out = self._adb.SendCommand('remount')
485     if out.strip() != 'remount succeeded':
486       raise errors.MsgException('Remount failed: %s' % out)
487
488   def RestartAdbdOnDevice(self):
489     logging.info('Killing adbd on the device...')
490     adb_pids = self.ExtractPid('adbd')
491     if not adb_pids:
492       raise errors.MsgException('Unable to obtain adbd pid')
493     try:
494       self.KillAll('adbd', signal=signal.SIGTERM, with_su=True)
495       logging.info('Waiting for device to settle...')
496       self._adb.SendCommand('wait-for-device')
497       new_adb_pids = self.ExtractPid('adbd')
498       if new_adb_pids == adb_pids:
499         logging.warning('adbd on the device may not have been restarted.')
500     except Exception as e:
501       logging.error('Exception when trying to kill adbd on the device [%s]', e)
502
503   def RestartAdbServer(self):
504     """Restart the adb server."""
505     ret = self.KillAdbServer()
506     if ret != 0:
507       raise errors.MsgException('KillAdbServer: %d' % ret)
508
509     ret = self.StartAdbServer()
510     if ret != 0:
511       raise errors.MsgException('StartAdbServer: %d' % ret)
512
513   def KillAdbServer(self):
514     """Kill adb server."""
515     adb_cmd = [constants.GetAdbPath(), 'kill-server']
516     ret = cmd_helper.RunCmd(adb_cmd)
517     retry = 0
518     while retry < 3:
519       ret, _ = cmd_helper.GetCmdStatusAndOutput(['pgrep', 'adb'])
520       if ret != 0:
521         # pgrep didn't find adb, kill-server succeeded.
522         return 0
523       retry += 1
524       time.sleep(retry)
525     return ret
526
527   def StartAdbServer(self):
528     """Start adb server."""
529     adb_cmd = ['taskset', '-c', '0', constants.GetAdbPath(), 'start-server']
530     ret, _ = cmd_helper.GetCmdStatusAndOutput(adb_cmd)
531     retry = 0
532     while retry < 3:
533       ret, _ = cmd_helper.GetCmdStatusAndOutput(['pgrep', 'adb'])
534       if ret == 0:
535         # pgrep found adb, start-server succeeded.
536         # Waiting for device to reconnect before returning success.
537         self._adb.SendCommand('wait-for-device')
538         return 0
539       retry += 1
540       time.sleep(retry)
541     return ret
542
543   def WaitForSystemBootCompleted(self, wait_time):
544     """Waits for targeted system's boot_completed flag to be set.
545
546     Args:
547       wait_time: time in seconds to wait
548
549     Raises:
550       WaitForResponseTimedOutError if wait_time elapses and flag still not
551       set.
552     """
553     logging.info('Waiting for system boot completed...')
554     self._adb.SendCommand('wait-for-device')
555     # Now the device is there, but system not boot completed.
556     # Query the sys.boot_completed flag with a basic command
557     boot_completed = False
558     attempts = 0
559     wait_period = 5
560     while not boot_completed and (attempts * wait_period) < wait_time:
561       output = self.system_properties['sys.boot_completed']
562       output = output.strip()
563       if output == '1':
564         boot_completed = True
565       else:
566         # If 'error: xxx' returned when querying the flag, it means
567         # adb server lost the connection to the emulator, so restart the adb
568         # server.
569         if 'error:' in output:
570           self.RestartAdbServer()
571         time.sleep(wait_period)
572         attempts += 1
573     if not boot_completed:
574       raise errors.WaitForResponseTimedOutError(
575           'sys.boot_completed flag was not set after %s seconds' % wait_time)
576
577   def WaitForSdCardReady(self, timeout_time):
578     """Wait for the SD card ready before pushing data into it."""
579     logging.info('Waiting for SD card ready...')
580     sdcard_ready = False
581     attempts = 0
582     wait_period = 5
583     external_storage = self.GetExternalStorage()
584     while not sdcard_ready and attempts * wait_period < timeout_time:
585       output = self.RunShellCommand('ls ' + external_storage)
586       if output:
587         sdcard_ready = True
588       else:
589         time.sleep(wait_period)
590         attempts += 1
591     if not sdcard_ready:
592       raise errors.WaitForResponseTimedOutError(
593           'SD card not ready after %s seconds' % timeout_time)
594
595   def _CheckCommandIsValid(self, command):
596     """Raises a ValueError if the command is not valid."""
597
598     # A dict of commands the user should not run directly and a mapping to the
599     # API they should use instead.
600     preferred_apis = {
601         'getprop': 'system_properties[<PROPERTY>]',
602         'setprop': 'system_properties[<PROPERTY>]',
603         'su': 'RunShellCommandWithSU()',
604         }
605
606     # A dict of commands to methods that may call them.
607     whitelisted_callers = {
608         'su': 'RunShellCommandWithSU',
609         }
610
611     base_command = shlex.split(command)[0]
612     if (base_command in preferred_apis and
613         (base_command not in whitelisted_callers or
614          whitelisted_callers[base_command] not in [
615           f[3] for f in inspect.stack()])):
616       error_msg = ('%s cannot be run directly. Instead use: %s' %
617                    (base_command, preferred_apis[base_command]))
618       if self._api_strict_mode:
619         raise ValueError(error_msg)
620       else:
621         logging.warning(error_msg)
622
623   # It is tempting to turn this function into a generator, however this is not
624   # possible without using a private (local) adb_shell instance (to ensure no
625   # other command interleaves usage of it), which would defeat the main aim of
626   # being able to reuse the adb shell instance across commands.
627   def RunShellCommand(self, command, timeout_time=20, log_result=False):
628     """Send a command to the adb shell and return the result.
629
630     Args:
631       command: String containing the shell command to send. Must not include
632                the single quotes as we use them to escape the whole command.
633       timeout_time: Number of seconds to wait for command to respond before
634         retrying, used by AdbInterface.SendShellCommand.
635       log_result: Boolean to indicate whether we should log the result of the
636                   shell command.
637
638     Returns:
639       list containing the lines of output received from running the command
640     """
641     self._CheckCommandIsValid(command)
642     self._LogShell(command)
643     if "'" in command: logging.warning(command + " contains ' quotes")
644     result = self._adb.SendShellCommand(
645         "'%s'" % command, timeout_time).splitlines()
646     # TODO(b.kelemen): we should really be able to drop the stderr of the
647     # command or raise an exception based on what the caller wants.
648     result = [ l for l in result if not l.startswith('WARNING') ]
649     if ['error: device not found'] == result:
650       raise errors.DeviceUnresponsiveError('device not found')
651     if log_result:
652       self._LogShell('\n'.join(result))
653     return result
654
655   def GetShellCommandStatusAndOutput(self, command, timeout_time=20,
656                                      log_result=False):
657     """See RunShellCommand() above.
658
659     Returns:
660       The tuple (exit code, list of output lines).
661     """
662     lines = self.RunShellCommand(
663         command + '; echo %$?', timeout_time, log_result)
664     last_line = lines[-1]
665     status_pos = last_line.rfind('%')
666     assert status_pos >= 0
667     status = int(last_line[status_pos + 1:])
668     if status_pos == 0:
669       lines = lines[:-1]
670     else:
671       lines = lines[:-1] + [last_line[:status_pos]]
672     return (status, lines)
673
674   def KillAll(self, process, signal=9, with_su=False):
675     """Android version of killall, connected via adb.
676
677     Args:
678       process: name of the process to kill off.
679       signal: signal to use, 9 (SIGKILL) by default.
680       with_su: wether or not to use su to kill the processes.
681
682     Returns:
683       the number of processes killed
684     """
685     pids = self.ExtractPid(process)
686     if pids:
687       cmd = 'kill -%d %s' % (signal, ' '.join(pids))
688       if with_su:
689         self.RunShellCommandWithSU(cmd)
690       else:
691         self.RunShellCommand(cmd)
692     return len(pids)
693
694   def KillAllBlocking(self, process, timeout_sec):
695     """Blocking version of killall, connected via adb.
696
697     This waits until no process matching the corresponding name appears in ps'
698     output anymore.
699
700     Args:
701       process: name of the process to kill off
702       timeout_sec: the timeout in seconds
703
704     Returns:
705       the number of processes killed
706     """
707     processes_killed = self.KillAll(process)
708     if processes_killed:
709       elapsed = 0
710       wait_period = 0.1
711       # Note that this doesn't take into account the time spent in ExtractPid().
712       while self.ExtractPid(process) and elapsed < timeout_sec:
713         time.sleep(wait_period)
714         elapsed += wait_period
715       if elapsed >= timeout_sec:
716         return 0
717     return processes_killed
718
719   def _GetActivityCommand(self, package, activity, wait_for_completion, action,
720                           category, data, extras, trace_file_name, force_stop,
721                           flags):
722     """Creates command to start |package|'s activity on the device.
723
724     Args - as for StartActivity
725
726     Returns:
727       the command to run on the target to start the activity
728     """
729     cmd = 'am start -a %s' % action
730     if force_stop:
731       cmd += ' -S'
732     if wait_for_completion:
733       cmd += ' -W'
734     if category:
735       cmd += ' -c %s' % category
736     if package and activity:
737       cmd += ' -n %s/%s' % (package, activity)
738     if data:
739       cmd += ' -d "%s"' % data
740     if extras:
741       for key in extras:
742         value = extras[key]
743         if isinstance(value, str):
744           cmd += ' --es'
745         elif isinstance(value, bool):
746           cmd += ' --ez'
747         elif isinstance(value, int):
748           cmd += ' --ei'
749         else:
750           raise NotImplementedError(
751               'Need to teach StartActivity how to pass %s extras' % type(value))
752         cmd += ' %s %s' % (key, value)
753     if trace_file_name:
754       cmd += ' --start-profiler ' + trace_file_name
755     if flags:
756       cmd += ' -f %s' % flags
757     return cmd
758
759   def StartActivity(self, package, activity, wait_for_completion=False,
760                     action='android.intent.action.VIEW',
761                     category=None, data=None,
762                     extras=None, trace_file_name=None,
763                     force_stop=False, flags=None):
764     """Starts |package|'s activity on the device.
765
766     Args:
767       package: Name of package to start (e.g. 'com.google.android.apps.chrome').
768       activity: Name of activity (e.g. '.Main' or
769         'com.google.android.apps.chrome.Main').
770       wait_for_completion: wait for the activity to finish launching (-W flag).
771       action: string (e.g. "android.intent.action.MAIN"). Default is VIEW.
772       category: string (e.g. "android.intent.category.HOME")
773       data: Data string to pass to activity (e.g. 'http://www.example.com/').
774       extras: Dict of extras to pass to activity. Values are significant.
775       trace_file_name: If used, turns on and saves the trace to this file name.
776       force_stop: force stop the target app before starting the activity (-S
777         flag).
778     """
779     cmd = self._GetActivityCommand(package, activity, wait_for_completion,
780                                    action, category, data, extras,
781                                    trace_file_name, force_stop, flags)
782     self.RunShellCommand(cmd)
783
784   def StartActivityTimed(self, package, activity, wait_for_completion=False,
785                          action='android.intent.action.VIEW',
786                          category=None, data=None,
787                          extras=None, trace_file_name=None,
788                          force_stop=False, flags=None):
789     """Starts |package|'s activity on the device, returning the start time
790
791     Args - as for StartActivity
792
793     Returns:
794       a timestamp string for the time at which the activity started
795     """
796     cmd = self._GetActivityCommand(package, activity, wait_for_completion,
797                                    action, category, data, extras,
798                                    trace_file_name, force_stop, flags)
799     self.StartMonitoringLogcat()
800     self.RunShellCommand('log starting activity; ' + cmd)
801     activity_started_re = re.compile('.*starting activity.*')
802     m = self.WaitForLogMatch(activity_started_re, None)
803     assert m
804     start_line = m.group(0)
805     return GetLogTimestamp(start_line, self.GetDeviceYear())
806
807   def StartCrashUploadService(self, package):
808     # TODO(frankf): We really need a python wrapper around Intent
809     # to be shared with StartActivity/BroadcastIntent.
810     cmd = (
811       'am startservice -a %s.crash.ACTION_FIND_ALL -n '
812       '%s/%s.crash.MinidumpUploadService' %
813       (constants.PACKAGE_INFO['chrome'].package,
814        package,
815        constants.PACKAGE_INFO['chrome'].package))
816     am_output = self.RunShellCommandWithSU(cmd)
817     assert am_output and 'Starting' in am_output[-1], (
818         'Service failed to start: %s' % am_output)
819     time.sleep(15)
820
821   def BroadcastIntent(self, package, intent, *args):
822     """Send a broadcast intent.
823
824     Args:
825       package: Name of package containing the intent.
826       intent: Name of the intent.
827       args: Optional extra arguments for the intent.
828     """
829     cmd = 'am broadcast -a %s.%s %s' % (package, intent, ' '.join(args))
830     self.RunShellCommand(cmd)
831
832   def GoHome(self):
833     """Tell the device to return to the home screen. Blocks until completion."""
834     self.RunShellCommand('am start -W '
835         '-a android.intent.action.MAIN -c android.intent.category.HOME')
836
837   def CloseApplication(self, package):
838     """Attempt to close down the application, using increasing violence.
839
840     Args:
841       package: Name of the process to kill off, e.g.
842       com.google.android.apps.chrome
843     """
844     self.RunShellCommand('am force-stop ' + package)
845
846   def GetApplicationPath(self, package):
847     """Get the installed apk path on the device for the given package.
848
849     Args:
850       package: Name of the package.
851
852     Returns:
853       Path to the apk on the device if it exists, None otherwise.
854     """
855     pm_path_output  = self.RunShellCommand('pm path ' + package)
856     # The path output contains anything if and only if the package
857     # exists.
858     if pm_path_output:
859       # pm_path_output is of the form: "package:/path/to/foo.apk"
860       return pm_path_output[0].split(':')[1]
861     else:
862       return None
863
864   def ClearApplicationState(self, package):
865     """Closes and clears all state for the given |package|."""
866     # Check that the package exists before clearing it. Necessary because
867     # calling pm clear on a package that doesn't exist may never return.
868     pm_path_output  = self.RunShellCommand('pm path ' + package)
869     # The path output only contains anything if and only if the package exists.
870     if pm_path_output:
871       self.RunShellCommand('pm clear ' + package)
872
873   def SendKeyEvent(self, keycode):
874     """Sends keycode to the device.
875
876     Args:
877       keycode: Numeric keycode to send (see "enum" at top of file).
878     """
879     self.RunShellCommand('input keyevent %d' % keycode)
880
881   def _RunMd5Sum(self, host_path, device_path):
882     """Gets the md5sum of a host path and device path.
883
884     Args:
885       host_path: Path (file or directory) on the host.
886       device_path: Path on the device.
887
888     Returns:
889       A tuple containing lists of the host and device md5sum results as
890       created by _ParseMd5SumOutput().
891     """
892     md5sum_dist_path = os.path.join(constants.GetOutDirectory(),
893                                     'md5sum_dist')
894     assert os.path.exists(md5sum_dist_path), 'Please build md5sum.'
895     command = 'push %s %s' % (md5sum_dist_path, MD5SUM_DEVICE_FOLDER)
896     assert _HasAdbPushSucceeded(self._adb.SendCommand(command))
897
898     cmd = (MD5SUM_LD_LIBRARY_PATH + ' ' + self._util_wrapper + ' ' +
899            MD5SUM_DEVICE_PATH + ' ' + device_path)
900     device_hash_tuples = _ParseMd5SumOutput(
901         self.RunShellCommand(cmd, timeout_time=2 * 60))
902     assert os.path.exists(host_path), 'Local path not found %s' % host_path
903     md5sum_output = cmd_helper.GetCmdOutput(
904         [os.path.join(constants.GetOutDirectory(), 'md5sum_bin_host'),
905          host_path])
906     host_hash_tuples = _ParseMd5SumOutput(md5sum_output.splitlines())
907     return (host_hash_tuples, device_hash_tuples)
908
909   def GetFilesChanged(self, host_path, device_path, ignore_filenames=False):
910     """Compares the md5sum of a host path against a device path.
911
912     Note: Ignores extra files on the device.
913
914     Args:
915       host_path: Path (file or directory) on the host.
916       device_path: Path on the device.
917       ignore_filenames: If True only the file contents are considered when
918           checking whether a file has changed, otherwise the relative path
919           must also match.
920
921     Returns:
922       A list of tuples of the form (host_path, device_path) for files whose
923       md5sums do not match.
924     """
925
926     # Md5Sum resolves symbolic links in path names so the calculation of
927     # relative path names from its output will need the real path names of the
928     # base directories. Having calculated these they are used throughout the
929     # function since this makes us less subject to any future changes to Md5Sum.
930     real_host_path = os.path.realpath(host_path)
931     real_device_path = self.RunShellCommand('realpath "%s"' % device_path)[0]
932
933     host_hash_tuples, device_hash_tuples = self._RunMd5Sum(
934         real_host_path, real_device_path)
935
936     # Ignore extra files on the device.
937     if not ignore_filenames:
938       host_files = [os.path.relpath(os.path.normpath(p.path),
939                                     real_host_path) for p in host_hash_tuples]
940
941       def HostHas(fname):
942         return any(path in fname for path in host_files)
943
944       device_hash_tuples = [h for h in device_hash_tuples if HostHas(h.path)]
945
946     if len(host_hash_tuples) > len(device_hash_tuples):
947       logging.info('%s files do not exist on the device' %
948                    (len(host_hash_tuples) - len(device_hash_tuples)))
949
950     # Constructs the target device path from a given host path. Don't use when
951     # only a single file is given as the base name given in device_path may
952     # differ from that in host_path.
953     def HostToDevicePath(host_file_path):
954       return os.path.join(device_path, os.path.relpath(host_file_path,
955                                                        real_host_path))
956
957     device_hashes = [h.hash for h in device_hash_tuples]
958     return [(t.path, HostToDevicePath(t.path) if
959              os.path.isdir(real_host_path) else real_device_path)
960             for t in host_hash_tuples if t.hash not in device_hashes]
961
962   def PushIfNeeded(self, host_path, device_path):
963     """Pushes |host_path| to |device_path|.
964
965     Works for files and directories. This method skips copying any paths in
966     |test_data_paths| that already exist on the device with the same hash.
967
968     All pushed files can be removed by calling RemovePushedFiles().
969     """
970     MAX_INDIVIDUAL_PUSHES = 50
971     assert os.path.exists(host_path), 'Local path not found %s' % host_path
972
973     # See if the file on the host changed since the last push (if any) and
974     # return early if it didn't. Note that this shortcut assumes that the tests
975     # on the device don't modify the files.
976     if not os.path.isdir(host_path):
977       if host_path in self._push_if_needed_cache:
978         host_path_mtime = self._push_if_needed_cache[host_path]
979         if host_path_mtime == os.stat(host_path).st_mtime:
980           return
981
982     def GetHostSize(path):
983       return int(cmd_helper.GetCmdOutput(['du', '-sb', path]).split()[0])
984
985     size = GetHostSize(host_path)
986     self._pushed_files.append(device_path)
987     self._potential_push_size += size
988
989     changed_files = self.GetFilesChanged(host_path, device_path)
990     logging.info('Found %d files that need to be pushed to %s',
991         len(changed_files), device_path)
992     if not changed_files:
993       return
994
995     def Push(host, device):
996       # NOTE: We can't use adb_interface.Push() because it hardcodes a timeout
997       # of 60 seconds which isn't sufficient for a lot of users of this method.
998       push_command = 'push %s %s' % (host, device)
999       self._LogShell(push_command)
1000
1001       # Retry push with increasing backoff if the device is busy.
1002       retry = 0
1003       while True:
1004         output = self._adb.SendCommand(push_command, timeout_time=30 * 60)
1005         if _HasAdbPushSucceeded(output):
1006           if not os.path.isdir(host_path):
1007             self._push_if_needed_cache[host] = os.stat(host).st_mtime
1008           return
1009         if retry < 3:
1010           retry += 1
1011           wait_time = 5 * retry
1012           logging.error('Push failed, retrying in %d seconds: %s' %
1013                         (wait_time, output))
1014           time.sleep(wait_time)
1015         else:
1016           raise Exception('Push failed: %s' % output)
1017
1018     diff_size = 0
1019     if len(changed_files) <= MAX_INDIVIDUAL_PUSHES:
1020       diff_size = sum(GetHostSize(f[0]) for f in changed_files)
1021
1022     # TODO(craigdh): Replace this educated guess with a heuristic that
1023     # approximates the push time for each method.
1024     if len(changed_files) > MAX_INDIVIDUAL_PUSHES or diff_size > 0.5 * size:
1025       self._actual_push_size += size
1026       if os.path.isdir(host_path):
1027         self.RunShellCommand('mkdir -p %s' % device_path)
1028       Push(host_path, device_path)
1029     else:
1030       for f in changed_files:
1031         Push(f[0], f[1])
1032       self._actual_push_size += diff_size
1033
1034   def GetPushSizeInfo(self):
1035     """Get total size of pushes to the device done via PushIfNeeded()
1036
1037     Returns:
1038       A tuple:
1039         1. Total size of push requests to PushIfNeeded (MB)
1040         2. Total size that was actually pushed (MB)
1041     """
1042     return (self._potential_push_size, self._actual_push_size)
1043
1044   def GetFileContents(self, filename, log_result=False):
1045     """Gets contents from the file specified by |filename|."""
1046     return self.RunShellCommand('cat "%s" 2>/dev/null' % filename,
1047                                 log_result=log_result)
1048
1049   def SetFileContents(self, filename, contents):
1050     """Writes |contents| to the file specified by |filename|."""
1051     with tempfile.NamedTemporaryFile() as f:
1052       f.write(contents)
1053       f.flush()
1054       self._adb.Push(f.name, filename)
1055
1056   _TEMP_FILE_BASE_FMT = 'temp_file_%d'
1057   _TEMP_SCRIPT_FILE_BASE_FMT = 'temp_script_file_%d.sh'
1058
1059   def _GetDeviceTempFileName(self, base_name):
1060     i = 0
1061     while self.FileExistsOnDevice(
1062         self.GetExternalStorage() + '/' + base_name % i):
1063       i += 1
1064     return self.GetExternalStorage() + '/' + base_name % i
1065
1066   def RunShellCommandWithSU(self, command, timeout_time=20, log_result=False):
1067     return self.RunShellCommand('su -c %s' % command, timeout_time, log_result)
1068
1069   def CanAccessProtectedFileContents(self):
1070     """Returns True if Get/SetProtectedFileContents would work via "su".
1071
1072     Devices running user builds don't have adb root, but may provide "su" which
1073     can be used for accessing protected files.
1074     """
1075     r = self.RunShellCommandWithSU('cat /dev/null')
1076     return r == [] or r[0].strip() == ''
1077
1078   def GetProtectedFileContents(self, filename, log_result=False):
1079     """Gets contents from the protected file specified by |filename|.
1080
1081     This is less efficient than GetFileContents, but will work for protected
1082     files and device files.
1083     """
1084     # Run the script as root
1085     return self.RunShellCommandWithSU('cat "%s" 2> /dev/null' % filename)
1086
1087   def SetProtectedFileContents(self, filename, contents):
1088     """Writes |contents| to the protected file specified by |filename|.
1089
1090     This is less efficient than SetFileContents, but will work for protected
1091     files and device files.
1092     """
1093     temp_file = self._GetDeviceTempFileName(AndroidCommands._TEMP_FILE_BASE_FMT)
1094     temp_script = self._GetDeviceTempFileName(
1095         AndroidCommands._TEMP_SCRIPT_FILE_BASE_FMT)
1096
1097     # Put the contents in a temporary file
1098     self.SetFileContents(temp_file, contents)
1099     # Create a script to copy the file contents to its final destination
1100     self.SetFileContents(temp_script, 'cat %s > %s' % (temp_file, filename))
1101     # Run the script as root
1102     self.RunShellCommandWithSU('sh %s' % temp_script)
1103     # And remove the temporary files
1104     self.RunShellCommand('rm ' + temp_file)
1105     self.RunShellCommand('rm ' + temp_script)
1106
1107   def RemovePushedFiles(self):
1108     """Removes all files pushed with PushIfNeeded() from the device."""
1109     for p in self._pushed_files:
1110       self.RunShellCommand('rm -r %s' % p, timeout_time=2 * 60)
1111
1112   def ListPathContents(self, path):
1113     """Lists files in all subdirectories of |path|.
1114
1115     Args:
1116       path: The path to list.
1117
1118     Returns:
1119       A dict of {"name": (size, lastmod), ...}.
1120     """
1121     # Example output:
1122     # /foo/bar:
1123     # -rw-r----- 1 user group   102 2011-05-12 12:29:54.131623387 +0100 baz.txt
1124     re_file = re.compile('^-(?P<perms>[^\s]+)\s+'
1125                          '(?P<user>[^\s]+)\s+'
1126                          '(?P<group>[^\s]+)\s+'
1127                          '(?P<size>[^\s]+)\s+'
1128                          '(?P<date>[^\s]+)\s+'
1129                          '(?P<time>[^\s]+)\s+'
1130                          '(?P<filename>[^\s]+)$')
1131     return _GetFilesFromRecursiveLsOutput(
1132         path, self.RunShellCommand('ls -lR %s' % path), re_file,
1133         self.GetUtcOffset())
1134
1135   def GetUtcOffset(self):
1136     if not self._device_utc_offset:
1137       self._device_utc_offset = self.RunShellCommand('date +%z')[0]
1138     return self._device_utc_offset
1139
1140   def SetJavaAssertsEnabled(self, enable):
1141     """Sets or removes the device java assertions property.
1142
1143     Args:
1144       enable: If True the property will be set.
1145
1146     Returns:
1147       True if the file was modified (reboot is required for it to take effect).
1148     """
1149     # First ensure the desired property is persisted.
1150     temp_props_file = tempfile.NamedTemporaryFile()
1151     properties = ''
1152     if self._adb.Pull(LOCAL_PROPERTIES_PATH, temp_props_file.name):
1153       properties = file(temp_props_file.name).read()
1154     re_search = re.compile(r'^\s*' + re.escape(JAVA_ASSERT_PROPERTY) +
1155                            r'\s*=\s*all\s*$', re.MULTILINE)
1156     if enable != bool(re.search(re_search, properties)):
1157       re_replace = re.compile(r'^\s*' + re.escape(JAVA_ASSERT_PROPERTY) +
1158                               r'\s*=\s*\w+\s*$', re.MULTILINE)
1159       properties = re.sub(re_replace, '', properties)
1160       if enable:
1161         properties += '\n%s=all\n' % JAVA_ASSERT_PROPERTY
1162
1163       file(temp_props_file.name, 'w').write(properties)
1164       self._adb.Push(temp_props_file.name, LOCAL_PROPERTIES_PATH)
1165
1166     # Next, check the current runtime value is what we need, and
1167     # if not, set it and report that a reboot is required.
1168     was_set = 'all' in self.system_properties[JAVA_ASSERT_PROPERTY]
1169     if was_set == enable:
1170       return False
1171     self.system_properties[JAVA_ASSERT_PROPERTY] = enable and 'all' or ''
1172     return True
1173
1174   def GetBuildId(self):
1175     """Returns the build ID of the system (e.g. JRM79C)."""
1176     build_id = self.system_properties['ro.build.id']
1177     assert build_id
1178     return build_id
1179
1180   def GetBuildType(self):
1181     """Returns the build type of the system (e.g. eng)."""
1182     build_type = self.system_properties['ro.build.type']
1183     assert build_type
1184     return build_type
1185
1186   def GetBuildProduct(self):
1187     """Returns the build product of the device (e.g. maguro)."""
1188     build_product = self.system_properties['ro.build.product']
1189     assert build_product
1190     return build_product
1191
1192   def GetProductName(self):
1193     """Returns the product name of the device (e.g. takju)."""
1194     name = self.system_properties['ro.product.name']
1195     assert name
1196     return name
1197
1198   def GetBuildFingerprint(self):
1199     """Returns the build fingerprint of the device."""
1200     build_fingerprint = self.system_properties['ro.build.fingerprint']
1201     assert build_fingerprint
1202     return build_fingerprint
1203
1204   def GetDescription(self):
1205     """Returns the description of the system.
1206
1207     For example, "yakju-userdebug 4.1 JRN54F 364167 dev-keys".
1208     """
1209     description = self.system_properties['ro.build.description']
1210     assert description
1211     return description
1212
1213   def GetProductModel(self):
1214     """Returns the name of the product model (e.g. "Galaxy Nexus") """
1215     model = self.system_properties['ro.product.model']
1216     assert model
1217     return model
1218
1219   def GetWifiIP(self):
1220     """Returns the wifi IP on the device."""
1221     wifi_ip = self.system_properties['dhcp.wlan0.ipaddress']
1222     # Do not assert here. Devices (e.g. emulators) may not have a WifiIP.
1223     return wifi_ip
1224
1225   def GetSubscriberInfo(self):
1226     """Returns the device subscriber info (e.g. GSM and device ID) as string."""
1227     iphone_sub = self.RunShellCommand('dumpsys iphonesubinfo')
1228     assert iphone_sub
1229     return '\n'.join(iphone_sub)
1230
1231   def GetBatteryInfo(self):
1232     """Returns the device battery info (e.g. status, level, etc) as string."""
1233     battery = self.RunShellCommand('dumpsys battery')
1234     assert battery
1235     return '\n'.join(battery)
1236
1237   def GetSetupWizardStatus(self):
1238     """Returns the status of the device setup wizard (e.g. DISABLED)."""
1239     status = self.system_properties['ro.setupwizard.mode']
1240     # On some devices, the status is empty if not otherwise set. In such cases
1241     # the caller should expect an empty string to be returned.
1242     return status
1243
1244   def StartMonitoringLogcat(self, clear=True, logfile=None, filters=None):
1245     """Starts monitoring the output of logcat, for use with WaitForLogMatch.
1246
1247     Args:
1248       clear: If True the existing logcat output will be cleared, to avoiding
1249              matching historical output lurking in the log.
1250       filters: A list of logcat filters to be used.
1251     """
1252     if clear:
1253       self.RunShellCommand('logcat -c')
1254     args = []
1255     if self._adb._target_arg:
1256       args += shlex.split(self._adb._target_arg)
1257     args += ['logcat', '-v', 'threadtime']
1258     if filters:
1259       args.extend(filters)
1260     else:
1261       args.append('*:v')
1262
1263     if logfile:
1264       logfile = NewLineNormalizer(logfile)
1265
1266     # Spawn logcat and synchronize with it.
1267     for _ in range(4):
1268       self._logcat = pexpect.spawn(constants.GetAdbPath(), args, timeout=10,
1269                                    logfile=logfile)
1270       if not clear or self.SyncLogCat():
1271         break
1272       self._logcat.close(force=True)
1273     else:
1274       logging.critical('Error reading from logcat: ' + str(self._logcat.match))
1275       sys.exit(1)
1276
1277   def SyncLogCat(self):
1278     """Synchronize with logcat.
1279
1280     Synchronize with the monitored logcat so that WaitForLogMatch will only
1281     consider new message that are received after this point in time.
1282
1283     Returns:
1284       True if the synchronization succeeded.
1285     """
1286     assert self._logcat
1287     tag = 'logcat_sync_%s' % time.time()
1288     self.RunShellCommand('log ' + tag)
1289     return self._logcat.expect([tag, pexpect.EOF, pexpect.TIMEOUT]) == 0
1290
1291   def GetMonitoredLogCat(self):
1292     """Returns an "adb logcat" command as created by pexpected.spawn."""
1293     if not self._logcat:
1294       self.StartMonitoringLogcat(clear=False)
1295     return self._logcat
1296
1297   def WaitForLogMatch(self, success_re, error_re, clear=False, timeout=10):
1298     """Blocks until a matching line is logged or a timeout occurs.
1299
1300     Args:
1301       success_re: A compiled re to search each line for.
1302       error_re: A compiled re which, if found, terminates the search for
1303           |success_re|. If None is given, no error condition will be detected.
1304       clear: If True the existing logcat output will be cleared, defaults to
1305           false.
1306       timeout: Timeout in seconds to wait for a log match.
1307
1308     Raises:
1309       pexpect.TIMEOUT after |timeout| seconds without a match for |success_re|
1310       or |error_re|.
1311
1312     Returns:
1313       The re match object if |success_re| is matched first or None if |error_re|
1314       is matched first.
1315     """
1316     logging.info('<<< Waiting for logcat:' + str(success_re.pattern))
1317     t0 = time.time()
1318     while True:
1319       if not self._logcat:
1320         self.StartMonitoringLogcat(clear)
1321       try:
1322         while True:
1323           # Note this will block for upto the timeout _per log line_, so we need
1324           # to calculate the overall timeout remaining since t0.
1325           time_remaining = t0 + timeout - time.time()
1326           if time_remaining < 0: raise pexpect.TIMEOUT(self._logcat)
1327           self._logcat.expect(PEXPECT_LINE_RE, timeout=time_remaining)
1328           line = self._logcat.match.group(1)
1329           if error_re:
1330             error_match = error_re.search(line)
1331             if error_match:
1332               return None
1333           success_match = success_re.search(line)
1334           if success_match:
1335             return success_match
1336           logging.info('<<< Skipped Logcat Line:' + str(line))
1337       except pexpect.TIMEOUT:
1338         raise pexpect.TIMEOUT(
1339             'Timeout (%ds) exceeded waiting for pattern "%s" (tip: use -vv '
1340             'to debug)' %
1341             (timeout, success_re.pattern))
1342       except pexpect.EOF:
1343         # It seems that sometimes logcat can end unexpectedly. This seems
1344         # to happen during Chrome startup after a reboot followed by a cache
1345         # clean. I don't understand why this happens, but this code deals with
1346         # getting EOF in logcat.
1347         logging.critical('Found EOF in adb logcat. Restarting...')
1348         # Rerun spawn with original arguments. Note that self._logcat.args[0] is
1349         # the path of adb, so we don't want it in the arguments.
1350         self._logcat = pexpect.spawn(constants.GetAdbPath(),
1351                                      self._logcat.args[1:],
1352                                      timeout=self._logcat.timeout,
1353                                      logfile=self._logcat.logfile)
1354
1355   def StartRecordingLogcat(self, clear=True, filters=['*:v']):
1356     """Starts recording logcat output to eventually be saved as a string.
1357
1358     This call should come before some series of tests are run, with either
1359     StopRecordingLogcat or SearchLogcatRecord following the tests.
1360
1361     Args:
1362       clear: True if existing log output should be cleared.
1363       filters: A list of logcat filters to be used.
1364     """
1365     if clear:
1366       self._adb.SendCommand('logcat -c')
1367     logcat_command = 'adb %s logcat -v threadtime %s' % (self._adb._target_arg,
1368                                                          ' '.join(filters))
1369     self._logcat_tmpoutfile = tempfile.NamedTemporaryFile(bufsize=0)
1370     self.logcat_process = subprocess.Popen(logcat_command, shell=True,
1371                                            stdout=self._logcat_tmpoutfile)
1372
1373   def GetCurrentRecordedLogcat(self):
1374     """Return the current content of the logcat being recorded.
1375        Call this after StartRecordingLogcat() and before StopRecordingLogcat().
1376        This can be useful to perform timed polling/parsing.
1377     Returns:
1378        Current logcat output as a single string, or None if
1379        StopRecordingLogcat() was already called.
1380     """
1381     if not self._logcat_tmpoutfile:
1382       return None
1383
1384     with open(self._logcat_tmpoutfile.name) as f:
1385       return f.read()
1386
1387   def StopRecordingLogcat(self):
1388     """Stops an existing logcat recording subprocess and returns output.
1389
1390     Returns:
1391       The logcat output as a string or an empty string if logcat was not
1392       being recorded at the time.
1393     """
1394     if not self.logcat_process:
1395       return ''
1396     # Cannot evaluate directly as 0 is a possible value.
1397     # Better to read the self.logcat_process.stdout before killing it,
1398     # Otherwise the communicate may return incomplete output due to pipe break.
1399     if self.logcat_process.poll() is None:
1400       self.logcat_process.kill()
1401     self.logcat_process.wait()
1402     self.logcat_process = None
1403     self._logcat_tmpoutfile.seek(0)
1404     output = self._logcat_tmpoutfile.read()
1405     self._logcat_tmpoutfile.close()
1406     self._logcat_tmpoutfile = None
1407     return output
1408
1409   def SearchLogcatRecord(self, record, message, thread_id=None, proc_id=None,
1410                          log_level=None, component=None):
1411     """Searches the specified logcat output and returns results.
1412
1413     This method searches through the logcat output specified by record for a
1414     certain message, narrowing results by matching them against any other
1415     specified criteria.  It returns all matching lines as described below.
1416
1417     Args:
1418       record: A string generated by Start/StopRecordingLogcat to search.
1419       message: An output string to search for.
1420       thread_id: The thread id that is the origin of the message.
1421       proc_id: The process that is the origin of the message.
1422       log_level: The log level of the message.
1423       component: The name of the component that would create the message.
1424
1425     Returns:
1426       A list of dictionaries represeting matching entries, each containing keys
1427       thread_id, proc_id, log_level, component, and message.
1428     """
1429     if thread_id:
1430       thread_id = str(thread_id)
1431     if proc_id:
1432       proc_id = str(proc_id)
1433     results = []
1434     reg = re.compile('(\d+)\s+(\d+)\s+([A-Z])\s+([A-Za-z]+)\s*:(.*)$',
1435                      re.MULTILINE)
1436     log_list = reg.findall(record)
1437     for (tid, pid, log_lev, comp, msg) in log_list:
1438       if ((not thread_id or thread_id == tid) and
1439           (not proc_id or proc_id == pid) and
1440           (not log_level or log_level == log_lev) and
1441           (not component or component == comp) and msg.find(message) > -1):
1442         match = dict({'thread_id': tid, 'proc_id': pid,
1443                       'log_level': log_lev, 'component': comp,
1444                       'message': msg})
1445         results.append(match)
1446     return results
1447
1448   def ExtractPid(self, process_name):
1449     """Extracts Process Ids for a given process name from Android Shell.
1450
1451     Args:
1452       process_name: name of the process on the device.
1453
1454     Returns:
1455       List of all the process ids (as strings) that match the given name.
1456       If the name of a process exactly matches the given name, the pid of
1457       that process will be inserted to the front of the pid list.
1458     """
1459     pids = []
1460     for line in self.RunShellCommand('ps', log_result=False):
1461       data = line.split()
1462       try:
1463         if process_name in data[-1]:  # name is in the last column
1464           if process_name == data[-1]:
1465             pids.insert(0, data[1])  # PID is in the second column
1466           else:
1467             pids.append(data[1])
1468       except IndexError:
1469         pass
1470     return pids
1471
1472   def GetIoStats(self):
1473     """Gets cumulative disk IO stats since boot (for all processes).
1474
1475     Returns:
1476       Dict of {num_reads, num_writes, read_ms, write_ms} or None if there
1477       was an error.
1478     """
1479     IoStats = collections.namedtuple(
1480         'IoStats',
1481         ['device',
1482          'num_reads_issued',
1483          'num_reads_merged',
1484          'num_sectors_read',
1485          'ms_spent_reading',
1486          'num_writes_completed',
1487          'num_writes_merged',
1488          'num_sectors_written',
1489          'ms_spent_writing',
1490          'num_ios_in_progress',
1491          'ms_spent_doing_io',
1492          'ms_spent_doing_io_weighted',
1493         ])
1494
1495     for line in self.GetFileContents('/proc/diskstats', log_result=False):
1496       fields = line.split()
1497       stats = IoStats._make([fields[2]] + [int(f) for f in fields[3:]])
1498       if stats.device == 'mmcblk0':
1499         return {
1500             'num_reads': stats.num_reads_issued,
1501             'num_writes': stats.num_writes_completed,
1502             'read_ms': stats.ms_spent_reading,
1503             'write_ms': stats.ms_spent_writing,
1504         }
1505     logging.warning('Could not find disk IO stats.')
1506     return None
1507
1508   def GetMemoryUsageForPid(self, pid):
1509     """Returns the memory usage for given pid.
1510
1511     Args:
1512       pid: The pid number of the specific process running on device.
1513
1514     Returns:
1515       A tuple containg:
1516       [0]: Dict of {metric:usage_kb}, for the process which has specified pid.
1517       The metric keys which may be included are: Size, Rss, Pss, Shared_Clean,
1518       Shared_Dirty, Private_Clean, Private_Dirty, Referenced, Swap,
1519       KernelPageSize, MMUPageSize, Nvidia (tablet only), VmHWM.
1520       [1]: Detailed /proc/[PID]/smaps information.
1521     """
1522     usage_dict = collections.defaultdict(int)
1523     smaps = collections.defaultdict(dict)
1524     current_smap = ''
1525     for line in self.GetProtectedFileContents('/proc/%s/smaps' % pid,
1526                                               log_result=False):
1527       items = line.split()
1528       # See man 5 proc for more details. The format is:
1529       # address perms offset dev inode pathname
1530       if len(items) > 5:
1531         current_smap = ' '.join(items[5:])
1532       elif len(items) > 3:
1533         current_smap = ' '.join(items[3:])
1534       match = re.match(MEMORY_INFO_RE, line)
1535       if match:
1536         key = match.group('key')
1537         usage_kb = int(match.group('usage_kb'))
1538         usage_dict[key] += usage_kb
1539         if key not in smaps[current_smap]:
1540           smaps[current_smap][key] = 0
1541         smaps[current_smap][key] += usage_kb
1542     if not usage_dict or not any(usage_dict.values()):
1543       # Presumably the process died between ps and calling this method.
1544       logging.warning('Could not find memory usage for pid ' + str(pid))
1545
1546     for line in self.GetProtectedFileContents('/d/nvmap/generic-0/clients',
1547                                               log_result=False):
1548       match = re.match(NVIDIA_MEMORY_INFO_RE, line)
1549       if match and match.group('pid') == pid:
1550         usage_bytes = int(match.group('usage_bytes'))
1551         usage_dict['Nvidia'] = int(round(usage_bytes / 1000.0))  # kB
1552         break
1553
1554     peak_value_kb = 0
1555     for line in self.GetProtectedFileContents('/proc/%s/status' % pid,
1556                                               log_result=False):
1557       if not line.startswith('VmHWM:'):  # Format: 'VmHWM: +[0-9]+ kB'
1558         continue
1559       peak_value_kb = int(line.split(':')[1].strip().split(' ')[0])
1560     usage_dict['VmHWM'] = peak_value_kb
1561     if not peak_value_kb:
1562       logging.warning('Could not find memory peak value for pid ' + str(pid))
1563
1564     return (usage_dict, smaps)
1565
1566   def GetMemoryUsageForPackage(self, package):
1567     """Returns the memory usage for all processes whose name contains |pacakge|.
1568
1569     Args:
1570       package: A string holding process name to lookup pid list for.
1571
1572     Returns:
1573       A tuple containg:
1574       [0]: Dict of {metric:usage_kb}, summed over all pids associated with
1575            |name|.
1576       The metric keys which may be included are: Size, Rss, Pss, Shared_Clean,
1577       Shared_Dirty, Private_Clean, Private_Dirty, Referenced, Swap,
1578       KernelPageSize, MMUPageSize, Nvidia (tablet only).
1579       [1]: a list with detailed /proc/[PID]/smaps information.
1580     """
1581     usage_dict = collections.defaultdict(int)
1582     pid_list = self.ExtractPid(package)
1583     smaps = collections.defaultdict(dict)
1584
1585     for pid in pid_list:
1586       usage_dict_per_pid, smaps_per_pid = self.GetMemoryUsageForPid(pid)
1587       smaps[pid] = smaps_per_pid
1588       for (key, value) in usage_dict_per_pid.items():
1589         usage_dict[key] += value
1590
1591     return usage_dict, smaps
1592
1593   def ProcessesUsingDevicePort(self, device_port):
1594     """Lists processes using the specified device port on loopback interface.
1595
1596     Args:
1597       device_port: Port on device we want to check.
1598
1599     Returns:
1600       A list of (pid, process_name) tuples using the specified port.
1601     """
1602     tcp_results = self.RunShellCommand('cat /proc/net/tcp', log_result=False)
1603     tcp_address = '0100007F:%04X' % device_port
1604     pids = []
1605     for single_connect in tcp_results:
1606       connect_results = single_connect.split()
1607       # Column 1 is the TCP port, and Column 9 is the inode of the socket
1608       if connect_results[1] == tcp_address:
1609         socket_inode = connect_results[9]
1610         socket_name = 'socket:[%s]' % socket_inode
1611         lsof_results = self.RunShellCommand('lsof', log_result=False)
1612         for single_process in lsof_results:
1613           process_results = single_process.split()
1614           # Ignore the line if it has less than nine columns in it, which may
1615           # be the case when a process stops while lsof is executing.
1616           if len(process_results) <= 8:
1617             continue
1618           # Column 0 is the executable name
1619           # Column 1 is the pid
1620           # Column 8 is the Inode in use
1621           if process_results[8] == socket_name:
1622             pids.append((int(process_results[1]), process_results[0]))
1623         break
1624     logging.info('PidsUsingDevicePort: %s', pids)
1625     return pids
1626
1627   def FileExistsOnDevice(self, file_name):
1628     """Checks whether the given file exists on the device.
1629
1630     Args:
1631       file_name: Full path of file to check.
1632
1633     Returns:
1634       True if the file exists, False otherwise.
1635     """
1636     assert '"' not in file_name, 'file_name cannot contain double quotes'
1637     try:
1638       status = self._adb.SendShellCommand(
1639           '\'test -e "%s"; echo $?\'' % (file_name))
1640       if 'test: not found' not in status:
1641         return int(status) == 0
1642
1643       status = self._adb.SendShellCommand(
1644           '\'ls "%s" >/dev/null 2>&1; echo $?\'' % (file_name))
1645       return int(status) == 0
1646     except ValueError:
1647       if IsDeviceAttached(self._device):
1648         raise errors.DeviceUnresponsiveError('Device may be offline.')
1649
1650       return False
1651
1652   def IsFileWritableOnDevice(self, file_name):
1653     """Checks whether the given file (or directory) is writable on the device.
1654
1655     Args:
1656       file_name: Full path of file/directory to check.
1657
1658     Returns:
1659       True if writable, False otherwise.
1660     """
1661     assert '"' not in file_name, 'file_name cannot contain double quotes'
1662     try:
1663       status = self._adb.SendShellCommand(
1664           '\'test -w "%s"; echo $?\'' % (file_name))
1665       if 'test: not found' not in status:
1666         return int(status) == 0
1667       raise errors.AbortError('"test" binary not found. OS too old.')
1668
1669     except ValueError:
1670       if IsDeviceAttached(self._device):
1671         raise errors.DeviceUnresponsiveError('Device may be offline.')
1672
1673       return False
1674
1675   def TakeScreenshot(self, host_file):
1676     """Saves a screenshot image to |host_file| on the host.
1677
1678     Args:
1679       host_file: Absolute path to the image file to store on the host or None to
1680                  use an autogenerated file name.
1681
1682     Returns:
1683       Resulting host file name of the screenshot.
1684     """
1685     return screenshot.TakeScreenshot(self, host_file)
1686
1687   def PullFileFromDevice(self, device_file, host_file):
1688     """Download |device_file| on the device from to |host_file| on the host.
1689
1690     Args:
1691       device_file: Absolute path to the file to retrieve from the device.
1692       host_file: Absolute path to the file to store on the host.
1693     """
1694     assert self._adb.Pull(device_file, host_file)
1695     assert os.path.exists(host_file)
1696
1697   def SetUtilWrapper(self, util_wrapper):
1698     """Sets a wrapper prefix to be used when running a locally-built
1699     binary on the device (ex.: md5sum_bin).
1700     """
1701     self._util_wrapper = util_wrapper
1702
1703   def RunInstrumentationTest(self, test, test_package, instr_args, timeout):
1704     """Runs a single instrumentation test.
1705
1706     Args:
1707       test: Test class/method.
1708       test_package: Package name of test apk.
1709       instr_args: Extra key/value to pass to am instrument.
1710       timeout: Timeout time in seconds.
1711
1712     Returns:
1713       An instance of am_instrument_parser.TestResult object.
1714     """
1715     instrumentation_path = ('%s/android.test.InstrumentationTestRunner' %
1716                             test_package)
1717     args_with_filter = dict(instr_args)
1718     args_with_filter['class'] = test
1719     logging.info(args_with_filter)
1720     (raw_results, _) = self._adb.StartInstrumentation(
1721         instrumentation_path=instrumentation_path,
1722         instrumentation_args=args_with_filter,
1723         timeout_time=timeout)
1724     assert len(raw_results) == 1
1725     return raw_results[0]
1726
1727   def RunUIAutomatorTest(self, test, test_package, timeout):
1728     """Runs a single uiautomator test.
1729
1730     Args:
1731       test: Test class/method.
1732       test_package: Name of the test jar.
1733       timeout: Timeout time in seconds.
1734
1735     Returns:
1736       An instance of am_instrument_parser.TestResult object.
1737     """
1738     cmd = 'uiautomator runtest %s -e class %s' % (test_package, test)
1739     self._LogShell(cmd)
1740     output = self._adb.SendShellCommand(cmd, timeout_time=timeout)
1741     # uiautomator doesn't fully conform to the instrumenation test runner
1742     # convention and doesn't terminate with INSTRUMENTATION_CODE.
1743     # Just assume the first result is valid.
1744     (test_results, _) = am_instrument_parser.ParseAmInstrumentOutput(output)
1745     if not test_results:
1746       raise errors.InstrumentationError(
1747           'no test results... device setup correctly?')
1748     return test_results[0]
1749
1750   def DismissCrashDialogIfNeeded(self):
1751     """Dismiss the error/ANR dialog if present.
1752
1753     Returns: Name of the crashed package if a dialog is focused,
1754              None otherwise.
1755     """
1756     re_focus = re.compile(
1757         r'\s*mCurrentFocus.*Application (Error|Not Responding): (\S+)}')
1758
1759     def _FindFocusedWindow():
1760       match = None
1761       for line in self.RunShellCommand('dumpsys window windows'):
1762         match = re.match(re_focus, line)
1763         if match:
1764           break
1765       return match
1766
1767     match = _FindFocusedWindow()
1768     if not match:
1769       return
1770     package = match.group(2)
1771     logging.warning('Trying to dismiss %s dialog for %s' % match.groups())
1772     self.SendKeyEvent(KEYCODE_DPAD_RIGHT)
1773     self.SendKeyEvent(KEYCODE_DPAD_RIGHT)
1774     self.SendKeyEvent(KEYCODE_ENTER)
1775     match = _FindFocusedWindow()
1776     if match:
1777       logging.error('Still showing a %s dialog for %s' % match.groups())
1778     return package
1779
1780
1781 class NewLineNormalizer(object):
1782   """A file-like object to normalize EOLs to '\n'.
1783
1784   Pexpect runs adb within a pseudo-tty device (see
1785   http://www.noah.org/wiki/pexpect), so any '\n' printed by adb is written
1786   as '\r\n' to the logfile. Since adb already uses '\r\n' to terminate
1787   lines, the log ends up having '\r\r\n' at the end of each line. This
1788   filter replaces the above with a single '\n' in the data stream.
1789   """
1790   def __init__(self, output):
1791     self._output = output
1792
1793   def write(self, data):
1794     data = data.replace('\r\r\n', '\n')
1795     self._output.write(data)
1796
1797   def flush(self):
1798     self._output.flush()