blob: 68466865bc730b76c4b5d001e434d06781f55d07 [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
Brendan Jackmana290d282017-09-20 13:02:44 +0100289 # Ensure server is started so the 'daemon started successfully' message
290 # doesn't confuse the parsing below
291 adb_command(None, 'start-server', adb_server=adb_server)
292
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100293 # The output of calling adb devices consists of a heading line then
294 # a list of the devices sperated by new line
295 # The last line is a blank new line. in otherwords, if there is a device found
296 # then the output length is 2 + (1 for each device)
297 start = time.time()
298 while True:
Anthony Barbier7919a562017-07-25 13:48:33 +0100299 output = adb_command(None, "devices", adb_server=adb_server).splitlines() # pylint: disable=E1103
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100300 output_length = len(output)
301 if output_length == 3:
302 # output[1] is the 2nd line in the output which has the device name
303 # Splitting the line by '\t' gives a list of two indexes, which has
304 # device serial in 0 number and device type in 1.
305 return output[1].split('\t')[0]
306 elif output_length > 3:
307 message = '{} Android devices found; either explicitly specify ' +\
Sebastian Goscikaab487c2016-02-15 15:21:40 +0000308 'the device you want, or make sure only one is connected.'
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100309 raise HostError(message.format(output_length - 2))
310 else:
311 if timeout < time.time() - start:
312 raise HostError('No device is connected and available')
313 time.sleep(1)
314
315
316def adb_connect(device, timeout=None, attempts=MAX_ATTEMPTS):
317 _check_env()
Patrick Bellasif714dd32016-07-15 11:18:09 +0100318 # Connect is required only for ADB-over-IP
319 if "." not in device:
320 logger.debug('Device connected via USB, connect not required')
321 return
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100322 tries = 0
323 output = None
324 while tries <= attempts:
325 tries += 1
326 if device:
327 command = 'adb connect {}'.format(device)
328 logger.debug(command)
329 output, _ = check_output(command, shell=True, timeout=timeout)
330 if _ping(device):
331 break
332 time.sleep(10)
333 else: # did not connect to the device
334 message = 'Could not connect to {}'.format(device or 'a device')
335 if output:
336 message += '; got: "{}"'.format(output)
337 raise HostError(message)
338
339
340def adb_disconnect(device):
341 _check_env()
342 if not device:
343 return
Chris Redpath119fd7d2016-10-06 16:34:14 +0100344 if ":" in device and device in adb_list_devices():
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100345 command = "adb disconnect " + device
346 logger.debug(command)
347 retval = subprocess.call(command, stdout=open(os.devnull, 'wb'), shell=True)
348 if retval:
349 raise TargetError('"{}" returned {}'.format(command, retval))
350
351
352def _ping(device):
353 _check_env()
354 device_string = ' -s {}'.format(device) if device else ''
355 command = "adb{} shell \"ls / > /dev/null\"".format(device_string)
356 logger.debug(command)
357 result = subprocess.call(command, stderr=subprocess.PIPE, shell=True)
358 if not result:
359 return True
360 else:
361 return False
362
363
Patrick Bellasic2329bd2016-03-28 12:29:45 +0100364def adb_shell(device, command, timeout=None, check_exit_code=False,
Anthony Barbier7919a562017-07-25 13:48:33 +0100365 as_root=False, newline_separator='\r\n', adb_server=None): # NOQA
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100366 _check_env()
367 if as_root:
Sergei Trofimov171cc252015-12-14 17:21:47 +0000368 command = 'echo \'{}\' | su'.format(escape_single_quotes(command))
Anthony Barbier7919a562017-07-25 13:48:33 +0100369 device_part = []
370 if adb_server:
371 device_part = ['-H', adb_server]
372 device_part += ['-s', device] if device else []
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100373
Brendan Jackman44fe0372017-01-25 12:06:38 +0000374 # On older combinations of ADB/Android versions, the adb host command always
375 # exits with 0 if it was able to run the command on the target, even if the
376 # command failed (https://code.google.com/p/android/issues/detail?id=3254).
377 # Homogenise this behaviour by running the command then echoing the exit
378 # code.
379 adb_shell_command = '({}); echo \"\n$?\"'.format(command)
380 actual_command = ['adb'] + device_part + ['shell', adb_shell_command]
381 logger.debug('adb {} shell {}'.format(' '.join(device_part), command))
382 raw_output, error = check_output(actual_command, timeout, shell=False)
383 if raw_output:
384 try:
385 output, exit_code, _ = raw_output.rsplit(newline_separator, 2)
386 except ValueError:
387 exit_code, _ = raw_output.rsplit(newline_separator, 1)
388 output = ''
389 else: # raw_output is empty
390 exit_code = '969696' # just because
391 output = ''
392
393 if check_exit_code:
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100394 exit_code = exit_code.strip()
Valentin Schneider2d968402017-06-12 18:59:44 +0100395 re_search = AM_START_ERROR.findall('{}\n{}'.format(output, error))
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100396 if exit_code.isdigit():
397 if int(exit_code):
Brendan Jackman44fe0372017-01-25 12:06:38 +0000398 message = ('Got exit code {}\nfrom target command: {}\n'
399 'STDOUT: {}\nSTDERR: {}')
400 raise TargetError(message.format(exit_code, command, output, error))
Valentin Schneider2d968402017-06-12 18:59:44 +0100401 elif re_search:
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100402 message = 'Could not start activity; got the following:\n{}'
Valentin Schneider2d968402017-06-12 18:59:44 +0100403 raise TargetError(message.format(re_search[0]))
404 else: # not all digits
405 if re_search:
406 message = 'Could not start activity; got the following:\n{}'
407 raise TargetError(message.format(re_search[0]))
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100408 else:
409 message = 'adb has returned early; did not get an exit code. '\
Sergei Trofimov85036fb2017-07-12 13:44:47 +0100410 'Was kill-server invoked?\nOUTPUT:\n-----\n{}\n'\
411 '-----\nERROR:\n-----\n{}\n-----'
412 raise TargetError(message.format(raw_output, error))
Brendan Jackman44fe0372017-01-25 12:06:38 +0000413
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100414 return output
415
416
417def adb_background_shell(device, command,
418 stdout=subprocess.PIPE,
419 stderr=subprocess.PIPE,
420 as_root=False):
421 """Runs the sepcified command in a subprocess, returning the the Popen object."""
422 _check_env()
423 if as_root:
424 command = 'echo \'{}\' | su'.format(escape_single_quotes(command))
425 device_string = ' -s {}'.format(device) if device else ''
426 full_command = 'adb{} shell "{}"'.format(device_string, escape_double_quotes(command))
427 logger.debug(full_command)
428 return subprocess.Popen(full_command, stdout=stdout, stderr=stderr, shell=True)
429
430
Anthony Barbier7919a562017-07-25 13:48:33 +0100431def adb_list_devices(adb_server=None):
432 output = adb_command(None, 'devices',adb_server=adb_server)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100433 devices = []
434 for line in output.splitlines():
435 parts = [p.strip() for p in line.split()]
436 if len(parts) == 2:
437 devices.append(AdbDevice(*parts))
438 return devices
439
440
Anthony Barbier7919a562017-07-25 13:48:33 +0100441def adb_command(device, command, timeout=None,adb_server=None):
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100442 _check_env()
Anthony Barbier7919a562017-07-25 13:48:33 +0100443 device_string = ""
444 if adb_server != None:
445 device_string = ' -H {}'.format(adb_server)
446 device_string += ' -s {}'.format(device) if device else ''
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100447 full_command = "adb{} {}".format(device_string, command)
448 logger.debug(full_command)
449 output, _ = check_output(full_command, timeout, shell=True)
450 return output
451
Valentin Schneider24d56302017-09-13 11:47:51 +0100452def grant_app_permissions(target, package):
453 """
454 Grant an app all the permissions it may ask for
455 """
456 dumpsys = target.execute('dumpsys package {}'.format(package))
457
458 permissions = re.search(
459 'requested permissions:\s*(?P<permissions>(android.permission.+\s*)+)', dumpsys
460 )
Sergei Trofimov90040e82017-09-13 14:03:59 +0100461 if permissions is None:
462 return
Valentin Schneider24d56302017-09-13 11:47:51 +0100463 permissions = permissions.group('permissions').replace(" ", "").splitlines()
464
465 for permission in permissions:
466 try:
467 target.execute('pm grant {} {}'.format(package, permission))
468 except TargetError:
469 logger.debug('Cannot grant {}'.format(permission))
470
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100471
472# Messy environment initialisation stuff...
473
474class _AndroidEnvironment(object):
475
476 def __init__(self):
477 self.android_home = None
478 self.platform_tools = None
479 self.adb = None
480 self.aapt = None
481 self.fastboot = None
482
483
484def _initialize_with_android_home(env):
485 logger.debug('Using ANDROID_HOME from the environment.')
486 env.android_home = android_home
487 env.platform_tools = os.path.join(android_home, 'platform-tools')
Sergei Trofimovd5460e12016-12-13 11:26:32 +0000488 os.environ['PATH'] = env.platform_tools + os.pathsep + os.environ['PATH']
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100489 _init_common(env)
490 return env
491
492
493def _initialize_without_android_home(env):
Javi Merino7f32efc2015-12-15 13:43:56 +0000494 adb_full_path = which('adb')
495 if adb_full_path:
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100496 env.adb = 'adb'
497 else:
498 raise HostError('ANDROID_HOME is not set and adb is not in PATH. '
499 'Have you installed Android SDK?')
500 logger.debug('Discovering ANDROID_HOME from adb path.')
Javi Merino7f32efc2015-12-15 13:43:56 +0000501 env.platform_tools = os.path.dirname(adb_full_path)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100502 env.android_home = os.path.dirname(env.platform_tools)
503 _init_common(env)
504 return env
505
506
507def _init_common(env):
508 logger.debug('ANDROID_HOME: {}'.format(env.android_home))
509 build_tools_directory = os.path.join(env.android_home, 'build-tools')
510 if not os.path.isdir(build_tools_directory):
511 msg = '''ANDROID_HOME ({}) does not appear to have valid Android SDK install
512 (cannot find build-tools)'''
513 raise HostError(msg.format(env.android_home))
514 versions = os.listdir(build_tools_directory)
515 for version in reversed(sorted(versions)):
516 aapt_path = os.path.join(build_tools_directory, version, 'aapt')
517 if os.path.isfile(aapt_path):
518 logger.debug('Using aapt for version {}'.format(version))
519 env.aapt = aapt_path
520 break
521 else:
522 raise HostError('aapt not found. Please make sure at least one Android '
523 'platform is installed.')
524
525
526def _check_env():
527 global android_home, platform_tools, adb, aapt # pylint: disable=W0603
528 if not android_home:
529 android_home = os.getenv('ANDROID_HOME')
530 if android_home:
531 _env = _initialize_with_android_home(_AndroidEnvironment())
532 else:
533 _env = _initialize_without_android_home(_AndroidEnvironment())
534 android_home = _env.android_home
535 platform_tools = _env.platform_tools
536 adb = _env.adb
537 aapt = _env.aapt
Valentin Schneider035181a2017-09-11 16:55:02 +0100538
539class LogcatMonitor(threading.Thread):
540
541 FLUSH_SIZE = 1000
542
543 @property
544 def logfile(self):
545 return self._logfile
546
547 def __init__(self, target, regexps=None):
548 super(LogcatMonitor, self).__init__()
549
550 self.target = target
551
Brendan Jackman8cf4a442017-09-20 13:40:43 +0100552 self._started = threading.Event()
Valentin Schneider035181a2017-09-11 16:55:02 +0100553 self._stopped = threading.Event()
554 self._match_found = threading.Event()
555
556 self._sought = None
557 self._found = None
558
559 self._lines = Queue.Queue()
560 self._datalock = threading.Lock()
561 self._regexps = regexps
562
563 def start(self, outfile=None):
564 if outfile:
565 self._logfile = outfile
566 else:
567 fd, self._logfile = tempfile.mkstemp()
568 os.close(fd)
569 logger.debug('Logging to {}'.format(self._logfile))
570
571 super(LogcatMonitor, self).start()
572
573 def run(self):
574 self.target.clear_logcat()
575
576 logcat_cmd = 'logcat'
577
578 # Join all requested regexps with an 'or'
579 if self._regexps:
580 regexp = '{}'.format('|'.join(self._regexps))
581 if len(self._regexps) > 1:
582 regexp = '({})'.format(regexp)
Valentin Schneider6bda0932017-09-12 15:44:25 +0100583 logcat_cmd = '{} -e "{}"'.format(logcat_cmd, regexp)
Valentin Schneider035181a2017-09-11 16:55:02 +0100584
585 logger.debug('logcat command ="{}"'.format(logcat_cmd))
586 self._logcat = self.target.background(logcat_cmd)
587
Brendan Jackman8cf4a442017-09-20 13:40:43 +0100588 self._started.set()
589
Valentin Schneider035181a2017-09-11 16:55:02 +0100590 while not self._stopped.is_set():
591 line = self._logcat.stdout.readline(1024)
Valentin Schneider6bda0932017-09-12 15:44:25 +0100592 if line:
593 self._add_line(line)
Valentin Schneider035181a2017-09-11 16:55:02 +0100594
595 def stop(self):
Brendan Jackman22c1f5e2017-09-20 15:04:15 +0100596 if not self.is_alive():
597 logger.warning('LogcatMonitor.stop called before start')
598 return
599
Brendan Jackman8cf4a442017-09-20 13:40:43 +0100600 # Make sure we've started before we try to kill anything
601 self._started.wait()
602
Valentin Schneider6bda0932017-09-12 15:44:25 +0100603 # Kill the underlying logcat process
604 # This will unblock self._logcat.stdout.readline()
605 host.kill_children(self._logcat.pid)
606 self._logcat.kill()
Valentin Schneider035181a2017-09-11 16:55:02 +0100607
608 self._stopped.set()
609 self.join()
610
611 self._flush_lines()
612
613 def _add_line(self, line):
614 self._lines.put(line)
615
616 if self._sought and re.match(self._sought, line):
617 self._found = line
618 self._match_found.set()
619
620 if self._lines.qsize() >= self.FLUSH_SIZE:
621 self._flush_lines()
622
623 def _flush_lines(self):
624 with self._datalock:
625 with open(self._logfile, 'a') as fh:
626 while not self._lines.empty():
627 fh.write(self._lines.get())
628
629 def clear_log(self):
630 with self._datalock:
631 while not self._lines.empty():
632 self._lines.get()
Brendan Jackmanb3242a12017-09-20 13:40:36 +0100633
Valentin Schneider035181a2017-09-11 16:55:02 +0100634 with open(self._logfile, 'w') as fh:
635 pass
636
637 def get_log(self):
638 """
639 Return the list of lines found by the monitor
640 """
641 self._flush_lines()
642
643 with self._datalock:
644 with open(self._logfile, 'r') as fh:
645 res = [line for line in fh]
646
647 return res
648
Valentin Schneider6bda0932017-09-12 15:44:25 +0100649 def search(self, regexp):
Valentin Schneider035181a2017-09-11 16:55:02 +0100650 """
651 Search a line that matches a regexp in the logcat log
Valentin Schneider6bda0932017-09-12 15:44:25 +0100652 Return immediatly
Valentin Schneider035181a2017-09-11 16:55:02 +0100653 """
654 res = []
655
656 self._flush_lines()
657
658 with self._datalock:
659 with open(self._logfile, 'r') as fh:
660 for line in fh:
661 if re.match(regexp, line):
662 res.append(line)
663
Valentin Schneider6bda0932017-09-12 15:44:25 +0100664 return res
665
666 def wait_for(self, regexp, timeout=30):
667 """
668 Search a line that matches a regexp in the logcat log
669 Wait for it to appear if it's not found
670 """
671 res = self.search(regexp)
672
Valentin Schneider035181a2017-09-11 16:55:02 +0100673 # Found some matches, return them
Valentin Schneider6bda0932017-09-12 15:44:25 +0100674 # Also return if thread not running
675 if len(res) > 0 or not self.is_alive():
Valentin Schneider035181a2017-09-11 16:55:02 +0100676 return res
677
678 # Did not find any match, wait for one to pop up
679 self._sought = regexp
680 found = self._match_found.wait(timeout)
681 self._match_found.clear()
682 self._sought = None
683
684 if found:
685 return [self._found]
686 else:
687 raise RuntimeError('Logcat monitor timeout ({}s)'.format(timeout))