1#!/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 20import os 21import time 22import traceback 23 24from acts import logger as acts_logger 25from acts import signals 26from acts import utils 27from acts.controllers import adb 28from acts.controllers import android 29from acts.controllers import event_dispatcher 30from acts.controllers import fastboot 31 32ACTS_CONTROLLER_CONFIG_NAME = "AndroidDevice" 33ACTS_CONTROLLER_REFERENCE_NAME = "android_devices" 34 35ANDROID_DEVICE_PICK_ALL_TOKEN = "*" 36# Key name for adb logcat extra params in config file. 37ANDROID_DEVICE_ADB_LOGCAT_PARAM_KEY = "adb_logcat_param" 38ANDROID_DEVICE_EMPTY_CONFIG_MSG = "Configuration is empty, abort!" 39ANDROID_DEVICE_NOT_LIST_CONFIG_MSG = "Configuration should be a list, abort!" 40 41class AndroidDeviceError(signals.ControllerError): 42 pass 43 44class DoesNotExistError(AndroidDeviceError): 45 """Raised when something that does not exist is referenced. 46 """ 47 48def create(configs, logger): 49 if not configs: 50 raise AndroidDeviceError(ANDROID_DEVICE_EMPTY_CONFIG_MSG) 51 elif configs == ANDROID_DEVICE_PICK_ALL_TOKEN: 52 ads = get_all_instances(logger=logger) 53 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. 57 ads = get_instances(configs, logger) 58 else: 59 # Configs is a list of dicts. 60 ads = get_instances_with_configs(configs, logger) 61 connected_ads = list_adb_devices() 62 active_ads = [] 63 64 for ad in ads: 65 if ad.serial not in connected_ads: 66 raise DoesNotExistError(("Android device %s is specified in config" 67 " but is not attached.") % ad.serial) 68 69 def _cleanup(msg, ad, active_ads): 70 # This exception is logged here to help with debugging under py2, 71 # because "exception raised while processing another exception" is 72 # only printed under py3. 73 logger.exception(msg) 74 # Incrementally clean up subprocesses in current ad 75 # before we continue 76 if ad.adb_logcat_process: 77 ad.stop_adb_logcat() 78 if ad.ed: 79 ad.ed.clean_up() 80 # Clean up all already-active ads before bombing out 81 destroy(active_ads) 82 raise AndroidDeviceError(msg) 83 84 for ad in ads: 85 try: 86 ad.start_adb_logcat() 87 except: 88 msg = "Failed to start logcat %s" % ad.serial 89 _cleanup(msg, ad, active_ads) 90 91 try: 92 ad.get_droid() 93 ad.ed.start() 94 except: 95 msg = "Failed to start sl4a on %s" % ad.serial 96 _cleanup(msg, ad, active_ads) 97 98 active_ads.append(ad) 99 return ads 100 101def destroy(ads): 102 for ad in ads: 103 try: 104 ad.terminate_all_sessions() 105 except: 106 pass 107 if ad.adb_logcat_process: 108 ad.stop_adb_logcat() 109 110def _parse_device_list(device_list_str, key): 111 """Parses a byte string representing a list of devices. The string is 112 generated by calling either adb or fastboot. 113 114 Args: 115 device_list_str: Output of adb or fastboot. 116 key: The token that signifies a device in device_list_str. 117 118 Returns: 119 A list of android device serial numbers. 120 """ 121 clean_lines = str(device_list_str, 'utf-8').strip().split('\n') 122 results = [] 123 for line in clean_lines: 124 tokens = line.strip().split('\t') 125 if len(tokens) == 2 and tokens[1] == key: 126 results.append(tokens[0]) 127 return results 128 129def list_adb_devices(): 130 """List all android devices connected to the computer that are detected by 131 adb. 132 133 Returns: 134 A list of android device serials. Empty if there's none. 135 """ 136 out = adb.AdbProxy().devices() 137 return _parse_device_list(out, "device") 138 139def list_fastboot_devices(): 140 """List all android devices connected to the computer that are in in 141 fastboot mode. These are detected by fastboot. 142 143 Returns: 144 A list of android device serials. Empty if there's none. 145 """ 146 out = fastboot.FastbootProxy().devices() 147 return _parse_device_list(out, "fastboot") 148 149def get_instances(serials, logger=None): 150 """Create AndroidDevice instances from a list of serials. 151 152 Args: 153 serials: A list of android device serials. 154 logger: A logger to be passed to each instance. 155 156 Returns: 157 A list of AndroidDevice objects. 158 """ 159 results = [] 160 for s in serials: 161 results.append(AndroidDevice(s, logger=logger)) 162 return results 163 164def get_instances_with_configs(configs, logger=None): 165 """Create AndroidDevice instances from a list of json configs. 166 167 Each config should have the required key-value pair "serial". 168 169 Args: 170 configs: A list of dicts each representing the configuration of one 171 android device. 172 logger: A logger to be passed to each instance. 173 174 Returns: 175 A list of AndroidDevice objects. 176 """ 177 results = [] 178 for c in configs: 179 try: 180 serial = c.pop("serial") 181 except KeyError: 182 raise AndroidDeviceError(('Required value "serial" is missing in ' 183 'AndroidDevice config %s.') % c) 184 ad = AndroidDevice(serial, logger=logger) 185 ad.load_config(c) 186 results.append(ad) 187 return results 188 189def get_all_instances(include_fastboot=False, logger=None): 190 """Create AndroidDevice instances for all attached android devices. 191 192 Args: 193 include_fastboot: Whether to include devices in bootloader mode or not. 194 logger: A logger to be passed to each instance. 195 196 Returns: 197 A list of AndroidDevice objects each representing an android device 198 attached to the computer. 199 """ 200 if include_fastboot: 201 serial_list = list_adb_devices() + list_fastboot_devices() 202 return get_instances(serial_list, logger=logger) 203 return get_instances(list_adb_devices(), logger=logger) 204 205def filter_devices(ads, func): 206 """Finds the AndroidDevice instances from a list that match certain 207 conditions. 208 209 Args: 210 ads: A list of AndroidDevice instances. 211 func: A function that takes an AndroidDevice object and returns True 212 if the device satisfies the filter condition. 213 214 Returns: 215 A list of AndroidDevice instances that satisfy the filter condition. 216 """ 217 results = [] 218 for ad in ads: 219 if func(ad): 220 results.append(ad) 221 return results 222 223def get_device(ads, **kwargs): 224 """Finds a unique AndroidDevice instance from a list that has specific 225 attributes of certain values. 226 227 Example: 228 get_device(android_devices, label="foo", phone_number="1234567890") 229 get_device(android_devices, model="angler") 230 231 Args: 232 ads: A list of AndroidDevice instances. 233 kwargs: keyword arguments used to filter AndroidDevice instances. 234 235 Returns: 236 The target AndroidDevice instance. 237 238 Raises: 239 AndroidDeviceError is raised if none or more than one device is 240 matched. 241 """ 242 def _get_device_filter(ad): 243 for k, v in kwargs.items(): 244 if not hasattr(ad, k): 245 return False 246 elif getattr(ad, k) != v: 247 return False 248 return True 249 filtered = filter_devices(ads, _get_device_filter) 250 if not filtered: 251 raise AndroidDeviceError(("Could not find a target device that matches" 252 " condition: %s.") % kwargs) 253 elif len(filtered) == 1: 254 return filtered[0] 255 else: 256 serials = [ad.serial for ad in filtered] 257 raise AndroidDeviceError("More than one device matched: %s" % serials) 258 259def take_bug_reports(ads, test_name, begin_time): 260 """Takes bug reports on a list of android devices. 261 262 If you want to take a bug report, call this function with a list of 263 android_device objects in on_fail. But reports will be taken on all the 264 devices in the list concurrently. Bug report takes a relative long 265 time to take, so use this cautiously. 266 267 Args: 268 ads: A list of AndroidDevice instances. 269 test_name: Name of the test case that triggered this bug report. 270 begin_time: Logline format timestamp taken when the test started. 271 """ 272 begin_time = acts_logger.normalize_log_line_timestamp(begin_time) 273 def take_br(test_name, begin_time, ad): 274 ad.take_bug_report(test_name, begin_time) 275 args = [(test_name, begin_time, ad) for ad in ads] 276 utils.concurrent_exec(take_br, args) 277 278class AndroidDevice: 279 """Class representing an android device. 280 281 Each object of this class represents one Android device in ACTS, including 282 handles to adb, fastboot, and sl4a clients. In addition to direct adb 283 commands, this object also uses adb port forwarding to talk to the Android 284 device. 285 286 Attributes: 287 serial: A string that's the serial number of the Androi device. 288 h_port: An integer that's the port number for adb port forwarding used 289 on the computer the Android device is connected 290 d_port: An integer that's the port number used on the Android device 291 for adb port forwarding. 292 log: A LoggerProxy object used for the class's internal logging. 293 log_path: A string that is the path where all logs collected on this 294 android device should be stored. 295 adb_logcat_process: A process that collects the adb logcat. 296 adb_logcat_file_path: A string that's the full path to the adb logcat 297 file collected, if any. 298 adb: An AdbProxy object used for interacting with the device via adb. 299 fastboot: A FastbootProxy object used for interacting with the device 300 via fastboot. 301 """ 302 303 def __init__(self, serial="", host_port=None, device_port=8080, 304 logger=None): 305 self.serial = serial 306 self.h_port = host_port 307 self.d_port = device_port 308 self.log = acts_logger.LoggerProxy(logger) 309 lp = self.log.log_path 310 self.log_path = os.path.join(lp, "AndroidDevice%s" % serial) 311 self._droid_sessions = {} 312 self._event_dispatchers = {} 313 self.adb_logcat_process = None 314 self.adb_logcat_file_path = None 315 self.adb = adb.AdbProxy(serial) 316 self.fastboot = fastboot.FastbootProxy(serial) 317 if not self.is_bootloader: 318 self.root_adb() 319 320 def __del__(self): 321 if self.h_port: 322 self.adb.forward("--remove tcp:%d" % self.h_port) 323 if self.adb_logcat_process: 324 self.stop_adb_logcat() 325 326 @property 327 def is_bootloader(self): 328 """True if the device is in bootloader mode. 329 """ 330 return self.serial in list_fastboot_devices() 331 332 @property 333 def is_adb_root(self): 334 """True if adb is running as root for this device. 335 """ 336 return "root" in self.adb.shell("id -u").decode("utf-8") 337 338 @property 339 def model(self): 340 """The Android code name for the device. 341 """ 342 # If device is in bootloader mode, get mode name from fastboot. 343 if self.is_bootloader: 344 out = self.fastboot.getvar("product").strip() 345 # "out" is never empty because of the "total time" message fastboot 346 # writes to stderr. 347 lines = out.decode("utf-8").split('\n', 1) 348 if lines: 349 tokens = lines[0].split(' ') 350 if len(tokens) > 1: 351 return tokens[1].lower() 352 return None 353 out = self.adb.shell('getprop | grep ro.build.product') 354 model = out.decode("utf-8").strip().split('[')[-1][:-1].lower() 355 if model == "sprout": 356 return model 357 else: 358 out = self.adb.shell('getprop | grep ro.product.name') 359 model = out.decode("utf-8").strip().split('[')[-1][:-1].lower() 360 return model 361 362 @property 363 def droid(self): 364 """The first sl4a session initiated on this device. None if there isn't 365 one. 366 """ 367 try: 368 session_id = sorted(self._droid_sessions)[0] 369 return self._droid_sessions[session_id][0] 370 except IndexError: 371 return None 372 373 @property 374 def ed(self): 375 """The first event_dispatcher instance created on this device. None if 376 there isn't one. 377 """ 378 try: 379 session_id = sorted(self._event_dispatchers)[0] 380 return self._event_dispatchers[session_id] 381 except IndexError: 382 return None 383 384 @property 385 def droids(self): 386 """A list of the active sl4a sessions on this device. 387 388 If multiple connections exist for the same session, only one connection 389 is listed. 390 """ 391 keys = sorted(self._droid_sessions) 392 results = [] 393 for k in keys: 394 results.append(self._droid_sessions[k][0]) 395 return results 396 397 @property 398 def eds(self): 399 """A list of the event_dispatcher objects on this device. 400 401 The indexing of the list matches that of the droids property. 402 """ 403 keys = sorted(self._event_dispatchers) 404 results = [] 405 for k in keys: 406 results.append(self._event_dispatchers[k]) 407 return results 408 409 @property 410 def is_adb_logcat_on(self): 411 """Whether there is an ongoing adb logcat collection. 412 """ 413 if self.adb_logcat_process: 414 return True 415 return False 416 417 def load_config(self, config): 418 """Add attributes to the AndroidDevice object based on json config. 419 420 Args: 421 config: A dictionary representing the configs. 422 423 Raises: 424 AndroidDeviceError is raised if the config is trying to overwrite 425 an existing attribute. 426 """ 427 for k, v in config.items(): 428 if hasattr(self, k): 429 raise AndroidDeviceError(("Attempting to set existing " 430 "attribute %s on %s") % (k, self.serial)) 431 setattr(self, k, v) 432 433 def root_adb(self): 434 """Change adb to root mode for this device. 435 """ 436 if not self.is_adb_root: 437 self.adb.root() 438 self.adb.wait_for_device() 439 440 def get_droid(self, handle_event=True): 441 """Create an sl4a connection to the device. 442 443 Return the connection handler 'droid'. By default, another connection 444 on the same session is made for EventDispatcher, and the dispatcher is 445 returned to the caller as well. 446 If sl4a server is not started on the device, try to start it. 447 448 Args: 449 handle_event: True if this droid session will need to handle 450 events. 451 452 Returns: 453 droid: Android object used to communicate with sl4a on the android 454 device. 455 ed: An optional EventDispatcher to organize events for this droid. 456 457 Examples: 458 Don't need event handling: 459 >>> ad = AndroidDevice() 460 >>> droid = ad.get_droid(False) 461 462 Need event handling: 463 >>> ad = AndroidDevice() 464 >>> droid, ed = ad.get_droid() 465 """ 466 if not self.h_port or not adb.is_port_available(self.h_port): 467 self.h_port = adb.get_available_host_port() 468 self.adb.tcp_forward(self.h_port, self.d_port) 469 try: 470 droid = self.start_new_session() 471 except: 472 self.adb.start_sl4a() 473 droid = self.start_new_session() 474 if handle_event: 475 ed = self.get_dispatcher(droid) 476 return droid, ed 477 return droid 478 479 def get_dispatcher(self, droid): 480 """Return an EventDispatcher for an sl4a session 481 482 Args: 483 droid: Session to create EventDispatcher for. 484 485 Returns: 486 ed: An EventDispatcher for specified session. 487 """ 488 ed_key = self.serial + str(droid.uid) 489 if ed_key in self._event_dispatchers: 490 if self._event_dispatchers[ed_key] is None: 491 raise AndroidDeviceError("EventDispatcher Key Empty") 492 self.log.debug("Returning existing key %s for event dispatcher!", 493 ed_key) 494 return self._event_dispatchers[ed_key] 495 event_droid = self.add_new_connection_to_session(droid.uid) 496 ed = event_dispatcher.EventDispatcher(event_droid) 497 self._event_dispatchers[ed_key] = ed 498 return ed 499 500 def _is_timestamp_in_range(self, target, begin_time, end_time): 501 low = acts_logger.logline_timestamp_comparator(begin_time, target) <= 0 502 high = acts_logger.logline_timestamp_comparator(end_time, target) >= 0 503 return low and high 504 505 def cat_adb_log(self, tag, begin_time): 506 """Takes an excerpt of the adb logcat log from a certain time point to 507 current time. 508 509 Args: 510 tag: An identifier of the time period, usualy the name of a test. 511 begin_time: Logline format timestamp of the beginning of the time 512 period. 513 """ 514 if not self.adb_logcat_file_path: 515 raise AndroidDeviceError(("Attempting to cat adb log when none has" 516 " been collected on Android device %s." 517 ) % self.serial) 518 end_time = acts_logger.get_log_line_timestamp() 519 self.log.debug("Extracting adb log from logcat.") 520 adb_excerpt_path = os.path.join(self.log_path, "AdbLogExcerpts") 521 utils.create_dir(adb_excerpt_path) 522 f_name = os.path.basename(self.adb_logcat_file_path) 523 out_name = f_name.replace("adblog,", "").replace(".txt", "") 524 out_name = ",{},{}.txt".format(begin_time, out_name) 525 tag_len = utils.MAX_FILENAME_LEN - len(out_name) 526 tag = tag[:tag_len] 527 out_name = tag + out_name 528 full_adblog_path = os.path.join(adb_excerpt_path, out_name) 529 with open(full_adblog_path, 'w', encoding='utf-8') as out: 530 in_file = self.adb_logcat_file_path 531 with open(in_file, 'r', encoding='utf-8', errors='replace') as f: 532 in_range = False 533 while True: 534 line = None 535 try: 536 line = f.readline() 537 if not line: 538 break 539 except: 540 continue 541 line_time = line[:acts_logger.log_line_timestamp_len] 542 if not acts_logger.is_valid_logline_timestamp(line_time): 543 continue 544 if self._is_timestamp_in_range(line_time, begin_time, 545 end_time): 546 in_range = True 547 if not line.endswith('\n'): 548 line += '\n' 549 out.write(line) 550 else: 551 if in_range: 552 break 553 554 def start_adb_logcat(self): 555 """Starts a standing adb logcat collection in separate subprocesses and 556 save the logcat in a file. 557 """ 558 if self.is_adb_logcat_on: 559 raise AndroidDeviceError(("Android device {} already has an adb " 560 "logcat thread going on. Cannot start " 561 "another one.").format(self.serial)) 562 # Disable adb log spam filter. 563 self.adb.shell("logpersist.start") 564 f_name = "adblog,{},{}.txt".format(self.model, self.serial) 565 utils.create_dir(self.log_path) 566 logcat_file_path = os.path.join(self.log_path, f_name) 567 try: 568 extra_params = self.adb_logcat_param 569 except AttributeError: 570 extra_params = "" 571 cmd = "adb -s {} logcat -v threadtime {} >> {}".format( 572 self.serial, extra_params, logcat_file_path) 573 self.adb_logcat_process = utils.start_standing_subprocess(cmd) 574 self.adb_logcat_file_path = logcat_file_path 575 576 def stop_adb_logcat(self): 577 """Stops the adb logcat collection subprocess. 578 """ 579 if not self.is_adb_logcat_on: 580 raise AndroidDeviceError(("Android device {} does not have an " 581 "ongoing adb logcat collection." 582 ).format(self.serial)) 583 utils.stop_standing_subprocess(self.adb_logcat_process) 584 self.adb_logcat_process = None 585 586 def take_bug_report(self, test_name, begin_time): 587 """Takes a bug report on the device and stores it in a file. 588 589 Args: 590 test_name: Name of the test case that triggered this bug report. 591 begin_time: Logline format timestamp taken when the test started. 592 """ 593 br_path = os.path.join(self.log_path, "BugReports") 594 utils.create_dir(br_path) 595 base_name = ",{},{}.txt".format(begin_time, self.serial) 596 test_name_len = utils.MAX_FILENAME_LEN - len(base_name) 597 out_name = test_name[:test_name_len] + base_name 598 full_out_path = os.path.join(br_path, out_name.replace(' ', '\ ')) 599 self.log.info("Taking bugreport for %s on %s", test_name, self.serial) 600 self.adb.bugreport(" > {}".format(full_out_path)) 601 self.log.info("Bugreport for %s taken at %s", test_name, full_out_path) 602 603 def start_new_session(self): 604 """Start a new session in sl4a. 605 606 Also caches the droid in a dict with its uid being the key. 607 608 Returns: 609 An Android object used to communicate with sl4a on the android 610 device. 611 612 Raises: 613 SL4AException: Something is wrong with sl4a and it returned an 614 existing uid to a new session. 615 """ 616 droid = android.Android(port=self.h_port) 617 if droid.uid in self._droid_sessions: 618 raise android.SL4AException(("SL4A returned an existing uid for a " 619 "new session. Abort.")) 620 self._droid_sessions[droid.uid] = [droid] 621 return droid 622 623 def add_new_connection_to_session(self, session_id): 624 """Create a new connection to an existing sl4a session. 625 626 Args: 627 session_id: UID of the sl4a session to add connection to. 628 629 Returns: 630 An Android object used to communicate with sl4a on the android 631 device. 632 633 Raises: 634 DoesNotExistError: Raised if the session it's trying to connect to 635 does not exist. 636 """ 637 if session_id not in self._droid_sessions: 638 raise DoesNotExistError("Session %d doesn't exist." % session_id) 639 droid = android.Android(cmd='continue', uid=session_id, 640 port=self.h_port) 641 return droid 642 643 def terminate_session(self, session_id): 644 """Terminate a session in sl4a. 645 646 Send terminate signal to sl4a server; stop dispatcher associated with 647 the session. Clear corresponding droids and dispatchers from cache. 648 649 Args: 650 session_id: UID of the sl4a session to terminate. 651 """ 652 if self._droid_sessions and (session_id in self._droid_sessions): 653 for droid in self._droid_sessions[session_id]: 654 droid.closeSl4aSession() 655 droid.close() 656 del self._droid_sessions[session_id] 657 ed_key = self.serial + str(session_id) 658 if ed_key in self._event_dispatchers: 659 self._event_dispatchers[ed_key].clean_up() 660 del self._event_dispatchers[ed_key] 661 662 def terminate_all_sessions(self): 663 """Terminate all sl4a sessions on the AndroidDevice instance. 664 665 Terminate all sessions and clear caches. 666 """ 667 if self._droid_sessions: 668 session_ids = list(self._droid_sessions.keys()) 669 for session_id in session_ids: 670 try: 671 self.terminate_session(session_id) 672 except: 673 msg = "Failed to terminate session %d." % session_id 674 self.log.exception(msg) 675 self.log.error(traceback.format_exc()) 676 if self.h_port: 677 self.adb.forward("--remove tcp:%d" % self.h_port) 678 self.h_port = None 679 680 def run_iperf_client(self, server_host, extra_args=""): 681 """Start iperf client on the device. 682 683 Return status as true if iperf client start successfully. 684 And data flow information as results. 685 686 Args: 687 server_host: Address of the iperf server. 688 extra_args: A string representing extra arguments for iperf client, 689 e.g. "-i 1 -t 30". 690 691 Returns: 692 status: true if iperf client start successfully. 693 results: results have data flow information 694 """ 695 out = self.adb.shell("iperf3 -c {} {}".format(server_host, extra_args)) 696 clean_out = str(out,'utf-8').strip().split('\n') 697 if "error" in clean_out[0].lower(): 698 return False, clean_out 699 return True, clean_out 700 701 @utils.timeout(15 * 60) 702 def wait_for_boot_completion(self): 703 """Waits for the Android framework to boot back up and ready to launch 704 apps. 705 706 This function times out after 15 minutes. 707 """ 708 self.adb.wait_for_device() 709 while True: 710 try: 711 out = self.adb.shell("getprop sys.boot_completed") 712 completed = out.decode('utf-8').strip() 713 if completed == '1': 714 return 715 except adb.AdbError: 716 # adb shell calls may fail during certain period of booting 717 # process, which is normal. Ignoring these errors. 718 pass 719 time.sleep(5) 720 721 def reboot(self): 722 """Reboots the device. 723 724 Terminate all sl4a sessions, reboot the device, wait for device to 725 complete booting, and restart an sl4a session. 726 727 This is a blocking method. 728 729 This is probably going to print some error messages in console. Only 730 use if there's no other option. 731 732 Example: 733 droid, ed = ad.reboot() 734 735 Returns: 736 An sl4a session with an event_dispatcher. 737 738 Raises: 739 AndroidDeviceError is raised if waiting for completion timed 740 out. 741 """ 742 if self.is_bootloader: 743 self.fastboot.reboot() 744 return 745 has_adb_log = self.is_adb_logcat_on 746 if has_adb_log: 747 self.stop_adb_logcat() 748 self.terminate_all_sessions() 749 self.adb.reboot() 750 self.wait_for_boot_completion() 751 self.root_adb() 752 droid, ed = self.get_droid() 753 ed.start() 754 if has_adb_log: 755 self.start_adb_logcat() 756 return droid, ed 757