blob: a24b34eb2dc9d334ff5d803f3d37620cc08601c6 [file] [log] [blame]
Ang Li93420002016-05-10 19:11:44 -07001#!/usr/bin/env python3.4
2#
3# Copyright 2016 - The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17from builtins import str
18from builtins import open
19
Ang Li7f0e1c72016-06-14 11:23:49 -070020import logging
Ang Li93420002016-05-10 19:11:44 -070021import os
22import time
23import traceback
24
25from vts.runners.host import logger as vts_logger
26from vts.runners.host import signals
27from vts.runners.host import utils
Ang Li7f0e1c72016-06-14 11:23:49 -070028from vts.utils.python.controllers import adb
29from vts.utils.python.controllers import event_dispatcher
30from vts.utils.python.controllers import fastboot
31
32from vts.utils.python.mirror import hal_mirror
Ang Li93420002016-05-10 19:11:44 -070033
34VTS_CONTROLLER_CONFIG_NAME = "AndroidDevice"
35VTS_CONTROLLER_REFERENCE_NAME = "android_devices"
36
37ANDROID_DEVICE_PICK_ALL_TOKEN = "*"
38# Key name for adb logcat extra params in config file.
39ANDROID_DEVICE_ADB_LOGCAT_PARAM_KEY = "adb_logcat_param"
40ANDROID_DEVICE_EMPTY_CONFIG_MSG = "Configuration is empty, abort!"
41ANDROID_DEVICE_NOT_LIST_CONFIG_MSG = "Configuration should be a list, abort!"
42
Ang Lie2139f12016-05-12 17:39:06 -070043
Ang Li93420002016-05-10 19:11:44 -070044class AndroidDeviceError(signals.ControllerError):
45 pass
46
Ang Lie2139f12016-05-12 17:39:06 -070047
Ang Li7f0e1c72016-06-14 11:23:49 -070048def create(configs):
Ang Li93420002016-05-10 19:11:44 -070049 if not configs:
50 raise AndroidDeviceError(ANDROID_DEVICE_EMPTY_CONFIG_MSG)
51 elif configs == ANDROID_DEVICE_PICK_ALL_TOKEN:
Ang Li7f0e1c72016-06-14 11:23:49 -070052 ads = get_all_instances()
Ang Li93420002016-05-10 19:11:44 -070053 elif not isinstance(configs, list):
54 raise AndroidDeviceError(ANDROID_DEVICE_NOT_LIST_CONFIG_MSG)
55 elif isinstance(configs[0], str):
56 # Configs is a list of serials.
Ang Li7f0e1c72016-06-14 11:23:49 -070057 ads = get_instances(configs)
Ang Li93420002016-05-10 19:11:44 -070058 else:
59 # Configs is a list of dicts.
Ang Li7f0e1c72016-06-14 11:23:49 -070060 ads = get_instances_with_configs(configs)
Ang Li93420002016-05-10 19:11:44 -070061 connected_ads = list_adb_devices()
62 for ad in ads:
63 if ad.serial not in connected_ads:
Ang Lie2139f12016-05-12 17:39:06 -070064 raise AndroidDeviceError(
65 ("Android device %s is specified in config"
66 " but is not attached.") % ad.serial)
Ang Li93420002016-05-10 19:11:44 -070067 ad.startAdbLogcat()
Ang Li93420002016-05-10 19:11:44 -070068 return ads
69
Ang Lie2139f12016-05-12 17:39:06 -070070
Ang Li93420002016-05-10 19:11:44 -070071def destroy(ads):
72 for ad in ads:
73 try:
74 ad.closeAllSl4aSession()
75 except:
76 pass
77 if ad.adb_logcat_process:
78 ad.stopAdbLogcat()
79
Ang Lie2139f12016-05-12 17:39:06 -070080
Ang Li93420002016-05-10 19:11:44 -070081def _parse_device_list(device_list_str, key):
82 """Parses a byte string representing a list of devices. The string is
83 generated by calling either adb or fastboot.
84
85 Args:
86 device_list_str: Output of adb or fastboot.
87 key: The token that signifies a device in device_list_str.
88
89 Returns:
90 A list of android device serial numbers.
91 """
92 clean_lines = str(device_list_str, 'utf-8').strip().split('\n')
93 results = []
94 for line in clean_lines:
95 tokens = line.strip().split('\t')
96 if len(tokens) == 2 and tokens[1] == key:
97 results.append(tokens[0])
98 return results
99
Ang Lie2139f12016-05-12 17:39:06 -0700100
Ang Li93420002016-05-10 19:11:44 -0700101def list_adb_devices():
Keun Soo Yimcbd8c052016-06-20 15:10:58 -0700102 """List all target devices connected to the host and detected by adb.
Ang Li93420002016-05-10 19:11:44 -0700103
104 Returns:
105 A list of android device serials. Empty if there's none.
106 """
107 out = adb.AdbProxy().devices()
108 return _parse_device_list(out, "device")
109
Ang Lie2139f12016-05-12 17:39:06 -0700110
Ang Li93420002016-05-10 19:11:44 -0700111def list_fastboot_devices():
112 """List all android devices connected to the computer that are in in
113 fastboot mode. These are detected by fastboot.
114
115 Returns:
116 A list of android device serials. Empty if there's none.
117 """
118 out = fastboot.FastbootProxy().devices()
119 return _parse_device_list(out, "fastboot")
120
Ang Lie2139f12016-05-12 17:39:06 -0700121
Ang Li7f0e1c72016-06-14 11:23:49 -0700122def get_instances(serials):
Ang Li93420002016-05-10 19:11:44 -0700123 """Create AndroidDevice instances from a list of serials.
124
125 Args:
126 serials: A list of android device serials.
Ang Li93420002016-05-10 19:11:44 -0700127
128 Returns:
129 A list of AndroidDevice objects.
130 """
131 results = []
132 for s in serials:
Ang Li7f0e1c72016-06-14 11:23:49 -0700133 results.append(AndroidDevice(s))
Ang Li93420002016-05-10 19:11:44 -0700134 return results
135
Ang Lie2139f12016-05-12 17:39:06 -0700136
Ang Li7f0e1c72016-06-14 11:23:49 -0700137def get_instances_with_configs(configs):
Ang Li93420002016-05-10 19:11:44 -0700138 """Create AndroidDevice instances from a list of json configs.
139
140 Each config should have the required key-value pair "serial".
141
142 Args:
143 configs: A list of dicts each representing the configuration of one
144 android device.
Ang Li93420002016-05-10 19:11:44 -0700145
146 Returns:
147 A list of AndroidDevice objects.
148 """
149 results = []
150 for c in configs:
151 try:
152 serial = c.pop("serial")
153 except KeyError:
154 raise AndroidDeviceError(('Required value "serial" is missing in '
Ang Lie2139f12016-05-12 17:39:06 -0700155 'AndroidDevice config %s.') % c)
Ang Li7f0e1c72016-06-14 11:23:49 -0700156 ad = AndroidDevice(serial)
Ang Li93420002016-05-10 19:11:44 -0700157 ad.loadConfig(c)
158 results.append(ad)
159 return results
160
Ang Lie2139f12016-05-12 17:39:06 -0700161
Ang Li7f0e1c72016-06-14 11:23:49 -0700162def get_all_instances(include_fastboot=False):
Ang Li93420002016-05-10 19:11:44 -0700163 """Create AndroidDevice instances for all attached android devices.
164
165 Args:
166 include_fastboot: Whether to include devices in bootloader mode or not.
Ang Li93420002016-05-10 19:11:44 -0700167
168 Returns:
169 A list of AndroidDevice objects each representing an android device
170 attached to the computer.
171 """
172 if include_fastboot:
173 serial_list = list_adb_devices() + list_fastboot_devices()
Ang Li7f0e1c72016-06-14 11:23:49 -0700174 return get_instances(serial_list)
175 return get_instances(list_adb_devices())
Ang Li93420002016-05-10 19:11:44 -0700176
Ang Lie2139f12016-05-12 17:39:06 -0700177
Ang Li93420002016-05-10 19:11:44 -0700178def filter_devices(ads, func):
179 """Finds the AndroidDevice instances from a list that match certain
180 conditions.
181
182 Args:
183 ads: A list of AndroidDevice instances.
184 func: A function that takes an AndroidDevice object and returns True
185 if the device satisfies the filter condition.
186
187 Returns:
188 A list of AndroidDevice instances that satisfy the filter condition.
189 """
190 results = []
191 for ad in ads:
192 if func(ad):
193 results.append(ad)
194 return results
195
Ang Lie2139f12016-05-12 17:39:06 -0700196
Ang Li93420002016-05-10 19:11:44 -0700197def get_device(ads, **kwargs):
198 """Finds a unique AndroidDevice instance from a list that has specific
199 attributes of certain values.
200
201 Example:
202 get_device(android_devices, label="foo", phone_number="1234567890")
203 get_device(android_devices, model="angler")
204
205 Args:
206 ads: A list of AndroidDevice instances.
207 kwargs: keyword arguments used to filter AndroidDevice instances.
208
209 Returns:
210 The target AndroidDevice instance.
211
212 Raises:
213 AndroidDeviceError is raised if none or more than one device is
214 matched.
215 """
Ang Lie2139f12016-05-12 17:39:06 -0700216
Ang Li93420002016-05-10 19:11:44 -0700217 def _get_device_filter(ad):
218 for k, v in kwargs.items():
219 if not hasattr(ad, k):
220 return False
221 elif getattr(ad, k) != v:
222 return False
223 return True
Ang Lie2139f12016-05-12 17:39:06 -0700224
Ang Li93420002016-05-10 19:11:44 -0700225 filtered = filter_devices(ads, _get_device_filter)
226 if not filtered:
227 raise AndroidDeviceError(("Could not find a target device that matches"
228 " condition: %s.") % kwargs)
229 elif len(filtered) == 1:
230 return filtered[0]
231 else:
232 serials = [ad.serial for ad in filtered]
233 raise AndroidDeviceError("More than one device matched: %s" % serials)
234
Ang Lie2139f12016-05-12 17:39:06 -0700235
Ang Li93420002016-05-10 19:11:44 -0700236def takeBugReports(ads, test_name, begin_time):
237 """Takes bug reports on a list of android devices.
238
239 If you want to take a bug report, call this function with a list of
240 android_device objects in on_fail. But reports will be taken on all the
241 devices in the list concurrently. Bug report takes a relative long
242 time to take, so use this cautiously.
243
244 Args:
245 ads: A list of AndroidDevice instances.
246 test_name: Name of the test case that triggered this bug report.
247 begin_time: Logline format timestamp taken when the test started.
248 """
249 begin_time = vts_logger.normalizeLogLineTimestamp(begin_time)
Ang Lie2139f12016-05-12 17:39:06 -0700250
Ang Li93420002016-05-10 19:11:44 -0700251 def take_br(test_name, begin_time, ad):
252 ad.takeBugReport(test_name, begin_time)
Ang Lie2139f12016-05-12 17:39:06 -0700253
Ang Li93420002016-05-10 19:11:44 -0700254 args = [(test_name, begin_time, ad) for ad in ads]
255 utils.concurrent_exec(take_br, args)
256
Ang Lie2139f12016-05-12 17:39:06 -0700257
Ang Li7f0e1c72016-06-14 11:23:49 -0700258class AndroidDevice(object):
Ang Li93420002016-05-10 19:11:44 -0700259 """Class representing an android device.
260
261 Each object of this class represents one Android device in ACTS, including
262 handles to adb, fastboot, and sl4a clients. In addition to direct adb
263 commands, this object also uses adb port forwarding to talk to the Android
264 device.
265
266 Attributes:
267 serial: A string that's the serial number of the Androi device.
Keun Soo Yimcbd8c052016-06-20 15:10:58 -0700268 device_command_port: int, the port number used on the Android device
269 for adb port forwarding (for command-response sessions).
270 device_callback_port: int, the port number used on the Android device
271 for adb port reverse forwarding (for callback sessions).
Ang Li93420002016-05-10 19:11:44 -0700272 log_path: A string that is the path where all logs collected on this
273 android device should be stored.
274 adb_logcat_process: A process that collects the adb logcat.
275 adb_logcat_file_path: A string that's the full path to the adb logcat
276 file collected, if any.
277 adb: An AdbProxy object used for interacting with the device via adb.
278 fastboot: A FastbootProxy object used for interacting with the device
279 via fastboot.
Keun Soo Yimcbd8c052016-06-20 15:10:58 -0700280 host_command_port: the host-side port for runner to agent sessions
281 (to send commands and receive responses).
282 host_callback_port: the host-side port for agent to runner sessions
283 (to get callbacks from agent).
Ang Li93420002016-05-10 19:11:44 -0700284 """
285
Keun Soo Yimcbd8c052016-06-20 15:10:58 -0700286 def __init__(self, serial="", device_port=5001, device_callback_port=5010):
Ang Li93420002016-05-10 19:11:44 -0700287 self.serial = serial
Keun Soo Yimcbd8c052016-06-20 15:10:58 -0700288 self.device_command_port = device_port
289 self.device_callback_port = device_callback_port
Ang Li93420002016-05-10 19:11:44 -0700290 self.log = logging.getLogger()
Ang Li7f0e1c72016-06-14 11:23:49 -0700291 base_log_path = getattr(self.log, "log_path", "/tmp/logs/")
292 self.log_path = os.path.join(base_log_path, "AndroidDevice%s" % serial)
Ang Li93420002016-05-10 19:11:44 -0700293 self.adb_logcat_process = None
294 self.adb_logcat_file_path = None
295 self.adb = adb.AdbProxy(serial)
296 self.fastboot = fastboot.FastbootProxy(serial)
297 if not self.isBootloaderMode:
298 self.rootAdb()
Keun Soo Yimcbd8c052016-06-20 15:10:58 -0700299 self.host_command_port = adb.get_available_host_port()
300 self.host_callback_port = adb.get_available_host_port()
301 self.adb.tcp_forward(self.host_command_port, self.device_command_port)
302 self.adb.reverse_tcp_forward(
303 self.device_callback_port, self.host_callback_port)
304 self.hal = hal_mirror.HalMirror(
305 self.host_command_port, self.host_callback_port)
Ang Li93420002016-05-10 19:11:44 -0700306
307 def __del__(self):
Keun Soo Yimcbd8c052016-06-20 15:10:58 -0700308 if self.host_command_port:
309 self.adb.forward("--remove tcp:%s" % self.host_command_port)
Ang Li93420002016-05-10 19:11:44 -0700310 if self.adb_logcat_process:
311 self.stopAdbLogcat()
312
313 @property
314 def isBootloaderMode(self):
315 """True if the device is in bootloader mode.
316 """
317 return self.serial in list_fastboot_devices()
318
319 @property
320 def isAdbRoot(self):
321 """True if adb is running as root for this device.
322 """
323 return "root" in self.adb.shell("id -u").decode("utf-8")
324
325 @property
326 def model(self):
327 """The Android code name for the device.
328 """
329 # If device is in bootloader mode, get mode name from fastboot.
330 if self.isBootloaderMode:
331 out = self.fastboot.getvar("product").strip()
332 # "out" is never empty because of the "total time" message fastboot
333 # writes to stderr.
334 lines = out.decode("utf-8").split('\n', 1)
335 if lines:
336 tokens = lines[0].split(' ')
337 if len(tokens) > 1:
338 return tokens[1].lower()
339 return None
340 out = self.adb.shell('getprop | grep ro.build.product')
341 model = out.decode("utf-8").strip().split('[')[-1][:-1].lower()
342 if model == "sprout":
343 return model
344 else:
345 out = self.adb.shell('getprop | grep ro.product.name')
346 model = out.decode("utf-8").strip().split('[')[-1][:-1].lower()
347 return model
348
349 @property
Ang Li93420002016-05-10 19:11:44 -0700350 def isAdbLogcatOn(self):
351 """Whether there is an ongoing adb logcat collection.
352 """
353 if self.adb_logcat_process:
354 return True
355 return False
356
357 def loadConfig(self, config):
358 """Add attributes to the AndroidDevice object based on json config.
359
360 Args:
361 config: A dictionary representing the configs.
362
363 Raises:
364 AndroidDeviceError is raised if the config is trying to overwrite
365 an existing attribute.
366 """
367 for k, v in config.items():
368 if hasattr(self, k):
Ang Li7f0e1c72016-06-14 11:23:49 -0700369 raise AndroidDeviceError(
370 "Attempting to set existing attribute %s on %s" %
371 (k, self.serial))
Ang Li93420002016-05-10 19:11:44 -0700372 setattr(self, k, v)
373
374 def rootAdb(self):
375 """Change adb to root mode for this device.
376 """
377 if not self.isAdbRoot:
378 self.adb.root()
379 self.adb.wait_for_device()
380 self.adb.remount()
381 self.adb.wait_for_device()
382
Ang Li93420002016-05-10 19:11:44 -0700383 def startAdbLogcat(self):
384 """Starts a standing adb logcat collection in separate subprocesses and
385 save the logcat in a file.
386 """
387 if self.isAdbLogcatOn:
Ang Li7f0e1c72016-06-14 11:23:49 -0700388 raise AndroidDeviceError(("Android device %s already has an adb "
Ang Lie2139f12016-05-12 17:39:06 -0700389 "logcat thread going on. Cannot start "
Ang Li7f0e1c72016-06-14 11:23:49 -0700390 "another one.") % self.serial)
Ang Li93420002016-05-10 19:11:44 -0700391 # Disable adb log spam filter.
392 self.adb.shell("logpersist.start")
Ang Li7f0e1c72016-06-14 11:23:49 -0700393 f_name = "adblog,%s,%s.txt" % (self.model, self.serial)
Ang Li93420002016-05-10 19:11:44 -0700394 utils.create_dir(self.log_path)
395 logcat_file_path = os.path.join(self.log_path, f_name)
396 try:
397 extra_params = self.adb_logcat_param
398 except AttributeError:
399 extra_params = "-b all"
Ang Li7f0e1c72016-06-14 11:23:49 -0700400 cmd = "adb -s %s logcat -v threadtime %s >> %s" % (
Ang Li93420002016-05-10 19:11:44 -0700401 self.serial, extra_params, logcat_file_path)
402 self.adb_logcat_process = utils.start_standing_subprocess(cmd)
403 self.adb_logcat_file_path = logcat_file_path
404
405 def stopAdbLogcat(self):
406 """Stops the adb logcat collection subprocess.
407 """
408 if not self.isAdbLogcatOn:
Ang Li7f0e1c72016-06-14 11:23:49 -0700409 raise AndroidDeviceError(
410 "Android device %s does not have an ongoing adb logcat collection."
411 % self.serial)
Ang Li93420002016-05-10 19:11:44 -0700412 utils.stop_standing_subprocess(self.adb_logcat_process)
413 self.adb_logcat_process = None
414
415 def takeBugReport(self, test_name, begin_time):
416 """Takes a bug report on the device and stores it in a file.
417
418 Args:
419 test_name: Name of the test case that triggered this bug report.
420 begin_time: Logline format timestamp taken when the test started.
421 """
422 br_path = os.path.join(self.log_path, "BugReports")
423 utils.create_dir(br_path)
Ang Li7f0e1c72016-06-14 11:23:49 -0700424 base_name = ",%s,%s.txt" % (begin_time, self.serial)
Ang Li93420002016-05-10 19:11:44 -0700425 test_name_len = utils.MAX_FILENAME_LEN - len(base_name)
426 out_name = test_name[:test_name_len] + base_name
427 full_out_path = os.path.join(br_path, out_name.replace(' ', '\ '))
428 self.log.info("Taking bugreport for %s on %s", test_name, self.serial)
Ang Li7f0e1c72016-06-14 11:23:49 -0700429 self.adb.bugreport(" > %s" % full_out_path)
Ang Li93420002016-05-10 19:11:44 -0700430 self.log.info("Bugreport for %s taken at %s", test_name, full_out_path)
431
Ang Li93420002016-05-10 19:11:44 -0700432 @utils.timeout(15 * 60)
433 def waitForBootCompletion(self):
434 """Waits for Android framework to broadcast ACTION_BOOT_COMPLETED.
435
436 This function times out after 15 minutes.
437 """
438 self.adb.wait_for_device()
439 while True:
440 try:
441 out = self.adb.shell("getprop sys.boot_completed")
442 completed = out.decode('utf-8').strip()
443 if completed == '1':
444 return
445 except adb.AdbError:
446 # adb shell calls may fail during certain period of booting
447 # process, which is normal. Ignoring these errors.
448 pass
449 time.sleep(5)
450
451 def reboot(self):
Ang Li7f0e1c72016-06-14 11:23:49 -0700452 """Reboots the device and wait for device to complete booting.
Ang Li93420002016-05-10 19:11:44 -0700453
454 This is probably going to print some error messages in console. Only
455 use if there's no other option.
456
Ang Li93420002016-05-10 19:11:44 -0700457 Raises:
458 AndroidDeviceError is raised if waiting for completion timed
459 out.
460 """
461 if self.isBootloaderMode:
462 self.fastboot.reboot()
463 return
464 has_adb_log = self.isAdbLogcatOn
465 if has_adb_log:
466 self.stopAdbLogcat()
Ang Li93420002016-05-10 19:11:44 -0700467 self.adb.reboot()
468 self.waitForBootCompletion()
469 self.rootAdb()
Ang Li93420002016-05-10 19:11:44 -0700470 if has_adb_log:
471 self.startAdbLogcat()