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