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