blob: 318b4d005f1d7751386a81de3fc7d9a545ce7267 [file] [log] [blame]
Sergei Trofimov4e6afe92015-10-09 09:30:04 +01001# Copyright 2013-2015 ARM Limited
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14#
15
16
17"""
18Utility functions for working with Android devices through adb.
19
20"""
21# pylint: disable=E1103
22import os
Brendan Jackman1d855012017-09-21 12:30:13 +010023import pexpect
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010024import time
25import subprocess
26import logging
27import re
Valentin Schneider035181a2017-09-11 16:55:02 +010028import threading
29import tempfile
30import Queue
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010031from collections import defaultdict
32
Sebastian Goscik8de24b52016-03-23 15:10:26 +000033from devlib.exception import TargetError, HostError, DevlibError
Marc Bonnici68be9d82017-07-14 16:59:52 +010034from devlib.utils.misc import check_output, which, memoized, ABI_MAP
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010035from devlib.utils.misc import escape_single_quotes, escape_double_quotes
Valentin Schneider6bda0932017-09-12 15:44:25 +010036from devlib import host
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010037
38
39logger = logging.getLogger('android')
40
41MAX_ATTEMPTS = 5
Valentin Schneider2d968402017-06-12 18:59:44 +010042AM_START_ERROR = re.compile(r"Error: Activity.*")
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010043
44# See:
45# http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels
46ANDROID_VERSION_MAP = {
Sebastian Goscik0c112892016-02-15 15:35:56 +000047 23: 'MARSHMALLOW',
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010048 22: 'LOLLYPOP_MR1',
49 21: 'LOLLYPOP',
50 20: 'KITKAT_WATCH',
51 19: 'KITKAT',
52 18: 'JELLY_BEAN_MR2',
53 17: 'JELLY_BEAN_MR1',
54 16: 'JELLY_BEAN',
55 15: 'ICE_CREAM_SANDWICH_MR1',
56 14: 'ICE_CREAM_SANDWICH',
57 13: 'HONEYCOMB_MR2',
58 12: 'HONEYCOMB_MR1',
59 11: 'HONEYCOMB',
60 10: 'GINGERBREAD_MR1',
61 9: 'GINGERBREAD',
62 8: 'FROYO',
63 7: 'ECLAIR_MR1',
64 6: 'ECLAIR_0_1',
65 5: 'ECLAIR',
66 4: 'DONUT',
67 3: 'CUPCAKE',
68 2: 'BASE_1_1',
69 1: 'BASE',
70}
71
72
73# Initialized in functions near the botton of the file
74android_home = None
75platform_tools = None
76adb = None
77aapt = None
78fastboot = None
79
80
81class AndroidProperties(object):
82
83 def __init__(self, text):
84 self._properties = {}
85 self.parse(text)
86
87 def parse(self, text):
88 self._properties = dict(re.findall(r'\[(.*?)\]:\s+\[(.*?)\]', text))
89
90 def iteritems(self):
91 return self._properties.iteritems()
92
93 def __iter__(self):
94 return iter(self._properties)
95
96 def __getattr__(self, name):
97 return self._properties.get(name)
98
99 __getitem__ = __getattr__
100
101
102class AdbDevice(object):
103
104 def __init__(self, name, status):
105 self.name = name
106 self.status = status
107
108 def __cmp__(self, other):
109 if isinstance(other, AdbDevice):
110 return cmp(self.name, other.name)
111 else:
112 return cmp(self.name, other)
113
114 def __str__(self):
115 return 'AdbDevice({}, {})'.format(self.name, self.status)
116
117 __repr__ = __str__
118
119
120class ApkInfo(object):
121
122 version_regex = re.compile(r"name='(?P<name>[^']+)' versionCode='(?P<vcode>[^']+)' versionName='(?P<vname>[^']+)'")
123 name_regex = re.compile(r"name='(?P<name>[^']+)'")
124
125 def __init__(self, path=None):
126 self.path = path
127 self.package = None
128 self.activity = None
129 self.label = None
130 self.version_name = None
131 self.version_code = None
Marc Bonnici68be9d82017-07-14 16:59:52 +0100132 self.native_code = None
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100133 self.parse(path)
134
135 def parse(self, apk_path):
136 _check_env()
137 command = [aapt, 'dump', 'badging', apk_path]
138 logger.debug(' '.join(command))
Brendan Jackman99aca252017-10-09 18:30:06 +0100139 try:
140 output = subprocess.check_output(command, stderr=subprocess.STDOUT)
141 except subprocess.CalledProcessError as e:
142 raise HostError('Error parsing APK file {}. `aapt` says:\n{}'
143 .format(apk_path, e.output))
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100144 for line in output.split('\n'):
145 if line.startswith('application-label:'):
146 self.label = line.split(':')[1].strip().replace('\'', '')
147 elif line.startswith('package:'):
148 match = self.version_regex.search(line)
149 if match:
150 self.package = match.group('name')
151 self.version_code = match.group('vcode')
152 self.version_name = match.group('vname')
153 elif line.startswith('launchable-activity:'):
154 match = self.name_regex.search(line)
155 self.activity = match.group('name')
Marc Bonnici68be9d82017-07-14 16:59:52 +0100156 elif line.startswith('native-code'):
157 apk_abis = [entry.strip() for entry in line.split(':')[1].split("'") if entry.strip()]
158 mapped_abis = []
159 for apk_abi in apk_abis:
160 found = False
161 for abi, architectures in ABI_MAP.iteritems():
162 if apk_abi in architectures:
163 mapped_abis.append(abi)
164 found = True
165 break
166 if not found:
167 mapped_abis.append(apk_abi)
168 self.native_code = mapped_abis
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100169 else:
170 pass # not interested
171
172
173class AdbConnection(object):
174
175 # maintains the count of parallel active connections to a device, so that
176 # adb disconnect is not invoked untill all connections are closed
177 active_connections = defaultdict(int)
Sergei Trofimov89256fd2016-05-17 14:00:01 +0100178 default_timeout = 10
Chris Redpathe8e945a2016-10-14 14:35:58 +0100179 ls_command = 'ls'
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100180
181 @property
182 def name(self):
183 return self.device
184
Sebastian Goscik8de24b52016-03-23 15:10:26 +0000185 @property
186 @memoized
187 def newline_separator(self):
Chris Redpathe8e945a2016-10-14 14:35:58 +0100188 output = adb_command(self.device,
Anthony Barbier7919a562017-07-25 13:48:33 +0100189 "shell '({}); echo \"\n$?\"'".format(self.ls_command), adb_server=self.adb_server)
Sebastian Goscik8de24b52016-03-23 15:10:26 +0000190 if output.endswith('\r\n'):
191 return '\r\n'
192 elif output.endswith('\n'):
193 return '\n'
194 else:
195 raise DevlibError("Unknown line ending")
196
Chris Redpathe8e945a2016-10-14 14:35:58 +0100197 # Again, we need to handle boards where the default output format from ls is
198 # single column *and* boards where the default output is multi-column.
199 # We need to do this purely because the '-1' option causes errors on older
200 # versions of the ls tool in Android pre-v7.
201 def _setup_ls(self):
202 command = "shell '(ls -1); echo \"\n$?\"'"
Brendan Jackman32982052017-03-01 18:49:27 +0000203 try:
Anthony Barbier7919a562017-07-25 13:48:33 +0100204 output = adb_command(self.device, command, timeout=self.timeout, adb_server=self.adb_server)
Brendan Jackman32982052017-03-01 18:49:27 +0000205 except subprocess.CalledProcessError as e:
206 raise HostError(
207 'Failed to set up ls command on Android device. Output:\n'
208 + e.output)
Chris Redpathe8e945a2016-10-14 14:35:58 +0100209 lines = output.splitlines()
210 retval = lines[-1].strip()
211 if int(retval) == 0:
212 self.ls_command = 'ls -1'
213 else:
214 self.ls_command = 'ls'
Sergei Trofimov97a89972017-05-12 10:45:42 +0100215 logger.debug("ls command is set to {}".format(self.ls_command))
Chris Redpathe8e945a2016-10-14 14:35:58 +0100216
Anthony Barbier7919a562017-07-25 13:48:33 +0100217 def __init__(self, device=None, timeout=None, platform=None, adb_server=None):
Sergei Trofimov89256fd2016-05-17 14:00:01 +0100218 self.timeout = timeout if timeout is not None else self.default_timeout
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100219 if device is None:
Anthony Barbier7919a562017-07-25 13:48:33 +0100220 device = adb_get_device(timeout=timeout, adb_server=adb_server)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100221 self.device = device
Anthony Barbier7919a562017-07-25 13:48:33 +0100222 self.adb_server = adb_server
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100223 adb_connect(self.device)
224 AdbConnection.active_connections[self.device] += 1
Chris Redpathe8e945a2016-10-14 14:35:58 +0100225 self._setup_ls()
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100226
227 def push(self, source, dest, timeout=None):
228 if timeout is None:
229 timeout = self.timeout
Sebastian Goscik1424ceb2016-02-15 15:27:19 +0000230 command = "push '{}' '{}'".format(source, dest)
Brendan Jackmane17b9c32017-04-28 11:26:35 +0100231 if not os.path.exists(source):
232 raise HostError('No such file "{}"'.format(source))
Anthony Barbier7919a562017-07-25 13:48:33 +0100233 return adb_command(self.device, command, timeout=timeout, adb_server=self.adb_server)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100234
235 def pull(self, source, dest, timeout=None):
236 if timeout is None:
237 timeout = self.timeout
Patrick Bellasic93e3d62015-11-25 11:19:08 +0000238 # Pull all files matching a wildcard expression
239 if os.path.isdir(dest) and \
Sebastian Goscikaab487c2016-02-15 15:21:40 +0000240 ('*' in source or '?' in source):
Chris Redpathe8e945a2016-10-14 14:35:58 +0100241 command = 'shell {} {}'.format(self.ls_command, source)
Anthony Barbier7919a562017-07-25 13:48:33 +0100242 output = adb_command(self.device, command, timeout=timeout, adb_server=self.adb_server)
Patrick Bellasic93e3d62015-11-25 11:19:08 +0000243 for line in output.splitlines():
Brendan Jackmanee38a422016-11-18 17:48:48 +0000244 command = "pull '{}' '{}'".format(line.strip(), dest)
Anthony Barbier7919a562017-07-25 13:48:33 +0100245 adb_command(self.device, command, timeout=timeout, adb_server=self.adb_server)
Patrick Bellasic93e3d62015-11-25 11:19:08 +0000246 return
Sebastian Goscik1424ceb2016-02-15 15:27:19 +0000247 command = "pull '{}' '{}'".format(source, dest)
Anthony Barbier7919a562017-07-25 13:48:33 +0100248 return adb_command(self.device, command, timeout=timeout, adb_server=self.adb_server)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100249
Anouk Van Laer29a79402017-01-31 12:48:58 +0000250 def execute(self, command, timeout=None, check_exit_code=False,
251 as_root=False, strip_colors=True):
Patrick Bellasic2329bd2016-03-28 12:29:45 +0100252 return adb_shell(self.device, command, timeout, check_exit_code,
Anthony Barbier7919a562017-07-25 13:48:33 +0100253 as_root, self.newline_separator,adb_server=self.adb_server)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100254
255 def background(self, command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, as_root=False):
256 return adb_background_shell(self.device, command, stdout, stderr, as_root)
257
258 def close(self):
259 AdbConnection.active_connections[self.device] -= 1
260 if AdbConnection.active_connections[self.device] <= 0:
261 adb_disconnect(self.device)
262 del AdbConnection.active_connections[self.device]
263
264 def cancel_running_command(self):
265 # adbd multiplexes commands so that they don't interfer with each
266 # other, so there is no need to explicitly cancel a running command
267 # before the next one can be issued.
268 pass
269
270
Patrick Bellasida588ea2017-02-16 13:08:31 +0000271def fastboot_command(command, timeout=None, device=None):
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100272 _check_env()
Patrick Bellasida588ea2017-02-16 13:08:31 +0000273 target = '-s {}'.format(device) if device else ''
274 full_command = 'fastboot {} {}'.format(target, command)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100275 logger.debug(full_command)
276 output, _ = check_output(full_command, timeout, shell=True)
277 return output
278
279
280def fastboot_flash_partition(partition, path_to_image):
281 command = 'flash {} {}'.format(partition, path_to_image)
282 fastboot_command(command)
283
284
Anthony Barbier7919a562017-07-25 13:48:33 +0100285def adb_get_device(timeout=None, adb_server=None):
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100286 """
287 Returns the serial number of a connected android device.
288
289 If there are more than one device connected to the machine, or it could not
290 find any device connected, :class:`devlib.exceptions.HostError` is raised.
291 """
292 # TODO this is a hacky way to issue a adb command to all listed devices
293
Brendan Jackmana290d282017-09-20 13:02:44 +0100294 # Ensure server is started so the 'daemon started successfully' message
295 # doesn't confuse the parsing below
296 adb_command(None, 'start-server', adb_server=adb_server)
297
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100298 # The output of calling adb devices consists of a heading line then
299 # a list of the devices sperated by new line
300 # The last line is a blank new line. in otherwords, if there is a device found
301 # then the output length is 2 + (1 for each device)
302 start = time.time()
303 while True:
Anthony Barbier7919a562017-07-25 13:48:33 +0100304 output = adb_command(None, "devices", adb_server=adb_server).splitlines() # pylint: disable=E1103
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100305 output_length = len(output)
306 if output_length == 3:
307 # output[1] is the 2nd line in the output which has the device name
308 # Splitting the line by '\t' gives a list of two indexes, which has
309 # device serial in 0 number and device type in 1.
310 return output[1].split('\t')[0]
311 elif output_length > 3:
312 message = '{} Android devices found; either explicitly specify ' +\
Sebastian Goscikaab487c2016-02-15 15:21:40 +0000313 'the device you want, or make sure only one is connected.'
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100314 raise HostError(message.format(output_length - 2))
315 else:
316 if timeout < time.time() - start:
317 raise HostError('No device is connected and available')
318 time.sleep(1)
319
320
321def adb_connect(device, timeout=None, attempts=MAX_ATTEMPTS):
322 _check_env()
Patrick Bellasif714dd32016-07-15 11:18:09 +0100323 # Connect is required only for ADB-over-IP
324 if "." not in device:
325 logger.debug('Device connected via USB, connect not required')
326 return
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100327 tries = 0
328 output = None
329 while tries <= attempts:
330 tries += 1
331 if device:
332 command = 'adb connect {}'.format(device)
333 logger.debug(command)
334 output, _ = check_output(command, shell=True, timeout=timeout)
335 if _ping(device):
336 break
337 time.sleep(10)
338 else: # did not connect to the device
339 message = 'Could not connect to {}'.format(device or 'a device')
340 if output:
341 message += '; got: "{}"'.format(output)
342 raise HostError(message)
343
344
345def adb_disconnect(device):
346 _check_env()
347 if not device:
348 return
Chris Redpath119fd7d2016-10-06 16:34:14 +0100349 if ":" in device and device in adb_list_devices():
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100350 command = "adb disconnect " + device
351 logger.debug(command)
352 retval = subprocess.call(command, stdout=open(os.devnull, 'wb'), shell=True)
353 if retval:
354 raise TargetError('"{}" returned {}'.format(command, retval))
355
356
357def _ping(device):
358 _check_env()
359 device_string = ' -s {}'.format(device) if device else ''
360 command = "adb{} shell \"ls / > /dev/null\"".format(device_string)
361 logger.debug(command)
362 result = subprocess.call(command, stderr=subprocess.PIPE, shell=True)
363 if not result:
364 return True
365 else:
366 return False
367
368
Patrick Bellasic2329bd2016-03-28 12:29:45 +0100369def adb_shell(device, command, timeout=None, check_exit_code=False,
Anthony Barbier7919a562017-07-25 13:48:33 +0100370 as_root=False, newline_separator='\r\n', adb_server=None): # NOQA
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100371 _check_env()
372 if as_root:
Sergei Trofimov171cc252015-12-14 17:21:47 +0000373 command = 'echo \'{}\' | su'.format(escape_single_quotes(command))
Anthony Barbier7919a562017-07-25 13:48:33 +0100374 device_part = []
375 if adb_server:
376 device_part = ['-H', adb_server]
377 device_part += ['-s', device] if device else []
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100378
Brendan Jackman44fe0372017-01-25 12:06:38 +0000379 # On older combinations of ADB/Android versions, the adb host command always
380 # exits with 0 if it was able to run the command on the target, even if the
381 # command failed (https://code.google.com/p/android/issues/detail?id=3254).
382 # Homogenise this behaviour by running the command then echoing the exit
383 # code.
384 adb_shell_command = '({}); echo \"\n$?\"'.format(command)
385 actual_command = ['adb'] + device_part + ['shell', adb_shell_command]
386 logger.debug('adb {} shell {}'.format(' '.join(device_part), command))
387 raw_output, error = check_output(actual_command, timeout, shell=False)
388 if raw_output:
389 try:
390 output, exit_code, _ = raw_output.rsplit(newline_separator, 2)
391 except ValueError:
392 exit_code, _ = raw_output.rsplit(newline_separator, 1)
393 output = ''
394 else: # raw_output is empty
395 exit_code = '969696' # just because
396 output = ''
397
398 if check_exit_code:
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100399 exit_code = exit_code.strip()
Valentin Schneider2d968402017-06-12 18:59:44 +0100400 re_search = AM_START_ERROR.findall('{}\n{}'.format(output, error))
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100401 if exit_code.isdigit():
402 if int(exit_code):
Brendan Jackman44fe0372017-01-25 12:06:38 +0000403 message = ('Got exit code {}\nfrom target command: {}\n'
404 'STDOUT: {}\nSTDERR: {}')
405 raise TargetError(message.format(exit_code, command, output, error))
Valentin Schneider2d968402017-06-12 18:59:44 +0100406 elif re_search:
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100407 message = 'Could not start activity; got the following:\n{}'
Valentin Schneider2d968402017-06-12 18:59:44 +0100408 raise TargetError(message.format(re_search[0]))
409 else: # not all digits
410 if re_search:
411 message = 'Could not start activity; got the following:\n{}'
412 raise TargetError(message.format(re_search[0]))
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100413 else:
414 message = 'adb has returned early; did not get an exit code. '\
Sergei Trofimov85036fb2017-07-12 13:44:47 +0100415 'Was kill-server invoked?\nOUTPUT:\n-----\n{}\n'\
416 '-----\nERROR:\n-----\n{}\n-----'
417 raise TargetError(message.format(raw_output, error))
Brendan Jackman44fe0372017-01-25 12:06:38 +0000418
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100419 return output
420
421
422def adb_background_shell(device, command,
423 stdout=subprocess.PIPE,
424 stderr=subprocess.PIPE,
425 as_root=False):
426 """Runs the sepcified command in a subprocess, returning the the Popen object."""
427 _check_env()
428 if as_root:
429 command = 'echo \'{}\' | su'.format(escape_single_quotes(command))
430 device_string = ' -s {}'.format(device) if device else ''
431 full_command = 'adb{} shell "{}"'.format(device_string, escape_double_quotes(command))
432 logger.debug(full_command)
433 return subprocess.Popen(full_command, stdout=stdout, stderr=stderr, shell=True)
434
435
Anthony Barbier7919a562017-07-25 13:48:33 +0100436def adb_list_devices(adb_server=None):
437 output = adb_command(None, 'devices',adb_server=adb_server)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100438 devices = []
439 for line in output.splitlines():
440 parts = [p.strip() for p in line.split()]
441 if len(parts) == 2:
442 devices.append(AdbDevice(*parts))
443 return devices
444
445
Brendan Jackmanda22bef2017-09-21 12:32:18 +0100446def get_adb_command(device, command, timeout=None,adb_server=None):
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100447 _check_env()
Anthony Barbier7919a562017-07-25 13:48:33 +0100448 device_string = ""
449 if adb_server != None:
450 device_string = ' -H {}'.format(adb_server)
451 device_string += ' -s {}'.format(device) if device else ''
Brendan Jackmanda22bef2017-09-21 12:32:18 +0100452 return "adb{} {}".format(device_string, command)
453
454def adb_command(device, command, timeout=None,adb_server=None):
455 full_command = get_adb_command(device, command, timeout, adb_server)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100456 logger.debug(full_command)
457 output, _ = check_output(full_command, timeout, shell=True)
458 return output
459
Valentin Schneider24d56302017-09-13 11:47:51 +0100460def grant_app_permissions(target, package):
461 """
462 Grant an app all the permissions it may ask for
463 """
464 dumpsys = target.execute('dumpsys package {}'.format(package))
465
466 permissions = re.search(
467 'requested permissions:\s*(?P<permissions>(android.permission.+\s*)+)', dumpsys
468 )
Sergei Trofimov90040e82017-09-13 14:03:59 +0100469 if permissions is None:
470 return
Valentin Schneider24d56302017-09-13 11:47:51 +0100471 permissions = permissions.group('permissions').replace(" ", "").splitlines()
472
473 for permission in permissions:
474 try:
475 target.execute('pm grant {} {}'.format(package, permission))
476 except TargetError:
477 logger.debug('Cannot grant {}'.format(permission))
478
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100479
480# Messy environment initialisation stuff...
481
482class _AndroidEnvironment(object):
483
484 def __init__(self):
485 self.android_home = None
486 self.platform_tools = None
487 self.adb = None
488 self.aapt = None
489 self.fastboot = None
490
491
492def _initialize_with_android_home(env):
493 logger.debug('Using ANDROID_HOME from the environment.')
494 env.android_home = android_home
495 env.platform_tools = os.path.join(android_home, 'platform-tools')
Sergei Trofimovd5460e12016-12-13 11:26:32 +0000496 os.environ['PATH'] = env.platform_tools + os.pathsep + os.environ['PATH']
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100497 _init_common(env)
498 return env
499
500
501def _initialize_without_android_home(env):
Javi Merino7f32efc2015-12-15 13:43:56 +0000502 adb_full_path = which('adb')
503 if adb_full_path:
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100504 env.adb = 'adb'
505 else:
506 raise HostError('ANDROID_HOME is not set and adb is not in PATH. '
507 'Have you installed Android SDK?')
508 logger.debug('Discovering ANDROID_HOME from adb path.')
Javi Merino7f32efc2015-12-15 13:43:56 +0000509 env.platform_tools = os.path.dirname(adb_full_path)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100510 env.android_home = os.path.dirname(env.platform_tools)
511 _init_common(env)
512 return env
513
514
515def _init_common(env):
516 logger.debug('ANDROID_HOME: {}'.format(env.android_home))
517 build_tools_directory = os.path.join(env.android_home, 'build-tools')
518 if not os.path.isdir(build_tools_directory):
519 msg = '''ANDROID_HOME ({}) does not appear to have valid Android SDK install
520 (cannot find build-tools)'''
521 raise HostError(msg.format(env.android_home))
522 versions = os.listdir(build_tools_directory)
523 for version in reversed(sorted(versions)):
524 aapt_path = os.path.join(build_tools_directory, version, 'aapt')
525 if os.path.isfile(aapt_path):
526 logger.debug('Using aapt for version {}'.format(version))
527 env.aapt = aapt_path
528 break
529 else:
530 raise HostError('aapt not found. Please make sure at least one Android '
531 'platform is installed.')
532
533
534def _check_env():
535 global android_home, platform_tools, adb, aapt # pylint: disable=W0603
536 if not android_home:
537 android_home = os.getenv('ANDROID_HOME')
538 if android_home:
539 _env = _initialize_with_android_home(_AndroidEnvironment())
540 else:
541 _env = _initialize_without_android_home(_AndroidEnvironment())
542 android_home = _env.android_home
543 platform_tools = _env.platform_tools
544 adb = _env.adb
545 aapt = _env.aapt
Valentin Schneider035181a2017-09-11 16:55:02 +0100546
Brendan Jackman1d855012017-09-21 12:30:13 +0100547class LogcatMonitor(object):
Brendan Jackmanfb5a2602017-09-21 13:20:46 +0100548 """
549 Helper class for monitoring Anroid's logcat
550
551 :param target: Android target to monitor
552 :type target: :class:`AndroidTarget`
553
Brendan Jackman1d855012017-09-21 12:30:13 +0100554 :param regexps: List of uncompiled regular expressions to filter on the
Brendan Jackmanfb5a2602017-09-21 13:20:46 +0100555 device. Logcat entries that don't match any will not be
556 seen. If omitted, all entries will be sent to host.
557 :type regexps: list(str)
558 """
Valentin Schneider035181a2017-09-11 16:55:02 +0100559
Valentin Schneider035181a2017-09-11 16:55:02 +0100560 @property
561 def logfile(self):
562 return self._logfile
563
564 def __init__(self, target, regexps=None):
565 super(LogcatMonitor, self).__init__()
566
567 self.target = target
Valentin Schneider035181a2017-09-11 16:55:02 +0100568 self._regexps = regexps
569
570 def start(self, outfile=None):
Brendan Jackmanfb5a2602017-09-21 13:20:46 +0100571 """
572 Start logcat and begin monitoring
573
574 :param outfile: Optional path to file to store all logcat entries
575 :type outfile: str
576 """
Valentin Schneider035181a2017-09-11 16:55:02 +0100577 if outfile:
Brendan Jackman1d855012017-09-21 12:30:13 +0100578 self._logfile = open(outfile)
Valentin Schneider035181a2017-09-11 16:55:02 +0100579 else:
Brendan Jackman1d855012017-09-21 12:30:13 +0100580 self._logfile = tempfile.NamedTemporaryFile()
Valentin Schneider035181a2017-09-11 16:55:02 +0100581
Valentin Schneider035181a2017-09-11 16:55:02 +0100582 self.target.clear_logcat()
583
584 logcat_cmd = 'logcat'
585
586 # Join all requested regexps with an 'or'
587 if self._regexps:
588 regexp = '{}'.format('|'.join(self._regexps))
589 if len(self._regexps) > 1:
590 regexp = '({})'.format(regexp)
Valentin Schneider6bda0932017-09-12 15:44:25 +0100591 logcat_cmd = '{} -e "{}"'.format(logcat_cmd, regexp)
Valentin Schneider035181a2017-09-11 16:55:02 +0100592
Brendan Jackman1d855012017-09-21 12:30:13 +0100593 logcat_cmd = get_adb_command(self.target.conn.device, logcat_cmd)
594
Valentin Schneider035181a2017-09-11 16:55:02 +0100595 logger.debug('logcat command ="{}"'.format(logcat_cmd))
Brendan Jackman1d855012017-09-21 12:30:13 +0100596 self._logcat = pexpect.spawn(logcat_cmd, logfile=self._logfile)
Valentin Schneider035181a2017-09-11 16:55:02 +0100597
598 def stop(self):
Brendan Jackman1d855012017-09-21 12:30:13 +0100599 self._logcat.terminate()
600 self._logfile.close()
Valentin Schneider035181a2017-09-11 16:55:02 +0100601
602 def get_log(self):
603 """
604 Return the list of lines found by the monitor
605 """
Brendan Jackman1d855012017-09-21 12:30:13 +0100606 with open(self._logfile.name) as fh:
607 return [line for line in fh]
Valentin Schneider035181a2017-09-11 16:55:02 +0100608
Brendan Jackman1d855012017-09-21 12:30:13 +0100609 def clear_log(self):
Brendan Jackman71d5b8b2017-10-12 14:39:27 +0100610 with open(self._logfile.name, 'w') as fh:
Brendan Jackman1d855012017-09-21 12:30:13 +0100611 pass
Valentin Schneider035181a2017-09-11 16:55:02 +0100612
Valentin Schneider6bda0932017-09-12 15:44:25 +0100613 def search(self, regexp):
Valentin Schneider035181a2017-09-11 16:55:02 +0100614 """
615 Search a line that matches a regexp in the logcat log
Valentin Schneider6bda0932017-09-12 15:44:25 +0100616 Return immediatly
Valentin Schneider035181a2017-09-11 16:55:02 +0100617 """
Brendan Jackman1d855012017-09-21 12:30:13 +0100618 return [line for line in self.get_log() if re.match(regexp, line)]
Valentin Schneider6bda0932017-09-12 15:44:25 +0100619
620 def wait_for(self, regexp, timeout=30):
621 """
622 Search a line that matches a regexp in the logcat log
623 Wait for it to appear if it's not found
Brendan Jackmanfb5a2602017-09-21 13:20:46 +0100624
625 :param regexp: regexp to search
626 :type regexp: str
627
628 :param timeout: Timeout in seconds, before rasing RuntimeError.
629 ``None`` means wait indefinitely
630 :type timeout: number
631
632 :returns: List of matched strings
Valentin Schneider6bda0932017-09-12 15:44:25 +0100633 """
Brendan Jackman1d855012017-09-21 12:30:13 +0100634 log = self.get_log()
635 res = [line for line in log if re.match(regexp, line)]
Valentin Schneider6bda0932017-09-12 15:44:25 +0100636
Valentin Schneider035181a2017-09-11 16:55:02 +0100637 # Found some matches, return them
Brendan Jackman1d855012017-09-21 12:30:13 +0100638 if len(res) > 0:
Valentin Schneider035181a2017-09-11 16:55:02 +0100639 return res
640
Brendan Jackman1d855012017-09-21 12:30:13 +0100641 # Store the number of lines we've searched already, so we don't have to
642 # re-grep them after 'expect' returns
643 next_line_num = len(log)
Valentin Schneider035181a2017-09-11 16:55:02 +0100644
Brendan Jackman1d855012017-09-21 12:30:13 +0100645 try:
646 self._logcat.expect(regexp, timeout=timeout)
647 except pexpect.TIMEOUT:
Valentin Schneider035181a2017-09-11 16:55:02 +0100648 raise RuntimeError('Logcat monitor timeout ({}s)'.format(timeout))
Brendan Jackman1d855012017-09-21 12:30:13 +0100649
650 return [line for line in self.get_log()[next_line_num:]
651 if re.match(regexp, line)]