blob: 1f1f7624f2eab4a8d9729ee15d67d09f812764a4 [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
23import time
24import subprocess
25import logging
26import re
Valentin Schneider035181a2017-09-11 16:55:02 +010027import threading
28import tempfile
29import Queue
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010030from collections import defaultdict
31
Sebastian Goscik8de24b52016-03-23 15:10:26 +000032from devlib.exception import TargetError, HostError, DevlibError
Marc Bonnici68be9d82017-07-14 16:59:52 +010033from devlib.utils.misc import check_output, which, memoized, ABI_MAP
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010034from devlib.utils.misc import escape_single_quotes, escape_double_quotes
Valentin Schneider6bda0932017-09-12 15:44:25 +010035from devlib import host
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010036
37
38logger = logging.getLogger('android')
39
40MAX_ATTEMPTS = 5
Valentin Schneider2d968402017-06-12 18:59:44 +010041AM_START_ERROR = re.compile(r"Error: Activity.*")
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010042
43# See:
44# http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels
45ANDROID_VERSION_MAP = {
Sebastian Goscik0c112892016-02-15 15:35:56 +000046 23: 'MARSHMALLOW',
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010047 22: 'LOLLYPOP_MR1',
48 21: 'LOLLYPOP',
49 20: 'KITKAT_WATCH',
50 19: 'KITKAT',
51 18: 'JELLY_BEAN_MR2',
52 17: 'JELLY_BEAN_MR1',
53 16: 'JELLY_BEAN',
54 15: 'ICE_CREAM_SANDWICH_MR1',
55 14: 'ICE_CREAM_SANDWICH',
56 13: 'HONEYCOMB_MR2',
57 12: 'HONEYCOMB_MR1',
58 11: 'HONEYCOMB',
59 10: 'GINGERBREAD_MR1',
60 9: 'GINGERBREAD',
61 8: 'FROYO',
62 7: 'ECLAIR_MR1',
63 6: 'ECLAIR_0_1',
64 5: 'ECLAIR',
65 4: 'DONUT',
66 3: 'CUPCAKE',
67 2: 'BASE_1_1',
68 1: 'BASE',
69}
70
71
72# Initialized in functions near the botton of the file
73android_home = None
74platform_tools = None
75adb = None
76aapt = None
77fastboot = None
78
79
80class AndroidProperties(object):
81
82 def __init__(self, text):
83 self._properties = {}
84 self.parse(text)
85
86 def parse(self, text):
87 self._properties = dict(re.findall(r'\[(.*?)\]:\s+\[(.*?)\]', text))
88
89 def iteritems(self):
90 return self._properties.iteritems()
91
92 def __iter__(self):
93 return iter(self._properties)
94
95 def __getattr__(self, name):
96 return self._properties.get(name)
97
98 __getitem__ = __getattr__
99
100
101class AdbDevice(object):
102
103 def __init__(self, name, status):
104 self.name = name
105 self.status = status
106
107 def __cmp__(self, other):
108 if isinstance(other, AdbDevice):
109 return cmp(self.name, other.name)
110 else:
111 return cmp(self.name, other)
112
113 def __str__(self):
114 return 'AdbDevice({}, {})'.format(self.name, self.status)
115
116 __repr__ = __str__
117
118
119class ApkInfo(object):
120
121 version_regex = re.compile(r"name='(?P<name>[^']+)' versionCode='(?P<vcode>[^']+)' versionName='(?P<vname>[^']+)'")
122 name_regex = re.compile(r"name='(?P<name>[^']+)'")
123
124 def __init__(self, path=None):
125 self.path = path
126 self.package = None
127 self.activity = None
128 self.label = None
129 self.version_name = None
130 self.version_code = None
Marc Bonnici68be9d82017-07-14 16:59:52 +0100131 self.native_code = None
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100132 self.parse(path)
133
134 def parse(self, apk_path):
135 _check_env()
136 command = [aapt, 'dump', 'badging', apk_path]
137 logger.debug(' '.join(command))
138 output = subprocess.check_output(command)
139 for line in output.split('\n'):
140 if line.startswith('application-label:'):
141 self.label = line.split(':')[1].strip().replace('\'', '')
142 elif line.startswith('package:'):
143 match = self.version_regex.search(line)
144 if match:
145 self.package = match.group('name')
146 self.version_code = match.group('vcode')
147 self.version_name = match.group('vname')
148 elif line.startswith('launchable-activity:'):
149 match = self.name_regex.search(line)
150 self.activity = match.group('name')
Marc Bonnici68be9d82017-07-14 16:59:52 +0100151 elif line.startswith('native-code'):
152 apk_abis = [entry.strip() for entry in line.split(':')[1].split("'") if entry.strip()]
153 mapped_abis = []
154 for apk_abi in apk_abis:
155 found = False
156 for abi, architectures in ABI_MAP.iteritems():
157 if apk_abi in architectures:
158 mapped_abis.append(abi)
159 found = True
160 break
161 if not found:
162 mapped_abis.append(apk_abi)
163 self.native_code = mapped_abis
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100164 else:
165 pass # not interested
166
167
168class AdbConnection(object):
169
170 # maintains the count of parallel active connections to a device, so that
171 # adb disconnect is not invoked untill all connections are closed
172 active_connections = defaultdict(int)
Sergei Trofimov89256fd2016-05-17 14:00:01 +0100173 default_timeout = 10
Chris Redpathe8e945a2016-10-14 14:35:58 +0100174 ls_command = 'ls'
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100175
176 @property
177 def name(self):
178 return self.device
179
Sebastian Goscik8de24b52016-03-23 15:10:26 +0000180 @property
181 @memoized
182 def newline_separator(self):
Chris Redpathe8e945a2016-10-14 14:35:58 +0100183 output = adb_command(self.device,
Anthony Barbier7919a562017-07-25 13:48:33 +0100184 "shell '({}); echo \"\n$?\"'".format(self.ls_command), adb_server=self.adb_server)
Sebastian Goscik8de24b52016-03-23 15:10:26 +0000185 if output.endswith('\r\n'):
186 return '\r\n'
187 elif output.endswith('\n'):
188 return '\n'
189 else:
190 raise DevlibError("Unknown line ending")
191
Chris Redpathe8e945a2016-10-14 14:35:58 +0100192 # Again, we need to handle boards where the default output format from ls is
193 # single column *and* boards where the default output is multi-column.
194 # We need to do this purely because the '-1' option causes errors on older
195 # versions of the ls tool in Android pre-v7.
196 def _setup_ls(self):
197 command = "shell '(ls -1); echo \"\n$?\"'"
Brendan Jackman32982052017-03-01 18:49:27 +0000198 try:
Anthony Barbier7919a562017-07-25 13:48:33 +0100199 output = adb_command(self.device, command, timeout=self.timeout, adb_server=self.adb_server)
Brendan Jackman32982052017-03-01 18:49:27 +0000200 except subprocess.CalledProcessError as e:
201 raise HostError(
202 'Failed to set up ls command on Android device. Output:\n'
203 + e.output)
Chris Redpathe8e945a2016-10-14 14:35:58 +0100204 lines = output.splitlines()
205 retval = lines[-1].strip()
206 if int(retval) == 0:
207 self.ls_command = 'ls -1'
208 else:
209 self.ls_command = 'ls'
Sergei Trofimov97a89972017-05-12 10:45:42 +0100210 logger.debug("ls command is set to {}".format(self.ls_command))
Chris Redpathe8e945a2016-10-14 14:35:58 +0100211
Anthony Barbier7919a562017-07-25 13:48:33 +0100212 def __init__(self, device=None, timeout=None, platform=None, adb_server=None):
Sergei Trofimov89256fd2016-05-17 14:00:01 +0100213 self.timeout = timeout if timeout is not None else self.default_timeout
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100214 if device is None:
Anthony Barbier7919a562017-07-25 13:48:33 +0100215 device = adb_get_device(timeout=timeout, adb_server=adb_server)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100216 self.device = device
Anthony Barbier7919a562017-07-25 13:48:33 +0100217 self.adb_server = adb_server
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100218 adb_connect(self.device)
219 AdbConnection.active_connections[self.device] += 1
Chris Redpathe8e945a2016-10-14 14:35:58 +0100220 self._setup_ls()
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100221
222 def push(self, source, dest, timeout=None):
223 if timeout is None:
224 timeout = self.timeout
Sebastian Goscik1424ceb2016-02-15 15:27:19 +0000225 command = "push '{}' '{}'".format(source, dest)
Brendan Jackmane17b9c32017-04-28 11:26:35 +0100226 if not os.path.exists(source):
227 raise HostError('No such file "{}"'.format(source))
Anthony Barbier7919a562017-07-25 13:48:33 +0100228 return adb_command(self.device, command, timeout=timeout, adb_server=self.adb_server)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100229
230 def pull(self, source, dest, timeout=None):
231 if timeout is None:
232 timeout = self.timeout
Patrick Bellasic93e3d62015-11-25 11:19:08 +0000233 # Pull all files matching a wildcard expression
234 if os.path.isdir(dest) and \
Sebastian Goscikaab487c2016-02-15 15:21:40 +0000235 ('*' in source or '?' in source):
Chris Redpathe8e945a2016-10-14 14:35:58 +0100236 command = 'shell {} {}'.format(self.ls_command, source)
Anthony Barbier7919a562017-07-25 13:48:33 +0100237 output = adb_command(self.device, command, timeout=timeout, adb_server=self.adb_server)
Patrick Bellasic93e3d62015-11-25 11:19:08 +0000238 for line in output.splitlines():
Brendan Jackmanee38a422016-11-18 17:48:48 +0000239 command = "pull '{}' '{}'".format(line.strip(), dest)
Anthony Barbier7919a562017-07-25 13:48:33 +0100240 adb_command(self.device, command, timeout=timeout, adb_server=self.adb_server)
Patrick Bellasic93e3d62015-11-25 11:19:08 +0000241 return
Sebastian Goscik1424ceb2016-02-15 15:27:19 +0000242 command = "pull '{}' '{}'".format(source, dest)
Anthony Barbier7919a562017-07-25 13:48:33 +0100243 return adb_command(self.device, command, timeout=timeout, adb_server=self.adb_server)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100244
Anouk Van Laer29a79402017-01-31 12:48:58 +0000245 def execute(self, command, timeout=None, check_exit_code=False,
246 as_root=False, strip_colors=True):
Patrick Bellasic2329bd2016-03-28 12:29:45 +0100247 return adb_shell(self.device, command, timeout, check_exit_code,
Anthony Barbier7919a562017-07-25 13:48:33 +0100248 as_root, self.newline_separator,adb_server=self.adb_server)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100249
250 def background(self, command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, as_root=False):
251 return adb_background_shell(self.device, command, stdout, stderr, as_root)
252
253 def close(self):
254 AdbConnection.active_connections[self.device] -= 1
255 if AdbConnection.active_connections[self.device] <= 0:
256 adb_disconnect(self.device)
257 del AdbConnection.active_connections[self.device]
258
259 def cancel_running_command(self):
260 # adbd multiplexes commands so that they don't interfer with each
261 # other, so there is no need to explicitly cancel a running command
262 # before the next one can be issued.
263 pass
264
265
Patrick Bellasida588ea2017-02-16 13:08:31 +0000266def fastboot_command(command, timeout=None, device=None):
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100267 _check_env()
Patrick Bellasida588ea2017-02-16 13:08:31 +0000268 target = '-s {}'.format(device) if device else ''
269 full_command = 'fastboot {} {}'.format(target, command)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100270 logger.debug(full_command)
271 output, _ = check_output(full_command, timeout, shell=True)
272 return output
273
274
275def fastboot_flash_partition(partition, path_to_image):
276 command = 'flash {} {}'.format(partition, path_to_image)
277 fastboot_command(command)
278
279
Anthony Barbier7919a562017-07-25 13:48:33 +0100280def adb_get_device(timeout=None, adb_server=None):
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100281 """
282 Returns the serial number of a connected android device.
283
284 If there are more than one device connected to the machine, or it could not
285 find any device connected, :class:`devlib.exceptions.HostError` is raised.
286 """
287 # TODO this is a hacky way to issue a adb command to all listed devices
288
289 # The output of calling adb devices consists of a heading line then
290 # a list of the devices sperated by new line
291 # The last line is a blank new line. in otherwords, if there is a device found
292 # then the output length is 2 + (1 for each device)
293 start = time.time()
294 while True:
Anthony Barbier7919a562017-07-25 13:48:33 +0100295 output = adb_command(None, "devices", adb_server=adb_server).splitlines() # pylint: disable=E1103
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100296 output_length = len(output)
297 if output_length == 3:
298 # output[1] is the 2nd line in the output which has the device name
299 # Splitting the line by '\t' gives a list of two indexes, which has
300 # device serial in 0 number and device type in 1.
301 return output[1].split('\t')[0]
302 elif output_length > 3:
303 message = '{} Android devices found; either explicitly specify ' +\
Sebastian Goscikaab487c2016-02-15 15:21:40 +0000304 'the device you want, or make sure only one is connected.'
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100305 raise HostError(message.format(output_length - 2))
306 else:
307 if timeout < time.time() - start:
308 raise HostError('No device is connected and available')
309 time.sleep(1)
310
311
312def adb_connect(device, timeout=None, attempts=MAX_ATTEMPTS):
313 _check_env()
Patrick Bellasif714dd32016-07-15 11:18:09 +0100314 # Connect is required only for ADB-over-IP
315 if "." not in device:
316 logger.debug('Device connected via USB, connect not required')
317 return
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100318 tries = 0
319 output = None
320 while tries <= attempts:
321 tries += 1
322 if device:
323 command = 'adb connect {}'.format(device)
324 logger.debug(command)
325 output, _ = check_output(command, shell=True, timeout=timeout)
326 if _ping(device):
327 break
328 time.sleep(10)
329 else: # did not connect to the device
330 message = 'Could not connect to {}'.format(device or 'a device')
331 if output:
332 message += '; got: "{}"'.format(output)
333 raise HostError(message)
334
335
336def adb_disconnect(device):
337 _check_env()
338 if not device:
339 return
Chris Redpath119fd7d2016-10-06 16:34:14 +0100340 if ":" in device and device in adb_list_devices():
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100341 command = "adb disconnect " + device
342 logger.debug(command)
343 retval = subprocess.call(command, stdout=open(os.devnull, 'wb'), shell=True)
344 if retval:
345 raise TargetError('"{}" returned {}'.format(command, retval))
346
347
348def _ping(device):
349 _check_env()
350 device_string = ' -s {}'.format(device) if device else ''
351 command = "adb{} shell \"ls / > /dev/null\"".format(device_string)
352 logger.debug(command)
353 result = subprocess.call(command, stderr=subprocess.PIPE, shell=True)
354 if not result:
355 return True
356 else:
357 return False
358
359
Patrick Bellasic2329bd2016-03-28 12:29:45 +0100360def adb_shell(device, command, timeout=None, check_exit_code=False,
Anthony Barbier7919a562017-07-25 13:48:33 +0100361 as_root=False, newline_separator='\r\n', adb_server=None): # NOQA
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100362 _check_env()
363 if as_root:
Sergei Trofimov171cc252015-12-14 17:21:47 +0000364 command = 'echo \'{}\' | su'.format(escape_single_quotes(command))
Anthony Barbier7919a562017-07-25 13:48:33 +0100365 device_part = []
366 if adb_server:
367 device_part = ['-H', adb_server]
368 device_part += ['-s', device] if device else []
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100369
Brendan Jackman44fe0372017-01-25 12:06:38 +0000370 # On older combinations of ADB/Android versions, the adb host command always
371 # exits with 0 if it was able to run the command on the target, even if the
372 # command failed (https://code.google.com/p/android/issues/detail?id=3254).
373 # Homogenise this behaviour by running the command then echoing the exit
374 # code.
375 adb_shell_command = '({}); echo \"\n$?\"'.format(command)
376 actual_command = ['adb'] + device_part + ['shell', adb_shell_command]
377 logger.debug('adb {} shell {}'.format(' '.join(device_part), command))
378 raw_output, error = check_output(actual_command, timeout, shell=False)
379 if raw_output:
380 try:
381 output, exit_code, _ = raw_output.rsplit(newline_separator, 2)
382 except ValueError:
383 exit_code, _ = raw_output.rsplit(newline_separator, 1)
384 output = ''
385 else: # raw_output is empty
386 exit_code = '969696' # just because
387 output = ''
388
389 if check_exit_code:
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100390 exit_code = exit_code.strip()
Valentin Schneider2d968402017-06-12 18:59:44 +0100391 re_search = AM_START_ERROR.findall('{}\n{}'.format(output, error))
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100392 if exit_code.isdigit():
393 if int(exit_code):
Brendan Jackman44fe0372017-01-25 12:06:38 +0000394 message = ('Got exit code {}\nfrom target command: {}\n'
395 'STDOUT: {}\nSTDERR: {}')
396 raise TargetError(message.format(exit_code, command, output, error))
Valentin Schneider2d968402017-06-12 18:59:44 +0100397 elif re_search:
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100398 message = 'Could not start activity; got the following:\n{}'
Valentin Schneider2d968402017-06-12 18:59:44 +0100399 raise TargetError(message.format(re_search[0]))
400 else: # not all digits
401 if re_search:
402 message = 'Could not start activity; got the following:\n{}'
403 raise TargetError(message.format(re_search[0]))
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100404 else:
405 message = 'adb has returned early; did not get an exit code. '\
Sergei Trofimov85036fb2017-07-12 13:44:47 +0100406 'Was kill-server invoked?\nOUTPUT:\n-----\n{}\n'\
407 '-----\nERROR:\n-----\n{}\n-----'
408 raise TargetError(message.format(raw_output, error))
Brendan Jackman44fe0372017-01-25 12:06:38 +0000409
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100410 return output
411
412
413def adb_background_shell(device, command,
414 stdout=subprocess.PIPE,
415 stderr=subprocess.PIPE,
416 as_root=False):
417 """Runs the sepcified command in a subprocess, returning the the Popen object."""
418 _check_env()
419 if as_root:
420 command = 'echo \'{}\' | su'.format(escape_single_quotes(command))
421 device_string = ' -s {}'.format(device) if device else ''
422 full_command = 'adb{} shell "{}"'.format(device_string, escape_double_quotes(command))
423 logger.debug(full_command)
424 return subprocess.Popen(full_command, stdout=stdout, stderr=stderr, shell=True)
425
426
Anthony Barbier7919a562017-07-25 13:48:33 +0100427def adb_list_devices(adb_server=None):
428 output = adb_command(None, 'devices',adb_server=adb_server)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100429 devices = []
430 for line in output.splitlines():
431 parts = [p.strip() for p in line.split()]
432 if len(parts) == 2:
433 devices.append(AdbDevice(*parts))
434 return devices
435
436
Anthony Barbier7919a562017-07-25 13:48:33 +0100437def adb_command(device, command, timeout=None,adb_server=None):
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100438 _check_env()
Anthony Barbier7919a562017-07-25 13:48:33 +0100439 device_string = ""
440 if adb_server != None:
441 device_string = ' -H {}'.format(adb_server)
442 device_string += ' -s {}'.format(device) if device else ''
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100443 full_command = "adb{} {}".format(device_string, command)
444 logger.debug(full_command)
445 output, _ = check_output(full_command, timeout, shell=True)
446 return output
447
Valentin Schneider24d56302017-09-13 11:47:51 +0100448def grant_app_permissions(target, package):
449 """
450 Grant an app all the permissions it may ask for
451 """
452 dumpsys = target.execute('dumpsys package {}'.format(package))
453
454 permissions = re.search(
455 'requested permissions:\s*(?P<permissions>(android.permission.+\s*)+)', dumpsys
456 )
Sergei Trofimov90040e82017-09-13 14:03:59 +0100457 if permissions is None:
458 return
Valentin Schneider24d56302017-09-13 11:47:51 +0100459 permissions = permissions.group('permissions').replace(" ", "").splitlines()
460
461 for permission in permissions:
462 try:
463 target.execute('pm grant {} {}'.format(package, permission))
464 except TargetError:
465 logger.debug('Cannot grant {}'.format(permission))
466
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100467
468# Messy environment initialisation stuff...
469
470class _AndroidEnvironment(object):
471
472 def __init__(self):
473 self.android_home = None
474 self.platform_tools = None
475 self.adb = None
476 self.aapt = None
477 self.fastboot = None
478
479
480def _initialize_with_android_home(env):
481 logger.debug('Using ANDROID_HOME from the environment.')
482 env.android_home = android_home
483 env.platform_tools = os.path.join(android_home, 'platform-tools')
Sergei Trofimovd5460e12016-12-13 11:26:32 +0000484 os.environ['PATH'] = env.platform_tools + os.pathsep + os.environ['PATH']
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100485 _init_common(env)
486 return env
487
488
489def _initialize_without_android_home(env):
Javi Merino7f32efc2015-12-15 13:43:56 +0000490 adb_full_path = which('adb')
491 if adb_full_path:
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100492 env.adb = 'adb'
493 else:
494 raise HostError('ANDROID_HOME is not set and adb is not in PATH. '
495 'Have you installed Android SDK?')
496 logger.debug('Discovering ANDROID_HOME from adb path.')
Javi Merino7f32efc2015-12-15 13:43:56 +0000497 env.platform_tools = os.path.dirname(adb_full_path)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100498 env.android_home = os.path.dirname(env.platform_tools)
499 _init_common(env)
500 return env
501
502
503def _init_common(env):
504 logger.debug('ANDROID_HOME: {}'.format(env.android_home))
505 build_tools_directory = os.path.join(env.android_home, 'build-tools')
506 if not os.path.isdir(build_tools_directory):
507 msg = '''ANDROID_HOME ({}) does not appear to have valid Android SDK install
508 (cannot find build-tools)'''
509 raise HostError(msg.format(env.android_home))
510 versions = os.listdir(build_tools_directory)
511 for version in reversed(sorted(versions)):
512 aapt_path = os.path.join(build_tools_directory, version, 'aapt')
513 if os.path.isfile(aapt_path):
514 logger.debug('Using aapt for version {}'.format(version))
515 env.aapt = aapt_path
516 break
517 else:
518 raise HostError('aapt not found. Please make sure at least one Android '
519 'platform is installed.')
520
521
522def _check_env():
523 global android_home, platform_tools, adb, aapt # pylint: disable=W0603
524 if not android_home:
525 android_home = os.getenv('ANDROID_HOME')
526 if android_home:
527 _env = _initialize_with_android_home(_AndroidEnvironment())
528 else:
529 _env = _initialize_without_android_home(_AndroidEnvironment())
530 android_home = _env.android_home
531 platform_tools = _env.platform_tools
532 adb = _env.adb
533 aapt = _env.aapt
Valentin Schneider035181a2017-09-11 16:55:02 +0100534
535class LogcatMonitor(threading.Thread):
Brendan Jackmanfb5a2602017-09-21 13:20:46 +0100536 """
537 Helper class for monitoring Anroid's logcat
538
539 :param target: Android target to monitor
540 :type target: :class:`AndroidTarget`
541
542 device. Logcat entries that don't match any will not be
543 seen. If omitted, all entries will be sent to host.
544 :type regexps: list(str)
545 """
Valentin Schneider035181a2017-09-11 16:55:02 +0100546
547 FLUSH_SIZE = 1000
548
549 @property
550 def logfile(self):
551 return self._logfile
552
553 def __init__(self, target, regexps=None):
554 super(LogcatMonitor, self).__init__()
555
556 self.target = target
557
558 self._stopped = threading.Event()
559 self._match_found = threading.Event()
560
561 self._sought = None
562 self._found = None
563
564 self._lines = Queue.Queue()
565 self._datalock = threading.Lock()
566 self._regexps = regexps
567
568 def start(self, outfile=None):
Brendan Jackmanfb5a2602017-09-21 13:20:46 +0100569 """
570 Start logcat and begin monitoring
571
572 :param outfile: Optional path to file to store all logcat entries
573 :type outfile: str
574 """
Valentin Schneider035181a2017-09-11 16:55:02 +0100575 if outfile:
576 self._logfile = outfile
577 else:
578 fd, self._logfile = tempfile.mkstemp()
579 os.close(fd)
580 logger.debug('Logging to {}'.format(self._logfile))
581
582 super(LogcatMonitor, self).start()
583
584 def run(self):
585 self.target.clear_logcat()
586
587 logcat_cmd = 'logcat'
588
589 # Join all requested regexps with an 'or'
590 if self._regexps:
591 regexp = '{}'.format('|'.join(self._regexps))
592 if len(self._regexps) > 1:
593 regexp = '({})'.format(regexp)
Valentin Schneider6bda0932017-09-12 15:44:25 +0100594 logcat_cmd = '{} -e "{}"'.format(logcat_cmd, regexp)
Valentin Schneider035181a2017-09-11 16:55:02 +0100595
596 logger.debug('logcat command ="{}"'.format(logcat_cmd))
597 self._logcat = self.target.background(logcat_cmd)
598
599 while not self._stopped.is_set():
600 line = self._logcat.stdout.readline(1024)
Valentin Schneider6bda0932017-09-12 15:44:25 +0100601 if line:
602 self._add_line(line)
Valentin Schneider035181a2017-09-11 16:55:02 +0100603
604 def stop(self):
Valentin Schneider6bda0932017-09-12 15:44:25 +0100605 # Kill the underlying logcat process
606 # This will unblock self._logcat.stdout.readline()
607 host.kill_children(self._logcat.pid)
608 self._logcat.kill()
Valentin Schneider035181a2017-09-11 16:55:02 +0100609
610 self._stopped.set()
611 self.join()
612
613 self._flush_lines()
614
615 def _add_line(self, line):
616 self._lines.put(line)
617
618 if self._sought and re.match(self._sought, line):
619 self._found = line
620 self._match_found.set()
621
622 if self._lines.qsize() >= self.FLUSH_SIZE:
623 self._flush_lines()
624
625 def _flush_lines(self):
626 with self._datalock:
627 with open(self._logfile, 'a') as fh:
628 while not self._lines.empty():
629 fh.write(self._lines.get())
630
631 def clear_log(self):
632 with self._datalock:
633 while not self._lines.empty():
634 self._lines.get()
635
636 with open(self._logfile, 'w') as fh:
637 pass
638
639 def get_log(self):
640 """
641 Return the list of lines found by the monitor
642 """
643 self._flush_lines()
644
645 with self._datalock:
646 with open(self._logfile, 'r') as fh:
647 res = [line for line in fh]
648
649 return res
650
Valentin Schneider6bda0932017-09-12 15:44:25 +0100651 def search(self, regexp):
Valentin Schneider035181a2017-09-11 16:55:02 +0100652 """
653 Search a line that matches a regexp in the logcat log
Valentin Schneider6bda0932017-09-12 15:44:25 +0100654 Return immediatly
Valentin Schneider035181a2017-09-11 16:55:02 +0100655 """
656 res = []
657
658 self._flush_lines()
659
660 with self._datalock:
661 with open(self._logfile, 'r') as fh:
662 for line in fh:
663 if re.match(regexp, line):
664 res.append(line)
665
Valentin Schneider6bda0932017-09-12 15:44:25 +0100666 return res
667
668 def wait_for(self, regexp, timeout=30):
669 """
670 Search a line that matches a regexp in the logcat log
671 Wait for it to appear if it's not found
Brendan Jackmanfb5a2602017-09-21 13:20:46 +0100672
673 :param regexp: regexp to search
674 :type regexp: str
675
676 :param timeout: Timeout in seconds, before rasing RuntimeError.
677 ``None`` means wait indefinitely
678 :type timeout: number
679
680 :returns: List of matched strings
Valentin Schneider6bda0932017-09-12 15:44:25 +0100681 """
682 res = self.search(regexp)
683
Valentin Schneider035181a2017-09-11 16:55:02 +0100684 # Found some matches, return them
Valentin Schneider6bda0932017-09-12 15:44:25 +0100685 # Also return if thread not running
686 if len(res) > 0 or not self.is_alive():
Valentin Schneider035181a2017-09-11 16:55:02 +0100687 return res
688
689 # Did not find any match, wait for one to pop up
690 self._sought = regexp
691 found = self._match_found.wait(timeout)
692 self._match_found.clear()
693 self._sought = None
694
695 if found:
696 return [self._found]
697 else:
698 raise RuntimeError('Logcat monitor timeout ({}s)'.format(timeout))